@link-assistant/hive-mind 0.54.0 → 0.54.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 +22 -0
- package/package.json +1 -1
- package/src/telegram-bot.mjs +15 -10
- package/src/telegram-solve-queue.lib.mjs +55 -31
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 0.54.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c5f5194: Fix Telegram message getting stuck at "Starting solve command..."
|
|
8
|
+
- Add error handling to `executeAndUpdateMessage` function to catch Telegram API errors
|
|
9
|
+
- Fix critical bug where `messageInfo` was being cleared before the final message update
|
|
10
|
+
- Add proper error logging for message edit failures in both immediate and queued execution paths
|
|
11
|
+
|
|
12
|
+
## 0.54.1
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 55576af: fix: allow parallel queue execution when no limits exceeded
|
|
17
|
+
|
|
18
|
+
Previously, "Claude process is already running" was treated as a blocking reason on its own, preventing parallel execution even when all system and API limits were within thresholds.
|
|
19
|
+
|
|
20
|
+
Changes:
|
|
21
|
+
- `claude_running` is now tracked as a metric, not a blocking reason
|
|
22
|
+
- Commands can run in parallel as long as actual limits are not exceeded
|
|
23
|
+
- When any limit >= threshold, allow exactly one claude command to pass
|
|
24
|
+
|
|
3
25
|
## 0.54.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/package.json
CHANGED
package/src/telegram-bot.mjs
CHANGED
|
@@ -673,20 +673,25 @@ function validateGitHubUrl(args, options = {}) {
|
|
|
673
673
|
*/
|
|
674
674
|
async function executeAndUpdateMessage(ctx, startingMessage, commandName, args, infoBlock) {
|
|
675
675
|
const result = await executeStartScreen(commandName, args);
|
|
676
|
+
const { chat, message_id } = startingMessage;
|
|
676
677
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
678
|
+
// Safely edit message - catch errors to prevent stuck "Starting..." messages (issue #1062)
|
|
679
|
+
const safeEdit = async text => {
|
|
680
|
+
try {
|
|
681
|
+
await ctx.telegram.editMessageText(chat.id, message_id, undefined, text, { parse_mode: 'Markdown' });
|
|
682
|
+
} catch (e) {
|
|
683
|
+
console.error(`[telegram-bot] Failed to update message for ${commandName}: ${e.message}`);
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
if (result.warning) return safeEdit(`⚠️ ${result.warning}`);
|
|
681
688
|
|
|
682
689
|
if (result.success) {
|
|
683
|
-
const
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
await ctx.telegram.editMessageText(startingMessage.chat.id, startingMessage.message_id, undefined, response, { parse_mode: 'Markdown' });
|
|
690
|
+
const match = result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -r\s+(\S+)/);
|
|
691
|
+
const session = match ? match[1] : 'unknown';
|
|
692
|
+
await safeEdit(`✅ ${commandName.charAt(0).toUpperCase() + commandName.slice(1)} command started successfully!\n\n📊 Session: \`${session}\`\n\n${infoBlock}`);
|
|
687
693
|
} else {
|
|
688
|
-
|
|
689
|
-
await ctx.telegram.editMessageText(startingMessage.chat.id, startingMessage.message_id, undefined, response, { parse_mode: 'Markdown' });
|
|
694
|
+
await safeEdit(`❌ Error executing ${commandName} command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\``);
|
|
690
695
|
}
|
|
691
696
|
}
|
|
692
697
|
|
|
@@ -357,6 +357,12 @@ export class SolveQueue {
|
|
|
357
357
|
|
|
358
358
|
/**
|
|
359
359
|
* Check if a new command can start
|
|
360
|
+
*
|
|
361
|
+
* Logic per issue #1061:
|
|
362
|
+
* 1. "Claude process is already running" is NOT a limit by itself - it's a metric
|
|
363
|
+
* 2. Commands can run in parallel as long as actual limits are not exceeded
|
|
364
|
+
* 3. When any limit >= threshold, allow exactly one claude command to pass
|
|
365
|
+
*
|
|
360
366
|
* @returns {Promise<{canStart: boolean, reason?: string, reasons?: string[], oneAtATime?: boolean}>}
|
|
361
367
|
*/
|
|
362
368
|
async canStartCommand() {
|
|
@@ -373,10 +379,12 @@ export class SolveQueue {
|
|
|
373
379
|
}
|
|
374
380
|
}
|
|
375
381
|
|
|
376
|
-
// Check running claude processes
|
|
382
|
+
// Check running claude processes (this is a metric, not a blocking reason by itself)
|
|
377
383
|
const claudeProcs = await getRunningClaudeProcesses(this.verbose);
|
|
378
|
-
|
|
379
|
-
|
|
384
|
+
const hasRunningClaude = claudeProcs.count > 0;
|
|
385
|
+
|
|
386
|
+
// Track claude_running as a metric (but don't add to reasons yet)
|
|
387
|
+
if (hasRunningClaude) {
|
|
380
388
|
this.recordThrottle('claude_running');
|
|
381
389
|
}
|
|
382
390
|
|
|
@@ -389,8 +397,8 @@ export class SolveQueue {
|
|
|
389
397
|
oneAtATime = true;
|
|
390
398
|
}
|
|
391
399
|
|
|
392
|
-
// Check API limits
|
|
393
|
-
const limitCheck = await this.checkApiLimits(
|
|
400
|
+
// Check API limits (pass hasRunningClaude to enable special handling)
|
|
401
|
+
const limitCheck = await this.checkApiLimits(hasRunningClaude);
|
|
394
402
|
if (!limitCheck.ok) {
|
|
395
403
|
reasons.push(...limitCheck.reasons);
|
|
396
404
|
}
|
|
@@ -398,6 +406,13 @@ export class SolveQueue {
|
|
|
398
406
|
oneAtATime = true;
|
|
399
407
|
}
|
|
400
408
|
|
|
409
|
+
// "Claude process running" only blocks if there are OTHER reasons too
|
|
410
|
+
// This allows parallel execution when limits are not exceeded
|
|
411
|
+
if (hasRunningClaude && reasons.length > 0) {
|
|
412
|
+
// Add claude_running info only when combined with actual limit reasons
|
|
413
|
+
reasons.unshift(formatWaitingReason('claude_running', claudeProcs.count, 0) + ` (${claudeProcs.count} processes)`);
|
|
414
|
+
}
|
|
415
|
+
|
|
401
416
|
const canStart = reasons.length === 0;
|
|
402
417
|
|
|
403
418
|
if (!canStart && this.verbose) {
|
|
@@ -461,6 +476,12 @@ export class SolveQueue {
|
|
|
461
476
|
|
|
462
477
|
/**
|
|
463
478
|
* Check API limits (Claude, GitHub) using cached values
|
|
479
|
+
*
|
|
480
|
+
* Simplified logic per issue #1061:
|
|
481
|
+
* - When any limit >= threshold, allow exactly one claude command to pass
|
|
482
|
+
* - Only block if there's already a command in progress
|
|
483
|
+
* - Running claude is the ultimate test of whether limits are really exhausted
|
|
484
|
+
*
|
|
464
485
|
* @param {boolean} hasRunningClaude - Whether claude processes are running
|
|
465
486
|
* @returns {Promise<{ok: boolean, reasons: string[], oneAtATime: boolean}>}
|
|
466
487
|
*/
|
|
@@ -475,29 +496,26 @@ export class SolveQueue {
|
|
|
475
496
|
const weeklyPercent = claudeResult.usage.allModels.percentage;
|
|
476
497
|
|
|
477
498
|
// Session limit (5-hour)
|
|
499
|
+
// When above threshold: allow exactly one command, block if one is running
|
|
478
500
|
if (sessionPercent !== null) {
|
|
479
501
|
const sessionRatio = sessionPercent / 100;
|
|
480
|
-
if (sessionRatio >=
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
this.recordThrottle('claude_session_high');
|
|
502
|
+
if (sessionRatio >= QUEUE_CONFIG.CLAUDE_SESSION_THRESHOLD) {
|
|
503
|
+
// Only block if Claude is already running
|
|
504
|
+
if (hasRunningClaude) {
|
|
505
|
+
reasons.push(formatWaitingReason('claude_session', sessionPercent, QUEUE_CONFIG.CLAUDE_SESSION_THRESHOLD));
|
|
506
|
+
}
|
|
507
|
+
this.recordThrottle(sessionRatio >= 1.0 ? 'claude_session_100' : 'claude_session_high');
|
|
486
508
|
}
|
|
487
509
|
}
|
|
488
510
|
|
|
489
511
|
// Weekly limit
|
|
512
|
+
// When above threshold: allow exactly one command, block if one is in progress
|
|
490
513
|
if (weeklyPercent !== null) {
|
|
491
514
|
const weeklyRatio = weeklyPercent / 100;
|
|
492
|
-
if (weeklyRatio >=
|
|
515
|
+
if (weeklyRatio >= QUEUE_CONFIG.CLAUDE_WEEKLY_THRESHOLD) {
|
|
493
516
|
oneAtATime = true;
|
|
494
|
-
this.recordThrottle('claude_weekly_100');
|
|
495
|
-
if
|
|
496
|
-
reasons.push('Claude weekly limit is 100% (waiting for current command)');
|
|
497
|
-
}
|
|
498
|
-
} else if (weeklyRatio >= QUEUE_CONFIG.CLAUDE_WEEKLY_THRESHOLD) {
|
|
499
|
-
oneAtATime = true;
|
|
500
|
-
this.recordThrottle('claude_weekly_high');
|
|
517
|
+
this.recordThrottle(weeklyRatio >= 1.0 ? 'claude_weekly_100' : 'claude_weekly_high');
|
|
518
|
+
// Only block if command is already in progress
|
|
501
519
|
if (this.processing.size > 0) {
|
|
502
520
|
reasons.push(formatWaitingReason('claude_weekly', weeklyPercent, QUEUE_CONFIG.CLAUDE_WEEKLY_THRESHOLD) + ' (waiting for current command)');
|
|
503
521
|
}
|
|
@@ -511,12 +529,9 @@ export class SolveQueue {
|
|
|
511
529
|
if (githubResult.success) {
|
|
512
530
|
const usedPercent = githubResult.githubRateLimit.usedPercentage;
|
|
513
531
|
const usedRatio = usedPercent / 100;
|
|
514
|
-
if (usedRatio >=
|
|
515
|
-
reasons.push('GitHub API limit is 100% (waiting for reset)');
|
|
516
|
-
this.recordThrottle('github_100');
|
|
517
|
-
} else if (usedRatio >= QUEUE_CONFIG.GITHUB_API_THRESHOLD) {
|
|
532
|
+
if (usedRatio >= QUEUE_CONFIG.GITHUB_API_THRESHOLD) {
|
|
518
533
|
reasons.push(formatWaitingReason('github', usedPercent, QUEUE_CONFIG.GITHUB_API_THRESHOLD));
|
|
519
|
-
this.recordThrottle('github_high');
|
|
534
|
+
this.recordThrottle(usedRatio >= 1.0 ? 'github_100' : 'github_high');
|
|
520
535
|
}
|
|
521
536
|
}
|
|
522
537
|
}
|
|
@@ -652,13 +667,18 @@ export class SolveQueue {
|
|
|
652
667
|
if (sessionMatch) sessionName = sessionMatch[1];
|
|
653
668
|
}
|
|
654
669
|
|
|
670
|
+
// IMPORTANT: Save messageInfo BEFORE calling setStarted, because setStarted clears it
|
|
671
|
+
// This was a bug where the final message update never happened because messageInfo was null
|
|
672
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1062
|
|
673
|
+
const savedMessageInfo = item.messageInfo;
|
|
674
|
+
|
|
655
675
|
// Update to Started status (terminal - forgets message tracking)
|
|
656
676
|
item.setStarted(sessionName);
|
|
657
677
|
this.stats.totalCompleted++;
|
|
658
678
|
|
|
659
|
-
// Final message update
|
|
660
|
-
if (item.ctx && result) {
|
|
661
|
-
const { chatId, messageId } =
|
|
679
|
+
// Final message update using saved messageInfo
|
|
680
|
+
if (item.ctx && result && savedMessageInfo) {
|
|
681
|
+
const { chatId, messageId } = savedMessageInfo;
|
|
662
682
|
if (chatId && messageId) {
|
|
663
683
|
try {
|
|
664
684
|
if (result.warning) {
|
|
@@ -670,8 +690,10 @@ export class SolveQueue {
|
|
|
670
690
|
const response = `❌ Error executing solve command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\``;
|
|
671
691
|
await item.ctx.telegram.editMessageText(chatId, messageId, undefined, response, { parse_mode: 'Markdown' });
|
|
672
692
|
}
|
|
673
|
-
} catch {
|
|
674
|
-
//
|
|
693
|
+
} catch (error) {
|
|
694
|
+
// Log message edit failures for debugging
|
|
695
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1062
|
|
696
|
+
console.error(`[solve-queue] Failed to update message for item ${item.id}: ${error.message}`);
|
|
675
697
|
}
|
|
676
698
|
}
|
|
677
699
|
}
|
|
@@ -689,8 +711,10 @@ export class SolveQueue {
|
|
|
689
711
|
if (chatId && messageId && item.ctx) {
|
|
690
712
|
try {
|
|
691
713
|
await item.ctx.telegram.editMessageText(chatId, messageId, undefined, `❌ Error: ${error.message}`, { parse_mode: 'Markdown' });
|
|
692
|
-
} catch {
|
|
693
|
-
//
|
|
714
|
+
} catch (editError) {
|
|
715
|
+
// Log the edit failure for debugging
|
|
716
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1062
|
|
717
|
+
console.error(`[solve-queue] Failed to update error message for item ${item.id}: ${editError.message}`);
|
|
694
718
|
}
|
|
695
719
|
}
|
|
696
720
|
} finally {
|