@link-assistant/hive-mind 1.56.12 → 1.56.14

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,17 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.56.14
4
+
5
+ ### Patch Changes
6
+
7
+ - 77d6be2: Prevent failure-log uploads from posting broken `null` links and replace green-check failure-log terminal status with neutral attachment wording.
8
+
9
+ ## 1.56.13
10
+
11
+ ### Patch Changes
12
+
13
+ - ca1ac93: Start Telegram work-session monitoring before Telegraf long polling can block startup code, and keep completed screen-isolated sessions in memory until their completion message is updated.
14
+
3
15
  ## 1.56.12
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.56.12",
3
+ "version": "1.56.14",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -15,7 +15,7 @@
15
15
  "hive-telegram-bot": "./src/telegram-bot.mjs"
16
16
  },
17
17
  "scripts": {
18
- "test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-codex-support.mjs && node tests/test-build-cost-info-string.mjs && node tests/test-claude-code-install-method.mjs && node tests/test-claude-quiet-config.mjs && node tests/test-configure-claude-bin.mjs && node tests/test-docker-release-order.mjs && node tests/test-docker-box-migration.mjs && node tests/test-hive-screens.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-pre-pr-failure-notifier-1640.mjs && node tests/test-ready-to-merge-pagination-1645.mjs && node tests/test-require-gh-paginate-rule.mjs && node tests/test-auto-restart-limits-1664.mjs && node tests/test-log-upload-output-1678.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-telegram-options-before-url.mjs && node tests/test-telegram-bot-configuration-isolation-links-notation.mjs && node tests/test-extract-isolation-from-args.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-issue-1670-screen-status-monitoring.mjs && node tests/test-telegram-bot-launcher.mjs",
18
+ "test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-codex-support.mjs && node tests/test-build-cost-info-string.mjs && node tests/test-claude-code-install-method.mjs && node tests/test-claude-quiet-config.mjs && node tests/test-configure-claude-bin.mjs && node tests/test-docker-release-order.mjs && node tests/test-docker-box-migration.mjs && node tests/test-hive-screens.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-pre-pr-failure-notifier-1640.mjs && node tests/test-ready-to-merge-pagination-1645.mjs && node tests/test-require-gh-paginate-rule.mjs && node tests/test-auto-restart-limits-1664.mjs && node tests/test-log-upload-output-1678.mjs && node tests/test-log-upload-output-1682.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-telegram-options-before-url.mjs && node tests/test-telegram-bot-configuration-isolation-links-notation.mjs && node tests/test-extract-isolation-from-args.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-issue-1670-screen-status-monitoring.mjs && node tests/test-issue-1680-session-monitoring.mjs && node tests/test-telegram-bot-launcher.mjs",
19
19
  "test:queue": "node tests/solve-queue.test.mjs",
20
20
  "test:limits-display": "node tests/limits-display.test.mjs",
21
21
  "test:usage-limit": "node tests/test-usage-limit.mjs",
@@ -299,6 +299,26 @@ Thank you! šŸ™`;
299
299
  return false;
300
300
  }
301
301
  };
302
+ export const selectLogUploadUrl = ({ uploadResult, isPublicRepo }) => {
303
+ if (!uploadResult?.success) return null;
304
+
305
+ const chunks = Number.isFinite(uploadResult.chunks) ? uploadResult.chunks : 1;
306
+ const rawUrl = uploadResult.rawUrl || null;
307
+ const pageUrl = uploadResult.url || null;
308
+ const canUseRawUrl = chunks === 1 && rawUrl && (isPublicRepo || uploadResult.type !== 'repository');
309
+
310
+ return canUseRawUrl ? rawUrl : pageUrl;
311
+ };
312
+
313
+ const isUsableLogUrl = value => typeof value === 'string' && /^https:\/\/[^\s)]+$/u.test(value);
314
+
315
+ const getLogUploadTerminalStatus = ({ errorMessage, errorDuringExecution, isUsageLimit }) => {
316
+ if (errorMessage) return { emoji: 'šŸ“Ž', label: 'Failure log' };
317
+ if (errorDuringExecution) return { emoji: 'šŸ“Ž', label: 'Finished-with-errors log' };
318
+ if (isUsageLimit) return { emoji: 'šŸ“Ž', label: 'Usage-limit execution log' };
319
+ return { emoji: 'āœ…', label: 'Solution draft log' };
320
+ };
321
+
302
322
  /** Attaches a log file to a GitHub PR or issue as a comment. Returns true if upload succeeded. */
303
323
  export async function attachLogToGitHub(options) {
304
324
  const fs = (await use('fs')).promises;
@@ -616,8 +636,14 @@ ${logContent}
616
636
  // Requirements: 1 chunk = direct raw link, >1 chunks = repo link
617
637
  // Private repository raw URLs can contain short-lived tokens, so keep
618
638
  // private uploads on the stable repository/tree page URL.
619
- const useRawLogUrl = uploadResult.chunks === 1 && uploadResult.rawUrl && (isPublicRepo || uploadResult.type !== 'repository');
620
- const logUrl = useRawLogUrl ? uploadResult.rawUrl : uploadResult.url;
639
+ const logUrl = selectLogUploadUrl({ uploadResult, isPublicRepo });
640
+ if (!isUsableLogUrl(logUrl)) {
641
+ await log(' āŒ gh-upload-log completed but no usable log URL was resolved');
642
+ await log(' āš ļø Full log upload failed; not posting a broken log link');
643
+ await log(` šŸ“ Full log remains available locally at: ${logFile}`);
644
+ return false;
645
+ }
646
+
621
647
  const uploadTypeLabel = uploadResult.type === 'gist' ? 'Gist' : 'Repository';
622
648
  const chunkInfo = uploadResult.chunks > 1 ? ` (${uploadResult.chunks} chunks)` : '';
623
649
 
@@ -745,7 +771,8 @@ ${sessionNote}
745
771
  const posted = await postTrackedCommentFromFile({ $, owner, repo, targetNumber, bodyFile: tempCommentFile });
746
772
  await fs.unlink(tempCommentFile).catch(() => {});
747
773
  if (posted.ok) {
748
- await log(` āœ… Solution draft log uploaded to ${targetName} as ${isPublicRepo ? 'public' : 'private'} ${uploadTypeLabel}${chunkInfo}${posted.commentId ? ` (comment id=${posted.commentId})` : ''}`);
774
+ const status = getLogUploadTerminalStatus({ errorMessage, errorDuringExecution, isUsageLimit });
775
+ await log(` ${status.emoji} ${status.label} uploaded to ${targetName} as ${isPublicRepo ? 'public' : 'private'} ${uploadTypeLabel}${chunkInfo}${posted.commentId ? ` (comment id=${posted.commentId})` : ''}`);
749
776
  await log(` šŸ”— Log URL: ${logUrl}`);
750
777
  await log(` šŸ“Š Log size: ${Math.round(logStats.size / 1024)}KB`);
751
778
  return true;
@@ -785,7 +812,7 @@ ${sessionNote}
785
812
  */
786
813
  async function attachRegularComment(options, logComment) {
787
814
  const fs = (await use('fs')).promises;
788
- const { targetType, targetNumber, owner, repo, $, log, logFile } = options;
815
+ const { targetType, targetNumber, owner, repo, $, log, logFile, errorMessage, errorDuringExecution, isUsageLimit } = options;
789
816
 
790
817
  const targetName = targetType === 'pr' ? 'Pull Request' : 'Issue';
791
818
  const ghCommand = targetType === 'pr' ? 'pr' : 'issue';
@@ -801,7 +828,8 @@ async function attachRegularComment(options, logComment) {
801
828
  await fs.unlink(tempFile).catch(() => {});
802
829
 
803
830
  if (posted.ok) {
804
- await log(` āœ… Solution draft log uploaded to ${targetName} as comment${posted.commentId ? ` (id=${posted.commentId})` : ''}`);
831
+ const status = getLogUploadTerminalStatus({ errorMessage, errorDuringExecution, isUsageLimit });
832
+ await log(` ${status.emoji} ${status.label} uploaded to ${targetName} as comment${posted.commentId ? ` (id=${posted.commentId})` : ''}`);
805
833
  await log(` šŸ“Š Log size: ${Math.round(logStats.size / 1024)}KB`);
806
834
  return true;
807
835
  } else {
@@ -34,6 +34,10 @@ async function getIsolationRunner() {
34
34
  // In-memory session store
35
35
  const activeSessions = new Map();
36
36
 
37
+ export function resetSessionMonitorForTests() {
38
+ activeSessions.clear();
39
+ }
40
+
37
41
  /**
38
42
  * Issue #1586: Timeout for non-isolation sessions.
39
43
  * Non-isolation (plain start-screen) sessions cannot reliably detect completion
@@ -124,6 +128,11 @@ function completeSession(sessionName, exitCode = 0, verbose = false) {
124
128
  }
125
129
  }
126
130
 
131
+ function isMessageAlreadyUpdatedError(error) {
132
+ const message = String(error?.message || '').toLowerCase();
133
+ return message.includes('message is not modified');
134
+ }
135
+
127
136
  function normalizeSessionUrl(url) {
128
137
  return url.replace(/\/+$/, '').replace(/#.*$/, '').toLowerCase();
129
138
  }
@@ -190,7 +199,7 @@ async function getIsolationSessionState(sessionName, sessionInfo, options = {})
190
199
  * @param {Object} bot - Telegraf bot instance for sending messages
191
200
  * @param {boolean} verbose - Whether to log verbose output
192
201
  */
193
- export async function monitorSessions(bot, verbose = false) {
202
+ export async function monitorSessions(bot, verbose = false, options = {}) {
194
203
  const sessions = getActiveSessions(verbose);
195
204
 
196
205
  if (sessions.length === 0) {
@@ -210,7 +219,10 @@ export async function monitorSessions(bot, verbose = false) {
210
219
  // Isolation mode: use $ --status, with screen -ls only as a fallback
211
220
  // when the status record is unavailable. Terminal $ statuses are
212
221
  // authoritative so completed screen sessions do not stay blocked.
213
- const state = await getIsolationSessionState(sessionName, sessionInfo, { verbose });
222
+ const state = await getIsolationSessionState(sessionName, sessionInfo, {
223
+ verbose,
224
+ statusProvider: options.statusProvider,
225
+ });
214
226
  stillRunning = state.running;
215
227
  exitCode = state.exitCode;
216
228
  statusResult = state.statusResult;
@@ -257,7 +269,16 @@ export async function monitorSessions(bot, verbose = false) {
257
269
  completeSession(sessionName, finalExitCode || 0, verbose);
258
270
  } catch (error) {
259
271
  console.error(`Failed to send completion notification for ${sessionName}:`, error);
260
- completeSession(sessionName, 1, verbose);
272
+ if (isMessageAlreadyUpdatedError(error)) {
273
+ completeSession(sessionName, exitCode || 0, verbose);
274
+ } else {
275
+ sessionInfo.lastNotificationError = error.message;
276
+ sessionInfo.lastKnownStatus = statusResult?.status || sessionInfo.lastKnownStatus || null;
277
+ sessionInfo.lastKnownExitCode = exitCode ?? sessionInfo.lastKnownExitCode ?? null;
278
+ if (verbose) {
279
+ console.log(`[VERBOSE] Session ${sessionName} kept in memory so the completion notification can be retried`);
280
+ }
281
+ }
261
282
  }
262
283
  }
263
284
  }
@@ -270,8 +291,14 @@ export async function monitorSessions(bot, verbose = false) {
270
291
  * @param {number} intervalMs - Monitoring interval in milliseconds (default: 30000)
271
292
  * @returns {NodeJS.Timer} The interval timer (can be cleared with clearInterval)
272
293
  */
273
- export function startSessionMonitoring(bot, verbose = false, intervalMs = 30000) {
274
- const timer = setInterval(() => monitorSessions(bot, verbose), intervalMs);
294
+ export function startSessionMonitoring(bot, verbose = false, intervalMs = 30000, options = {}) {
295
+ const runMonitor = () => {
296
+ monitorSessions(bot, verbose, options).catch(error => {
297
+ console.error(`[session-monitor] Session monitoring tick failed: ${error.message}`);
298
+ });
299
+ };
300
+ const timer = setInterval(runMonitor, intervalMs);
301
+ runMonitor();
275
302
  console.log(`šŸ“Š Session monitoring started (checking every ${intervalMs / 1000} seconds, storage: in-memory)`);
276
303
  return timer;
277
304
  }
package/src/solve.mjs CHANGED
@@ -1124,7 +1124,7 @@ try {
1124
1124
  });
1125
1125
 
1126
1126
  if (logUploadSuccess) {
1127
- await log(` āœ… Failure logs uploaded to ${logTargetLabel} successfully`);
1127
+ await log(` šŸ“Ž Failure logs attached to ${logTargetLabel}`);
1128
1128
  } else {
1129
1129
  // Issue #1212: Always show log upload failures (not just verbose)
1130
1130
  await log(' āš ļø Failed to upload failure logs');
@@ -105,13 +105,28 @@ export function formatDelay(delayMs) {
105
105
  * @param {number} [retryOptions.jitterFraction] - Jitter fraction (default: 0.1)
106
106
  * @param {boolean} [retryOptions.verbose] - Enable verbose logging
107
107
  * @param {Function} [retryOptions.onRetry] - Callback on each retry: (attempt, error, delayMs) => void
108
+ * @param {Function} [retryOptions.onLaunch] - Callback when Telegraf reports that launch has started
108
109
  * @param {AbortSignal} [retryOptions.signal] - AbortSignal to cancel retry loop
109
110
  * @returns {Promise<void>} Resolves when bot is successfully launched
110
111
  * @throws {Error} If a non-retryable error occurs or signal is aborted
111
112
  */
112
113
  export async function launchBotWithRetry(bot, launchOptions, retryOptions = {}) {
113
- const { verbose = false, onRetry, signal, ...backoffConfig } = retryOptions;
114
+ const { verbose = false, onRetry, onLaunch, signal, ...backoffConfig } = retryOptions;
114
115
  let attempt = 0;
116
+ let launchNotified = false;
117
+
118
+ const notifyLaunched = () => {
119
+ if (launchNotified || typeof onLaunch !== 'function') return;
120
+ launchNotified = true;
121
+ try {
122
+ const result = onLaunch();
123
+ if (result && typeof result.catch === 'function') {
124
+ result.catch(error => console.error(`[telegram-bot-launcher] onLaunch callback failed: ${error.message}`));
125
+ }
126
+ } catch (error) {
127
+ console.error(`[telegram-bot-launcher] onLaunch callback failed: ${error.message}`);
128
+ }
129
+ };
115
130
 
116
131
  while (true) {
117
132
  // Check if abort was requested (e.g., during shutdown)
@@ -130,10 +145,13 @@ export async function launchBotWithRetry(bot, launchOptions, retryOptions = {})
130
145
 
131
146
  if (verbose) console.log(`[VERBOSE] Launch attempt ${attempt}: starting polling...`);
132
147
 
133
- // Step 2: Launch bot in polling mode
134
- await bot.launch(launchOptions);
148
+ // Step 2: Launch bot in polling mode. In Telegraf long-polling mode the
149
+ // launch promise may stay pending while polling is active, so use the
150
+ // launch callback for startup side effects such as session monitoring.
151
+ await bot.launch(launchOptions, notifyLaunched);
135
152
 
136
153
  // Success -- bot is running
154
+ notifyLaunched();
137
155
  if (attempt > 1) {
138
156
  console.log(`āœ… Bot launched successfully after ${attempt} attempts`);
139
157
  }
@@ -1376,6 +1376,64 @@ if (VERBOSE) {
1376
1376
  // The launcher handles deleteWebhook + bot.launch() with retry on transient errors.
1377
1377
  // Non-retryable errors (401 Unauthorized) cause immediate exit.
1378
1378
  const launchAbortController = new AbortController();
1379
+ let sessionMonitoringTimer = null;
1380
+ let launchAnnouncementShown = false;
1381
+
1382
+ function startSessionMonitoringOnce() {
1383
+ if (sessionMonitoringTimer) return;
1384
+ sessionMonitoringTimer = startSessionMonitoring(bot, VERBOSE);
1385
+ }
1386
+
1387
+ async function onBotLaunched() {
1388
+ if (isShuttingDown || launchAnnouncementShown) return;
1389
+ launchAnnouncementShown = true;
1390
+
1391
+ console.log('āœ… SwarmMindBot is now running!');
1392
+ console.log('Press Ctrl+C to stop');
1393
+ startSessionMonitoringOnce();
1394
+
1395
+ if (VERBOSE) {
1396
+ console.log('[VERBOSE] Bot launched successfully');
1397
+ console.log('[VERBOSE] Polling is active, waiting for messages...');
1398
+
1399
+ // Get bot info and webhook status for diagnostics
1400
+ try {
1401
+ const botInfo = await bot.telegram.getMe();
1402
+ const webhookInfo = await bot.telegram.getWebhookInfo();
1403
+
1404
+ console.log('[VERBOSE] Bot info:');
1405
+ console.log('[VERBOSE] Username: @' + botInfo.username);
1406
+ console.log('[VERBOSE] Bot ID:', botInfo.id);
1407
+ console.log('[VERBOSE] Webhook info:');
1408
+ console.log('[VERBOSE] URL:', webhookInfo.url || 'none (polling mode)');
1409
+ console.log('[VERBOSE] Pending updates:', webhookInfo.pending_update_count);
1410
+ if (webhookInfo.last_error_date) {
1411
+ console.log('[VERBOSE] Last error:', new Date(webhookInfo.last_error_date * 1000).toISOString());
1412
+ console.log('[VERBOSE] Error message:', webhookInfo.last_error_message);
1413
+ }
1414
+
1415
+ console.log('[VERBOSE]');
1416
+ console.log('[VERBOSE] āš ļø IMPORTANT: If bot is not receiving messages in group chats:');
1417
+ console.log('[VERBOSE] 1. Privacy Mode: Check if bot has privacy mode enabled in @BotFather');
1418
+ console.log('[VERBOSE] - Send /setprivacy to @BotFather');
1419
+ console.log('[VERBOSE] - Select @' + botInfo.username);
1420
+ console.log('[VERBOSE] - Choose "Disable" to receive all group messages');
1421
+ console.log('[VERBOSE] - IMPORTANT: Remove bot from group and re-add after changing!');
1422
+ console.log('[VERBOSE] 2. Admin Status: Make bot an admin in the group (admins see all messages)');
1423
+ console.log('[VERBOSE] 3. Run diagnostic: node experiments/test-telegram-bot-privacy-mode.mjs');
1424
+ console.log('[VERBOSE]');
1425
+ } catch (err) {
1426
+ console.log('[VERBOSE] Could not fetch bot info:', err.message);
1427
+ }
1428
+
1429
+ console.log('[VERBOSE] Send a message to the bot to test message reception');
1430
+ }
1431
+ }
1432
+
1433
+ // Start completion polling before entering Telegraf long polling. The active
1434
+ // session map is empty until commands are received, but bot.launch() may stay
1435
+ // pending while polling is active.
1436
+ startSessionMonitoringOnce();
1379
1437
 
1380
1438
  launchBotWithRetry(
1381
1439
  bot,
@@ -1386,52 +1444,11 @@ launchBotWithRetry(
1386
1444
  {
1387
1445
  verbose: VERBOSE,
1388
1446
  signal: launchAbortController.signal,
1447
+ onLaunch: onBotLaunched,
1389
1448
  }
1390
1449
  )
1391
- .then(async () => {
1392
- if (isShuttingDown) return; // Skip success messages if shutting down
1393
-
1394
- console.log('āœ… SwarmMindBot is now running!');
1395
- console.log('Press Ctrl+C to stop');
1396
- if (VERBOSE) {
1397
- console.log('[VERBOSE] Bot launched successfully');
1398
- console.log('[VERBOSE] Polling is active, waiting for messages...');
1399
-
1400
- // Get bot info and webhook status for diagnostics
1401
- try {
1402
- const botInfo = await bot.telegram.getMe();
1403
- const webhookInfo = await bot.telegram.getWebhookInfo();
1404
-
1405
- console.log('[VERBOSE] Bot info:');
1406
- console.log('[VERBOSE] Username: @' + botInfo.username);
1407
- console.log('[VERBOSE] Bot ID:', botInfo.id);
1408
- console.log('[VERBOSE] Webhook info:');
1409
- console.log('[VERBOSE] URL:', webhookInfo.url || 'none (polling mode)');
1410
- console.log('[VERBOSE] Pending updates:', webhookInfo.pending_update_count);
1411
- if (webhookInfo.last_error_date) {
1412
- console.log('[VERBOSE] Last error:', new Date(webhookInfo.last_error_date * 1000).toISOString());
1413
- console.log('[VERBOSE] Error message:', webhookInfo.last_error_message);
1414
- }
1415
-
1416
- console.log('[VERBOSE]');
1417
- console.log('[VERBOSE] āš ļø IMPORTANT: If bot is not receiving messages in group chats:');
1418
- console.log('[VERBOSE] 1. Privacy Mode: Check if bot has privacy mode enabled in @BotFather');
1419
- console.log('[VERBOSE] - Send /setprivacy to @BotFather');
1420
- console.log('[VERBOSE] - Select @' + botInfo.username);
1421
- console.log('[VERBOSE] - Choose "Disable" to receive all group messages');
1422
- console.log('[VERBOSE] - IMPORTANT: Remove bot from group and re-add after changing!');
1423
- console.log('[VERBOSE] 2. Admin Status: Make bot an admin in the group (admins see all messages)');
1424
- console.log('[VERBOSE] 3. Run diagnostic: node experiments/test-telegram-bot-privacy-mode.mjs');
1425
- console.log('[VERBOSE]');
1426
- } catch (err) {
1427
- console.log('[VERBOSE] Could not fetch bot info:', err.message);
1428
- }
1429
-
1430
- console.log('[VERBOSE] Send a message to the bot to test message reception');
1431
- }
1432
-
1433
- // Start session monitoring - check for completed sessions every 30 seconds
1434
- startSessionMonitoring(bot, VERBOSE);
1450
+ .then(() => {
1451
+ if (!isShuttingDown && VERBOSE) console.log('[VERBOSE] Bot launch promise resolved');
1435
1452
  })
1436
1453
  .catch(error => {
1437
1454
  console.error('āŒ Failed to start bot:', error);
@@ -1460,6 +1477,7 @@ process.once('SIGINT', () => {
1460
1477
  console.log('\nšŸ›‘ Received SIGINT (Ctrl+C), stopping bot...');
1461
1478
  if (VERBOSE) console.log(`[VERBOSE] Signal: SIGINT, PID: ${process.pid}, PPID: ${process.ppid}`);
1462
1479
  launchAbortController.abort(); // Cancel retry loop if still retrying (issue #1240)
1480
+ if (sessionMonitoringTimer) clearInterval(sessionMonitoringTimer);
1463
1481
  stopSolveQueue();
1464
1482
  bot.stop('SIGINT');
1465
1483
  });
@@ -1469,6 +1487,7 @@ process.once('SIGTERM', () => {
1469
1487
  console.log('\nšŸ›‘ Received SIGTERM, stopping bot... (Check system logs: journalctl -u <service> or dmesg)');
1470
1488
  if (VERBOSE) console.log(`[VERBOSE] Signal: SIGTERM, PID: ${process.pid}, PPID: ${process.ppid}`);
1471
1489
  launchAbortController.abort(); // Cancel retry loop if still retrying (issue #1240)
1490
+ if (sessionMonitoringTimer) clearInterval(sessionMonitoringTimer);
1472
1491
  stopSolveQueue();
1473
1492
  bot.stop('SIGTERM');
1474
1493
  });