@link-assistant/hive-mind 1.50.14 → 1.51.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.51.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fd3c76c: Add per-tool Telegram solve aliases: /claude, /codex, /opencode, and /agent.
8
+
9
+ ## 1.50.15
10
+
11
+ ### Patch Changes
12
+
13
+ - 7cecf09: Fix auto-resume reset time parsing when usage-limit output includes a month/day prefix such as `Apr 17, 4:00 AM`.
14
+
3
15
  ## 1.50.14
4
16
 
5
17
  ### Patch Changes
package/README.hi.md CHANGED
@@ -444,6 +444,18 @@ Examples:
444
444
  /solve https://github.com/owner/repo/issues/123 --model sonnet
445
445
  /solve https://github.com/owner/repo/issues/123 --model opus --think max
446
446
 
447
+ Aliases:
448
+ /do और /continue /solve के बराबर हैं
449
+ /claude /solve --tool claude के बराबर है
450
+ /codex /solve --tool codex के बराबर है
451
+ /opencode /solve --tool opencode के बराबर है
452
+ /agent /solve --tool agent के बराबर है
453
+
454
+ Tool alias examples:
455
+ /codex https://github.com/owner/repo/issues/123 --model gpt-5.4
456
+ /opencode https://github.com/owner/repo/issues/123 --model grok-code-fast-1
457
+ /agent https://github.com/owner/repo/issues/123 --model nemotron-3-super-free
458
+
447
459
  Free Models (with --tool agent):
448
460
  /solve https://github.com/owner/repo/issues/123 --tool agent --model nemotron-3-super-free
449
461
  /solve https://github.com/owner/repo/issues/123 --tool agent --model opencode/nemotron-3-super-free
package/README.md CHANGED
@@ -453,6 +453,18 @@ Examples:
453
453
  /solve https://github.com/owner/repo/issues/123 --model sonnet
454
454
  /solve https://github.com/owner/repo/issues/123 --model opus --think max
455
455
 
456
+ Aliases:
457
+ /do and /continue are equivalent to /solve
458
+ /claude is equivalent to /solve --tool claude
459
+ /codex is equivalent to /solve --tool codex
460
+ /opencode is equivalent to /solve --tool opencode
461
+ /agent is equivalent to /solve --tool agent
462
+
463
+ Tool alias examples:
464
+ /codex https://github.com/owner/repo/issues/123 --model gpt-5.4
465
+ /opencode https://github.com/owner/repo/issues/123 --model grok-code-fast-1
466
+ /agent https://github.com/owner/repo/issues/123 --model nemotron-3-super-free
467
+
456
468
  Free Models (with --tool agent):
457
469
  /solve https://github.com/owner/repo/issues/123 --tool agent --model nemotron-3-super-free
458
470
  /solve https://github.com/owner/repo/issues/123 --tool agent --model opencode/nemotron-3-super-free
package/README.ru.md CHANGED
@@ -444,6 +444,18 @@ Examples:
444
444
  /solve https://github.com/owner/repo/issues/123 --model sonnet
445
445
  /solve https://github.com/owner/repo/issues/123 --model opus --think max
446
446
 
447
+ Aliases:
448
+ /do и /continue эквивалентны /solve
449
+ /claude эквивалентна /solve --tool claude
450
+ /codex эквивалентна /solve --tool codex
451
+ /opencode эквивалентна /solve --tool opencode
452
+ /agent эквивалентна /solve --tool agent
453
+
454
+ Tool alias examples:
455
+ /codex https://github.com/owner/repo/issues/123 --model gpt-5.4
456
+ /opencode https://github.com/owner/repo/issues/123 --model grok-code-fast-1
457
+ /agent https://github.com/owner/repo/issues/123 --model nemotron-3-super-free
458
+
447
459
  Free Models (with --tool agent):
448
460
  /solve https://github.com/owner/repo/issues/123 --tool agent --model nemotron-3-super-free
449
461
  /solve https://github.com/owner/repo/issues/123 --tool agent --model opencode/nemotron-3-super-free
package/README.zh.md CHANGED
@@ -444,6 +444,18 @@ Examples:
444
444
  /solve https://github.com/owner/repo/issues/123 --model sonnet
