@link-assistant/hive-mind 1.31.3 → 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 +34 -0
- package/package.json +2 -2
- package/src/config.lib.mjs +3 -1
- package/src/github-merge.lib.mjs +1 -3
- package/src/hive.mjs +7 -3
- package/src/memory-check.mjs +4 -1
- package/src/queue-config.lib.mjs +3 -1
- package/src/solve.config.lib.mjs +4 -1
- package/src/solve.mjs +6 -20
- package/src/telegram-bot.mjs +30 -63
- package/src/telegram-message-filters.lib.mjs +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
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
|
+
|
|
14
|
+
## 1.31.4
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Extract large inline script blocks from release.yml into ./scripts/ to fix CI line-limit violation (issue #1428)
|
|
19
|
+
|
|
20
|
+
fix: configure release pipeline to react to docker=true so Dockerfile changes trigger Docker image rebuild (Issue #1423)
|
|
21
|
+
|
|
22
|
+
Previously, commits that changed only `Dockerfile` or `coolify/Dockerfile` produced `docker=true` but `code=false`. The `release` job required all test jobs to `succeed` — but those tests were correctly skipped (no JavaScript code changed). Since `skipped != 'success'`, the release job was also skipped, and no Docker image was rebuilt.
|
|
23
|
+
|
|
24
|
+
This was observed when PR #1420 (fixing `/home/hive/.config` ownership) was merged: both Dockerfiles changed, but CI run `23040959919` showed all Docker publish jobs as skipped.
|
|
25
|
+
|
|
26
|
+
The `release` job condition is now updated to:
|
|
27
|
+
- Also trigger when `docker-changed == 'true'` (not only `code=true`)
|
|
28
|
+
- Accept `skipped` as well as `success` for test/lint jobs (skipped = intentionally not run, not a failure)
|
|
29
|
+
- Block on any actual job `failure`
|
|
30
|
+
|
|
31
|
+
This directly configures CI/CD to react to `docker=true` — without misclassifying Dockerfiles as "code" files.
|
|
32
|
+
|
|
33
|
+
Full root cause analysis and timeline in `docs/case-studies/issue-1423/`.
|
|
34
|
+
|
|
35
|
+
Migrate GitHub Actions to Node.js 24 compatible versions to eliminate deprecation warnings before the June 2026 deadline
|
|
36
|
+
|
|
3
37
|
## 1.31.3
|
|
4
38
|
|
|
5
39
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/hive-mind",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.32.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",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"homepage": "https://github.com/link-assistant/hive-mind#readme",
|
|
49
49
|
"engines": {
|
|
50
|
-
"node": ">=
|
|
50
|
+
"node": ">=24.0.0"
|
|
51
51
|
},
|
|
52
52
|
"files": [
|
|
53
53
|
"src",
|
package/src/config.lib.mjs
CHANGED
|
@@ -18,7 +18,9 @@ if (typeof globalThis.use === 'undefined') {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const getenvModule = await use('getenv');
|
|
22
|
+
// Node 24 CJS/ESM interop may return the whole module object instead of the function directly
|
|
23
|
+
const getenv = typeof getenvModule === 'function' ? getenvModule : getenvModule.default || getenvModule;
|
|
22
24
|
|
|
23
25
|
// Use semver package for version comparison (see issue #1146)
|
|
24
26
|
import semver from 'semver';
|
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/hive.mjs
CHANGED
|
@@ -20,8 +20,10 @@ if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
|
|
|
20
20
|
globalThis.use = use;
|
|
21
21
|
const yargsModule = await use('yargs@17.7.2');
|
|
22
22
|
const yargs = yargsModule.default || yargsModule;
|
|
23
|
-
const
|
|
24
|
-
const
|
|
23
|
+
const helpersModuleHelp = await use('yargs@17.7.2/helpers');
|
|
24
|
+
const _helpersHelp = helpersModuleHelp.default || helpersModuleHelp;
|
|
25
|
+
const hideBinHelp = _helpersHelp.hideBin || (argv => argv.slice(2));
|
|
26
|
+
const rawArgs = hideBinHelp(process.argv);
|
|
25
27
|
// Reuse createYargsConfig from shared module to avoid duplication
|
|
26
28
|
const { createYargsConfig } = await import('./hive.config.lib.mjs');
|
|
27
29
|
const helpYargs = createYargsConfig(yargs(rawArgs)).version(false);
|
|
@@ -86,7 +88,9 @@ if (isDirectExecution) {
|
|
|
86
88
|
);
|
|
87
89
|
const yargsModule = await withTimeout(use('yargs@17.7.2'), 30000, 'loading yargs');
|
|
88
90
|
const yargs = yargsModule.default || yargsModule;
|
|
89
|
-
const
|
|
91
|
+
const helpersModuleMain = await withTimeout(use('yargs@17.7.2/helpers'), 30000, 'loading yargs helpers');
|
|
92
|
+
const _helpersMain = helpersModuleMain.default || helpersModuleMain;
|
|
93
|
+
const hideBin = _helpersMain.hideBin || (argv => argv.slice(2));
|
|
90
94
|
const path = (await withTimeout(use('path'), 30000, 'loading path')).default;
|
|
91
95
|
const fs = (await withTimeout(use('fs'), 30000, 'loading fs')).promises;
|
|
92
96
|
// Import shared library functions
|
package/src/memory-check.mjs
CHANGED
|
@@ -19,7 +19,10 @@ const $silent = $({ mirror: false, capture: true });
|
|
|
19
19
|
|
|
20
20
|
const yargsModule = await use('yargs@17.7.2');
|
|
21
21
|
const yargs = yargsModule.default || yargsModule;
|
|
22
|
-
const
|
|
22
|
+
const helpersModule = await use('yargs@17.7.2/helpers');
|
|
23
|
+
// Node 24 CJS/ESM interop may return the whole module object instead of named exports directly
|
|
24
|
+
const _helpers = helpersModule.default || helpersModule;
|
|
25
|
+
const hideBin = _helpers.hideBin || (argv => argv.slice(2));
|
|
23
26
|
const fs = (await use('fs')).promises;
|
|
24
27
|
|
|
25
28
|
// Import log function from lib.mjs
|
package/src/queue-config.lib.mjs
CHANGED
|
@@ -36,7 +36,9 @@ if (typeof globalThis.use === 'undefined') {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
const
|
|
39
|
+
const getenvModule = await use('getenv');
|
|
40
|
+
// Node 24 CJS/ESM interop may return the whole module object instead of the function directly
|
|
41
|
+
const getenv = typeof getenvModule === 'function' ? getenvModule : getenvModule.default || getenvModule;
|
|
40
42
|
const linoModule = await use('links-notation');
|
|
41
43
|
const LinoParser = linoModule.Parser || linoModule.default?.Parser;
|
|
42
44
|
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -17,7 +17,10 @@ export const initializeConfig = async use => {
|
|
|
17
17
|
// Import yargs with specific version for hideBin support
|
|
18
18
|
const yargsModule = await use('yargs@17.7.2');
|
|
19
19
|
const yargs = yargsModule.default || yargsModule;
|
|
20
|
-
const
|
|
20
|
+
const helpersModule = await use('yargs@17.7.2/helpers');
|
|
21
|
+
// Node 24 CJS/ESM interop may return the whole module object instead of named exports directly
|
|
22
|
+
const helpers = helpersModule.default || helpersModule;
|
|
23
|
+
const hideBin = helpers.hideBin || (argv => argv.slice(2));
|
|
21
24
|
|
|
22
25
|
return { yargs, hideBin };
|
|
23
26
|
};
|
package/src/solve.mjs
CHANGED
|
@@ -77,8 +77,7 @@ const { startAutoRestartUntilMergeable } = await import('./solve.auto-merge.lib.
|
|
|
77
77
|
const { runAutoEnsureRequirements } = await import('./solve.auto-ensure.lib.mjs');
|
|
78
78
|
const exitHandler = await import('./exit-handler.lib.mjs');
|
|
79
79
|
const { initializeExitHandler, installGlobalExitHandlers, safeExit } = exitHandler;
|
|
80
|
-
const
|
|
81
|
-
const { createInterruptWrapper } = interruptLib;
|
|
80
|
+
const { createInterruptWrapper } = await import('./solve.interrupt.lib.mjs');
|
|
82
81
|
const getResourceSnapshot = memoryCheck.getResourceSnapshot;
|
|
83
82
|
|
|
84
83
|
// Import new modular components
|
|
@@ -99,12 +98,10 @@ const { validateAndExitOnInvalidModel } = modelValidation;
|
|
|
99
98
|
const acceptInviteLib = await import('./solve.accept-invite.lib.mjs');
|
|
100
99
|
const { autoAcceptInviteForRepo } = acceptInviteLib;
|
|
101
100
|
|
|
102
|
-
// Initialize log file EARLY
|
|
103
|
-
// 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)
|
|
104
102
|
const logFile = await initializeLogFile(null);
|
|
105
103
|
|
|
106
|
-
// Log version and raw command IMMEDIATELY after log file initialization
|
|
107
|
-
// 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)
|
|
108
105
|
const versionInfo = await getVersionInfo();
|
|
109
106
|
await log('');
|
|
110
107
|
await log(`🚀 solve v${versionInfo}`);
|
|
@@ -125,9 +122,7 @@ try {
|
|
|
125
122
|
}
|
|
126
123
|
global.verboseMode = argv.verbose;
|
|
127
124
|
|
|
128
|
-
//
|
|
129
|
-
// However, this adds complexity, so we accept that early logs go to cwd
|
|
130
|
-
// The trade-off is: early logs in cwd vs missing version/command in error cases
|
|
125
|
+
// Early logs go to cwd; custom log dir takes effect after argv is parsed
|
|
131
126
|
|
|
132
127
|
// Conditionally import tool-specific functions after argv is parsed
|
|
133
128
|
let checkForUncommittedChanges;
|
|
@@ -190,7 +185,6 @@ if (!urlValidation.isValid) {
|
|
|
190
185
|
}
|
|
191
186
|
const { isIssueUrl, isPrUrl, normalizedUrl, owner, repo, number: urlNumber } = urlValidation;
|
|
192
187
|
issueUrl = normalizedUrl || issueUrl;
|
|
193
|
-
// Store owner and repo globally for error handlers and interrupt context
|
|
194
188
|
global.owner = owner;
|
|
195
189
|
global.repo = repo;
|
|
196
190
|
cleanupContext.owner = owner;
|
|
@@ -225,9 +219,7 @@ if (!(await validateContinueOnlyOnFeedback(argv, isPrUrl, isIssueUrl))) {
|
|
|
225
219
|
const tool = argv.tool || 'claude';
|
|
226
220
|
await validateAndExitOnInvalidModel(argv.model, tool, safeExit);
|
|
227
221
|
|
|
228
|
-
// Perform all system checks
|
|
229
|
-
// Skip tool CONNECTION validation in dry-run mode or when --skip-tool-connection-check or --no-tool-connection-check is enabled
|
|
230
|
-
// 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)
|
|
231
223
|
const skipToolConnectionCheck = argv.dryRun || argv.skipToolConnectionCheck || argv.toolConnectionCheck === false;
|
|
232
224
|
if (!(await performSystemChecks(argv.minDiskSpace || 2048, skipToolConnectionCheck, argv.model, argv))) {
|
|
233
225
|
await safeExit(1, 'System checks failed');
|
|
@@ -240,9 +232,7 @@ if (argv.verbose) {
|
|
|
240
232
|
await log(` Is PR URL: ${!!isPrUrl}`, { verbose: true });
|
|
241
233
|
}
|
|
242
234
|
const claudePath = argv.executeToolWithBun ? 'bunx claude' : process.env.CLAUDE_PATH || 'claude';
|
|
243
|
-
// Note: owner, repo, and urlNumber are
|
|
244
|
-
// The parseUrlComponents() call was removed as it had a bug with hash fragments (#issuecomment-xyz)
|
|
245
|
-
// 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)
|
|
246
236
|
|
|
247
237
|
// Handle --auto-fork option: automatically fork public repositories without write access
|
|
248
238
|
if (argv.autoFork && !argv.fork) {
|
|
@@ -517,17 +507,13 @@ if (isPrUrl) {
|
|
|
517
507
|
issueNumber = urlNumber;
|
|
518
508
|
await log(`📝 Issue mode: Working with issue #${issueNumber}`);
|
|
519
509
|
}
|
|
520
|
-
// Create or find temporary directory for cloning the repository
|
|
521
|
-
// Pass workspace info for --enable-workspaces mode (works with all tools)
|
|
522
510
|
const workspaceInfo = argv.enableWorkspaces ? { owner, repo, issueNumber } : null;
|
|
523
511
|
const { tempDir, workspaceTmpDir, needsClone } = await setupTempDirectory(argv, workspaceInfo);
|
|
524
|
-
// Populate cleanup context for signal handlers (owner/repo updated again here for redundancy)
|
|
525
512
|
cleanupContext.tempDir = tempDir;
|
|
526
513
|
cleanupContext.argv = argv;
|
|
527
514
|
cleanupContext.owner = owner;
|
|
528
515
|
cleanupContext.repo = repo;
|
|
529
516
|
if (prNumber) cleanupContext.prNumber = prNumber;
|
|
530
|
-
// Initialize limitReached variable outside try block for finally clause
|
|
531
517
|
let limitReached = false;
|
|
532
518
|
try {
|
|
533
519
|
// Set up repository and clone using the new module
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -24,7 +24,9 @@ const { loadLenvConfig } = await import('./lenv-reader.lib.mjs');
|
|
|
24
24
|
const dotenvxModule = await use('@dotenvx/dotenvx');
|
|
25
25
|
const dotenvx = dotenvxModule.default || dotenvxModule;
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const getenvModule = await use('getenv');
|
|
28
|
+
// Node 24 CJS/ESM interop may return the whole module object instead of the function directly
|
|
29
|
+
const getenv = typeof getenvModule === 'function' ? getenvModule : getenvModule.default || getenvModule;
|
|
28
30
|
|
|
29
31
|
// Load .env configuration as base
|
|
30
32
|
// quiet: true suppresses info messages, ignore: ['MISSING_ENV_FILE'] suppresses error when .env doesn't exist
|
|
@@ -37,7 +39,10 @@ loadLenvConfig({ override: true, quiet: true });
|
|
|
37
39
|
|
|
38
40
|
const yargsModule = await use('yargs@17.7.2');
|
|
39
41
|
const yargs = yargsModule.default || yargsModule;
|
|
40
|
-
const
|
|
42
|
+
const helpersModuleBot = await use('yargs@17.7.2/helpers');
|
|
43
|
+
// Node 24 CJS/ESM interop may return the whole module object instead of named exports directly
|
|
44
|
+
const _helpersBot = helpersModuleBot.default || helpersModuleBot;
|
|
45
|
+
const hideBin = _helpersBot.hideBin || (argv => argv.slice(2));
|
|
41
46
|
// Import yargs configurations, GitHub utilities, and telegram helpers
|
|
42
47
|
const { createYargsConfig: createSolveYargsConfig, detectMalformedFlags } = await import('./solve.config.lib.mjs');
|
|
43
48
|
const { createYargsConfig: createHiveYargsConfig } = await import('./hive.config.lib.mjs');
|
|
@@ -47,7 +52,7 @@ const { formatUsageMessage, getAllCachedLimits } = await import('./limits.lib.mj
|
|
|
47
52
|
const { getVersionInfo, formatVersionMessage } = await import('./version-info.lib.mjs');
|
|
48
53
|
const { escapeMarkdown, escapeMarkdownV2, cleanNonPrintableChars, makeSpecialCharsVisible } = await import('./telegram-markdown.lib.mjs');
|
|
49
54
|
const { getSolveQueue, createQueueExecuteCallback } = await import('./telegram-solve-queue.lib.mjs');
|
|
50
|
-
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');
|
|
51
56
|
// Import bot launcher with exponential backoff retry (issue #1240)
|
|
52
57
|
const { launchBotWithRetry } = await import('./telegram-bot-launcher.lib.mjs');
|
|
53
58
|
|
|
@@ -308,10 +313,6 @@ function isOldMessage(ctx) {
|
|
|
308
313
|
return _isOldMessage(ctx, BOT_START_TIME, { verbose: VERBOSE });
|
|
309
314
|
}
|
|
310
315
|
|
|
311
|
-
function isGroupChat(ctx) {
|
|
312
|
-
return _isGroupChat(ctx);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
316
|
function isForwardedOrReply(ctx) {
|
|
316
317
|
return _isForwardedOrReply(ctx, { verbose: VERBOSE });
|
|
317
318
|
}
|
|
@@ -591,46 +592,6 @@ async function executeAndUpdateMessage(ctx, startingMessage, commandName, args,
|
|
|
591
592
|
}
|
|
592
593
|
}
|
|
593
594
|
|
|
594
|
-
/**
|
|
595
|
-
* Extract GitHub issue/PR URL from message text
|
|
596
|
-
* Validates that message contains exactly one GitHub issue/PR link
|
|
597
|
-
*
|
|
598
|
-
* @param {string} text - Message text to search
|
|
599
|
-
* @returns {{ url: string|null, error: string|null, linkCount: number }}
|
|
600
|
-
*/
|
|
601
|
-
function extractGitHubUrl(text) {
|
|
602
|
-
if (!text || typeof text !== 'string') {
|
|
603
|
-
return { url: null, error: null, linkCount: 0 };
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
text = cleanNonPrintableChars(text); // Clean non-printable chars before processing
|
|
607
|
-
const words = text.split(/\s+/);
|
|
608
|
-
const foundUrls = [];
|
|
609
|
-
|
|
610
|
-
for (const word of words) {
|
|
611
|
-
// Try to parse as GitHub URL
|
|
612
|
-
const parsed = parseGitHubUrl(word);
|
|
613
|
-
|
|
614
|
-
// Accept issue or PR URLs
|
|
615
|
-
if (parsed.valid && (parsed.type === 'issue' || parsed.type === 'pull')) {
|
|
616
|
-
foundUrls.push(parsed.normalized);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// Check if multiple links were found
|
|
621
|
-
if (foundUrls.length === 0) {
|
|
622
|
-
return { url: null, error: null, linkCount: 0 };
|
|
623
|
-
} else if (foundUrls.length === 1) {
|
|
624
|
-
return { url: foundUrls[0], error: null, linkCount: 1 };
|
|
625
|
-
} else {
|
|
626
|
-
return {
|
|
627
|
-
url: null,
|
|
628
|
-
error: `Found ${foundUrls.length} GitHub links in the message. Please reply to a message with only one GitHub issue or PR link.`,
|
|
629
|
-
linkCount: foundUrls.length,
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
595
|
bot.command('help', async ctx => {
|
|
635
596
|
if (VERBOSE) {
|
|
636
597
|
console.log('[VERBOSE] /help command received');
|
|
@@ -755,7 +716,7 @@ bot.command('limits', async ctx => {
|
|
|
755
716
|
return;
|
|
756
717
|
}
|
|
757
718
|
|
|
758
|
-
if (!
|
|
719
|
+
if (!_isGroupChat(ctx)) {
|
|
759
720
|
if (VERBOSE) {
|
|
760
721
|
console.log('[VERBOSE] /limits ignored: not a group chat');
|
|
761
722
|
}
|
|
@@ -804,7 +765,7 @@ bot.command('version', async ctx => {
|
|
|
804
765
|
data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
|
|
805
766
|
});
|
|
806
767
|
if (isOldMessage(ctx) || isForwardedOrReply(ctx)) return;
|
|
807
|
-
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 });
|
|
808
769
|
const chatId = ctx.chat.id;
|
|
809
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 });
|
|
810
771
|
const fetchingMessage = await ctx.reply('🔄 Gathering version information...', {
|
|
@@ -822,7 +783,7 @@ registerAcceptInvitesCommand(bot, {
|
|
|
822
783
|
VERBOSE,
|
|
823
784
|
isOldMessage,
|
|
824
785
|
isForwardedOrReply,
|
|
825
|
-
isGroupChat,
|
|
786
|
+
isGroupChat: _isGroupChat,
|
|
826
787
|
isChatAuthorized,
|
|
827
788
|
addBreadcrumb,
|
|
828
789
|
});
|
|
@@ -833,7 +794,7 @@ registerMergeCommand(bot, {
|
|
|
833
794
|
VERBOSE,
|
|
834
795
|
isOldMessage,
|
|
835
796
|
isForwardedOrReply,
|
|
836
|
-
isGroupChat,
|
|
797
|
+
isGroupChat: _isGroupChat,
|
|
837
798
|
isChatAuthorized,
|
|
838
799
|
addBreadcrumb,
|
|
839
800
|
});
|
|
@@ -844,7 +805,7 @@ const { handleSolveQueueCommand } = registerSolveQueueCommand(bot, {
|
|
|
844
805
|
VERBOSE,
|
|
845
806
|
isOldMessage,
|
|
846
807
|
isForwardedOrReply,
|
|
847
|
-
isGroupChat,
|
|
808
|
+
isGroupChat: _isGroupChat,
|
|
848
809
|
isChatAuthorized,
|
|
849
810
|
addBreadcrumb,
|
|
850
811
|
getSolveQueue,
|
|
@@ -898,7 +859,7 @@ async function handleSolveCommand(ctx) {
|
|
|
898
859
|
return;
|
|
899
860
|
}
|
|
900
861
|
|
|
901
|
-
if (!
|
|
862
|
+
if (!_isGroupChat(ctx)) {
|
|
902
863
|
if (VERBOSE) {
|
|
903
864
|
console.log('[VERBOSE] /solve ignored: not a group chat');
|
|
904
865
|
}
|
|
@@ -921,17 +882,23 @@ async function handleSolveCommand(ctx) {
|
|
|
921
882
|
|
|
922
883
|
let userArgs = parseCommandArgs(ctx.message.text);
|
|
923
884
|
|
|
924
|
-
// 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
|
|
925
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")
|
|
926
888
|
const isReply = message.reply_to_message && message.reply_to_message.message_id && !message.reply_to_message.forum_topic_created;
|
|
927
889
|
|
|
928
|
-
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) {
|
|
929
895
|
if (VERBOSE) {
|
|
930
|
-
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);
|
|
931
898
|
}
|
|
932
899
|
|
|
933
900
|
const replyText = message.reply_to_message.text || '';
|
|
934
|
-
const extraction =
|
|
901
|
+
const extraction = _extractGitHubUrl(replyText, { parseGitHubUrl, cleanNonPrintableChars });
|
|
935
902
|
|
|
936
903
|
if (extraction.error) {
|
|
937
904
|
// Multiple links found
|
|
@@ -944,18 +911,18 @@ async function handleSolveCommand(ctx) {
|
|
|
944
911
|
});
|
|
945
912
|
return;
|
|
946
913
|
} else if (extraction.url) {
|
|
947
|
-
// Single link found
|
|
914
|
+
// Single link found - prepend it to existing user args (issue #1325)
|
|
948
915
|
if (VERBOSE) {
|
|
949
916
|
console.log('[VERBOSE] Extracted URL from reply:', extraction.url);
|
|
950
917
|
}
|
|
951
|
-
//
|
|
952
|
-
userArgs = [extraction.url];
|
|
918
|
+
// Prepend the extracted URL to user's options (e.g., ['--model', 'opus'] -> ['url', '--model', 'opus'])
|
|
919
|
+
userArgs = [extraction.url, ...userArgs];
|
|
953
920
|
} else {
|
|
954
921
|
// No link found
|
|
955
922
|
if (VERBOSE) {
|
|
956
923
|
console.log('[VERBOSE] No GitHub URL found in replied message');
|
|
957
924
|
}
|
|
958
|
-
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 });
|
|
959
926
|
return;
|
|
960
927
|
}
|
|
961
928
|
}
|
|
@@ -1108,7 +1075,7 @@ async function handleHiveCommand(ctx) {
|
|
|
1108
1075
|
return;
|
|
1109
1076
|
}
|
|
1110
1077
|
|
|
1111
|
-
if (!
|
|
1078
|
+
if (!_isGroupChat(ctx)) {
|
|
1112
1079
|
if (VERBOSE) {
|
|
1113
1080
|
console.log('[VERBOSE] /hive ignored: not a group chat');
|
|
1114
1081
|
}
|
|
@@ -1212,7 +1179,7 @@ registerTopCommand(bot, {
|
|
|
1212
1179
|
VERBOSE,
|
|
1213
1180
|
isOldMessage,
|
|
1214
1181
|
isForwardedOrReply,
|
|
1215
|
-
isGroupChat,
|
|
1182
|
+
isGroupChat: _isGroupChat,
|
|
1216
1183
|
isChatAuthorized,
|
|
1217
1184
|
});
|
|
1218
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
|
+
}
|