@link-assistant/hive-mind 1.17.0 → 1.17.1

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,11 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.17.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 0e59647: Fix /solve-queue command: register /solve_queue handler, fix hint text to use underscore instead of hyphen (Telegram Bot API only supports underscores in command names)
8
+
3
9
  ## 1.17.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.17.0",
3
+ "version": "1.17.1",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -13,7 +13,7 @@
13
13
  "hive-telegram-bot": "./src/telegram-bot.mjs"
14
14
  },
15
15
  "scripts": {
16
- "test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-telegram-message-filters.mjs",
16
+ "test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-solve-queue-command.mjs",
17
17
  "test:queue": "node tests/solve-queue.test.mjs",
18
18
  "test:limits-display": "node tests/limits-display.test.mjs",
19
19
  "test:usage-limit": "node tests/test-usage-limit.mjs",
@@ -122,10 +122,10 @@ export function registerAcceptInvitesCommand(bot, options) {
122
122
  const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, addBreadcrumb } = options;
123
123
 
124
124
  bot.command(/^accept[_-]?invites$/i, async ctx => {
125
- VERBOSE && console.log('[VERBOSE] /accept-invites command received');
125
+ VERBOSE && console.log('[VERBOSE] /accept_invites command received');
126
126
  await addBreadcrumb({
127
127
  category: 'telegram.command',
128
- message: '/accept-invites command received',
128
+ message: '/accept_invites command received',
129
129
  level: 'info',
130
130
  data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
131
131
  });
@@ -165,7 +165,7 @@ export function registerAcceptInvitesCommand(bot, options) {
165
165
  } catch (err) {
166
166
  // Ignore "message not modified" errors
167
167
  if (!err.message?.includes('message is not modified')) {
168
- VERBOSE && console.log(`[VERBOSE] /accept-invites: Error updating message: ${err.message}`);
168
+ VERBOSE && console.log(`[VERBOSE] /accept_invites: Error updating message: ${err.message}`);
169
169
  }
170
170
  }
171
171
  };
@@ -228,7 +228,7 @@ export function registerAcceptInvitesCommand(bot, options) {
228
228
  state.isComplete = true;
229
229
  await updateMessage();
230
230
  } catch (error) {
231
- console.error('Error in /accept-invites:', error);
231
+ console.error('Error in /accept_invites:', error);
232
232
  const escapedError = escapeMarkdown(error.message);
233
233
  await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, `❌ Error fetching invitations: ${escapedError}\n\nMake sure \`gh\` CLI is installed and authenticated\\.`, { parse_mode: 'MarkdownV2' });
234
234
  }
@@ -683,14 +683,15 @@ bot.command('help', async ctx => {
683
683
  message += '*/hive* - ❌ Disabled\n\n';
684
684
  }
685
685
 
686
+ message += '`/solve_queue` - Show solve queue status\n';
686
687
  message += '*/limits* - Show usage limits\n';
687
688
  message += '*/version* - Show bot and runtime versions\n';
688
- message += '*/accept\\_invites* - Accept all pending GitHub invitations\n';
689
+ message += '`/accept_invites` - Accept all pending GitHub invitations\n';
689
690
  message += '*/merge* - Merge queue (experimental)\n';
690
691
  message += 'Usage: `/merge <github-repo-url>`\n';
691
692
  message += "Merges all PRs with 'ready' label sequentially.\n";
692
693
  message += '*/help* - Show this help message\n\n';
693
- message += '⚠️ *Note:* /solve, /hive, /limits, /version, /accept\\_invites and /merge commands only work in group chats.\n\n';
694
+ message += '⚠️ *Note:* /solve, /hive, /solve\\_queue, /limits, /version, /accept\\_invites and /merge commands only work in group chats.\n\n';
694
695
  message += '🔧 *Common Options:*\n';
695
696
  message += '• `--model <model>` or `-m` - Specify AI model (sonnet, opus, haiku, haiku-3-5, haiku-3)\n';
696
697
  message += '• `--base-branch <branch>` or `-b` - Target branch for PR (default: repo default branch)\n';
@@ -839,6 +840,19 @@ registerMergeCommand(bot, {
839
840
  addBreadcrumb,
840
841
  });
841
842
 
843
+ // Register /solve_queue command from separate module (issue #1232)
844
+ const { registerSolveQueueCommand } = await import('./telegram-solve-queue-command.lib.mjs');
845
+ const { handleSolveQueueCommand } = registerSolveQueueCommand(bot, {
846
+ VERBOSE,
847
+ isOldMessage,
848
+ isForwardedOrReply,
849
+ isGroupChat,
850
+ isChatAuthorized,
851
+ addBreadcrumb,
852
+ getSolveQueue,
853
+ getRunningClaudeProcesses,
854
+ });
855
+
842
856
  // Named handler for /solve command - extracted for reuse by text-based fallback (issue #1207)
843
857
  async function handleSolveCommand(ctx) {
844
858
  if (VERBOSE) {
@@ -1023,7 +1037,7 @@ async function handleSolveCommand(ctx) {
1023
1037
  const existingItem = solveQueue.findByUrl(normalizedUrl);
1024
1038
  if (existingItem) {
1025
1039
  const statusText = existingItem.status === 'starting' || existingItem.status === 'started' ? 'being processed' : 'already in the queue';
1026
- await ctx.reply(`❌ This URL is ${statusText}.\n\nURL: ${escapeMarkdown(normalizedUrl)}\nStatus: ${existingItem.status}\n\n💡 Use /solve-queue to check the queue status.`, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
1040
+ await ctx.reply(`❌ This URL is ${statusText}.\n\nURL: ${escapeMarkdown(normalizedUrl)}\nStatus: ${existingItem.status}\n\n💡 Use /solve_queue to check the queue status.`, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
1027
1041
  return;
1028
1042
  }
1029
1043
 
@@ -1261,6 +1275,8 @@ bot.on('message', async (ctx, next) => {
1261
1275
  const handlers = {
1262
1276
  solve: handleSolveCommand,
1263
1277
  hive: handleHiveCommand,
1278
+ solve_queue: handleSolveQueueCommand,
1279
+ solvequeue: handleSolveQueueCommand,
1264
1280
  };
1265
1281
 
1266
1282
  const handler = handlers[extracted.command];
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Telegram /solve_queue command implementation
3
+ *
4
+ * This module provides the /solve_queue command functionality for the Telegram bot,
5
+ * allowing users to view the current solve queue status.
6
+ *
7
+ * Features:
8
+ * - Shows pending, processing, completed, and failed queue items
9
+ * - Per-tool queue breakdown (claude, opencode, etc.)
10
+ * - Lists currently processing and waiting items
11
+ * - Running Claude process count
12
+ *
13
+ * @see https://github.com/link-assistant/hive-mind/issues/1232
14
+ */
15
+
16
+ /**
17
+ * Registers the /solve_queue command handler with the bot
18
+ * @param {Object} bot - The Telegraf bot instance
19
+ * @param {Object} options - Options object
20
+ * @param {boolean} options.VERBOSE - Whether to enable verbose logging
21
+ * @param {Function} options.isOldMessage - Function to check if message is old
22
+ * @param {Function} options.isForwardedOrReply - Function to check if message is forwarded/reply
23
+ * @param {Function} options.isGroupChat - Function to check if chat is a group
24
+ * @param {Function} options.isChatAuthorized - Function to check if chat is authorized
25
+ * @param {Function} options.addBreadcrumb - Function to add breadcrumbs for monitoring
26
+ * @param {Function} options.getSolveQueue - Function to get the solve queue instance
27
+ * @param {Function} options.getRunningClaudeProcesses - Function to get running claude processes
28
+ * @returns {{ handleSolveQueueCommand: Function }} The command handler for use in text fallback
29
+ */
30
+ export function registerSolveQueueCommand(bot, options) {
31
+ const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, addBreadcrumb, getSolveQueue, getRunningClaudeProcesses } = options;
32
+
33
+ async function handleSolveQueueCommand(ctx) {
34
+ VERBOSE && console.log('[VERBOSE] /solve_queue command received');
35
+
36
+ await addBreadcrumb({
37
+ category: 'telegram.command',
38
+ message: '/solve_queue command received',
39
+ level: 'info',
40
+ data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
41
+ });
42
+
43
+ // Ignore messages sent before bot started
44
+ if (isOldMessage(ctx)) {
45
+ VERBOSE && console.log('[VERBOSE] /solve_queue ignored: old message');
46
+ return;
47
+ }
48
+
49
+ // Ignore forwarded or reply messages
50
+ if (isForwardedOrReply(ctx)) {
51
+ VERBOSE && console.log('[VERBOSE] /solve_queue ignored: forwarded or reply');
52
+ return;
53
+ }
54
+
55
+ if (!isGroupChat(ctx)) {
56
+ VERBOSE && console.log('[VERBOSE] /solve_queue ignored: not a group chat');
57
+ await ctx.reply('❌ The /solve_queue command only works in group chats. Please add this bot to a group and make it an admin.', {
58
+ reply_to_message_id: ctx.message.message_id,
59
+ });
60
+ return;
61
+ }
62
+
63
+ const chatId = ctx.chat.id;
64
+ if (!isChatAuthorized(chatId)) {
65
+ VERBOSE && console.log('[VERBOSE] /solve_queue ignored: chat not authorized');
66
+ await ctx.reply(`❌ This chat (ID: ${chatId}) is not authorized to use this bot. Please contact the bot administrator.`, {
67
+ reply_to_message_id: ctx.message.message_id,
68
+ });
69
+ return;
70
+ }
71
+
72
+ VERBOSE && console.log('[VERBOSE] /solve_queue passed all checks, generating status...');
73
+
74
+ const solveQueue = getSolveQueue({ verbose: VERBOSE });
75
+ const claudeProcs = await getRunningClaudeProcesses(VERBOSE);
76
+
77
+ // Use the queue's built-in detailed status formatter
78
+ let message = solveQueue.formatDetailedStatus();
79
+
80
+ // Add running Claude processes info
81
+ message += `\n🖥️ Running Claude processes: ${claudeProcs.count}`;
82
+
83
+ await ctx.reply(message, {
84
+ parse_mode: 'Markdown',
85
+ reply_to_message_id: ctx.message.message_id,
86
+ });
87
+ }
88
+
89
+ // Match /solve_queue, /solve-queue, or /solvequeue (case-insensitive)
90
+ // Note: Telegram Bot API only supports underscores in command names, not hyphens.
91
+ // The entity-based matching handles /solve_queue and /solvequeue.
92
+ // /solve-queue is handled by the text-based fallback in telegram-bot.mjs (issue #1232).
93
+ bot.command(/^solve[_-]?queue$/i, handleSolveQueueCommand);
94
+
95
+ return { handleSolveQueueCommand };
96
+ }
@@ -37,11 +37,12 @@ export const QUEUE_CONFIG = {
37
37
  RAM_THRESHOLD: 0.65, // Enqueue if RAM usage >= 65%
38
38
  // CPU threshold uses 5-minute load average, not instantaneous CPU usage
39
39
  CPU_THRESHOLD: 0.65, // Enqueue if 5-minute load average >= 65% of CPU count
40
- DISK_THRESHOLD: 0.9, // One-at-a-time if disk usage >= 90%
40
+ DISK_THRESHOLD: 0.9, // One-at-a-time if disk usage >= 90%, tuned for VM with 100 GB drive
41
41
 
42
42
  // API limit thresholds (usage ratios: 0.0 - 1.0)
43
43
  // All thresholds use >= comparison (inclusive)
44
- CLAUDE_5_HOUR_SESSION_THRESHOLD: 0.75, // One-at-a-time if 5-hour limit >= 75%
44
+ // Fine-tuned for Claude MAX $200 subscription
45
+ CLAUDE_5_HOUR_SESSION_THRESHOLD: 0.65, // One-at-a-time if 5-hour limit >= 65%
45
46
  CLAUDE_WEEKLY_THRESHOLD: 0.97, // One-at-a-time if weekly limit >= 97%
46
47
  GITHUB_API_THRESHOLD: 0.75, // Enqueue if GitHub >= 75% with parallel claude
47
48
 
@@ -92,9 +93,9 @@ export async function getRunningClaudeProcesses(verbose = false) {
92
93
  .filter(p => p.pid);
93
94
 
94
95
  if (verbose) {
95
- console.log(`[VERBOSE] /solve-queue found ${processes.length} running claude processes`);
96
+ console.log(`[VERBOSE] /solve_queue found ${processes.length} running claude processes`);
96
97
  if (processes.length > 0) {
97
- console.log(`[VERBOSE] /solve-queue processes: ${JSON.stringify(processes)}`);
98
+ console.log(`[VERBOSE] /solve_queue processes: ${JSON.stringify(processes)}`);
98
99
  }
99
100
  }
100
101
 
@@ -104,7 +105,7 @@ export async function getRunningClaudeProcesses(verbose = false) {
104
105
  };
105
106
  } catch (error) {
106
107
  if (verbose) {
107
- console.error('[VERBOSE] /solve-queue error counting claude processes:', error.message);
108
+ console.error('[VERBOSE] /solve_queue error counting claude processes:', error.message);
108
109
  }
109
110
  return { count: 0, processes: [] };
110
111
  }
@@ -333,7 +334,7 @@ export class SolveQueue {
333
334
  */
334
335
  log(message) {
335
336
  if (this.verbose) {
336
- console.log(`[VERBOSE] /solve-queue: ${message}`);
337
+ console.log(`[VERBOSE] /solve_queue: ${message}`);
337
338
  }
338
339
  }
339
340
 
@@ -837,7 +838,7 @@ export class SolveQueue {
837
838
 
838
839
  this.consumerTask = this.runConsumer();
839
840
  this.consumerTask.catch(error => {
840
- console.error('[solve-queue] Consumer error:', error);
841
+ console.error('[solve_queue] Consumer error:', error);
841
842
  this.consumerTask = null;
842
843
  });
843
844
  }
@@ -933,7 +934,7 @@ export class SolveQueue {
933
934
 
934
935
  // Execute in background
935
936
  this.executeItem(item).catch(error => {
936
- console.error(`[solve-queue] Execution error for ${item.id}:`, error);
937
+ console.error(`[solve_queue] Execution error for ${item.id}:`, error);
937
938
  });
938
939
  }
939
940
  }
@@ -1011,7 +1012,7 @@ export class SolveQueue {
1011
1012
  } catch (error) {
1012
1013
  // Log message edit failures for debugging
1013
1014
  // See: https://github.com/link-assistant/hive-mind/issues/1062
1014
- console.error(`[solve-queue] Failed to update message for item ${item.id}: ${error.message}`);
1015
+ console.error(`[solve_queue] Failed to update message for item ${item.id}: ${error.message}`);
1015
1016
  }
1016
1017
  }
1017
1018
  }
@@ -1022,7 +1023,7 @@ export class SolveQueue {
1022
1023
  } catch (error) {
1023
1024
  item.setFailed(error);
1024
1025
  this.stats.totalFailed++;
1025
- console.error(`[solve-queue] Item failed: ${item.id}`, error);
1026
+ console.error(`[solve_queue] Item failed: ${item.id}`, error);
1026
1027
 
1027
1028
  // Try to update message with error
1028
1029
  const { chatId, messageId } = item.messageInfo || {};
@@ -1032,7 +1033,7 @@ export class SolveQueue {
1032
1033
  } catch (editError) {
1033
1034
  // Log the edit failure for debugging
1034
1035
  // See: https://github.com/link-assistant/hive-mind/issues/1062
1035
- console.error(`[solve-queue] Failed to update error message for item ${item.id}: ${editError.message}`);
1036
+ console.error(`[solve_queue] Failed to update error message for item ${item.id}: ${editError.message}`);
1036
1037
  }
1037
1038
  }
1038
1039
  } finally {