@link-assistant/hive-mind 1.50.6 → 1.50.7
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
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.50.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 84b9853: fix: make all long sleeps interruptible so CTRL+C responds immediately (#1574)
|
|
8
|
+
- Replace raw `setTimeout` sleeps with an interruptible sleep utility that listens for SIGINT
|
|
9
|
+
- Ensure CTRL+C during CI polling, auto-merge waits, and auto-continue delays terminates the process immediately
|
|
10
|
+
- Add `interruptible-sleep.lib.mjs` with full test coverage
|
|
11
|
+
|
|
3
12
|
## 1.50.6
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -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
|
|
@@ -104,7 +107,7 @@ export const watchUntilMergeable = async params => {
|
|
|
104
107
|
// Issue #1567: Wait for initial cooldown before first check.
|
|
105
108
|
// This gives CI/CD time to start and solution logs time to be posted.
|
|
106
109
|
await log(formatAligned('⏳', 'Initial cooldown:', `Waiting ${INITIAL_COOLDOWN_SECONDS}s before first check...`));
|
|
107
|
-
await
|
|
110
|
+
await interruptibleSleep(INITIAL_COOLDOWN_SECONDS * 1000);
|
|
108
111
|
await log(formatAligned('✅', 'Cooldown complete:', 'Starting monitoring loop'));
|
|
109
112
|
await log('');
|
|
110
113
|
|
|
@@ -200,7 +203,7 @@ export const watchUntilMergeable = async params => {
|
|
|
200
203
|
if (!noCiConfigured) {
|
|
201
204
|
const DOUBLE_CHECK_DELAY_MS = 10000; // 10 seconds
|
|
202
205
|
await log(formatAligned('🔍', 'Multi-mechanism CI consensus check:', `Waiting ${DOUBLE_CHECK_DELAY_MS / 1000}s then verifying...`, 2));
|
|
203
|
-
await
|
|
206
|
+
await interruptibleSleep(DOUBLE_CHECK_DELAY_MS);
|
|
204
207
|
|
|
205
208
|
// Run multi-mechanism consensus: Check Runs API + Workflow Runs API + Repo-wide actions
|
|
206
209
|
const consensus = await checkCIConsensus({
|
|
@@ -223,7 +226,7 @@ export const watchUntilMergeable = async params => {
|
|
|
223
226
|
const actualWaitSeconds = currentBackoffSeconds;
|
|
224
227
|
await log(formatAligned('⏱️', 'Next check in:', `${actualWaitSeconds} seconds...`, 2));
|
|
225
228
|
await log('');
|
|
226
|
-
await
|
|
229
|
+
await interruptibleSleep(actualWaitSeconds * 1000);
|
|
227
230
|
continue;
|
|
228
231
|
}
|
|
229
232
|
await log(formatAligned('✅', 'All CI mechanisms agree:', `CheckRuns=${consensus.mechanisms.checkRunsAPI.status}, WorkflowRuns=complete(${consensus.mechanisms.workflowRunsAPI.total}), RepoActions=${consensus.mechanisms.repoActions.skipped ? 'skipped' : 'clear'}`, 2));
|
|
@@ -236,7 +239,7 @@ export const watchUntilMergeable = async params => {
|
|
|
236
239
|
const actualWaitSeconds = currentBackoffSeconds;
|
|
237
240
|
await log(formatAligned('⏱️', 'Next check in:', `${actualWaitSeconds} seconds...`, 2));
|
|
238
241
|
await log('');
|
|
239
|
-
await
|
|
242
|
+
await interruptibleSleep(actualWaitSeconds * 1000);
|
|
240
243
|
continue;
|
|
241
244
|
}
|
|
242
245
|
}
|
|
@@ -606,7 +609,7 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
606
609
|
}
|
|
607
610
|
|
|
608
611
|
// Wait until the limit resets
|
|
609
|
-
await
|
|
612
|
+
await interruptibleSleep(waitMs);
|
|
610
613
|
|
|
611
614
|
await log(formatAligned('✅', 'Usage limit wait complete', 'Resuming session...'));
|
|
612
615
|
await log('');
|
|
@@ -841,7 +844,7 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
841
844
|
const actualWaitSeconds = currentBackoffSeconds;
|
|
842
845
|
await log(formatAligned('⏱️', 'Next check in:', `${actualWaitSeconds} seconds...`, 2));
|
|
843
846
|
await log('');
|
|
844
|
-
await
|
|
847
|
+
await interruptibleSleep(actualWaitSeconds * 1000);
|
|
845
848
|
}
|
|
846
849
|
};
|
|
847
850
|
|
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));
|