@link-assistant/hive-mind 1.50.6 → 1.50.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/package.json +1 -1
- package/src/github-merge-repo-actions.lib.mjs +66 -2
- package/src/github-merge.lib.mjs +3 -3
- package/src/interruptible-sleep.lib.mjs +42 -0
- package/src/solve.auto-continue.lib.mjs +4 -1
- package/src/solve.auto-merge.lib.mjs +17 -10
- package/src/solve.config.lib.mjs +7 -2
- package/src/solve.watch.lib.mjs +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.50.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5760755: fix: default to PR-branch-only CI check, add pagination and typo fix (#1573)
|
|
8
|
+
- Fix typo: `--wait-for-all-actions-in-repository-before-mergable` → `--wait-for-all-actions-in-repository-before-mergeable` (deprecated alias kept for backward compatibility)
|
|
9
|
+
- When repo-wide flag is enabled, block on ALL active runs regardless of branch (no branch filtering) to ensure safety when CI/CD pipelines interact
|
|
10
|
+
- Add `--paginate` to `getPRCommitShas()` to load all PR commits (not just first page)
|
|
11
|
+
- Add all-commits CI check: verify CI completes for every commit on the PR branch, not just HEAD
|
|
12
|
+
- Add `getPRCommitShas()` and `checkAllPRCommitsCI()` for per-commit CI verification
|
|
13
|
+
|
|
14
|
+
## 1.50.7
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 84b9853: fix: make all long sleeps interruptible so CTRL+C responds immediately (#1574)
|
|
19
|
+
- Replace raw `setTimeout` sleeps with an interruptible sleep utility that listens for SIGINT
|
|
20
|
+
- Ensure CTRL+C during CI polling, auto-merge waits, and auto-continue delays terminates the process immediately
|
|
21
|
+
- Add `interruptible-sleep.lib.mjs` with full test coverage
|
|
22
|
+
|
|
3
23
|
## 1.50.6
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -69,9 +69,61 @@ export async function waitForAllRepoActions(owner, repo, options = {}, verbose =
|
|
|
69
69
|
return { success: false, waitedForRuns: true, timedOut: true, remainingRuns: finalRuns.runs };
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Get all commit SHAs for a pull request branch.
|
|
74
|
+
* @param {string} owner - Repository owner
|
|
75
|
+
* @param {string} repo - Repository name
|
|
76
|
+
* @param {number} prNumber - Pull request number
|
|
77
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
78
|
+
* @returns {Promise<string[]>} Array of commit SHAs
|
|
79
|
+
*/
|
|
80
|
+
export async function getPRCommitShas(owner, repo, prNumber, verbose = false) {
|
|
81
|
+
try {
|
|
82
|
+
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/pulls/${prNumber}/commits" --paginate --jq '[.[].sha]'`);
|
|
83
|
+
const shas = JSON.parse(stdout.trim() || '[]');
|
|
84
|
+
if (verbose && shas.length > 1) {
|
|
85
|
+
console.log(`[VERBOSE] pr-commits: ${shas.length} commits on PR #${prNumber}`);
|
|
86
|
+
}
|
|
87
|
+
return shas;
|
|
88
|
+
} catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check that workflow runs for ALL commits on the PR branch have completed.
|
|
95
|
+
* @param {string} owner - Repository owner
|
|
96
|
+
* @param {string} repo - Repository name
|
|
97
|
+
* @param {number} prNumber - Pull request number
|
|
98
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
99
|
+
* @param {Function} getWorkflowRunsForSha - Function to get workflow runs for a SHA
|
|
100
|
+
* @returns {Promise<{allComplete: boolean, totalCommits: number, pendingCommits: string[], details: Object[]}>}
|
|
101
|
+
*/
|
|
102
|
+
export async function checkAllPRCommitsCI(owner, repo, prNumber, verbose, getWorkflowRunsForSha) {
|
|
103
|
+
const shas = await getPRCommitShas(owner, repo, prNumber, verbose);
|
|
104
|
+
if (shas.length === 0) return { allComplete: true, totalCommits: 0, pendingCommits: [], details: [] };
|
|
105
|
+
|
|
106
|
+
const details = [];
|
|
107
|
+
const pendingCommits = [];
|
|
108
|
+
for (const sha of shas) {
|
|
109
|
+
const runs = await getWorkflowRunsForSha(owner, repo, sha, false);
|
|
110
|
+
const inProgress = runs.filter(r => r.status !== 'completed');
|
|
111
|
+
const complete = inProgress.length === 0;
|
|
112
|
+
details.push({ sha: sha.substring(0, 7), total: runs.length, inProgress: inProgress.length, complete });
|
|
113
|
+
if (!complete) pendingCommits.push(sha.substring(0, 7));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (verbose && pendingCommits.length > 0) {
|
|
117
|
+
console.log(`[VERBOSE] pr-commits: ${pendingCommits.length}/${shas.length} commits have in-progress CI: ${pendingCommits.join(', ')}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { allComplete: pendingCommits.length === 0, totalCommits: shas.length, pendingCommits, details };
|
|
121
|
+
}
|
|
122
|
+
|
|
72
123
|
/**
|
|
73
124
|
* Multi-mechanism CI consensus check. Requires Check Runs API, Workflow Runs API,
|
|
74
125
|
* and optionally repo-wide active runs to ALL agree before concluding CI is complete.
|
|
126
|
+
* Issue #1573: Also checks all PR commits' CI (not just head SHA) by default.
|
|
75
127
|
* @param {Object} params
|
|
76
128
|
* @returns {Promise<{allAgree: boolean, mechanisms: Object, ciStatus: Object, workflowRuns: Array}>}
|
|
77
129
|
*/
|
|
@@ -82,6 +134,15 @@ export async function checkCIConsensus({ owner, repo, prNumber, sha, waitForAllR
|
|
|
82
134
|
const workflowRuns = await getWorkflowRunsForSha(owner, repo, sha, verbose);
|
|
83
135
|
const workflowsOK = workflowRuns.length === 0 || workflowRuns.every(r => r.status === 'completed');
|
|
84
136
|
|
|
137
|
+
let allCommitsOK = true;
|
|
138
|
+
let allCommitsInfo = null;
|
|
139
|
+
if (checkRunsOK && workflowsOK && prNumber) {
|
|
140
|
+
allCommitsInfo = await checkAllPRCommitsCI(owner, repo, prNumber, verbose, getWorkflowRunsForSha);
|
|
141
|
+
allCommitsOK = allCommitsInfo.allComplete;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// When enabled, block on ANY active CI/CD run in the repository regardless of branch.
|
|
145
|
+
// This ensures safety when CI/CD pipelines interact or depend on each other.
|
|
85
146
|
let repoOK = true;
|
|
86
147
|
let repoInfo = null;
|
|
87
148
|
if (waitForAllRepoActionsFlag) {
|
|
@@ -89,15 +150,18 @@ export async function checkCIConsensus({ owner, repo, prNumber, sha, waitForAllR
|
|
|
89
150
|
repoOK = !repoInfo.hasActiveRuns;
|
|
90
151
|
}
|
|
91
152
|
|
|
92
|
-
const allAgree = checkRunsOK && workflowsOK && repoOK;
|
|
153
|
+
const allAgree = checkRunsOK && workflowsOK && allCommitsOK && repoOK;
|
|
93
154
|
const mechanisms = {
|
|
94
155
|
checkRunsAPI: { complete: checkRunsOK, status: ciStatus.status },
|
|
95
156
|
workflowRunsAPI: { complete: workflowsOK, total: workflowRuns.length, inProgress: workflowRuns.filter(r => r.status !== 'completed').length },
|
|
157
|
+
allCommitsCI: allCommitsInfo ? { complete: allCommitsOK, totalCommits: allCommitsInfo.totalCommits, pendingCommits: allCommitsInfo.pendingCommits } : { skipped: true },
|
|
96
158
|
repoActions: waitForAllRepoActionsFlag ? { complete: repoOK, count: repoInfo?.count ?? 0 } : { skipped: true },
|
|
97
159
|
};
|
|
98
160
|
|
|
99
161
|
if (verbose) {
|
|
100
|
-
|
|
162
|
+
const repoLabel = waitForAllRepoActionsFlag ? `${repoOK}(${repoInfo?.count ?? 0} active)` : 'skip';
|
|
163
|
+
const commitsLabel = allCommitsInfo ? `${allCommitsOK}(${allCommitsInfo.totalCommits} commits${allCommitsInfo.pendingCommits.length > 0 ? `, ${allCommitsInfo.pendingCommits.length} pending` : ''})` : 'skip';
|
|
164
|
+
console.log(`[VERBOSE] consensus: CheckRuns=${checkRunsOK}(${ciStatus.status}), WorkflowRuns=${workflowsOK}(${workflowRuns.length}), AllCommits=${commitsLabel}, RepoActions=${repoLabel} → ${allAgree ? 'AGREE' : 'DISAGREE'}`);
|
|
101
165
|
}
|
|
102
166
|
return { allAgree, mechanisms, ciStatus, workflowRuns };
|
|
103
167
|
}
|
package/src/github-merge.lib.mjs
CHANGED
|
@@ -1332,7 +1332,7 @@ export async function getCommitDate(owner, repo, sha, verbose = false) {
|
|
|
1332
1332
|
export async function checkPreviousPRCommitsHadCI(owner, repo, prNumber, headSha, verbose = false) {
|
|
1333
1333
|
try {
|
|
1334
1334
|
// Get all commits in the PR
|
|
1335
|
-
const { stdout: commitsJson } = await exec(`gh api "repos/${owner}/${repo}/pulls/${prNumber}/commits
|
|
1335
|
+
const { stdout: commitsJson } = await exec(`gh api "repos/${owner}/${repo}/pulls/${prNumber}/commits" --paginate --jq '[.[].sha]'`);
|
|
1336
1336
|
const allShas = JSON.parse(commitsJson.trim() || '[]');
|
|
1337
1337
|
|
|
1338
1338
|
// Exclude the current HEAD SHA
|
|
@@ -1455,8 +1455,8 @@ export async function checkWorkflowsHavePRTriggers(owner, repo, verbose = false,
|
|
|
1455
1455
|
import { waitForCommitCI, checkBranchCIHealth, getMergeCommitSha } from './github-merge-ci.lib.mjs';
|
|
1456
1456
|
export { waitForCommitCI, checkBranchCIHealth, getMergeCommitSha };
|
|
1457
1457
|
|
|
1458
|
-
import { getAllActiveRepoRuns, waitForAllRepoActions, checkCIConsensus } from './github-merge-repo-actions.lib.mjs'; // Issue #1503
|
|
1459
|
-
export { getAllActiveRepoRuns, waitForAllRepoActions, checkCIConsensus };
|
|
1458
|
+
import { getAllActiveRepoRuns, waitForAllRepoActions, checkCIConsensus, checkAllPRCommitsCI, getPRCommitShas } from './github-merge-repo-actions.lib.mjs'; // Issue #1503
|
|
1459
|
+
export { getAllActiveRepoRuns, waitForAllRepoActions, checkCIConsensus, checkAllPRCommitsCI, getPRCommitShas };
|
|
1460
1460
|
|
|
1461
1461
|
export default {
|
|
1462
1462
|
READY_LABEL,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interruptible sleep utility for long-running wait loops.
|
|
3
|
+
*
|
|
4
|
+
* Replaces raw `await new Promise(r => setTimeout(r, ms))` with a sleep
|
|
5
|
+
* that resolves immediately on SIGINT, so the process exit handler chain
|
|
6
|
+
* is not blocked by a lingering timer.
|
|
7
|
+
*
|
|
8
|
+
* @see https://github.com/link-assistant/hive-mind/issues/1574
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sleep for `ms` milliseconds, but resolve early if SIGINT is received.
|
|
13
|
+
*
|
|
14
|
+
* When SIGINT fires during the sleep, the timer is cleared and the promise
|
|
15
|
+
* resolves with `{ interrupted: true }`. The existing SIGINT handler (from
|
|
16
|
+
* exit-handler.lib.mjs) continues to run normally — this function does NOT
|
|
17
|
+
* consume or re-emit the signal, it only ensures its own timer doesn't
|
|
18
|
+
* block the event loop.
|
|
19
|
+
*
|
|
20
|
+
* @param {number} ms - Duration in milliseconds
|
|
21
|
+
* @returns {Promise<{interrupted: boolean}>}
|
|
22
|
+
*/
|
|
23
|
+
export function interruptibleSleep(ms) {
|
|
24
|
+
return new Promise(resolve => {
|
|
25
|
+
let timer;
|
|
26
|
+
|
|
27
|
+
const onInterrupt = () => {
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
process.removeListener('SIGINT', onInterrupt);
|
|
30
|
+
resolve({ interrupted: true });
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
timer = setTimeout(() => {
|
|
34
|
+
process.removeListener('SIGINT', onInterrupt);
|
|
35
|
+
resolve({ interrupted: false });
|
|
36
|
+
}, ms);
|
|
37
|
+
|
|
38
|
+
process.on('SIGINT', onInterrupt);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default { interruptibleSleep };
|
|
@@ -49,6 +49,9 @@ const { extractLinkedIssueNumber } = githubLinking;
|
|
|
49
49
|
// Import configuration
|
|
50
50
|
import { autoContinue, limitReset } from './config.lib.mjs';
|
|
51
51
|
|
|
52
|
+
// Issue #1574: Interruptible sleep so CTRL+C is never blocked by a lingering timer
|
|
53
|
+
const { interruptibleSleep } = await import('./interruptible-sleep.lib.mjs');
|
|
54
|
+
|
|
52
55
|
const { calculateWaitTime } = validation;
|
|
53
56
|
|
|
54
57
|
/**
|
|
@@ -116,7 +119,7 @@ export const autoContinueWhenLimitResets = async (issueUrl, sessionId, argv, sho
|
|
|
116
119
|
}, countdownInterval);
|
|
117
120
|
|
|
118
121
|
// Wait until reset time
|
|
119
|
-
await
|
|
122
|
+
await interruptibleSleep(waitMs);
|
|
120
123
|
clearInterval(countdownTimer);
|
|
121
124
|
|
|
122
125
|
const actionType = isRestart ? 'Restarting' : 'Resuming';
|
|
@@ -54,6 +54,9 @@ import { limitReset } from './config.lib.mjs';
|
|
|
54
54
|
const autoMergeHelpers = await import('./solve.auto-merge-helpers.lib.mjs');
|
|
55
55
|
const { checkForExistingComment, checkForNonBotComments, getMergeBlockers } = autoMergeHelpers;
|
|
56
56
|
|
|
57
|
+
// Issue #1574: Interruptible sleep so CTRL+C is never blocked by a lingering timer
|
|
58
|
+
const { interruptibleSleep } = await import('./interruptible-sleep.lib.mjs');
|
|
59
|
+
|
|
57
60
|
/**
|
|
58
61
|
* Main function: Watch and restart until PR becomes mergeable
|
|
59
62
|
* This implements --auto-restart-until-mergeable functionality
|
|
@@ -66,8 +69,9 @@ export const watchUntilMergeable = async params => {
|
|
|
66
69
|
const MIN_CI_CHECK_INTERVAL_SECONDS = 120;
|
|
67
70
|
const watchInterval = Math.max(rawWatchInterval, MIN_CI_CHECK_INTERVAL_SECONDS);
|
|
68
71
|
const isAutoMerge = argv.autoMerge || false;
|
|
69
|
-
// Issue #1503: --wait-for-all-actions-in-repository-before-
|
|
70
|
-
|
|
72
|
+
// Issue #1503/#1573: --wait-for-all-actions-in-repository-before-mergeable
|
|
73
|
+
// When enabled (default: true), blocks merge if ANY CI/CD run in the repo is active — ensures safety when pipelines interact.
|
|
74
|
+
const waitForAllRepoActionsFlag = argv.waitForAllActionsInRepositoryBeforeMergeable ?? argv['wait-for-all-actions-in-repository-before-mergeable'] ?? argv.waitForAllActionsInRepositoryBeforeMergable ?? argv['wait-for-all-actions-in-repository-before-mergable'] ?? false;
|
|
71
75
|
|
|
72
76
|
// Track latest session data across all iterations for accurate pricing
|
|
73
77
|
let latestSessionId = null;
|
|
@@ -104,7 +108,7 @@ export const watchUntilMergeable = async params => {
|
|
|
104
108
|
// Issue #1567: Wait for initial cooldown before first check.
|
|
105
109
|
// This gives CI/CD time to start and solution logs time to be posted.
|
|
106
110
|
await log(formatAligned('⏳', 'Initial cooldown:', `Waiting ${INITIAL_COOLDOWN_SECONDS}s before first check...`));
|
|
107
|
-
await
|
|
111
|
+
await interruptibleSleep(INITIAL_COOLDOWN_SECONDS * 1000);
|
|
108
112
|
await log(formatAligned('✅', 'Cooldown complete:', 'Starting monitoring loop'));
|
|
109
113
|
await log('');
|
|
110
114
|
|
|
@@ -200,7 +204,7 @@ export const watchUntilMergeable = async params => {
|
|
|
200
204
|
if (!noCiConfigured) {
|
|
201
205
|
const DOUBLE_CHECK_DELAY_MS = 10000; // 10 seconds
|
|
202
206
|
await log(formatAligned('🔍', 'Multi-mechanism CI consensus check:', `Waiting ${DOUBLE_CHECK_DELAY_MS / 1000}s then verifying...`, 2));
|
|
203
|
-
await
|
|
207
|
+
await interruptibleSleep(DOUBLE_CHECK_DELAY_MS);
|
|
204
208
|
|
|
205
209
|
// Run multi-mechanism consensus: Check Runs API + Workflow Runs API + Repo-wide actions
|
|
206
210
|
const consensus = await checkCIConsensus({
|
|
@@ -216,17 +220,20 @@ export const watchUntilMergeable = async params => {
|
|
|
216
220
|
|
|
217
221
|
if (!consensus.allAgree) {
|
|
218
222
|
const m = consensus.mechanisms;
|
|
219
|
-
|
|
223
|
+
const repoLabel = m.repoActions.skipped ? 'skipped' : `${m.repoActions.count} active`;
|
|
224
|
+
const commitsLabel = m.allCommitsCI.skipped ? 'skipped' : `${m.allCommitsCI.pendingCommits.length} pending of ${m.allCommitsCI.totalCommits}`;
|
|
225
|
+
await log(formatAligned('🔄', 'CI mechanisms DISAGREE:', `CheckRuns=${m.checkRunsAPI.status}, WorkflowRuns=${m.workflowRunsAPI.inProgress} in-progress, AllCommits=${commitsLabel}, RepoActions=${repoLabel}`, 2));
|
|
220
226
|
await log(formatAligned('⏳', 'Continuing to monitor...', 'Mechanisms must agree before declaring mergeable', 2));
|
|
221
227
|
consecutiveNoRunsChecks = 0;
|
|
222
228
|
lastCheckTime = currentTime;
|
|
223
229
|
const actualWaitSeconds = currentBackoffSeconds;
|
|
224
230
|
await log(formatAligned('⏱️', 'Next check in:', `${actualWaitSeconds} seconds...`, 2));
|
|
225
231
|
await log('');
|
|
226
|
-
await
|
|
232
|
+
await interruptibleSleep(actualWaitSeconds * 1000);
|
|
227
233
|
continue;
|
|
228
234
|
}
|
|
229
|
-
|
|
235
|
+
const acLabel = consensus.mechanisms.allCommitsCI.skipped ? '' : `, AllCommits=complete(${consensus.mechanisms.allCommitsCI.totalCommits})`;
|
|
236
|
+
await log(formatAligned('✅', 'All CI mechanisms agree:', `CheckRuns=${consensus.mechanisms.checkRunsAPI.status}, WorkflowRuns=complete(${consensus.mechanisms.workflowRunsAPI.total})${acLabel}, RepoActions=${consensus.mechanisms.repoActions.skipped ? 'skipped' : 'clear'}`, 2));
|
|
230
237
|
} else if (waitForAllRepoActionsFlag) {
|
|
231
238
|
// Even with no CI configured, check repo-wide actions for absolute safety
|
|
232
239
|
const repoRuns = await getAllActiveRepoRuns(owner, repo, argv.verbose);
|
|
@@ -236,7 +243,7 @@ export const watchUntilMergeable = async params => {
|
|
|
236
243
|
const actualWaitSeconds = currentBackoffSeconds;
|
|
237
244
|
await log(formatAligned('⏱️', 'Next check in:', `${actualWaitSeconds} seconds...`, 2));
|
|
238
245
|
await log('');
|
|
239
|
-
await
|
|
246
|
+
await interruptibleSleep(actualWaitSeconds * 1000);
|
|
240
247
|
continue;
|
|
241
248
|
}
|
|
242
249
|
}
|
|
@@ -606,7 +613,7 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
606
613
|
}
|
|
607
614
|
|
|
608
615
|
// Wait until the limit resets
|
|
609
|
-
await
|
|
616
|
+
await interruptibleSleep(waitMs);
|
|
610
617
|
|
|
611
618
|
await log(formatAligned('✅', 'Usage limit wait complete', 'Resuming session...'));
|
|
612
619
|
await log('');
|
|
@@ -841,7 +848,7 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
841
848
|
const actualWaitSeconds = currentBackoffSeconds;
|
|
842
849
|
await log(formatAligned('⏱️', 'Next check in:', `${actualWaitSeconds} seconds...`, 2));
|
|
843
850
|
await log('');
|
|
844
|
-
await
|
|
851
|
+
await interruptibleSleep(actualWaitSeconds * 1000);
|
|
845
852
|
}
|
|
846
853
|
};
|
|
847
854
|
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -186,11 +186,16 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
186
186
|
description: 'Auto-restart until PR becomes mergeable (no iteration limit). Restarts on new comments from non-bot users, CI failures, merge conflicts, or other issues. Does NOT auto-merge.',
|
|
187
187
|
default: true,
|
|
188
188
|
},
|
|
189
|
-
'wait-for-all-actions-in-repository-before-
|
|
189
|
+
'wait-for-all-actions-in-repository-before-mergeable': {
|
|
190
190
|
type: 'boolean',
|
|
191
|
-
description: 'Wait for ALL active GitHub Actions workflow runs in the entire repository to complete before declaring PR mergeable.
|
|
191
|
+
description: 'Wait for ALL active GitHub Actions workflow runs in the entire repository to complete before declaring PR mergeable. When enabled, blocks merge if ANY CI/CD run in the repository is active, regardless of branch — this ensures safety when CI/CD pipelines interact or depend on each other. Enabled by default.',
|
|
192
192
|
default: true,
|
|
193
193
|
},
|
|
194
|
+
'wait-for-all-actions-in-repository-before-mergable': {
|
|
195
|
+
type: 'boolean',
|
|
196
|
+
description: 'Deprecated alias for --wait-for-all-actions-in-repository-before-mergeable (fixes typo).',
|
|
197
|
+
hidden: true,
|
|
198
|
+
},
|
|
194
199
|
'auto-restart-on-non-updated-pull-request-description': {
|
|
195
200
|
type: 'boolean',
|
|
196
201
|
description: 'Automatically restart if PR title or description still contains auto-generated placeholder text after agent execution. Restarts with a hint about what was not updated.',
|
package/src/solve.watch.lib.mjs
CHANGED
|
@@ -37,6 +37,9 @@ const { detectAndCountFeedback } = feedbackLib;
|
|
|
37
37
|
const restartShared = await import('./solve.restart-shared.lib.mjs');
|
|
38
38
|
const { checkPRMerged, checkForUncommittedChanges, getUncommittedChangesDetails, executeToolIteration, buildUncommittedChangesFeedback, isApiError } = restartShared;
|
|
39
39
|
|
|
40
|
+
// Issue #1574: Interruptible sleep so CTRL+C is never blocked by a lingering timer
|
|
41
|
+
const { interruptibleSleep } = await import('./interruptible-sleep.lib.mjs');
|
|
42
|
+
|
|
40
43
|
/**
|
|
41
44
|
* Monitor for feedback in a loop and trigger restart when detected
|
|
42
45
|
*/
|
|
@@ -446,7 +449,7 @@ export const watchForFeedback = async params => {
|
|
|
446
449
|
const actualWaitMs = actualWaitSeconds * 1000;
|
|
447
450
|
await log(formatAligned('⏱️', 'Next check in:', `${actualWaitSeconds} seconds...`, 2));
|
|
448
451
|
await log(''); // Blank line for readability
|
|
449
|
-
await
|
|
452
|
+
await interruptibleSleep(actualWaitMs);
|
|
450
453
|
} else if (isTemporaryWatch && !firstIterationInTemporaryMode) {
|
|
451
454
|
// In auto-restart mode, check immediately without waiting
|
|
452
455
|
await log(formatAligned('', 'Checking immediately for uncommitted changes...', '', 2));
|