@link-assistant/hive-mind 1.31.4 → 1.32.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 +11 -0
- package/package.json +1 -1
- package/src/github-merge.lib.mjs +1 -3
- package/src/solve.mjs +4 -10
- package/src/telegram-bot.mjs +23 -61
- package/src/telegram-message-filters.lib.mjs +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.32.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b2c94db: Support all options via /solve command when replying to a message containing a GitHub link (issue #1325)
|
|
8
|
+
|
|
9
|
+
Previously, `/solve` as a reply only worked when used without any arguments. Now users can reply to a message containing a GitHub issue/PR link with `/solve --model opus` or any other options, and the bot will:
|
|
10
|
+
1. Extract the GitHub URL from the replied message
|
|
11
|
+
2. Use the provided options
|
|
12
|
+
3. Execute the solve command with both the extracted URL and the user-provided options
|
|
13
|
+
|
|
3
14
|
## 1.31.4
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/package.json
CHANGED
package/src/github-merge.lib.mjs
CHANGED
|
@@ -516,9 +516,7 @@ export async function checkMergePermissions(owner, repo, verbose = false) {
|
|
|
516
516
|
* @param {string} repo - Repository name
|
|
517
517
|
* @param {number} prNumber - Pull request number
|
|
518
518
|
* @param {Object} options - Merge options
|
|
519
|
-
* @param {string} options.mergeMethod - Merge method: 'merge', 'squash', or 'rebase' (default: 'merge')
|
|
520
|
-
* Note: Must specify one method when running non-interactively.
|
|
521
|
-
* See Issue #1269 for details.
|
|
519
|
+
* @param {string} options.mergeMethod - Merge method: 'merge', 'squash', or 'rebase' (default: 'merge'). Must specify one method non-interactively (Issue #1269).
|
|
522
520
|
* @param {boolean} options.squash - DEPRECATED: Use mergeMethod: 'squash' instead
|
|
523
521
|
* @param {boolean} options.deleteAfter - Whether to delete branch after merge (default: false)
|
|
524
522
|
* @param {boolean} verbose - Whether to log verbose output
|
package/src/solve.mjs
CHANGED
|
@@ -98,12 +98,10 @@ const { validateAndExitOnInvalidModel } = modelValidation;
|
|
|
98
98
|
const acceptInviteLib = await import('./solve.accept-invite.lib.mjs');
|
|
99
99
|
const { autoAcceptInviteForRepo } = acceptInviteLib;
|
|
100
100
|
|
|
101
|
-
// Initialize log file EARLY
|
|
102
|
-
// Use default directory (cwd) initially, will be set from argv.logDir after parsing
|
|
101
|
+
// Initialize log file EARLY (use cwd initially, will be updated after argv parsing)
|
|
103
102
|
const logFile = await initializeLogFile(null);
|
|
104
103
|
|
|
105
|
-
// Log version and raw command IMMEDIATELY after log file initialization
|
|
106
|
-
// This ensures they appear in both console and log file, even if argument parsing fails
|
|
104
|
+
// Log version and raw command IMMEDIATELY after log file initialization (ensures they appear even if parsing fails)
|
|
107
105
|
const versionInfo = await getVersionInfo();
|
|
108
106
|
await log('');
|
|
109
107
|
await log(`🚀 solve v${versionInfo}`);
|
|
@@ -221,9 +219,7 @@ if (!(await validateContinueOnlyOnFeedback(argv, isPrUrl, isIssueUrl))) {
|
|
|
221
219
|
const tool = argv.tool || 'claude';
|
|
222
220
|
await validateAndExitOnInvalidModel(argv.model, tool, safeExit);
|
|
223
221
|
|
|
224
|
-
// Perform all system checks
|
|
225
|
-
// Skip tool CONNECTION validation in dry-run mode or when --skip-tool-connection-check or --no-tool-connection-check is enabled
|
|
226
|
-
// Note: This does NOT skip model validation which is performed above
|
|
222
|
+
// Perform all system checks (skip tool connection check in dry-run or when --skip-tool-connection-check; model validation always runs)
|
|
227
223
|
const skipToolConnectionCheck = argv.dryRun || argv.skipToolConnectionCheck || argv.toolConnectionCheck === false;
|
|
228
224
|
if (!(await performSystemChecks(argv.minDiskSpace || 2048, skipToolConnectionCheck, argv.model, argv))) {
|
|
229
225
|
await safeExit(1, 'System checks failed');
|
|
@@ -236,9 +232,7 @@ if (argv.verbose) {
|
|
|
236
232
|
await log(` Is PR URL: ${!!isPrUrl}`, { verbose: true });
|
|
237
233
|
}
|
|
238
234
|
const claudePath = argv.executeToolWithBun ? 'bunx claude' : process.env.CLAUDE_PATH || 'claude';
|
|
239
|
-
// Note: owner, repo, and urlNumber are
|
|
240
|
-
// The parseUrlComponents() call was removed as it had a bug with hash fragments (#issuecomment-xyz)
|
|
241
|
-
// and the validation result already provides these values correctly parsed
|
|
235
|
+
// Note: owner, repo, and urlNumber are extracted from validateGitHubUrl() above (parseUrlComponents() removed due to hash fragment bug)
|
|
242
236
|
|
|
243
237
|
// Handle --auto-fork option: automatically fork public repositories without write access
|
|
244
238
|
if (argv.autoFork && !argv.fork) {
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -52,7 +52,7 @@ const { formatUsageMessage, getAllCachedLimits } = await import('./limits.lib.mj
|
|
|
52
52
|
const { getVersionInfo, formatVersionMessage } = await import('./version-info.lib.mjs');
|
|
53
53
|
const { escapeMarkdown, escapeMarkdownV2, cleanNonPrintableChars, makeSpecialCharsVisible } = await import('./telegram-markdown.lib.mjs');
|
|
54
54
|
const { getSolveQueue, createQueueExecuteCallback } = await import('./telegram-solve-queue.lib.mjs');
|
|
55
|
-
const { isOldMessage: _isOldMessage, isGroupChat: _isGroupChat, isChatAuthorized: _isChatAuthorized, isForwardedOrReply: _isForwardedOrReply, extractCommandFromText } = await import('./telegram-message-filters.lib.mjs');
|
|
55
|
+
const { isOldMessage: _isOldMessage, isGroupChat: _isGroupChat, isChatAuthorized: _isChatAuthorized, isForwardedOrReply: _isForwardedOrReply, extractCommandFromText, extractGitHubUrl: _extractGitHubUrl } = await import('./telegram-message-filters.lib.mjs');
|
|
56
56
|
// Import bot launcher with exponential backoff retry (issue #1240)
|
|
57
57
|
const { launchBotWithRetry } = await import('./telegram-bot-launcher.lib.mjs');
|
|
58
58
|
|
|
@@ -313,10 +313,6 @@ function isOldMessage(ctx) {
|
|
|
313
313
|
return _isOldMessage(ctx, BOT_START_TIME, { verbose: VERBOSE });
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
function isGroupChat(ctx) {
|
|
317
|
-
return _isGroupChat(ctx);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
316
|
function isForwardedOrReply(ctx) {
|
|
321
317
|
return _isForwardedOrReply(ctx, { verbose: VERBOSE });
|
|
322
318
|
}
|
|
@@ -596,46 +592,6 @@ async function executeAndUpdateMessage(ctx, startingMessage, commandName, args,
|
|
|
596
592
|
}
|
|
597
593
|
}
|
|
598
594
|
|
|
599
|
-
/**
|
|
600
|
-
* Extract GitHub issue/PR URL from message text
|
|
601
|
-
* Validates that message contains exactly one GitHub issue/PR link
|
|
602
|
-
*
|
|
603
|
-
* @param {string} text - Message text to search
|
|
604
|
-
* @returns {{ url: string|null, error: string|null, linkCount: number }}
|
|
605
|
-
*/
|
|
606
|
-
function extractGitHubUrl(text) {
|
|
607
|
-
if (!text || typeof text !== 'string') {
|
|
608
|
-
return { url: null, error: null, linkCount: 0 };
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
text = cleanNonPrintableChars(text); // Clean non-printable chars before processing
|
|
612
|
-
const words = text.split(/\s+/);
|
|
613
|
-
const foundUrls = [];
|
|
614
|
-
|
|
615
|
-
for (const word of words) {
|
|
616
|
-
// Try to parse as GitHub URL
|
|
617
|
-
const parsed = parseGitHubUrl(word);
|
|
618
|
-
|
|
619
|
-
// Accept issue or PR URLs
|
|
620
|
-
if (parsed.valid && (parsed.type === 'issue' || parsed.type === 'pull')) {
|
|
621
|
-
foundUrls.push(parsed.normalized);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// Check if multiple links were found
|
|
626
|
-
if (foundUrls.length === 0) {
|
|
627
|
-
return { url: null, error: null, linkCount: 0 };
|
|
628
|
-
} else if (foundUrls.length === 1) {
|
|
629
|
-
return { url: foundUrls[0], error: null, linkCount: 1 };
|
|
630
|
-
} else {
|
|
631
|
-
return {
|
|
632
|
-
url: null,
|
|
633
|
-
error: `Found ${foundUrls.length} GitHub links in the message. Please reply to a message with only one GitHub issue or PR link.`,
|
|
634
|
-
linkCount: foundUrls.length,
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
595
|
bot.command('help', async ctx => {
|
|
640
596
|
if (VERBOSE) {
|
|
641
597
|
console.log('[VERBOSE] /help command received');
|
|
@@ -760,7 +716,7 @@ bot.command('limits', async ctx => {
|
|
|
760
716
|
return;
|
|
761
717
|
}
|
|
762
718
|
|
|
763
|
-
if (!
|
|
719
|
+
if (!_isGroupChat(ctx)) {
|
|
764
720
|
if (VERBOSE) {
|
|
765
721
|
console.log('[VERBOSE] /limits ignored: not a group chat');
|
|
766
722
|
}
|
|
@@ -809,7 +765,7 @@ bot.command('version', async ctx => {
|
|
|
809
765
|
data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
|
|
810
766
|
});
|
|
811
767
|
if (isOldMessage(ctx) || isForwardedOrReply(ctx)) return;
|
|
812
|
-
if (!
|
|
768
|
+
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 });
|
|
813
769
|
const chatId = ctx.chat.id;
|
|
814
770
|
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 });
|
|
815
771
|
const fetchingMessage = await ctx.reply('🔄 Gathering version information...', {
|
|
@@ -827,7 +783,7 @@ registerAcceptInvitesCommand(bot, {
|
|
|
827
783
|
VERBOSE,
|
|
828
784
|
isOldMessage,
|
|
829
785
|
isForwardedOrReply,
|
|
830
|
-
isGroupChat,
|
|
786
|
+
isGroupChat: _isGroupChat,
|
|
831
787
|
isChatAuthorized,
|
|
832
788
|
addBreadcrumb,
|
|
833
789
|
});
|
|
@@ -838,7 +794,7 @@ registerMergeCommand(bot, {
|
|
|
838
794
|
VERBOSE,
|
|
839
795
|
isOldMessage,
|
|
840
796
|
isForwardedOrReply,
|
|
841
|
-
isGroupChat,
|
|
797
|
+
isGroupChat: _isGroupChat,
|
|
842
798
|
isChatAuthorized,
|
|
843
799
|
addBreadcrumb,
|
|
844
800
|
});
|
|
@@ -849,7 +805,7 @@ const { handleSolveQueueCommand } = registerSolveQueueCommand(bot, {
|
|
|
849
805
|
VERBOSE,
|
|
850
806
|
isOldMessage,
|
|
851
807
|
isForwardedOrReply,
|
|
852
|
-
isGroupChat,
|
|
808
|
+
isGroupChat: _isGroupChat,
|
|
853
809
|
isChatAuthorized,
|
|
854
810
|
addBreadcrumb,
|
|
855
811
|
getSolveQueue,
|
|
@@ -903,7 +859,7 @@ async function handleSolveCommand(ctx) {
|
|
|
903
859
|
return;
|
|
904
860
|
}
|
|
905
861
|
|
|
906
|
-
if (!
|
|
862
|
+
if (!_isGroupChat(ctx)) {
|
|
907
863
|
if (VERBOSE) {
|
|
908
864
|
console.log('[VERBOSE] /solve ignored: not a group chat');
|
|
909
865
|
}
|
|
@@ -926,17 +882,23 @@ async function handleSolveCommand(ctx) {
|
|
|
926
882
|
|
|
927
883
|
let userArgs = parseCommandArgs(ctx.message.text);
|
|
928
884
|
|
|
929
|
-
// Check if this is a reply to a message and user didn't provide URL
|
|
885
|
+
// Check if this is a reply to a message and user didn't provide URL as first argument
|
|
930
886
|
// In that case, try to extract GitHub URL from the replied message
|
|
887
|
+
// Issue #1325: Support all options via /solve command when replying (e.g., "/solve --model opus")
|
|
931
888
|
const isReply = message.reply_to_message && message.reply_to_message.message_id && !message.reply_to_message.forum_topic_created;
|
|
932
889
|
|
|
933
|
-
if
|
|
890
|
+
// Check if the first argument looks like a GitHub URL
|
|
891
|
+
// If not, we should try to extract the URL from the replied message
|
|
892
|
+
const firstArgIsUrl = userArgs.length > 0 && (userArgs[0].includes('github.com') || userArgs[0].match(/^https?:\/\//));
|
|
893
|
+
|
|
894
|
+
if (isReply && !firstArgIsUrl) {
|
|
934
895
|
if (VERBOSE) {
|
|
935
|
-
console.log('[VERBOSE] /solve is a reply without URL, extracting from replied message...');
|
|
896
|
+
console.log('[VERBOSE] /solve is a reply without URL in args, extracting from replied message...');
|
|
897
|
+
console.log('[VERBOSE] User args:', userArgs);
|
|
936
898
|
}
|
|
937
899
|
|
|
938
900
|
const replyText = message.reply_to_message.text || '';
|
|
939
|
-
const extraction =
|
|
901
|
+
const extraction = _extractGitHubUrl(replyText, { parseGitHubUrl, cleanNonPrintableChars });
|
|
940
902
|
|
|
941
903
|
if (extraction.error) {
|
|
942
904
|
// Multiple links found
|
|
@@ -949,18 +911,18 @@ async function handleSolveCommand(ctx) {
|
|
|
949
911
|
});
|
|
950
912
|
return;
|
|
951
913
|
} else if (extraction.url) {
|
|
952
|
-
// Single link found
|
|
914
|
+
// Single link found - prepend it to existing user args (issue #1325)
|
|
953
915
|
if (VERBOSE) {
|
|
954
916
|
console.log('[VERBOSE] Extracted URL from reply:', extraction.url);
|
|
955
917
|
}
|
|
956
|
-
//
|
|
957
|
-
userArgs = [extraction.url];
|
|
918
|
+
// Prepend the extracted URL to user's options (e.g., ['--model', 'opus'] -> ['url', '--model', 'opus'])
|
|
919
|
+
userArgs = [extraction.url, ...userArgs];
|
|
958
920
|
} else {
|
|
959
921
|
// No link found
|
|
960
922
|
if (VERBOSE) {
|
|
961
923
|
console.log('[VERBOSE] No GitHub URL found in replied message');
|
|
962
924
|
}
|
|
963
|
-
await ctx.reply('❌ No GitHub issue/PR link found in the replied message.\n\nExample: Reply to a message containing a GitHub issue link with `/solve`', { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
|
|
925
|
+
await ctx.reply('❌ No GitHub issue/PR link found in the replied message.\n\nExample: Reply to a message containing a GitHub issue link with `/solve`\n\nOr with options: `/solve --model opus`', { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
|
|
964
926
|
return;
|
|
965
927
|
}
|
|
966
928
|
}
|
|
@@ -1113,7 +1075,7 @@ async function handleHiveCommand(ctx) {
|
|
|
1113
1075
|
return;
|
|
1114
1076
|
}
|
|
1115
1077
|
|
|
1116
|
-
if (!
|
|
1078
|
+
if (!_isGroupChat(ctx)) {
|
|
1117
1079
|
if (VERBOSE) {
|
|
1118
1080
|
console.log('[VERBOSE] /hive ignored: not a group chat');
|
|
1119
1081
|
}
|
|
@@ -1217,7 +1179,7 @@ registerTopCommand(bot, {
|
|
|
1217
1179
|
VERBOSE,
|
|
1218
1180
|
isOldMessage,
|
|
1219
1181
|
isForwardedOrReply,
|
|
1220
|
-
isGroupChat,
|
|
1182
|
+
isGroupChat: _isGroupChat,
|
|
1221
1183
|
isChatAuthorized,
|
|
1222
1184
|
});
|
|
1223
1185
|
|
|
@@ -171,3 +171,48 @@ export function extractCommandFromText(text, botUsername = null) {
|
|
|
171
171
|
|
|
172
172
|
return { command, botMention };
|
|
173
173
|
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Extract GitHub issue/PR URL from message text.
|
|
177
|
+
* Validates that message contains exactly one GitHub issue/PR link.
|
|
178
|
+
* Extracted from telegram-bot.mjs to reduce file size (issue #1325).
|
|
179
|
+
*
|
|
180
|
+
* @param {string} text - Message text to search
|
|
181
|
+
* @param {Object} deps - Dependencies for parsing
|
|
182
|
+
* @param {Function} deps.parseGitHubUrl - Function to parse GitHub URLs
|
|
183
|
+
* @param {Function} deps.cleanNonPrintableChars - Function to clean non-printable characters
|
|
184
|
+
* @returns {{ url: string|null, error: string|null, linkCount: number }}
|
|
185
|
+
* @see https://github.com/link-assistant/hive-mind/issues/1325
|
|
186
|
+
*/
|
|
187
|
+
export function extractGitHubUrl(text, { parseGitHubUrl, cleanNonPrintableChars }) {
|
|
188
|
+
if (!text || typeof text !== 'string') {
|
|
189
|
+
return { url: null, error: null, linkCount: 0 };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
text = cleanNonPrintableChars(text); // Clean non-printable chars before processing
|
|
193
|
+
const words = text.split(/\s+/);
|
|
194
|
+
const foundUrls = [];
|
|
195
|
+
|
|
196
|
+
for (const word of words) {
|
|
197
|
+
// Try to parse as GitHub URL
|
|
198
|
+
const parsed = parseGitHubUrl(word);
|
|
199
|
+
|
|
200
|
+
// Accept issue or PR URLs
|
|
201
|
+
if (parsed.valid && (parsed.type === 'issue' || parsed.type === 'pull')) {
|
|
202
|
+
foundUrls.push(parsed.normalized);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Check if multiple links were found
|
|
207
|
+
if (foundUrls.length === 0) {
|
|
208
|
+
return { url: null, error: null, linkCount: 0 };
|
|
209
|
+
} else if (foundUrls.length === 1) {
|
|
210
|
+
return { url: foundUrls[0], error: null, linkCount: 1 };
|
|
211
|
+
} else {
|
|
212
|
+
return {
|
|
213
|
+
url: null,
|
|
214
|
+
error: `Found ${foundUrls.length} GitHub links in the message. Please reply to a message with only one GitHub issue or PR link.`,
|
|
215
|
+
linkCount: foundUrls.length,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|