@link-assistant/hive-mind 2.0.27 → 2.0.29

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
+ ## 2.0.29
4
+
5
+ ### Patch Changes
6
+
7
+ - 0cafc64: Add solve resource diagnostics and Docker disk-usage fallback markers so Telegram completion messages can show full container filesystem usage even when the task container cannot be inspected after exit.
8
+
9
+ ## 2.0.28
10
+
11
+ ### Patch Changes
12
+
13
+ - 5b4f3df: Recommend and accept `/queue` instead of the legacy solve-prefixed queue commands, and recommend `/stop` for cancelling running sessions.
14
+
3
15
  ## 2.0.27
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": "2.0.27",
3
+ "version": "2.0.29",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -12,6 +12,8 @@
12
12
  * @see https://github.com/link-assistant/hive-mind/issues/1927
13
13
  */
14
14
 
15
+ import { RESOURCE_PHASE_BOT_HEARTBEAT, captureResourceSnapshot, summarizeResourceSnapshot } from './solve.resource-diagnostics.lib.mjs';
16
+
15
17
  const DEFAULT_HEARTBEAT_INTERVAL_MS = 60 * 1000;
16
18
 
17
19
  /**
@@ -26,14 +28,28 @@ const DEFAULT_HEARTBEAT_INTERVAL_MS = 60 * 1000;
26
28
  *
27
29
  * @returns {{ start: () => void, stop: () => void, beat: () => void, get timer(): any }}
28
30
  */
