@link-assistant/hive-mind 0.46.0 → 0.47.0

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +26 -13
  2. package/README.md +42 -8
  3. package/package.json +16 -3
  4. package/src/agent.lib.mjs +49 -70
  5. package/src/agent.prompts.lib.mjs +6 -20
  6. package/src/buildUserMention.lib.mjs +4 -17
  7. package/src/claude-limits.lib.mjs +15 -15
  8. package/src/claude.lib.mjs +617 -626
  9. package/src/claude.prompts.lib.mjs +7 -22
  10. package/src/codex.lib.mjs +39 -71
  11. package/src/codex.prompts.lib.mjs +6 -20
  12. package/src/config.lib.mjs +3 -16
  13. package/src/contributing-guidelines.lib.mjs +5 -18
  14. package/src/exit-handler.lib.mjs +4 -4
  15. package/src/git.lib.mjs +7 -7
  16. package/src/github-issue-creator.lib.mjs +17 -17
  17. package/src/github-linking.lib.mjs +8 -33
  18. package/src/github.batch.lib.mjs +20 -16
  19. package/src/github.graphql.lib.mjs +18 -18
  20. package/src/github.lib.mjs +89 -91
  21. package/src/hive.config.lib.mjs +50 -50
  22. package/src/hive.mjs +1293 -1296
  23. package/src/instrument.mjs +7 -11
  24. package/src/interactive-mode.lib.mjs +112 -138
  25. package/src/lenv-reader.lib.mjs +1 -6
  26. package/src/lib.mjs +36 -45
  27. package/src/lino.lib.mjs +2 -2
  28. package/src/local-ci-checks.lib.mjs +15 -14
  29. package/src/memory-check.mjs +52 -60
  30. package/src/model-mapping.lib.mjs +25 -32
  31. package/src/model-validation.lib.mjs +31 -31
  32. package/src/opencode.lib.mjs +37 -62
  33. package/src/opencode.prompts.lib.mjs +7 -21
  34. package/src/protect-branch.mjs +14 -15
  35. package/src/review.mjs +28 -27
  36. package/src/reviewers-hive.mjs +64 -69
  37. package/src/sentry.lib.mjs +13 -10
  38. package/src/solve.auto-continue.lib.mjs +48 -38
  39. package/src/solve.auto-pr.lib.mjs +111 -69
  40. package/src/solve.branch-errors.lib.mjs +17 -46
  41. package/src/solve.branch.lib.mjs +16 -23
  42. package/src/solve.config.lib.mjs +263 -261
  43. package/src/solve.error-handlers.lib.mjs +21 -79
  44. package/src/solve.execution.lib.mjs +10 -18
  45. package/src/solve.feedback.lib.mjs +25 -46
  46. package/src/solve.mjs +59 -60
  47. package/src/solve.preparation.lib.mjs +10 -36
  48. package/src/solve.repo-setup.lib.mjs +4 -19
  49. package/src/solve.repository.lib.mjs +37 -37
  50. package/src/solve.results.lib.mjs +32 -46
  51. package/src/solve.session.lib.mjs +7 -22
  52. package/src/solve.validation.lib.mjs +19 -17
  53. package/src/solve.watch.lib.mjs +20 -33
  54. package/src/start-screen.mjs +24 -24
  55. package/src/task.mjs +38 -44
  56. package/src/telegram-bot.mjs +125 -121
  57. package/src/telegram-top-command.lib.mjs +32 -48
  58. package/src/usage-limit.lib.mjs +9 -13
  59. package/src/version-info.lib.mjs +1 -1
  60. package/src/version.lib.mjs +1 -1
  61. package/src/youtrack/solve.youtrack.lib.mjs +3 -8
  62. package/src/youtrack/youtrack-sync.mjs +8 -14
  63. package/src/youtrack/youtrack.lib.mjs +26 -28
@@ -59,61 +59,61 @@ const config = yargs(hideBin(process.argv))
59
59
  type: 'string',
60
60
  description: 'LINO configuration string for environment variables',
61
61
  alias: 'c',
62
- default: getenv('TELEGRAM_CONFIGURATION', '')
62
+ default: getenv('TELEGRAM_CONFIGURATION', ''),
63
63
  })
64
64
  .option('token', {
65
65
  type: 'string',
66
66
  description: 'Telegram bot token from @BotFather',
67
67
  alias: 't',
68
- default: getenv('TELEGRAM_BOT_TOKEN', '')
68
+ default: getenv('TELEGRAM_BOT_TOKEN', ''),
69
69
  })
