@link-assistant/hive-mind 1.7.0 → 1.7.2

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,21 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.7.2
4
+
5
+ ### Patch Changes
6
+
7
+ - e6a656f: Use `screen -R` instead of `screen -S` and `screen -r` in all docs and code for better session management. The `-R` flag ensures we open existing screen if created, and new if not yet created, making it the most safe and universal option.
8
+
9
+ ## 1.7.1
10
+
11
+ ### Patch Changes
12
+
13
+ - d86ba79: Prevent duplicate URLs from being added to the /solve queue (Issue #1080)
14
+ - Added `findByUrl()` method to SolveQueue to detect existing items by URL
15
+ - Updated /solve command handler to check for duplicates before queueing
16
+ - Uses normalized URLs for consistent comparison
17
+ - Returns informative error message when duplicate is detected
18
+
3
19
  ## 1.7.0
4
20
 
5
21
  ### Minor Changes
package/README.md CHANGED
@@ -209,7 +209,7 @@ See [docs/HELM.md](./docs/HELM.md) for detailed Helm configuration options.
209
209
  **Using Links Notation (recommended):**
210
210
 
211
211
  ```
212
- screen -S bot # Enter new screen for bot
212
+ screen -R bot # Enter new screen for bot
213
213
 
214
214
  hive-telegram-bot --configuration "
215
215
  TELEGRAM_BOT_TOKEN: '849...355:AAG...rgk_YZk...aPU'
@@ -238,7 +238,7 @@ See [docs/HELM.md](./docs/HELM.md) for detailed Helm configuration options.
238
238
  **Using individual command-line options:**
239
239
 
240
240
  ```
241
- screen -S bot # Enter new screen for bot
241
+ screen -R bot # Enter new screen for bot
242
242
 
243
243
  hive-telegram-bot --token 849...355:AAG...rgk_YZk...aPU --allowed-chats "(
244
244
  -1002975819706
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -163,7 +163,7 @@ async function createOrEnterScreen(sessionName, command, args, autoTerminate = f
163
163
  // The \n at the end simulates pressing Enter
164
164
  await execAsync(`screen -S ${sessionName} -X stuff '${escapedCommand}\n'`);
165
165
  console.log(`Command sent to session '${sessionName}' successfully.`);
166
- console.log(`To attach and view the session, run: screen -r ${sessionName}`);
166
+ console.log(`To attach and view the session, run: screen -R ${sessionName}`);
167
167
  } catch (error) {
168
168
  console.error('Failed to send command to existing screen session:', error.message);
169
169
  console.error('You may need to terminate the old session and try again.');
@@ -208,7 +208,7 @@ async function createOrEnterScreen(sessionName, command, args, autoTerminate = f
208
208
  } else {
209
209
  console.log('Session will remain active after command completes');
210
210
  }
211
- console.log(`To attach to this session, run: screen -r ${sessionName}`);
211
+ console.log(`To attach to this session, run: screen -R ${sessionName}`);
212
212
  } catch (error) {
213
213
  console.error('Failed to create screen session:', error.message);
214
214
  process.exit(1);
@@ -654,7 +654,7 @@ async function executeAndUpdateMessage(ctx, startingMessage, commandName, args,
654
654
  if (result.warning) return safeEdit(`⚠️ ${result.warning}`);
655
655
 
656
656
  if (result.success) {
657
- const match = result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -r\s+(\S+)/);
657
+ const match = result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -R\s+(\S+)/);
658
658
  const session = match ? match[1] : 'unknown';
659
659
  await safeEdit(`✅ ${commandName.charAt(0).toUpperCase() + commandName.slice(1)} command started successfully!\n\n📊 Session: \`${session}\`\n\n${infoBlock}`);
660
660
  } else {
@@ -1069,18 +1069,32 @@ bot.command(/^solve$/i, async ctx => {
1069
1069
  return;
1070
1070
  }
1071
1071
 
1072
+ // Use normalized URL from validation to ensure consistent duplicate detection
1073
+ // See: https://github.com/link-assistant/hive-mind/issues/1080
1074
+ const normalizedUrl = validation.parsed.normalized;
1075
+
1072
1076
  const requester = buildUserMention({ user: ctx.from, parseMode: 'Markdown' });
1073
1077
  const optionsText = args.slice(1).join(' ') || 'none';
1074
- let infoBlock = `Requested by: ${requester}\nURL: ${escapeMarkdown(args[0])}\nOptions: ${optionsText}`;
1078
+ let infoBlock = `Requested by: ${requester}\nURL: ${escapeMarkdown(normalizedUrl)}\nOptions: ${optionsText}`;
1075
1079
  if (solveOverrides.length > 0) infoBlock += `\n🔒 Locked options: ${solveOverrides.join(' ')}`;
1076
1080
  const solveQueue = getSolveQueue({ verbose: VERBOSE });
1081
+
1082
+ // Check for duplicate URL in queue
1083
+ // See: https://github.com/link-assistant/hive-mind/issues/1080
1084
+ const existingItem = solveQueue.findByUrl(normalizedUrl);
1085
+ if (existingItem) {
1086
+ const statusText = existingItem.status === 'starting' || existingItem.status === 'started' ? 'being processed' : 'already in the queue';
1087
+ await ctx.reply(`❌ This URL is ${statusText}.\n\nURL: ${escapeMarkdown(normalizedUrl)}\nStatus: ${existingItem.status}\n\n💡 Use /solve-queue to check the queue status.`, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
1088
+ return;
1089
+ }
1090
+
1077
1091
  const check = await solveQueue.canStartCommand();
1078
1092
  const queueStats = solveQueue.getStats();
1079
1093
  if (check.canStart && queueStats.queued === 0) {
1080
1094
  const startingMessage = await ctx.reply(`🚀 Starting solve command...\n\n${infoBlock}`, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
1081
1095
  await executeAndUpdateMessage(ctx, startingMessage, 'solve', args, infoBlock);
1082
1096
  } else {
1083
- const queueItem = solveQueue.enqueue({ url: args[0], args, ctx, requester, infoBlock, tool: solveTool });
1097
+ const queueItem = solveQueue.enqueue({ url: normalizedUrl, args, ctx, requester, infoBlock, tool: solveTool });
1084
1098
  let queueMessage = `📋 Solve command queued (position #${queueStats.queued + 1})\n\n${infoBlock}`;
1085
1099
  if (check.reason) queueMessage += `\n\n⏳ Waiting: ${check.reason}`;
1086
1100
  const queuedMessage = await ctx.reply(queueMessage, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
@@ -34,22 +34,22 @@ import { getCachedClaudeLimits, getCachedGitHubLimits, getCachedMemoryInfo, getC
34
34
  export const QUEUE_CONFIG = {
35
35
  // Resource thresholds (usage ratios: 0.0 - 1.0)
36
36
  // All thresholds use >= comparison (inclusive)
37
- RAM_THRESHOLD: 0.5, // Stop if RAM usage >= 50%
37
+ RAM_THRESHOLD: 0.65, // Stop if RAM usage >= 65%
38
38
  // CPU threshold uses 5-minute load average, not instantaneous CPU usage
39
- CPU_THRESHOLD: 0.5, // Stop if 5-minute load average >= 50% of CPU count
39
+ CPU_THRESHOLD: 0.75, // Stop if 5-minute load average >= 75% of CPU count
40
40
  DISK_THRESHOLD: 0.95, // One-at-a-time if disk usage >= 95%
41
41
 
42
42
  // API limit thresholds (usage ratios: 0.0 - 1.0)
43
43
  // All thresholds use >= comparison (inclusive)
44
- CLAUDE_5_HOUR_SESSION_THRESHOLD: 0.9, // Stop if 5-hour limit >= 90%
45
- CLAUDE_WEEKLY_THRESHOLD: 0.99, // One-at-a-time if weekly limit >= 99%
44
+ CLAUDE_5_HOUR_SESSION_THRESHOLD: 0.85, // Stop if 5-hour limit >= 85%
45
+ CLAUDE_WEEKLY_THRESHOLD: 0.98, // One-at-a-time if weekly limit >= 98%
46
46
  GITHUB_API_THRESHOLD: 0.8, // Stop if GitHub >= 80% with parallel claude
47
47
 
48
48
  // Timing
49
49
  // MIN_START_INTERVAL_MS: Time to allow solve command to start actual claude process
50
50
  // This ensures that when API limits are checked, the running process is counted
51
- MIN_START_INTERVAL_MS: 120000, // 2 minutes between starts (was 1 minute)
52
- CONSUMER_POLL_INTERVAL_MS: 60000, // 1 minute between queue checks (was 5 seconds)
51
+ MIN_START_INTERVAL_MS: 60000, // 1 minutes between starts
52
+ CONSUMER_POLL_INTERVAL_MS: 60000, // 1 minute between queue checks
53
53
  MESSAGE_UPDATE_INTERVAL_MS: 60000, // 1 minute between status message updates
54
54
 
55
55
  // Process detection
@@ -303,6 +303,30 @@ export class SolveQueue {
303
303
  return item;
304
304
  }
305
305
 
306
+ /**
307
+ * Find an item by URL in the queue or processing items
308
+ * Used to prevent duplicate URLs from being added to the queue
309
+ * @param {string} url - The URL to search for
310
+ * @returns {SolveQueueItem|null} The found item or null
311
+ * @see https://github.com/link-assistant/hive-mind/issues/1080
312
+ */
313
+ findByUrl(url) {
314
+ // Check queued items
315
+ const queuedItem = this.queue.find(item => item.url === url);
316
+ if (queuedItem) {
317
+ return queuedItem;
318
+ }
319
+
320
+ // Check processing items
321
+ for (const item of this.processing.values()) {
322
+ if (item.url === url) {
323
+ return item;
324
+ }
325
+ }
326
+
327
+ return null;
328
+ }
329
+
306
330
  /**
307
331
  * Cancel a queued item by ID
308
332
  * @param {string} id - Item ID
@@ -734,7 +758,7 @@ export class SolveQueue {
734
758
  // Extract session name from result
735
759
  let sessionName = 'unknown';
736
760
  if (result && result.output) {
737
- const sessionMatch = result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -r\s+(\S+)/);
761
+ const sessionMatch = result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -R\s+(\S+)/);
738
762
  if (sessionMatch) sessionName = sessionMatch[1];
739
763
  }
740
764