29
- export function createHeartbeat({ logger, getActiveSessionCount, intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS, processImpl = process, setIntervalImpl = setInterval, clearIntervalImpl = clearInterval } = {}) {
31
+ export function createHeartbeat({ logger, getActiveSessionCount, intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS, processImpl = process, setIntervalImpl = setInterval, clearIntervalImpl = clearInterval, captureResources = captureResourceSnapshot, resourceDiskPath = '/' } = {}) {
30
32
  let timer = null;
31
33
 
32
34
  const beat = () => {
33
35
  try {
36
+ let resources = null;
37
+ try {
38
+ if (typeof captureResources === 'function') {
39
+ resources = summarizeResourceSnapshot(
40
+ captureResources({
41
+ phase: RESOURCE_PHASE_BOT_HEARTBEAT,
42
+ diskPath: resourceDiskPath,
43
+ })
44
+ );
45
+ }
46
+ } catch {
47
+ resources = null;
48
+ }
34
49
  logger.heartbeat({
35
50
  activeSessions: typeof getActiveSessionCount === 'function' ? getActiveSessionCount(false) : undefined,
36
51
  uptimeSec: Math.floor(processImpl.uptime()),
52
+ resources,
37
53
  });
38
54
  } catch {
39
55
  /* heartbeat must never crash the bot */
@@ -355,7 +355,7 @@ en
355
355
  queue
356
356
  only
357
357
  in
358
- groups "❌ The /solve_queue command only works in group chats. Please add this bot to a group and make it an admin."
358
+ groups "❌ The /queue command only works in group chats. Please add this bot to a group and make it an admin."
359
359
  disabled "❌ The solve command is disabled on this bot instance."
360
360
  rejected """
361
361
  ❌ Solve command rejected.
@@ -413,7 +413,7 @@ en
413
413
  URL: {{url}}
414
414
  Status: {{status}}
415
415
 
416
- 💡 Use /solve_queue to check the queue status.
416
+ 💡 Use /queue to check the queue status.
417
417
  """
418
418
  session
419
419
  running """
@@ -422,7 +422,7 @@ en
422
422
  URL: {{url}}
423
423
  Session: `{{session}}`
424
424
 
425
- 💡 Wait for the current session to complete, or use /solve\_stop to cancel it.
425
+ 💡 Wait for the current session to complete, or use /stop to cancel it.
426
426
  """
427
427
  issues
428
428
  list
@@ -515,7 +515,7 @@ en
515
515
  detail "Tool aliases imply `--tool <tool>`: `/codex <github-url>` equals `/solve <github-url> --tool codex`"
516
516
  reply "Or reply to a message with a GitHub link: `/solve`"
517
517
  disabled "*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*, */gemini*, */qwen*) - ❌ Disabled"
518
- queue "`/solve_queue` (alias: `/queue`) - Show solve queue status"
518
+ queue "`/queue` - Show solve queue status"
519
519
  locked
520
520
  options "🔒 Locked options: `{{options}}`"
521
521
  task
@@ -553,7 +553,7 @@ en
553
553
  isolation
554
554
  mode "🔒 *Isolation Mode:* `{{isolationBackend}}` (experimental)"
555
555
  group
556
- note "⚠️ *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats. /terminal\\_watch, /watch, /subscribe and /unsubscribe work in private and group chats."
556
+ note "⚠️ *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /queue, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats. /terminal\\_watch, /watch, /subscribe and /unsubscribe work in private and group chats."
557
557
  common
558
558
  options "🔧 *Common Options:*"
559
559
  model
@@ -355,7 +355,7 @@ hi
355
355
  queue
356
356
  only
357
357
  in
358
- groups "❌ /solve_queue कमांड केवल समूह चैट में काम करती है। कृपया बॉट को समूह में जोड़ें और व्यवस्थापक बनाएँ।"
358
+ groups "❌ /queue कमांड केवल समूह चैट में काम करती है। कृपया बॉट को समूह में जोड़ें और व्यवस्थापक बनाएँ।"
359
359
  disabled "❌ इस बॉट इंस्टेंस पर solve कमांड अक्षम है।"
360
360
  rejected """
361
361
  ❌ Solve कमांड अस्वीकृत।
@@ -413,7 +413,7 @@ hi
413
413
  URL: {{url}}
414
414
  स्थिति: {{status}}
415
415
 
416
- 💡 कतार स्थिति देखने के लिए /solve_queue का उपयोग करें।
416
+ 💡 कतार स्थिति देखने के लिए /queue का उपयोग करें।
417
417
  """
418
418
  session
419
419
  running """
@@ -422,7 +422,7 @@ hi
422
422
  URL: {{url}}
423
423
  सत्र: `{{session}}`
424
424
 
425
- 💡 वर्तमान सत्र के पूरा होने की प्रतीक्षा करें, या रद्द करने के लिए /solve\_stop का उपयोग करें।
425
+ 💡 वर्तमान सत्र के पूरा होने की प्रतीक्षा करें, या रद्द करने के लिए /stop का उपयोग करें।
426
426
  """
427
427
  issues
428
428
  list
@@ -515,7 +515,7 @@ hi
515
515
  detail "Tool aliases `--tool <tool>` लगाते हैं: `/codex <github-url>` का अर्थ `/solve <github-url> --tool codex` है"
516
516
  reply "या GitHub लिंक वाले संदेश का उत्तर दें: `/solve`"
517
517
  disabled "*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*, */gemini*, */qwen*) - ❌ अक्षम"
518
- queue "`/solve_queue` (उपनाम: `/queue`) - solve queue status दिखाएँ"
518
+ queue "`/queue` - solve queue status दिखाएँ"
519
519
  locked
520
520
  options "🔒 लॉक किए गए विकल्प: `{{options}}`"
521
521
  task
@@ -553,7 +553,7 @@ hi
553
553
  isolation
554
554
  mode "🔒 *Isolation Mode:* `{{isolationBackend}}` (experimental)"
555
555
  group
556
- note "⚠️ *नोट:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop और /start commands केवल group chats में काम करती हैं। /terminal\\_watch, /watch, /subscribe और /unsubscribe private और group chats में काम करती हैं।"
556
+ note "⚠️ *नोट:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /queue, /limits, /version, /accept\\_invites, /merge, /stop और /start commands केवल group chats में काम करती हैं। /terminal\\_watch, /watch, /subscribe और /unsubscribe private और group chats में काम करती हैं।"
557
557
  common
558
558
  options "🔧 *Common Options:*"
559
559
  model
@@ -355,7 +355,7 @@ ru
355
355
  queue
356
356
  only
357
357
  in
358
- groups "❌ Команда /solve_queue работает только в групповых чатах. Добавьте бота в группу и сделайте его администратором."
358
+ groups "❌ Команда /queue работает только в групповых чатах. Добавьте бота в группу и сделайте его администратором."
359
359
  disabled "❌ Команда solve отключена в этом экземпляре бота."
360
360
  rejected """
361
361
  ❌ Команда solve отклонена.
@@ -413,7 +413,7 @@ ru
413
413
  URL: {{url}}
414
414
  Статус: {{status}}
415
415
 
416
- 💡 Используйте /solve_queue для проверки очереди.
416
+ 💡 Используйте /queue для проверки очереди.
417
417
  """
418
418
  session
419
419
  running """
@@ -422,7 +422,7 @@ ru
422
422
  URL: {{url}}
423
423
  Сеанс: `{{session}}`
424
424
 
425
- 💡 Дождитесь завершения текущего сеанса или используйте /solve\_stop для отмены.
425
+ 💡 Дождитесь завершения текущего сеанса или используйте /stop для отмены.
426
426
  """
427
427
  issues
428
428
  list
@@ -515,7 +515,7 @@ ru
515
515
  detail "Алиасы инструментов добавляют `--tool <tool>`: `/codex <github-url>` равно `/solve <github-url> --tool codex`"
516
516
  reply "Или ответьте на сообщение со ссылкой GitHub: `/solve`"
517
517
  disabled "*/solve* (алиасы: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*, */gemini*, */qwen*) - ❌ Отключено"
518
- queue "`/solve_queue` (псевдоним: `/queue`) - Показать состояние очереди solve"
518
+ queue "`/queue` - Показать состояние очереди solve"
519
519
  locked
520
520
  options "🔒 Заблокированные опции: `{{options}}`"
521
521
  task
@@ -553,7 +553,7 @@ ru
553
553
  isolation
554
554
  mode "🔒 *Режим изоляции:* `{{isolationBackend}}` (экспериментально)"
555
555
  group
556
- note "⚠️ *Замечание:* команды /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop и /start работают только в групповых чатах. /terminal\\_watch, /watch, /subscribe и /unsubscribe работают в личных и групповых чатах."
556
+ note "⚠️ *Замечание:* команды /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /queue, /limits, /version, /accept\\_invites, /merge, /stop и /start работают только в групповых чатах. /terminal\\_watch, /watch, /subscribe и /unsubscribe работают в личных и групповых чатах."
557
557
  common
558
558
  options "🔧 *Общие опции:*"
559
559
  model
@@ -355,7 +355,7 @@ zh
355
355
  queue
356
356
  only
357
357
  in
358
- groups "❌ /solve_queue 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
358
+ groups "❌ /queue 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
359
359
  disabled "❌ 此机器人实例已禁用 solve 命令。"
360
360
  rejected """
361
361
  ❌ Solve 命令被拒绝。
@@ -413,7 +413,7 @@ zh
413
413
  URL:{{url}}
414
414
  状态:{{status}}
415
415
 
416
- 💡 使用 /solve_queue 查看队列状态。
416
+ 💡 使用 /queue 查看队列状态。
417
417
  """
418
418
  session
419
419
  running """
@@ -422,7 +422,7 @@ zh
422
422
  URL:{{url}}
423
423
  会话:`{{session}}`
424
424
 
425
- 💡 等待当前会话完成,或使用 /solve\_stop 取消。
425
+ 💡 等待当前会话完成,或使用 /stop 取消。
426
426
  """
427
427
  issues
428
428
  list
@@ -515,7 +515,7 @@ zh
515
515
  detail "工具别名会添加 `--tool <tool>`:`/codex <github-url>` 等同于 `/solve <github-url> --tool codex`"
516
516
  reply "也可以回复包含 GitHub 链接的消息:`/solve`"
517
517
  disabled "*/solve*(别名:*/do*、*/continue*、*/claude*、*/codex*、*/opencode*、*/agent*、*/gemini*、*/qwen*)- ❌ 已禁用"
518
- queue "`/solve_queue`(别名:`/queue`)- 显示 solve 队列状态"
518
+ queue "`/queue` - 显示 solve 队列状态"
519
519
  locked
520
520
  options "🔒 锁定选项:`{{options}}`"
521
521
  task
@@ -553,7 +553,7 @@ zh
553
553
  isolation
554
554
  mode "🔒 *隔离模式:* `{{isolationBackend}}`(实验性)"
555
555
  group
556
- note "⚠️ *注意:* /solve、/do、/continue、/claude、/codex、/opencode、/agent、/gemini、/qwen、/task、/split、/hive、/solve\\_queue、/limits、/version、/accept\\_invites、/merge、/stop 和 /start 仅在群聊中有效。/terminal\\_watch、/watch、/subscribe 和 /unsubscribe 在私聊和群聊中有效。"
556
+ note "⚠️ *注意:* /solve、/do、/continue、/claude、/codex、/opencode、/agent、/gemini、/qwen、/task、/split、/hive、/queue、/limits、/version、/accept\\_invites、/merge、/stop 和 /start 仅在群聊中有效。/terminal\\_watch、/watch、/subscribe 和 /unsubscribe 在私聊和群聊中有效。"
557
557
  common
558
558
  options "🔧 *常用选项:*"
559
559
  model
@@ -282,7 +282,7 @@ export const QUEUE_CONFIG = {
282
282
 
283
283
  // Display
284
284
  // Maximum number of items shown per section (pending/processing/completed/failed)
285
- // in the /solve_queue (/queue) detailed status before collapsing into a
285
+ // in the /queue detailed status before collapsing into a
286
286
  // "... and N more" line. Keeps the Telegram message under the 4096-char cap.
287
287
  // See: https://github.com/link-assistant/hive-mind/issues/1837
288
288
  MAX_DISPLAY_ITEMS_PER_QUEUE: parseIntWithDefault('HIVE_MIND_MAX_DISPLAY_ITEMS_PER_QUEUE', 5),
@@ -343,10 +343,11 @@ async function resolvePullRequestUrlFromSessionLog(logPath, ctx, { verbose = fal
343
343
  * build the Telegram extraSection. Returns an empty string if there is no
344
344
  * repository or docker filesystem data to show.
345
345
  */
346
- async function buildDiskDiagnosticsExtraSection(logPath, { verbose = false, readFile = fs.readFile, isolationBackend = null, containerFilesystemStartBytes = null, containerFilesystemAfterBytes = null } = {}) {
346
+ export async function buildDiskDiagnosticsExtraSection(logPath, { verbose = false, readFile = fs.readFile, isolationBackend = null, containerFilesystemStartBytes = null, containerFilesystemAfterBytes = null } = {}) {
347
347
  if (!logPath && !Number.isFinite(containerFilesystemStartBytes) && !Number.isFinite(containerFilesystemAfterBytes)) return '';
348
348
  try {
349
349
  const diskLib = await import('./solve.disk-diagnostics.lib.mjs');
350
+ const resourceLib = await import('./solve.resource-diagnostics.lib.mjs');
350
351
  let logText = '';
351
352
  if (logPath) {
352
353
  try {
@@ -358,11 +359,17 @@ async function buildDiskDiagnosticsExtraSection(logPath, { verbose = false, read
358
359
  }
359
360
  }
360
361
  const parsed = diskLib.parseDiskMarkers(logText);
361
- if (!parsed.afterClone && !parsed.afterAgent && !Number.isFinite(containerFilesystemStartBytes) && !Number.isFinite(containerFilesystemAfterBytes)) return '';
362
+ const parsedResources = resourceLib.parseResourceMarkers(logText);
363
+ const bestResourceMarker = resourceLib.selectBestDiskResourceMarker(parsedResources);
364
+ const solveStartResourceMarker = parsedResources.byPhase?.[resourceLib.RESOURCE_PHASE_SOLVE_START] || null;
365
+ const useResourceFallback = String(isolationBackend || '').toLowerCase() === 'docker' && !Number.isFinite(containerFilesystemAfterBytes) && Number.isFinite(bestResourceMarker?.disk?.usedBytes);
366
+ const effectiveContainerFilesystemAfterBytes = useResourceFallback ? bestResourceMarker.disk.usedBytes : containerFilesystemAfterBytes;
367
+ const effectiveContainerFilesystemStartBytes = useResourceFallback && Number.isFinite(solveStartResourceMarker?.disk?.usedBytes) ? solveStartResourceMarker.disk.usedBytes : containerFilesystemStartBytes;
368
+ if (!parsed.afterClone && !parsed.afterAgent && !Number.isFinite(effectiveContainerFilesystemStartBytes) && !Number.isFinite(effectiveContainerFilesystemAfterBytes)) return '';
362
369
  return diskLib.formatDiskDiagnosticsBlock(parsed, {
363
370
  isolationBackend,
364
- containerFilesystemStartBytes,
365
- containerFilesystemAfterBytes,
371
+ containerFilesystemStartBytes: effectiveContainerFilesystemStartBytes,
372
+ containerFilesystemAfterBytes: effectiveContainerFilesystemAfterBytes,
366
373
  });
367
374
  } catch (error) {
368
375
  if (verbose) {
@@ -1274,7 +1281,7 @@ export async function getRunningTrackedIsolationSessions(verbose = false, option
1274
1281
 
1275
1282
  /**
1276
1283
  * Return the currently-executing tracked sessions with the details needed to
1277
- * render them as a clickable list in `/solve_queue` (`/queue`): the issue/PR
1284
+ * render them as a clickable list in `/queue`: the issue/PR
1278
1285
  * `url`, the `tool`, the start time, and (for isolation sessions) the backend
1279
1286
  * status. Both isolation and non-isolation screen sessions are included so the
1280
1287
  * list matches what is actually executing — the queue's own in-memory
package/src/solve.mjs CHANGED
@@ -49,7 +49,8 @@ const { runKeepWorkingUntilDone } = await import('./solve.keep-working.lib.mjs')
49
49
  const { runEscalation } = await import('./solve.escalate.lib.mjs');
50
50
  const { finalizeSolveProcess } = await import('./solve.finalize.lib.mjs');
51
51
  const exitHandler = await import('./exit-handler.lib.mjs');
52
- const { initializeExitHandler, installGlobalExitHandlers, safeExit, logActiveHandles } = exitHandler;
52
+ const { initializeExitHandler, installGlobalExitHandlers, safeExit: baseSafeExit, logActiveHandles } = exitHandler;
53
+ const { RESOURCE_PHASE_AFTER_AGENT, RESOURCE_PHASE_AFTER_CLONE, RESOURCE_PHASE_SOLVE_EXIT, RESOURCE_PHASE_SOLVE_START, recordResourceSnapshot } = await import('./solve.resource-diagnostics.lib.mjs');
53
54
  const { createInterruptWrapper } = await import('./solve.interrupt.lib.mjs');
54
55
  // Issue #1823: working-session guard for --do-not-shutdown-in-the-middle-of-working-session.
55
56
  const { configureWorkingSession, beginWorkingSession, endWorkingSession } = await import('./working-session.lib.mjs');
@@ -69,9 +70,7 @@ const { prepareFeedbackAndTimestamps, checkUncommittedChanges, checkForkActions
69
70
  const { validateAndExitOnInvalidClaudeSubAgentModel, validateAndExitOnInvalidModel } = await import('./models/index.mjs');
70
71
  const { autoAcceptInviteForRepo } = await import('./solve.accept-invite.lib.mjs');
71
72
  const { handleAutoForkOption, handleMaintainerForkAccess } = await import('./solve.fork-detection.lib.mjs');
72
- // Initialize log file early (before argument parsing) to capture all output
73
73
  const logFile = await initializeLogFile(null);
74
- // Log version and raw command IMMEDIATELY after log file initialization
75
74
  const versionInfo = await getVersionInfo();
76
75
  await log('');
77
76
  await log(`🚀 solve v${versionInfo}`);
@@ -80,6 +79,15 @@ await log('🔧 Raw command executed:');
80
79
  await log(` ${rawCommand}`);
81
80
  await log('');
82
81
 
82
+ let finalResourceSnapshotRecorded = false;
83
+ const safeExit = async (code = 0, reason = 'Process completed', options = {}) => {
84
+ if (!finalResourceSnapshotRecorded) {
85
+ finalResourceSnapshotRecorded = true;
86
+ await recordResourceSnapshot({ phase: RESOURCE_PHASE_SOLVE_EXIT, log, diskPath: '/', label: `solve exit ${code}` });
87
+ }
88
+ return await baseSafeExit(code, reason, options);
89
+ };
90
+
83
91
  let argv;
84
92
  try {
85
93
  argv = await parseArguments(yargs, hideBin);
@@ -98,6 +106,7 @@ configureGitHubRateLimitLogging({
98
106
  enabled: argv.githubRateLimitsLogging === true,
99
107
  log,
100
108
  });
109
+ await recordResourceSnapshot({ phase: RESOURCE_PHASE_SOLVE_START, log, diskPath: '/', label: 'solve start' });
101
110
 
102
111
  // Early logs go to cwd; custom log dir takes effect after argv is parsed
103
112
  // Conditionally import tool-specific functions after argv is parsed
@@ -507,6 +516,7 @@ try {
507
516
  });
508
517
 
509
518
  cleanupContext.diskDiagnostics = { beforeBytes: await recordAfterCloneSize({ tempDir, log }) };
519
+ await recordResourceSnapshot({ phase: RESOURCE_PHASE_AFTER_CLONE, log, diskPath: '/', label: 'after repository clone' });
510
520
 
511
521
  // Verify default branch and status using the new module
512
522
  // Pass argv, owner, repo, issueUrl for empty repository auto-initialization (--auto-init-repository)
@@ -830,6 +840,7 @@ try {
830
840
  } catch (diskError) {
831
841
  await log(`⚠️ Disk-size measurement failed: ${cleanErrorMessage(diskError)}`, { level: 'warning', verbose: true });
832
842
  }
843
+ await recordResourceSnapshot({ phase: RESOURCE_PHASE_AFTER_AGENT, log, diskPath: '/', label: 'after AI execution' });
833
844
 
834
845
  // Issue #1823: Mark the end of the AI working session. If a graceful-shutdown interrupt arrived
835
846
  // during the session (deferred by the working-session guard), honor it now: auto-commit any
@@ -0,0 +1,322 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+
4
+ export const RESOURCE_MARKER_PREFIX = '📈 [RESOURCES]';
5
+
6
+ export const RESOURCE_PHASE_SOLVE_START = 'solve_start';
7
+ export const RESOURCE_PHASE_AFTER_CLONE = 'after_clone';
8
+ export const RESOURCE_PHASE_AFTER_AGENT = 'after_agent';
9
+ export const RESOURCE_PHASE_SOLVE_EXIT = 'solve_exit';
10
+ export const RESOURCE_PHASE_RESTART_BEFORE = 'restart_before';
11
+ export const RESOURCE_PHASE_RESTART_AFTER = 'restart_after';
12
+ export const RESOURCE_PHASE_BOT_HEARTBEAT = 'bot_heartbeat';
13
+
14
+ const RESOURCE_PHASES_BY_PREFERENCE = [RESOURCE_PHASE_SOLVE_EXIT, RESOURCE_PHASE_AFTER_AGENT, RESOURCE_PHASE_RESTART_AFTER, RESOURCE_PHASE_AFTER_CLONE, RESOURCE_PHASE_SOLVE_START, RESOURCE_PHASE_RESTART_BEFORE];
15
+
16
+ function finiteNumber(value) {
17
+ return Number.isFinite(value) ? value : null;
18
+ }
19
+
20
+ function clampPercent(value) {
21
+ if (!Number.isFinite(value)) return null;
22
+ return Math.max(0, Math.min(100, value));
23
+ }
24
+
25
+ function readLinuxMemAvailableBytes(readFileSync = fs.readFileSync, platform = process.platform) {
26
+ if (platform !== 'linux') return null;
27
+ try {
28
+ const text = readFileSync('/proc/meminfo', 'utf8');
29
+ const match = text.match(/^MemAvailable:\s+(\d+)\s+kB$/m);
30
+ if (!match) return null;
31
+ return Number.parseInt(match[1], 10) * 1024;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ export function captureResourceSnapshot(options = {}) {
38
+ const { phase = 'snapshot', diskPath = '/', now = () => new Date(), osImpl = os, fsImpl = fs, processImpl = process } = options;
39
+
40
+ const timestamp = (() => {
41
+ try {
42
+ return now().toISOString();
43
+ } catch {
44
+ return new Date().toISOString();
45
+ }
46
+ })();
47
+
48
+ const load = (() => {
49
+ try {
50
+ const values = osImpl.loadavg();
51
+ return {
52
+ load1: finiteNumber(values[0]),
53
+ load5: finiteNumber(values[1]),
54
+ load15: finiteNumber(values[2]),
55
+ };
56
+ } catch {
57
+ return { load1: null, load5: null, load15: null };
58
+ }
59
+ })();
60
+
61
+ const cpuCount = (() => {
62
+ try {
63
+ const cpus = osImpl.cpus();
64
+ return Array.isArray(cpus) ? cpus.length : null;
65
+ } catch {
66
+ return null;
67
+ }
68
+ })();
69
+
70
+ const totalMemoryBytes = (() => {
71
+ try {
72
+ return finiteNumber(osImpl.totalmem());
73
+ } catch {
74
+ return null;
75
+ }
76
+ })();
77
+
78
+ const freeMemoryBytes = (() => {
79
+ try {
80
+ return finiteNumber(osImpl.freemem());
81
+ } catch {
82
+ return null;
83
+ }
84
+ })();
85
+
86
+ const availableMemoryBytes = readLinuxMemAvailableBytes(fsImpl.readFileSync?.bind(fsImpl), processImpl.platform || process.platform) ?? freeMemoryBytes;
87
+ const usedMemoryBytes = totalMemoryBytes !== null && availableMemoryBytes !== null ? Math.max(0, totalMemoryBytes - availableMemoryBytes) : null;
88
+
89
+ const processMemory = (() => {
90
+ try {
91
+ const usage = processImpl.memoryUsage();
92
+ return {
93
+ rssBytes: finiteNumber(usage.rss),
94
+ heapUsedBytes: finiteNumber(usage.heapUsed),
95
+ };
96
+ } catch {
97
+ return { rssBytes: null, heapUsedBytes: null };
98
+ }
99
+ })();
100
+
101
+ const disk = (() => {
102
+ const path = String(diskPath || '/');
103
+ try {
104
+ if (typeof fsImpl.statfsSync !== 'function') {
105
+ return { path, totalBytes: null, freeBytes: null, availableBytes: null, usedBytes: null, usedPercent: null, error: 'statfs unavailable' };
106
+ }
107
+ const stat = fsImpl.statfsSync(path);
108
+ const blockSize = Number(stat.bsize || stat.frsize || 0);
109
+ const blocks = Number(stat.blocks);
110
+ const bfree = Number(stat.bfree);
111
+ const bavail = Number(stat.bavail);
112
+ const totalBytes = Number.isFinite(blockSize) && Number.isFinite(blocks) ? blockSize * blocks : null;
113
+ const freeBytes = Number.isFinite(blockSize) && Number.isFinite(bfree) ? blockSize * bfree : null;
114
+ const availableBytes = Number.isFinite(blockSize) && Number.isFinite(bavail) ? blockSize * bavail : freeBytes;
115
+ const usedBytes = totalBytes !== null && freeBytes !== null ? Math.max(0, totalBytes - freeBytes) : null;
116
+ const usedPercent = totalBytes && usedBytes !== null ? clampPercent((usedBytes / totalBytes) * 100) : null;
117
+ return { path, totalBytes, freeBytes, availableBytes, usedBytes, usedPercent, error: null };
118
+ } catch (error) {
119
+ return {
120
+ path,
121
+ totalBytes: null,
122
+ freeBytes: null,
123
+ availableBytes: null,
124
+ usedBytes: null,
125
+ usedPercent: null,
126
+ error: error?.message || String(error),
127
+ };
128
+ }
129
+ })();
130
+
131
+ return {
132
+ phase: String(phase || 'snapshot'),
133
+ timestamp,
134
+ cpu: { ...load, cpuCount },
135
+ memory: {
136
+ totalBytes: totalMemoryBytes,
137
+ freeBytes: freeMemoryBytes,
138
+ availableBytes: availableMemoryBytes,
139
+ usedBytes: usedMemoryBytes,
140
+ processRssBytes: processMemory.rssBytes,
141
+ processHeapUsedBytes: processMemory.heapUsedBytes,
142
+ },
143
+ disk,
144
+ };
145
+ }
146
+
147
+ export function formatBytes(bytes) {
148
+ if (!Number.isFinite(bytes)) return '? B';
149
+ const abs = Math.abs(bytes);
150
+ if (abs >= 1024 ** 3) return `${(bytes / 1024 ** 3).toFixed(1)} GB`;
151
+ if (abs >= 1024 ** 2) return `${Math.round(bytes / 1024 ** 2)} MB`;
152
+ if (abs >= 1024) return `${Math.round(bytes / 1024)} KB`;
153
+ return `${Math.round(bytes)} B`;
154
+ }
155
+
156
+ function formatNumber(value, decimals = 2) {
157
+ return Number.isFinite(value) ? value.toFixed(decimals) : '?';
158
+ }
159
+
160
+ function encodeValue(value) {
161
+ if (value === null || value === undefined) return 'null';
162
+ return encodeURIComponent(String(value));
163
+ }
164
+
165
+ function numberField(name, value) {
166
+ return Number.isFinite(value) ? `${name}=${value}` : `${name}=null`;
167
+ }
168
+
169
+ export function buildResourceMarker(snapshot) {
170
+ const s = snapshot || {};
171
+ const cpu = s.cpu || {};
172
+ const memory = s.memory || {};
173
+ const disk = s.disk || {};
174
+ return [
175
+ RESOURCE_MARKER_PREFIX,
176
+ `phase=${encodeValue(s.phase || 'snapshot')}`,
177
+ `ts=${encodeValue(s.timestamp || new Date().toISOString())}`,
178
+ numberField('load1', cpu.load1),
179
+ numberField('load5', cpu.load5),
180
+ numberField('load15', cpu.load15),
181
+ numberField('cpuCount', cpu.cpuCount),
182
+ numberField('memTotalBytes', memory.totalBytes),
183
+ numberField('memAvailableBytes', memory.availableBytes),
184
+ numberField('memUsedBytes', memory.usedBytes),
185
+ numberField('processRssBytes', memory.processRssBytes),
186
+ `diskPath=${encodeValue(disk.path || '/')}`,
187
+ numberField('diskTotalBytes', disk.totalBytes),
188
+ numberField('diskAvailableBytes', disk.availableBytes),
189
+ numberField('diskUsedBytes', disk.usedBytes),
190
+ numberField('diskUsedPercent', disk.usedPercent),
191
+ disk.error ? `error=${encodeValue(disk.error)}` : null,
192
+ `mem=${encodeValue(`${formatBytes(memory.availableBytes)} available / ${formatBytes(memory.totalBytes)} total`)}`,
193
+ `disk=${encodeValue(`${formatBytes(disk.availableBytes)} available / ${formatBytes(disk.totalBytes)} total`)}`,
194
+ ]
195
+ .filter(Boolean)
196
+ .join(' ');
197
+ }
198
+
199
+ function parseNumber(value) {
200
+ if (value === 'null' || value === undefined) return null;
201
+ const n = Number(value);
202
+ return Number.isFinite(n) ? n : null;
203
+ }
204
+
205
+ function parseMarkerLine(line) {
206
+ const idx = line.indexOf(RESOURCE_MARKER_PREFIX);
207
+ if (idx < 0) return null;
208
+ const payload = line.slice(idx + RESOURCE_MARKER_PREFIX.length).trim();
209
+ const parts = payload.split(/\s+/).filter(Boolean);
210
+ const fields = {};
211
+ for (const part of parts) {
212
+ const eq = part.indexOf('=');
213
+ if (eq <= 0) continue;
214
+ fields[part.slice(0, eq)] = part.slice(eq + 1);
215
+ }
216
+ const phase = decodeURIComponent(fields.phase || 'snapshot');
217
+ return {
218
+ phase,
219
+ timestamp: decodeURIComponent(fields.ts || ''),
220
+ cpu: {
221
+ load1: parseNumber(fields.load1),
222
+ load5: parseNumber(fields.load5),
223
+ load15: parseNumber(fields.load15),
224
+ cpuCount: parseNumber(fields.cpuCount),
225
+ },
226
+ memory: {
227
+ totalBytes: parseNumber(fields.memTotalBytes),
228
+ availableBytes: parseNumber(fields.memAvailableBytes),
229
+ usedBytes: parseNumber(fields.memUsedBytes),
230
+ processRssBytes: parseNumber(fields.processRssBytes),
231
+ },
232
+ disk: {
233
+ path: decodeURIComponent(fields.diskPath || '/'),
234
+ totalBytes: parseNumber(fields.diskTotalBytes),
235
+ availableBytes: parseNumber(fields.diskAvailableBytes),
236
+ usedBytes: parseNumber(fields.diskUsedBytes),
237
+ usedPercent: parseNumber(fields.diskUsedPercent),
238
+ error: fields.error ? decodeURIComponent(fields.error) : null,
239
+ },
240
+ };
241
+ }
242
+
243
+ export function parseResourceMarkers(logText) {
244
+ if (typeof logText !== 'string' || !logText) return { markers: [], byPhase: {} };
245
+ const markers = [];
246
+ const byPhase = {};
247
+ for (const line of logText.split(/\r?\n/)) {
248
+ const marker = parseMarkerLine(line);
249
+ if (!marker) continue;
250
+ markers.push(marker);
251
+ byPhase[marker.phase] = marker;
252
+ }
253
+ return { markers, byPhase };
254
+ }
255
+
256
+ export function selectBestDiskResourceMarker(parsed) {
257
+ const byPhase = parsed?.byPhase || {};
258
+ for (const phase of RESOURCE_PHASES_BY_PREFERENCE) {
259
+ const marker = byPhase[phase];
260
+ if (Number.isFinite(marker?.disk?.usedBytes)) return marker;
261
+ }
262
+ const markers = Array.isArray(parsed?.markers) ? parsed.markers : [];
263
+ for (let i = markers.length - 1; i >= 0; i--) {
264
+ if (Number.isFinite(markers[i]?.disk?.usedBytes)) return markers[i];
265
+ }
266
+ return null;
267
+ }
268
+
269
+ export function formatResourceSnapshotForLog(snapshot, label = null) {
270
+ const s = snapshot || {};
271
+ const phaseLabel = label || String(s.phase || 'snapshot').replace(/_/g, ' ');
272
+ const cpu = s.cpu || {};
273
+ const memory = s.memory || {};
274
+ const disk = s.disk || {};
275
+ const lines = [`📈 Resource usage (${phaseLabel}):`, ` CPU load: ${formatNumber(cpu.load1)} ${formatNumber(cpu.load5)} ${formatNumber(cpu.load15)}${Number.isFinite(cpu.cpuCount) ? ` (${cpu.cpuCount} CPUs)` : ''}`, ` Memory: ${formatBytes(memory.availableBytes)} available / ${formatBytes(memory.totalBytes)} total (${formatBytes(memory.usedBytes)} used)`, ` Process RSS: ${formatBytes(memory.processRssBytes)}${Number.isFinite(memory.processHeapUsedBytes) ? `, heap ${formatBytes(memory.processHeapUsedBytes)}` : ''}`, ` Disk (${disk.path || '/'}): ${formatBytes(disk.availableBytes)} available / ${formatBytes(disk.totalBytes)} total${Number.isFinite(disk.usedPercent) ? ` (${disk.usedPercent.toFixed(1)}% used)` : ''}`];
276
+ if (disk.error) lines.push(` Disk probe error: ${disk.error}`);
277
+ lines.push(buildResourceMarker(snapshot));
278
+ return lines.join('\n');
279
+ }
280
+
281
+ export async function recordResourceSnapshot({ phase, log, diskPath = '/', label = null, capture = captureResourceSnapshot } = {}) {
282
+ if (typeof log !== 'function') return null;
283
+ try {
284
+ const snapshot = capture({ phase, diskPath });
285
+ await log(formatResourceSnapshotForLog(snapshot, label));
286
+ return snapshot;
287
+ } catch (error) {
288
+ await log(`⚠️ Resource usage measurement failed (${phase || 'snapshot'}): ${error?.message || error}`, { level: 'warning', verbose: true });
289
+ return null;
290
+ }
291
+ }
292
+
293
+ export function summarizeResourceSnapshot(snapshot) {
294
+ if (!snapshot) return null;
295
+ const cpu = snapshot.cpu || {};
296
+ const memory = snapshot.memory || {};
297
+ const disk = snapshot.disk || {};
298
+ return {
299
+ phase: snapshot.phase || null,
300
+ timestamp: snapshot.timestamp || null,
301
+ cpu: {
302
+ load1: cpu.load1,
303
+ load5: cpu.load5,
304
+ load15: cpu.load15,
305
+ cpuCount: cpu.cpuCount,
306
+ },
307
+ memory: {
308
+ totalBytes: memory.totalBytes,
309
+ availableBytes: memory.availableBytes,
310
+ usedBytes: memory.usedBytes,
311
+ processRssBytes: memory.processRssBytes,
312
+ },
313
+ disk: {
314
+ path: disk.path,
315
+ totalBytes: disk.totalBytes,
316
+ availableBytes: disk.availableBytes,
317
+ usedBytes: disk.usedBytes,
318
+ usedPercent: disk.usedPercent,
319
+ error: disk.error || null,
320
+ },
321
+ };
322
+ }
@@ -32,6 +32,7 @@ const fs = (await use('fs')).promises;
32
32
  const lib = await import('./lib.mjs');
33
33
  const { log, formatAligned, extractToolErrorCore } = lib;
34
34
  const { ensurePullRequestBaseBranch } = await import('./solve.pr-base-guard.lib.mjs');
35
+ const { RESOURCE_PHASE_RESTART_AFTER, RESOURCE_PHASE_RESTART_BEFORE, recordResourceSnapshot } = await import('./solve.resource-diagnostics.lib.mjs');
35
36
 
36
37
  // Import Sentry integration
37
38
  const sentryLib = await import('./sentry.lib.mjs');
@@ -177,6 +178,13 @@ export const getUncommittedChangesDetails = async tempDir => {
177
178
  export const executeToolIteration = async params => {
178
179
  const { issueUrl, owner, repo, issueNumber, prNumber, branchName, tempDir, workspaceTmpDir, mergeStateStatus, feedbackLines, argv } = params;
179
180
 
181
+ await recordResourceSnapshot({
182
+ phase: RESOURCE_PHASE_RESTART_BEFORE,
183
+ log,
184
+ diskPath: '/',
185
+ label: 'before AI restart iteration',
186
+ });
187
+
180
188
  // Import necessary modules for tool execution
181
189
  const memoryCheck = await import('./memory-check.mjs');
182
190
  const { getResourceSnapshot } = memoryCheck;
@@ -462,6 +470,12 @@ export const executeToolIteration = async params => {
462
470
  }
463
471
 
464
472
  await ensurePullRequestBaseBranch({ owner, repo, prNumber, argv, log, formatAligned, $ });
473
+ await recordResourceSnapshot({
474
+ phase: RESOURCE_PHASE_RESTART_AFTER,
475
+ log,
476
+ diskPath: '/',
477
+ label: 'after AI restart iteration',
478
+ });
465
479
  return toolResult;
466
480
  };
467
481
 
@@ -1234,8 +1234,7 @@ bot.on('message', async (ctx, next) => {
1234
1234
  // /subscribe + /unsubscribe (#1688) are intentionally not in the text fallback — Telegraf's bot.command() is sufficient.
1235
1235
  const solveHandlers = Object.fromEntries(SOLVE_COMMAND_NAMES.map(command => [command, handleSolveCommand]));
1236
1236
  const taskHandlers = Object.fromEntries(TASK_COMMAND_NAMES.map(command => [command, handleTaskCommand]));
1237
- // /queue is the short alias for /solve_queue (issue #1837)
1238
- const handlers = { ...solveHandlers, ...taskHandlers, auth: handleAuthCommand, hive: handleHiveCommand, solve_queue: handleSolveQueueCommand, solvequeue: handleSolveQueueCommand, queue: handleSolveQueueCommand };
1237
+ const handlers = { ...solveHandlers, ...taskHandlers, auth: handleAuthCommand, hive: handleHiveCommand, queue: handleSolveQueueCommand };
1239
1238
 
1240
1239
  const handler = handlers[extracted.command];
1241
1240
  if (!handler) return next();
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Telegram /solve_queue command implementation
2
+ * Telegram /queue command implementation
3
3
  *
4
- * This module provides the /solve_queue command functionality for the Telegram bot,
4
+ * This module provides the /queue command functionality for the Telegram bot,
5
5
  * allowing users to view the current solve queue status.
6
6
  *
7
7
  * Features:
@@ -15,7 +15,7 @@
15
15
 
16
16
  import { t } from './i18n.lib.mjs';
17
17
 
18
- const GROUP_ONLY_MESSAGE = '❌ The /solve_queue command only works in group chats. Please add this bot to a group and make it an admin.';
18
+ const GROUP_ONLY_MESSAGE = '❌ The /queue command only works in group chats. Please add this bot to a group and make it an admin.';
19
19
 
20
20
  function commandText(key, params = {}, locale = null, fallback = key) {
21
21
  const translated = t(key, params, locale ? { locale } : {});
@@ -23,7 +23,7 @@ function commandText(key, params = {}, locale = null, fallback = key) {
23
23
  }
24
24
 
25
25
  /**
26
- * Registers the /solve_queue command handler with the bot
26
+ * Registers the /queue command handler with the bot
27
27
  * @param {Object} bot - The Telegraf bot instance
28
28
  * @param {Object} options - Options object
29
29
  * @param {boolean} options.VERBOSE - Whether to enable verbose logging
@@ -41,11 +41,11 @@ export function registerSolveQueueCommand(bot, options) {
41
41
  const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage, addBreadcrumb, getSolveQueue, safeReply, resolveLocale } = options;
42
42
 
43
43
  async function handleSolveQueueCommand(ctx) {
44
- VERBOSE && console.log('[VERBOSE] /solve_queue command received');
44
+ VERBOSE && console.log('[VERBOSE] /queue command received');
45
45
 
46
46
  await addBreadcrumb({
47
47
  category: 'telegram.command',
48
- message: '/solve_queue command received',
48
+ message: '/queue command received',
49
49
  level: 'info',
50
50
  data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
51
51
  });
@@ -54,18 +54,18 @@ export function registerSolveQueueCommand(bot, options) {
54
54
 
55
55
  // Ignore messages sent before bot started
56
56
  if (isOldMessage(ctx)) {
57
- VERBOSE && console.log('[VERBOSE] /solve_queue ignored: old message');
57
+ VERBOSE && console.log('[VERBOSE] /queue ignored: old message');
58
58
  return;
59
59
  }
60
60
 
61
61
  // Ignore forwarded or reply messages
62
62
  if (isForwardedOrReply(ctx)) {
63
- VERBOSE && console.log('[VERBOSE] /solve_queue ignored: forwarded or reply');
63
+ VERBOSE && console.log('[VERBOSE] /queue ignored: forwarded or reply');
64
64
  return;
65
65
  }
66
66
 
67
67
  if (!isGroupChat(ctx)) {
68
- VERBOSE && console.log('[VERBOSE] /solve_queue ignored: not a group chat');
68
+ VERBOSE && console.log('[VERBOSE] /queue ignored: not a group chat');
69
69
  await replyWithFallback(commandText('telegram.solve_queue_only_in_groups', {}, locale, GROUP_ONLY_MESSAGE), {
70
70
  reply_to_message_id: ctx.message.message_id,
71
71
  fallbackLocale: locale,
@@ -75,13 +75,13 @@ export function registerSolveQueueCommand(bot, options) {
75
75
 
76
76
  const authorize = isTopicAuthorized || (ctx => isChatAuthorized(ctx.chat.id));
77
77
  if (!authorize(ctx)) {
78
- VERBOSE && console.log('[VERBOSE] /solve_queue ignored: not authorized');
78
+ VERBOSE && console.log('[VERBOSE] /queue ignored: not authorized');
79
79
  const errMsg = buildAuthErrorMessage ? buildAuthErrorMessage(ctx) : `❌ This chat (ID: ${ctx.chat.id}) is not authorized.`;
80
80
  await replyWithFallback(errMsg, { reply_to_message_id: ctx.message.message_id, fallbackLocale: locale });
81
81
  return;
82
82
  }
83
83
 
84
- VERBOSE && console.log('[VERBOSE] /solve_queue passed all checks, generating status...');
84
+ VERBOSE && console.log('[VERBOSE] /queue passed all checks, generating status...');
85
85
 
86
86
  const solveQueue = getSolveQueue({ verbose: VERBOSE });
87
87
 
@@ -97,12 +97,7 @@ export function registerSolveQueueCommand(bot, options) {
97
97
  });
98
98
  }
99
99
 
100
- // Match /solve_queue, /solve-queue, /solvequeue, or the short /queue alias (case-insensitive)
101
- // Note: Telegram Bot API only supports underscores in command names, not hyphens.
102
- // The entity-based matching handles /solve_queue, /solvequeue, and /queue.
103
- // /solve-queue is handled by the text-based fallback in telegram-bot.mjs (issue #1232).
104
- // The /queue alias was added in issue #1837 to make checking the queue faster to type.
105
- bot.command(/^(?:solve[_-]?queue|queue)$/i, handleSolveQueueCommand);
100
+ bot.command(/^queue$/i, handleSolveQueueCommand);
106
101
 
107
102
  return { handleSolveQueueCommand };
108
103
  }
@@ -8,7 +8,7 @@ const execAsync = promisify(exec);
8
8
 
9
9
  /**
10
10
  * Build a clickable, human-readable link to a queued issue/PR for the
11
- * /solve_queue (/queue) detailed status (issue #1837).
11
+ * /queue detailed status (issue #1837).
12
12
  *
13
13
  * For GitHub issue/PR URLs we render a compact `[owner/repo#number](url)`
14
14
  * Markdown link so the list is scannable and clickable. When the label would
@@ -269,7 +269,7 @@ export async function getRunningSessionItems(verbose = false) {
269
269
  return await impl(verbose);
270
270
  } catch (error) {
271
271
  if (verbose) {
272
- console.error('[VERBOSE] /solve_queue error getting running session items:', error.message);
272
+ console.error('[VERBOSE] /queue error getting running session items:', error.message);
273
273
  }
274
274
  return [];
275
275
  }
@@ -300,9 +300,9 @@ export async function getRunningProcesses(processName, verbose = false) {
300
300
  .filter(p => p.pid);
301
301
 
302
302
  if (verbose) {
303
- console.log(`[VERBOSE] /solve_queue found ${processes.length} running ${processName} processes`);
303
+ console.log(`[VERBOSE] /queue found ${processes.length} running ${processName} processes`);
304
304
  if (processes.length > 0) {
305
- console.log(`[VERBOSE] /solve_queue processes: ${JSON.stringify(processes)}`);
305
+ console.log(`[VERBOSE] /queue processes: ${JSON.stringify(processes)}`);
306
306
  }
307
307
  }
308
308
 
@@ -312,7 +312,7 @@ export async function getRunningProcesses(processName, verbose = false) {
312
312
  };
313
313
  } catch (error) {
314
314
  if (verbose) {
315
- console.error(`[VERBOSE] /solve_queue error counting ${processName} processes:`, error.message);
315
+ console.error(`[VERBOSE] /queue error counting ${processName} processes:`, error.message);
316
316
  }
317
317
  return { count: 0, processes: [] };
318
318
  }
@@ -250,7 +250,7 @@ export class SolveQueue {
250
250
  */
251
251
  log(message) {
252
252
  if (this.verbose) {
253
- console.log(`[VERBOSE] /solve_queue: ${message}`);
253
+ console.log(`[VERBOSE] /queue: ${message}`);
254
254
  }
255
255
  }
256
256
 
@@ -1469,7 +1469,7 @@ export async function getRunningIsolatedSessions(verbose = false) {
1469
1469
  return await getRunningTrackedIsolationSessions(verbose);
1470
1470
  } catch (error) {
1471
1471
  if (verbose) {
1472
- console.error(`[VERBOSE] /solve_queue error getting isolated sessions:`, error.message);
1472
+ console.error(`[VERBOSE] /queue error getting isolated sessions:`, error.message);
1473
1473
  }
1474
1474
  return { count: 0, sessions: [], byTool: {} };
1475
1475
  }