70
70
  .option('allowedChats', {
71
71
  type: 'string',
72
72
  description: 'Allowed chat IDs in lino notation, e.g., "(\n 123456789\n 987654321\n)"',
73
73
  alias: 'allowed-chats',
74
- default: getenv('TELEGRAM_ALLOWED_CHATS', '')
74
+ default: getenv('TELEGRAM_ALLOWED_CHATS', ''),
75
75
  })
76
76
  .option('solveOverrides', {
77
77
  type: 'string',
78
78
  description: 'Override options for /solve command in lino notation, e.g., "(\n --auto-continue\n --attach-logs\n)"',
79
79
  alias: 'solve-overrides',
80
- default: getenv('TELEGRAM_SOLVE_OVERRIDES', '')
80
+ default: getenv('TELEGRAM_SOLVE_OVERRIDES', ''),
81
81
  })
82
82
  .option('hiveOverrides', {
83
83
  type: 'string',
84
84
  description: 'Override options for /hive command in lino notation, e.g., "(\n --verbose\n --all-issues\n)"',
85
85
  alias: 'hive-overrides',
86
- default: getenv('TELEGRAM_HIVE_OVERRIDES', '')
86
+ default: getenv('TELEGRAM_HIVE_OVERRIDES', ''),
87
87
  })
88
88
  .option('solve', {
89
89
  type: 'boolean',
90
90
  description: 'Enable /solve command (use --no-solve to disable)',
91
- default: getenv('TELEGRAM_SOLVE', 'true') !== 'false'
91
+ default: getenv('TELEGRAM_SOLVE', 'true') !== 'false',
92
92
  })
93
93
  .option('hive', {
94
94
  type: 'boolean',
95
95
  description: 'Enable /hive command (use --no-hive to disable)',
96
- default: getenv('TELEGRAM_HIVE', 'true') !== 'false'
96
+ default: getenv('TELEGRAM_HIVE', 'true') !== 'false',
97
97
  })
98
98
  .option('dryRun', {
99
99
  type: 'boolean',
100
100
  description: 'Validate configuration and options without starting the bot',
101
101
  alias: 'dry-run',
102
- default: false
102
+ default: false,
103
103
  })
104
104
  .option('verbose', {
105
105
  type: 'boolean',
106
106
  description: 'Enable verbose logging for debugging',
107
107
  alias: 'v',
108
- default: getenv('TELEGRAM_BOT_VERBOSE', 'false') === 'true'
108
+ default: getenv('TELEGRAM_BOT_VERBOSE', 'false') === 'true',
109
109
  })
110
110
  .help('h')
111
111
  .alias('h', 'help')
112
112
  .parserConfiguration({
113
113
  'boolean-negation': true,
114
- 'strip-dashed': true // Remove dashed keys from argv to simplify validation
114
+ 'strip-dashed': true, // Remove dashed keys from argv to simplify validation
115
115
  })
116
- .strict() // Enable strict mode to reject unknown options (consistent with solve.mjs and hive.mjs)
116
+ .strict() // Enable strict mode to reject unknown options (consistent with solve.mjs and hive.mjs)
117
117
  .parse();
118
118
 
119
119
  // Load configuration from --configuration option if provided