445
445
  /solve https://github.com/owner/repo/issues/123 --model opus --think max
446
446
 
447
+ Aliases:
448
+ /do 和 /continue 等同于 /solve
449
+ /claude 等同于 /solve --tool claude
450
+ /codex 等同于 /solve --tool codex
451
+ /opencode 等同于 /solve --tool opencode
452
+ /agent 等同于 /solve --tool agent
453
+
454
+ Tool alias examples:
455
+ /codex https://github.com/owner/repo/issues/123 --model gpt-5.4
456
+ /opencode https://github.com/owner/repo/issues/123 --model grok-code-fast-1
457
+ /agent https://github.com/owner/repo/issues/123 --model nemotron-3-super-free
458
+
447
459
  Free Models (with --tool agent):
448
460
  /solve https://github.com/owner/repo/issues/123 --tool agent --model nemotron-3-super-free
449
461
  /solve https://github.com/owner/repo/issues/123 --tool agent --model opencode/nemotron-3-super-free
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.50.14",
3
+ "version": "1.51.0",
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-issue-1616-pr-issue-link-preservation.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-telegram-bot-launcher.mjs",
16
+ "test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-telegram-bot-launcher.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",
@@ -371,10 +371,12 @@ export const parseUrlComponents = issueUrl => {
371
371
  export const parseResetTime = timeStr => {
372
372
  // Normalize and parse time formats like:
373
373
  // "5:30am", "11:45pm", "12:16 PM", "07:05 Am", "5am", "5 AM"
374
+ // Also accepts date+time forms like "Apr 17, 4:00 AM" and ignores the date portion.
374
375
  const normalized = (timeStr || '').toString().trim();
376
+ const timePortion = normalized.replace(/^(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\s+\d{1,2},\s+/i, '');
375
377
 
376
378
  // Accept both HH:MM am/pm and HH am/pm
377
- let match = normalized.match(/^(\d{1,2})(?::(\d{2}))?\s*([ap]m)$/i);
379
+ let match = timePortion.match(/^(\d{1,2})(?::(\d{2}))?\s*([ap]m)$/i);
378
380
  if (!match) {
379
381
  throw new Error(`Invalid time format: ${timeStr}`);
380
382
  }
@@ -45,6 +45,7 @@ const { formatUsageMessage, formatCodexLimitsSection, getAllCachedLimits } = awa
45
45
  const { getVersionInfo, formatVersionMessage } = await import('./version-info.lib.mjs');
46
46
  const { escapeMarkdown, escapeMarkdownV2, cleanNonPrintableChars, makeSpecialCharsVisible } = await import('./telegram-markdown.lib.mjs');
47
47
  const { getSolveQueue, createQueueExecuteCallback } = await import('./telegram-solve-queue.lib.mjs');
48
+ const { applySolveToolAlias, getSolveCommandNameFromText, getSolveToolAliasFromText, parseCommandArgs, SOLVE_COMMAND_NAMES } = await import('./telegram-solve-command.lib.mjs');
48
49
  const { isChatStopped, getChatStopInfo, getStoppedChatRejectMessage, DEFAULT_STOP_REASON } = await import('./telegram-start-stop-command.lib.mjs');
49
50
  const { isOldMessage: _isOldMessage, isGroupChat: _isGroupChat, isChatAuthorized: _isChatAuthorized, isForwardedOrReply: _isForwardedOrReply, extractCommandFromText, extractGitHubUrl: _extractGitHubUrl } = await import('./telegram-message-filters.lib.mjs');
50
51
  const { launchBotWithRetry } = await import('./telegram-bot-launcher.lib.mjs');
@@ -447,49 +448,6 @@ function validateModelInArgs(args, tool = 'claude') {
447
448
  return null;
448
449
  }
449
450
 
450
- function parseCommandArgs(text) {
451
- // Use only first line and trim it
452
- const firstLine = text.split('\n')[0].trim();
453
- const argsText = firstLine.replace(/^\/\w+\s*/, '');
454
-
455
- if (!argsText.trim()) {
456
- return [];
457
- }
458
-
459
- // Replace em-dash (—) with double-dash (--) to fix Telegram auto-replacement
460
- const normalizedArgsText = argsText.replace(/—/g, '--');
461
-
462
- const args = [];
463
- let currentArg = '';
464
- let inQuotes = false;
465
- let quoteChar = null;
466
-
467
- for (let i = 0; i < normalizedArgsText.length; i++) {
468
- const char = normalizedArgsText[i];
469
-
470
- if ((char === '"' || char === "'") && !inQuotes) {
471
- inQuotes = true;
472
- quoteChar = char;
473
- } else if (char === quoteChar && inQuotes) {
474
- inQuotes = false;
475
- quoteChar = null;
476
- } else if (char === ' ' && !inQuotes) {
477
- if (currentArg) {
478
- args.push(currentArg);
479
- currentArg = '';
480
- }
481
- } else {
482
- currentArg += char;
483
- }
484
- }
485
-
486
- if (currentArg) {
487
- args.push(currentArg);
488
- }
489
-
490
- return args;
491
- }
492
-
493
451
  function mergeArgsWithOverrides(userArgs, overrides) {
494
452
  if (!overrides || overrides.length === 0) {
495
453
  return userArgs;
@@ -651,16 +609,17 @@ bot.command('help', async ctx => {
651
609
  message += '📝 *Available Commands:*\n\n';
652
610
 
653
611
  if (solveEnabled) {
654
- message += '*/solve* (aliases: */do*, */continue*) - Solve a GitHub issue\n';
612
+ message += '*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*) - Solve a GitHub issue\n';
655
613
  message += 'Usage: `/solve <github-url> [options]`\n';
656
614
  message += 'Example: `/solve https://github.com/owner/repo/issues/123 --model sonnet`\n';
615
+ message += 'Tool aliases imply `--tool <tool>`: `/codex <github-url>` equals `/solve <github-url> --tool codex`\n';
657
616
  message += 'Or reply to a message with a GitHub link: `/solve`\n';
658
617
  if (solveOverrides.length > 0) {
659
618
  message += `🔒 Locked options: \`${solveOverrides.join(' ')}\`\n`;
660
619
  }
661
620
  message += '\n';
662
621
  } else {
663
- message += '*/solve* (aliases: */do*, */continue*) - ❌ Disabled\n\n';
622
+ message += '*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*) - ❌ Disabled\n\n';
664
623
  }
665
624
 
666
625
  if (hiveEnabled) {
@@ -688,7 +647,7 @@ bot.command('help', async ctx => {
688
647
  message += '🔔 *Session Notifications:* The bot monitors sessions and notifies when they complete.\n';
689
648
  if (ISOLATION_BACKEND) message += `🔒 *Isolation Mode:* \`${ISOLATION_BACKEND}\` (experimental)\n`;
690
649
  message += '\n';
691
- message += '⚠️ *Note:* /solve, /do, /continue, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats.\n\n';
650
+ message += '⚠️ *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats.\n\n';
692
651
  message += '🔧 *Common Options:*\n';
693
652
  message += `• \`--model <model>\` or \`-m\` - ${buildModelOptionDescription()}\n`;
694
653
  message += '• `--base-branch <branch>` or `-b` - Target branch for PR (default: repo default branch)\n';
@@ -795,12 +754,14 @@ const { handleSolveQueueCommand } = registerSolveQueueCommand(bot, { ...sharedCo
795
754
 
796
755
  // Named handler for /solve command - extracted for reuse by text-based fallback (issue #1207)
797
756
  async function handleSolveCommand(ctx) {
798
- VERBOSE && console.log('[VERBOSE] /solve command received');
757
+ const solveCommandName = getSolveCommandNameFromText(ctx.message?.text) || 'solve';
758
+ const solveCommandDisplay = `/${solveCommandName}`;
759
+ VERBOSE && console.log(`[VERBOSE] ${solveCommandDisplay} command received`);
799
760
 
800
761
  // Add breadcrumb for error tracking
801
762
  await addBreadcrumb({
802
763
  category: 'telegram.command',
803
- message: '/solve command received',
764
+ message: `${solveCommandDisplay} command received`,
804
765
  level: 'info',
805
766
  data: {
806
767
  chatId: ctx.chat?.id,
@@ -812,16 +773,16 @@ async function handleSolveCommand(ctx) {
812
773
 
813
774
  if (!solveEnabled) {
814
775
  if (VERBOSE) {
815
- console.log('[VERBOSE] /solve ignored: command disabled');
776
+ console.log(`[VERBOSE] ${solveCommandDisplay} ignored: command disabled`);
816
777
  }
817
- await ctx.reply('❌ The /solve command is disabled on this bot instance.');
778
+ await ctx.reply('❌ The solve command is disabled on this bot instance.');
818
779
  return;
819
780
  }
820
781
 
821
782
  // Ignore messages sent before bot started
822
783
  if (isOldMessage(ctx)) {
823
784
  if (VERBOSE) {
824
- console.log('[VERBOSE] /solve ignored: old message');
785
+ console.log(`[VERBOSE] ${solveCommandDisplay} ignored: old message`);
825
786
  }
826
787
  return;
827
788
  }
@@ -834,22 +795,22 @@ async function handleSolveCommand(ctx) {
834
795
 
835
796
  if (isForwarded || isOldApiForwarded) {
836
797
  if (VERBOSE) {
837
- console.log('[VERBOSE] /solve ignored: forwarded message');
798
+ console.log(`[VERBOSE] ${solveCommandDisplay} ignored: forwarded message`);
838
799
  }
839
800
  return;
840
801
  }
841
802
 
842
803
  if (!_isGroupChat(ctx)) {
843
804
  if (VERBOSE) {
844
- console.log('[VERBOSE] /solve ignored: not a group chat');
805
+ console.log(`[VERBOSE] ${solveCommandDisplay} ignored: not a group chat`);
845
806
  }
846
- await ctx.reply('❌ The /solve 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 });
807
+ await ctx.reply(`❌ The ${solveCommandDisplay} 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 });
847
808
  return;
848
809
  }
849
810
 
850
811
  if (!isTopicAuthorized(ctx)) {
851
812
  if (VERBOSE) {
852
- console.log('[VERBOSE] /solve ignored: not authorized');
813
+ console.log(`[VERBOSE] ${solveCommandDisplay} ignored: not authorized`);
853
814
  }
854
815
  await ctx.reply(buildAuthErrorMessage(ctx), { reply_to_message_id: ctx.message.message_id });
855
816
  return;
@@ -863,8 +824,9 @@ async function handleSolveCommand(ctx) {
863
824
  return;
864
825
  }
865
826
 
866
- VERBOSE && console.log('[VERBOSE] /solve passed all checks, executing...');
827
+ VERBOSE && console.log(`[VERBOSE] ${solveCommandDisplay} passed all checks, executing...`);
867
828
 
829
+ const solveToolAlias = getSolveToolAliasFromText(ctx.message.text);
868
830
  let userArgs = parseCommandArgs(ctx.message.text);
869
831
 
870
832
  // Check if this is a reply to a message and user didn't provide URL as first argument
@@ -911,6 +873,8 @@ async function handleSolveCommand(ctx) {
911
873
  }
912
874
  }
913
875
 
876
+ userArgs = applySolveToolAlias(userArgs, solveToolAlias);
877
+
914
878
  const validation = validateGitHubUrl(userArgs);
915
879
  if (!validation.valid) {
916
880
  let errorMsg = `❌ ${validation.error}`;
@@ -1040,7 +1004,10 @@ async function handleSolveCommand(ctx) {
1040
1004
  }
1041
1005
  }
1042
1006
 
1043
- bot.command([/^solve$/i, /^do$/i, /^continue$/i], handleSolveCommand); // /do and /continue are aliases (issue #525)
1007
+ bot.command(
1008
+ SOLVE_COMMAND_NAMES.map(command => new RegExp(`^${command}$`, 'i')),
1009
+ handleSolveCommand
1010
+ );
1044
1011
 
1045
1012
  // Named handler for /hive command - extracted for reuse by text-based fallback (issue #1207)
1046
1013
  async function handleHiveCommand(ctx) {
@@ -1267,8 +1234,8 @@ bot.on('message', async (ctx, next) => {
1267
1234
  }
1268
1235
 
1269
1236
  // Check if this is a command we handle
1270
- // /do and /continue are aliases for /solve (issue #525)
1271
- const handlers = { solve: handleSolveCommand, do: handleSolveCommand, continue: handleSolveCommand, hive: handleHiveCommand, solve_queue: handleSolveQueueCommand, solvequeue: handleSolveQueueCommand };
1237
+ const solveHandlers = Object.fromEntries(SOLVE_COMMAND_NAMES.map(command => [command, handleSolveCommand]));
1238
+ const handlers = { ...solveHandlers, hive: handleHiveCommand, solve_queue: handleSolveQueueCommand, solvequeue: handleSolveQueueCommand };
1272
1239
 
1273
1240
  const handler = handlers[extracted.command];
1274
1241
  if (!handler) return next();
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Shared parsing helpers for Telegram solve commands.
3
+ *
4
+ * Keeps /solve aliases and argument normalization testable without loading the
5
+ * full Telegram bot entry point.
6
+ *
7
+ * @see https://github.com/link-assistant/hive-mind/issues/525
8
+ * @see https://github.com/link-assistant/hive-mind/issues/1618
9
+ */
10
+
11
+ export const TOOL_SOLVE_COMMAND_ALIASES = Object.freeze({
12
+ claude: 'claude',
13
+ codex: 'codex',
14
+ opencode: 'opencode',
15
+ agent: 'agent',
16
+ });
17
+
18
+ export const SOLVE_COMMAND_NAMES = Object.freeze(['solve', 'do', 'continue', ...Object.keys(TOOL_SOLVE_COMMAND_ALIASES)]);
19
+
20
+ export function parseCommandArgs(text) {
21
+ const firstLine = text.split('\n')[0].trim();
22
+ const argsText = firstLine.replace(/^\/\w+(?:@\S+)?\s*/, '');
23
+
24
+ if (!argsText.trim()) {
25
+ return [];
26
+ }
27
+
28
+ // Replace em-dash with double-dash to fix Telegram auto-replacement.
29
+ const normalizedArgsText = argsText.replace(/—/g, '--');
30
+
31
+ const args = [];
32
+ let currentArg = '';
33
+ let inQuotes = false;
34
+ let quoteChar = null;
35
+
36
+ for (let i = 0; i < normalizedArgsText.length; i++) {
37
+ const char = normalizedArgsText[i];
38
+
39
+ if ((char === '"' || char === "'") && !inQuotes) {
40
+ inQuotes = true;
41
+ quoteChar = char;
42
+ } else if (char === quoteChar && inQuotes) {
43
+ inQuotes = false;
44
+ quoteChar = null;
45
+ } else if (char === ' ' && !inQuotes) {
46
+ if (currentArg) {
47
+ args.push(currentArg);
48
+ currentArg = '';
49
+ }
50
+ } else {
51
+ currentArg += char;
52
+ }
53
+ }
54
+
55
+ if (currentArg) {
56
+ args.push(currentArg);
57
+ }
58
+
59
+ return args;
60
+ }
61
+
62
+ export function getSolveCommandNameFromText(text) {
63
+ if (!text || typeof text !== 'string') {
64
+ return null;
65
+ }
66
+
67
+ const firstLine = text.split('\n')[0].trim();
68
+ const match = firstLine.match(/^\/(\w+)(?:@\S+)?(?:\s|$)/);
69
+ return match ? match[1].toLowerCase() : null;
70
+ }
71
+
72
+ export function getSolveToolAliasFromText(text) {
73
+ const command = getSolveCommandNameFromText(text);
74
+ return command ? TOOL_SOLVE_COMMAND_ALIASES[command] || null : null;
75
+ }
76
+
77
+ export function applySolveToolAlias(args, toolAlias) {
78
+ if (!toolAlias || args.length === 0) {
79
+ return args;
80
+ }
81
+
82
+ const filteredArgs = [];
83
+
84
+ for (let i = 0; i < args.length; i++) {
85
+ const arg = args[i];
86
+
87
+ if (arg === '--tool') {
88
+ if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
89
+ i++;
90
+ }
91
+ continue;
92
+ }
93
+
94
+ if (arg.startsWith('--tool=')) {
95
+ continue;
96
+ }
97
+
98
+ filteredArgs.push(arg);
99
+ }
100
+
101
+ return [...filteredArgs, '--tool', toolAlias];
102
+ }