@@ -147,19 +147,23 @@ if (!BOT_TOKEN) {
147
147
  // Priority: CLI option > environment variable (from .lenv or .env)
148
148
  // NOTE: This section moved BEFORE loading telegraf for faster dry-run mode (issue #801)
149
149
  const resolvedAllowedChats = config.allowedChats || getenv('TELEGRAM_ALLOWED_CHATS', '');
150
- const allowedChats = resolvedAllowedChats
151
- ? lino.parseNumericIds(resolvedAllowedChats)
152
- : null;
150
+ const allowedChats = resolvedAllowedChats ? lino.parseNumericIds(resolvedAllowedChats) : null;
153
151
 
154
152
  // Parse override options
155
153
  const resolvedSolveOverrides = config.solveOverrides || getenv('TELEGRAM_SOLVE_OVERRIDES', '');
156
154
  const solveOverrides = resolvedSolveOverrides
157
- ? lino.parse(resolvedSolveOverrides).map(line => line.trim()).filter(line => line)
155
+ ? lino
156
+ .parse(resolvedSolveOverrides)
157
+ .map(line => line.trim())
158
+ .filter(line => line)
158
159
  : [];
159
160
 
160
161
  const resolvedHiveOverrides = config.hiveOverrides || getenv('TELEGRAM_HIVE_OVERRIDES', '');
161
162
  const hiveOverrides = resolvedHiveOverrides
162
- ? lino.parse(resolvedHiveOverrides).map(line => line.trim()).filter(line => line)
163
+ ? lino
164
+ .parse(resolvedHiveOverrides)
165
+ .map(line => line.trim())
166
+ .filter(line => line)
163
167
  : [];
164
168
 
165
169
  // Command enable/disable flags
@@ -179,7 +183,7 @@ if (solveEnabled && solveOverrides.length > 0) {
179
183
  // Temporarily suppress stderr to avoid yargs error output during validation
180
184
  const originalStderrWrite = process.stderr.write;
181
185
  const stderrBuffer = [];
182
- process.stderr.write = (chunk) => {
186
+ process.stderr.write = chunk => {
183
187
  stderrBuffer.push(chunk);
184
188
  return true;
185
189
  };
@@ -219,7 +223,7 @@ if (hiveEnabled && hiveOverrides.length > 0) {
219
223
  // Temporarily suppress stderr to avoid yargs error output during validation
220
224
  const originalStderrWrite = process.stderr.write;
221
225
  const stderrBuffer = [];
222
- process.stderr.write = (chunk) => {
226
+ process.stderr.write = chunk => {
223
227
  stderrBuffer.push(chunk);
224
228
  return true;
225
229
  };
@@ -289,7 +293,7 @@ const { Telegraf } = telegrafModule;
289
293
  const bot = new Telegraf(BOT_TOKEN, {
290
294
  // Remove the default 90-second timeout for message handlers
291
295
  // This is important because command handlers (like /solve) spawn long-running processes
292
- handlerTimeout: Infinity
296
+ handlerTimeout: Infinity,
293
297
  });
294
298
 
295
299
  // Track bot startup time to ignore messages sent before bot started
@@ -351,9 +355,7 @@ function isForwardedOrReply(ctx) {
351
355
  return true;
352
356
  }
353
357
  // Also check old forwarding API fields for backward compatibility
354
- if (message.forward_from || message.forward_from_chat ||
355
- message.forward_from_message_id || message.forward_signature ||
356
- message.forward_sender_name || message.forward_date) {
358
+ if (message.forward_from || message.forward_from_chat || message.forward_from_message_id || message.forward_signature || message.forward_sender_name || message.forward_date) {
357
359
  if (VERBOSE) {
358
360
  console.log('[VERBOSE] isForwardedOrReply: TRUE - old forwarding API field detected');
359
361
  if (message.forward_from) console.log('[VERBOSE] Triggered by: forward_from');
@@ -409,16 +411,14 @@ async function executeStartScreen(command, args) {
409
411
  const whichPath = await findStartScreenCommand();
410
412
 
411
413
  if (!whichPath) {
412
- const warningMsg = '⚠️ WARNING: start-screen command not found in PATH\n' +
413
- 'Please ensure @link-assistant/hive-mind is properly installed\n' +
414
- 'You may need to run: npm install -g @link-assistant/hive-mind';
414
+ const warningMsg = '⚠️ WARNING: start-screen command not found in PATH\n' + 'Please ensure @link-assistant/hive-mind is properly installed\n' + 'You may need to run: npm install -g @link-assistant/hive-mind';
415
415
  console.warn(warningMsg);
416
416
 
417
417
  // Still try to execute with 'start-screen' in case it's available in PATH but 'which' failed
418
418
  return {
419
419
  success: false,
420
420
  warning: warningMsg,
421
- error: 'start-screen command not found in PATH'
421
+ error: 'start-screen command not found in PATH',
422
422
  };
423
423
  }
424
424
 
@@ -433,13 +433,13 @@ async function executeStartScreen(command, args) {
433
433
  return {
434
434
  success: false,
435
435
  output: '',
436
- error: error.message
436
+ error: error.message,
437
437
  };
438
438
  }
439
439
  }
440
440
 
441
441
  function executeWithCommand(startScreenCmd, command, args) {
442
- return new Promise((resolve) => {
442
+ return new Promise(resolve => {
443
443
  const allArgs = [command, ...args];
444
444
 
445
445
  if (VERBOSE) {
@@ -451,39 +451,39 @@ function executeWithCommand(startScreenCmd, command, args) {
451
451
  const child = spawn(startScreenCmd, allArgs, {
452
452
  stdio: ['ignore', 'pipe', 'pipe'],
453
453
  detached: false,
454
- env: process.env
454
+ env: process.env,
455
455
  });
456
456
 
457
457
  let stdout = '';
458
458
  let stderr = '';
459
459
 
460
- child.stdout.on('data', (data) => {
460
+ child.stdout.on('data', data => {
461
461
  stdout += data.toString();
462
462
  });
463
463
 
464
- child.stderr.on('data', (data) => {
464
+ child.stderr.on('data', data => {
465
465
  stderr += data.toString();
466
466
  });
467
467
 
468
- child.on('error', (error) => {
468
+ child.on('error', error => {
469
469
  resolve({
470
470
  success: false,
471
471
  output: stdout,
472
- error: error.message
472
+ error: error.message,
473
473
  });
474
474
  });
475
475
 
476
- child.on('close', (code) => {
476
+ child.on('close', code => {
477
477
  if (code === 0) {
478
478
  resolve({
479
479
  success: true,
480
- output: stdout
480
+ output: stdout,
481
481
  });
482
482
  } else {
483
483
  resolve({
484
484
  success: false,
485
485
  output: stdout,
486
- error: stderr || `Command exited with code ${code}`
486
+ error: stderr || `Command exited with code ${code}`,
487
487
  });
488
488
  }
489
489
  });
@@ -616,15 +616,12 @@ function mergeArgsWithOverrides(userArgs, overrides) {
616
616
  */
617
617
  function validateGitHubUrl(args, options = {}) {
618
618
  // Default options for /solve command (backward compatibility)
619
- const {
620
- allowedTypes = ['issue', 'pull'],
621
- commandName = 'solve'
622
- } = options;
619
+ const { allowedTypes = ['issue', 'pull'], commandName = 'solve' } = options;
623
620
 
624
621
  if (args.length === 0) {
625
622
  return {
626
623
  valid: false,
627
- error: `Missing GitHub URL. Usage: /${commandName} <github-url> [options]`
624
+ error: `Missing GitHub URL. Usage: /${commandName} <github-url> [options]`,
628
625
  };
629
626
  }
630
627
 
@@ -632,7 +629,7 @@ function validateGitHubUrl(args, options = {}) {
632
629
  if (!url.includes('github.com')) {
633
630
  return {
634
631
  valid: false,
635
- error: 'First argument must be a GitHub URL'
632
+ error: 'First argument must be a GitHub URL',
636
633
  };
637
634
  }
638
635
 
@@ -642,16 +639,16 @@ function validateGitHubUrl(args, options = {}) {
642
639
  return {
643
640
  valid: false,
644
641
  error: parsed.error || 'Invalid GitHub URL',
645
- suggestion: parsed.suggestion
642
+ suggestion: parsed.suggestion,
646
643
  };
647
644
  }
648
645
 
649
646
  // Check if the URL type is allowed for this command
650
647
  if (!allowedTypes.includes(parsed.type)) {
651
- const allowedTypesStr = allowedTypes.map(t => t === 'pull' ? 'pull request' : t).join(', ');
648
+ const allowedTypesStr = allowedTypes.map(t => (t === 'pull' ? 'pull request' : t)).join(', ');
652
649
  return {
653
650
  valid: false,
654
- error: `URL must be a GitHub ${allowedTypesStr} (not ${parsed.type})`
651
+ error: `URL must be a GitHub ${allowedTypesStr} (not ${parsed.type})`,
655
652
  };
656
653
  }
657
654
 
@@ -702,12 +699,12 @@ function extractGitHubUrl(text) {
702
699
  return {
703
700
  url: null,
704
701
  error: `Found ${foundUrls.length} GitHub links in the message. Please reply to a message with only one GitHub issue or PR link.`,
705
- linkCount: foundUrls.length
702
+ linkCount: foundUrls.length,
706
703
  };
707
704
  }
708
705
  }
709
706
 
710
- bot.command('help', async (ctx) => {
707
+ bot.command('help', async ctx => {
711
708
  if (VERBOSE) {
712
709
  console.log('[VERBOSE] /help command received');
713
710
  }
@@ -795,7 +792,7 @@ bot.command('help', async (ctx) => {
795
792
  await ctx.reply(message, { parse_mode: 'Markdown' });
796
793
  });
797
794
 
798
- bot.command('limits', async (ctx) => {
795
+ bot.command('limits', async ctx => {
799
796
  if (VERBOSE) {
800
797
  console.log('[VERBOSE] /limits command received');
801
798
  }
@@ -847,7 +844,9 @@ bot.command('limits', async (ctx) => {
847
844
  }
848
845
 
849
846
  // Send "fetching" message to indicate work is in progress
850
- const fetchingMessage = await ctx.reply('🔄 Fetching Claude usage limits...', { reply_to_message_id: ctx.message.message_id });
847
+ const fetchingMessage = await ctx.reply('🔄 Fetching Claude usage limits...', {
848
+ reply_to_message_id: ctx.message.message_id,
849
+ });
851
850
 
852
851
  // Get the usage limits using the library function
853
852
  const result = await getClaudeUsageLimits(VERBOSE);
@@ -856,39 +855,36 @@ bot.command('limits', async (ctx) => {
856
855
  // Edit the fetching message to show the error
857
856
  // Escape the error message for MarkdownV2, preserving inline code blocks
858
857
  const escapedError = escapeMarkdownV2(result.error, { preserveCodeBlocks: true });
859
- await ctx.telegram.editMessageText(
860
- fetchingMessage.chat.id,
861
- fetchingMessage.message_id,
862
- undefined,
863
- `❌ ${escapedError}`,
864
- { parse_mode: 'MarkdownV2' }
865
- );
858
+ await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, `❌ ${escapedError}`, { parse_mode: 'MarkdownV2' });
866
859
  return;
867
860
  }
868
861
 
869
862
  // Format and edit the fetching message with the results
870
863
  const message = '📊 *Claude Usage Limits*\n\n' + formatUsageMessage(result.usage);
871
- await ctx.telegram.editMessageText(
872
- fetchingMessage.chat.id,
873
- fetchingMessage.message_id,
874
- undefined,
875
- message,
876
- { parse_mode: 'Markdown' }
877
- );
864
+ await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, message, {
865
+ parse_mode: 'Markdown',
866
+ });
878
867
  });
879
- bot.command('version', async (ctx) => {
868
+ bot.command('version', async ctx => {
880
869
  VERBOSE && console.log('[VERBOSE] /version command received');
881
- await addBreadcrumb({ category: 'telegram.command', message: '/version command received', level: 'info', data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username } });
870
+ await addBreadcrumb({
871
+ category: 'telegram.command',
872
+ message: '/version command received',
873
+ level: 'info',
874
+ data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
875
+ });
882
876
  if (isOldMessage(ctx) || isForwardedOrReply(ctx)) return;
883
877
  if (!isGroupChat(ctx)) return await ctx.reply('❌ The /version command only works in group chats. Please add this bot to a group and make it an admin.', { reply_to_message_id: ctx.message.message_id });
884
878
  const chatId = ctx.chat.id;
885
879
  if (!isChatAuthorized(chatId)) return await ctx.reply(`❌ This chat (ID: ${chatId}) is not authorized to use this bot. Please contact the bot administrator.`, { reply_to_message_id: ctx.message.message_id });
886
- const fetchingMessage = await ctx.reply('🔄 Gathering version information...', { reply_to_message_id: ctx.message.message_id });
880
+ const fetchingMessage = await ctx.reply('🔄 Gathering version information...', {
881
+ reply_to_message_id: ctx.message.message_id,
882
+ });
887
883
  const result = await getVersionInfo(VERBOSE);
888
884
  if (!result.success) return await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, `❌ ${escapeMarkdownV2(result.error, { preserveCodeBlocks: true })}`, { parse_mode: 'MarkdownV2' });
889
885
  await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, '🤖 *Version Information*\n\n' + formatVersionMessage(result.versions), { parse_mode: 'Markdown' });
890
886
  });
891
- bot.command(/^solve$/i, async (ctx) => {
887
+ bot.command(/^solve$/i, async ctx => {
892
888
  if (VERBOSE) {
893
889
  console.log('[VERBOSE] /solve command received');
894
890
  }
@@ -926,9 +922,7 @@ bot.command(/^solve$/i, async (ctx) => {
926
922
  // But allow reply messages for URL extraction feature
927
923
  const message = ctx.message;
928
924
  const isForwarded = message.forward_origin && message.forward_origin.type;
929
- const isOldApiForwarded = message.forward_from || message.forward_from_chat ||
930
- message.forward_from_message_id || message.forward_signature ||
931
- message.forward_sender_name || message.forward_date;
925
+ const isOldApiForwarded = message.forward_from || message.forward_from_chat || message.forward_from_message_id || message.forward_signature || message.forward_sender_name || message.forward_date;
932
926
 
933
927
  if (isForwarded || isOldApiForwarded) {
934
928
  if (VERBOSE) {
@@ -962,9 +956,7 @@ bot.command(/^solve$/i, async (ctx) => {
962
956
 
963
957
  // Check if this is a reply to a message and user didn't provide URL
964
958
  // In that case, try to extract GitHub URL from the replied message
965
- const isReply = message.reply_to_message &&
966
- message.reply_to_message.message_id &&
967
- !message.reply_to_message.forum_topic_created;
959
+ const isReply = message.reply_to_message && message.reply_to_message.message_id && !message.reply_to_message.forum_topic_created;
968
960
 
969
961
  if (isReply && userArgs.length === 0) {
970
962
  if (VERBOSE) {
@@ -979,7 +971,10 @@ bot.command(/^solve$/i, async (ctx) => {
979
971
  if (VERBOSE) {
980
972
  console.log('[VERBOSE] Multiple GitHub URLs found in replied message');
981
973
  }
982
- await ctx.reply(`❌ ${extraction.error}`, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
974
+ await ctx.reply(`❌ ${extraction.error}`, {
975
+ parse_mode: 'Markdown',
976
+ reply_to_message_id: ctx.message.message_id,
977
+ });
983
978
  return;
984
979
  } else if (extraction.url) {
985
980
  // Single link found
@@ -1037,17 +1032,18 @@ bot.command(/^solve$/i, async (ctx) => {
1037
1032
  // Configure yargs to throw errors instead of trying to exit the process
1038
1033
  // This prevents confusing error messages when validation fails but execution continues
1039
1034
  let failureMessage = null;
1040
- testYargs
1041
- .exitProcess(false)
1042
- .fail((msg, err) => {
1043
- // Capture the failure message instead of letting yargs print it
1044
- failureMessage = msg || (err && err.message) || 'Unknown validation error';
1045
- throw new Error(failureMessage);
1046
- });
1035
+ testYargs.exitProcess(false).fail((msg, err) => {
1036
+ // Capture the failure message instead of letting yargs print it
1037
+ failureMessage = msg || (err && err.message) || 'Unknown validation error';
1038
+ throw new Error(failureMessage);
1039
+ });
1047
1040
 
1048
1041
  testYargs.parse(args);
1049
1042
  } catch (error) {
1050
- await ctx.reply(`❌ Invalid options: ${error.message || String(error)}\n\nUse /help to see available options`, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
1043
+ await ctx.reply(`❌ Invalid options: ${error.message || String(error)}\n\nUse /help to see available options`, {
1044
+ parse_mode: 'Markdown',
1045
+ reply_to_message_id: ctx.message.message_id,
1046
+ });
1051
1047
  return;
1052
1048
  }
1053
1049
 
@@ -1068,8 +1064,7 @@ bot.command(/^solve$/i, async (ctx) => {
1068
1064
  }
1069
1065
 
1070
1066
  if (result.success) {
1071
- const sessionNameMatch = result.output.match(/session:\s*(\S+)/i) ||
1072
- result.output.match(/screen -r\s+(\S+)/);
1067
+ const sessionNameMatch = result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -r\s+(\S+)/);
1073
1068
  const sessionName = sessionNameMatch ? sessionNameMatch[1] : 'unknown';
1074
1069
 
1075
1070
  let response = '✅ Solve command started successfully!\n\n';
@@ -1083,7 +1078,7 @@ bot.command(/^solve$/i, async (ctx) => {
1083
1078
  }
1084
1079
  });
1085
1080
 
1086
- bot.command(/^hive$/i, async (ctx) => {
1081
+ bot.command(/^hive$/i, async ctx => {
1087
1082
  if (VERBOSE) {
1088
1083
  console.log('[VERBOSE] /hive command received');
1089
1084
  }
@@ -1151,7 +1146,7 @@ bot.command(/^hive$/i, async (ctx) => {
1151
1146
  const validation = validateGitHubUrl(userArgs, {
1152
1147
  allowedTypes: ['repo', 'organization', 'user'],
1153
1148
  commandName: 'hive',
1154
- exampleUrl: 'https://github.com/owner/repo'
1149
+ exampleUrl: 'https://github.com/owner/repo',
1155
1150
  });
1156
1151
  if (!validation.valid) {
1157
1152
  let errorMsg = `❌ ${validation.error}`;
@@ -1191,17 +1186,18 @@ bot.command(/^hive$/i, async (ctx) => {
1191
1186
  // Configure yargs to throw errors instead of trying to exit the process
1192
1187
  // This prevents confusing error messages when validation fails but execution continues
1193
1188
  let failureMessage = null;
1194
- testYargs
1195
- .exitProcess(false)
1196
- .fail((msg, err) => {
1197
- // Capture the failure message instead of letting yargs print it
1198
- failureMessage = msg || (err && err.message) || 'Unknown validation error';
1199
- throw new Error(failureMessage);
1200
- });
1189
+ testYargs.exitProcess(false).fail((msg, err) => {
1190
+ // Capture the failure message instead of letting yargs print it
1191
+ failureMessage = msg || (err && err.message) || 'Unknown validation error';
1192
+ throw new Error(failureMessage);
1193
+ });
1201
1194
 
1202
1195
  testYargs.parse(args);
1203
1196
  } catch (error) {
1204
- await ctx.reply(`❌ Invalid options: ${error.message || String(error)}\n\nUse /help to see available options`, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
1197
+ await ctx.reply(`❌ Invalid options: ${error.message || String(error)}\n\nUse /help to see available options`, {
1198
+ parse_mode: 'Markdown',
1199
+ reply_to_message_id: ctx.message.message_id,
1200
+ });
1205
1201
  return;
1206
1202
  }
1207
1203
 
@@ -1222,8 +1218,7 @@ bot.command(/^hive$/i, async (ctx) => {
1222
1218
  }
1223
1219
 
1224
1220
  if (result.success) {
1225
- const sessionNameMatch = result.output.match(/session:\s*(\S+)/i) ||
1226
- result.output.match(/screen -r\s+(\S+)/);
1221
+ const sessionNameMatch = result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -r\s+(\S+)/);
1227
1222
  const sessionName = sessionNameMatch ? sessionNameMatch[1] : 'unknown';
1228
1223
 
1229
1224
  let response = '✅ Hive command started successfully!\n\n';
@@ -1245,7 +1240,7 @@ registerTopCommand(bot, {
1245
1240
  isOldMessage,
1246
1241
  isForwardedOrReply,
1247
1242
  isGroupChat,
1248
- isChatAuthorized
1243
+ isChatAuthorized,
1249
1244
  });
1250
1245
 
1251
1246
  // Add message listener for verbose debugging
@@ -1253,19 +1248,29 @@ if (VERBOSE) {
1253
1248
  bot.on('message', (ctx, next) => {
1254
1249
  const msg = ctx.message;
1255
1250
  console.log('[VERBOSE] Message:', {
1256
- chatId: ctx.chat?.id, chatType: ctx.chat?.type, isForum: ctx.chat?.is_forum,
1257
- isTopicMsg: msg?.is_topic_message, threadId: msg?.message_thread_id, date: msg?.date,
1258
- text: msg?.text?.substring(0, 100), user: ctx.from?.username || ctx.from?.id,
1259
- botStartTime: BOT_START_TIME, isOld: isOldMessage(ctx), isForwarded: isForwardedOrReply(ctx),
1260
- isAuthorized: isChatAuthorized(ctx.chat?.id)
1251
+ chatId: ctx.chat?.id,
1252
+ chatType: ctx.chat?.type,
1253
+ isForum: ctx.chat?.is_forum,
1254
+ isTopicMsg: msg?.is_topic_message,
1255
+ threadId: msg?.message_thread_id,
1256
+ date: msg?.date,
1257
+ text: msg?.text?.substring(0, 100),
1258
+ user: ctx.from?.username || ctx.from?.id,
1259
+ botStartTime: BOT_START_TIME,
1260
+ isOld: isOldMessage(ctx),
1261
+ isForwarded: isForwardedOrReply(ctx),
1262
+ isAuthorized: isChatAuthorized(ctx.chat?.id),
1261
1263
  });
1262
1264
  if (msg) {
1263
1265
  console.log('[VERBOSE] Msg fields:', Object.keys(msg));
1264
1266
  console.log('[VERBOSE] Forward/reply:', {
1265
- forward_origin: msg.forward_origin, forward_from: msg.forward_from,
1266
- forward_from_chat: msg.forward_from_chat, forward_date: msg.forward_date,
1267
- reply_to_message: msg.reply_to_message, reply_id: msg.reply_to_message?.message_id,
1268
- forum_topic_created: msg.reply_to_message?.forum_topic_created
1267
+ forward_origin: msg.forward_origin,
1268
+ forward_from: msg.forward_from,
1269
+ forward_from_chat: msg.forward_from_chat,
1270
+ forward_date: msg.forward_date,
1271
+ reply_to_message: msg.reply_to_message,
1272
+ reply_id: msg.reply_to_message?.message_id,
1273
+ forum_topic_created: msg.reply_to_message?.forum_topic_created,
1269
1274
  });
1270
1275
  }
1271
1276
  return next();
@@ -1335,15 +1340,13 @@ bot.catch((error, ctx) => {
1335
1340
  errorMessage += `\n🔍 **Debug info:** Update ID: ${ctx.update.update_id}`;
1336
1341
  }
1337
1342
 
1338
- ctx.reply(errorMessage, { parse_mode: 'Markdown' })
1339
- .catch(replyError => {
1340
- console.error('Failed to send error message to user:', replyError);
1341
- // Try sending a simple text message without Markdown if Markdown parsing failed
1342
- ctx.reply(' An error occurred while processing your request. Please try again or contact support.')
1343
- .catch(fallbackError => {
1344
- console.error('Failed to send fallback error message:', fallbackError);
1345
- });
1343
+ ctx.reply(errorMessage, { parse_mode: 'Markdown' }).catch(replyError => {
1344
+ console.error('Failed to send error message to user:', replyError);
1345
+ // Try sending a simple text message without Markdown if Markdown parsing failed
1346
+ ctx.reply('❌ An error occurred while processing your request. Please try again or contact support.').catch(fallbackError => {
1347
+ console.error('Failed to send fallback error message:', fallbackError);
1346
1348
  });
1349
+ });
1347
1350
  }
1348
1351
  });
1349
1352
 
@@ -1359,7 +1362,7 @@ if (allowedChats && allowedChats.length > 0) {
1359
1362
  }
1360
1363
  console.log('Commands enabled:', {
1361
1364
  solve: solveEnabled,
1362
- hive: hiveEnabled
1365
+ hive: hiveEnabled,
1363
1366
  });
1364
1367
  if (solveOverrides.length > 0) {
1365
1368
  console.log('Solve overrides (lino):', lino.format(solveOverrides));
@@ -1380,8 +1383,9 @@ if (VERBOSE) {
1380
1383
  if (VERBOSE) {
1381
1384
  console.log('[VERBOSE] Deleting webhook...');
1382
1385
  }
1383
- bot.telegram.deleteWebhook({ drop_pending_updates: true })
1384
- .then((result) => {
1386
+ bot.telegram
1387
+ .deleteWebhook({ drop_pending_updates: true })
1388
+ .then(result => {
1385
1389
  if (VERBOSE) {
1386
1390
  console.log('[VERBOSE] Webhook deletion result:', result);
1387
1391
  }
@@ -1389,7 +1393,7 @@ bot.telegram.deleteWebhook({ drop_pending_updates: true })
1389
1393
  if (VERBOSE) {
1390
1394
  console.log('[VERBOSE] Launching bot with config:', {
1391
1395
  allowedUpdates: ['message'],
1392
- dropPendingUpdates: true
1396
+ dropPendingUpdates: true,
1393
1397
  });
1394
1398
  }
1395
1399
  return bot.launch({
@@ -1398,7 +1402,7 @@ bot.telegram.deleteWebhook({ drop_pending_updates: true })
1398
1402
  allowedUpdates: ['message', 'callback_query'],
1399
1403
  // Drop any pending updates that were sent before the bot started
1400
1404
  // This ensures we only process new messages sent after this bot instance started
1401
- dropPendingUpdates: true
1405
+ dropPendingUpdates: true,
1402
1406
  });
1403
1407
  })
1404
1408
  .then(async () => {
@@ -1446,12 +1450,12 @@ bot.telegram.deleteWebhook({ drop_pending_updates: true })
1446
1450
  console.log('[VERBOSE] Send a message to the bot to test message reception');
1447
1451
  }
1448
1452
  })
1449
- .catch((error) => {
1453
+ .catch(error => {
1450
1454
  console.error('❌ Failed to start bot:', error);
1451
1455
  console.error('Error details:', {
1452
1456
  message: error.message,
1453
1457
  code: error.code,
1454
- stack: error.stack?.split('\n').slice(0, 5).join('\n')
1458
+ stack: error.stack?.split('\n').slice(0, 5).join('\n'),
1455
1459
  });
1456
1460
  if (VERBOSE) {
1457
1461
  console.error('[VERBOSE] Full error:', error);