@link-assistant/hive-mind 1.0.4 → 1.0.5
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 +19 -0
- package/package.json +3 -2
- package/src/telegram-solve-queue.lib.mjs +68 -19
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a68a9f2: fix(queue): simplify queue logic based on PR feedback
|
|
8
|
+
- **Use 5-minute load average for CPU**: Uses `loadAvg5` instead of instantaneous CPU usage,
|
|
9
|
+
providing a more stable metric not affected by transient spikes during claude startup.
|
|
10
|
+
Cache TTL is 2 minutes.
|
|
11
|
+
- **Keep RAM threshold with caching**: RAM_THRESHOLD (50%) is still checked but uses cached
|
|
12
|
+
values only (no uncached rechecks) to simplify the logic.
|
|
13
|
+
- **Increase MIN_START_INTERVAL_MS to 2 minutes**: Allows enough time for solve command to
|
|
14
|
+
start actual claude process, ensuring running processes are counted when API limits are checked.
|
|
15
|
+
- **Increase CONSUMER_POLL_INTERVAL_MS to 1 minute**: Reduces unnecessary system checks.
|
|
16
|
+
One-minute polling is sufficient for queue management.
|
|
17
|
+
- **Running processes not a blocking limit**: Commands can run in parallel as long as actual
|
|
18
|
+
limits (CPU, API, etc.) are not exceeded. Claude process info is only supplementary.
|
|
19
|
+
|
|
20
|
+
Fixes #1078
|
|
21
|
+
|
|
3
22
|
## 1.0.4
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/hive-mind",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
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,8 @@
|
|
|
13
13
|
"hive-telegram-bot": "./src/telegram-bot.mjs"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"test": "
|
|
16
|
+
"test": "node tests/solve-queue.test.mjs",
|
|
17
|
+
"test:queue": "node tests/solve-queue.test.mjs",
|
|
17
18
|
"lint": "eslint 'src/**/*.{js,mjs,cjs}'",
|
|
18
19
|
"lint:fix": "eslint 'src/**/*.{js,mjs,cjs}' --fix",
|
|
19
20
|
"format": "prettier --write \"**/*.{js,mjs,json,md}\"",
|
|
@@ -26,11 +26,16 @@ import { getCachedClaudeLimits, getCachedGitHubLimits, getCachedMemoryInfo, getC
|
|
|
26
26
|
/**
|
|
27
27
|
* Configuration constants for queue throttling
|
|
28
28
|
* All thresholds use ratios (0.0 - 1.0) representing usage percentage
|
|
29
|
+
*
|
|
30
|
+
* IMPORTANT: Running claude processes is NOT a blocking limit by itself.
|
|
31
|
+
* Commands can run in parallel as long as actual limits (CPU, API, etc.) are not exceeded.
|
|
32
|
+
* See: https://github.com/link-assistant/hive-mind/issues/1078
|
|
29
33
|
*/
|
|
30
34
|
export const QUEUE_CONFIG = {
|
|
31
35
|
// Resource thresholds (usage ratios: 0.0 - 1.0)
|
|
32
36
|
RAM_THRESHOLD: 0.5, // Stop if RAM usage > 50%
|
|
33
|
-
|
|
37
|
+
// CPU threshold uses 5-minute load average, not instantaneous CPU usage
|
|
38
|
+
CPU_THRESHOLD: 0.5, // Stop if 5-minute load average > 50% of CPU count
|
|
34
39
|
DISK_THRESHOLD: 0.95, // One-at-a-time if disk usage > 95%
|
|
35
40
|
|
|
36
41
|
// API limit thresholds (usage ratios: 0.0 - 1.0)
|
|
@@ -39,8 +44,11 @@ export const QUEUE_CONFIG = {
|
|
|
39
44
|
GITHUB_API_THRESHOLD: 0.8, // Stop if GitHub > 80% with parallel claude
|
|
40
45
|
|
|
41
46
|
// Timing
|
|
42
|
-
MIN_START_INTERVAL_MS:
|
|
43
|
-
|
|
47
|
+
// MIN_START_INTERVAL_MS: Time to allow solve command to start actual claude process
|
|
48
|
+
// This ensures that when API limits are checked, the running process is counted
|
|
49
|
+
MIN_START_INTERVAL_MS: 120000, // 2 minutes between starts (was 1 minute)
|
|
50
|
+
CONSUMER_POLL_INTERVAL_MS: 60000, // 1 minute between queue checks (was 5 seconds)
|
|
51
|
+
MESSAGE_UPDATE_INTERVAL_MS: 60000, // 1 minute between status message updates
|
|
44
52
|
|
|
45
53
|
// Process detection
|
|
46
54
|
CLAUDE_PROCESS_NAMES: ['claude'], // Process names to detect
|
|
@@ -163,6 +171,9 @@ class SolveQueueItem {
|
|
|
163
171
|
this.sessionName = null;
|
|
164
172
|
// Message tracking - forget after STARTED
|
|
165
173
|
this.messageInfo = null; // { chatId, messageId }
|
|
174
|
+
// Track when we last updated the Telegram message
|
|
175
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1078
|
|
176
|
+
this.lastMessageUpdateTime = null;
|
|
166
177
|
}
|
|
167
178
|
|
|
168
179
|
/**
|
|
@@ -409,8 +420,10 @@ export class SolveQueue {
|
|
|
409
420
|
// "Claude process running" only blocks if there are OTHER reasons too
|
|
410
421
|
// This allows parallel execution when limits are not exceeded
|
|
411
422
|
if (hasRunningClaude && reasons.length > 0) {
|
|
412
|
-
// Add claude_running info
|
|
413
|
-
|
|
423
|
+
// Add claude_running info at the END (not beginning) of reasons
|
|
424
|
+
// Since it's supplementary info, not the primary blocking reason
|
|
425
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1078
|
|
426
|
+
reasons.push(formatWaitingReason('claude_running', claudeProcs.count, 0) + ` (${claudeProcs.count} processes)`);
|
|
414
427
|
}
|
|
415
428
|
|
|
416
429
|
const canStart = reasons.length === 0;
|
|
@@ -430,6 +443,11 @@ export class SolveQueue {
|
|
|
430
443
|
|
|
431
444
|
/**
|
|
432
445
|
* Check system resources (RAM, CPU, disk) using cached values
|
|
446
|
+
*
|
|
447
|
+
* Uses 5-minute load average for CPU instead of instantaneous usage.
|
|
448
|
+
* This provides a more stable metric that isn't affected by brief spikes
|
|
449
|
+
* during claude process startup.
|
|
450
|
+
*
|
|
433
451
|
* @returns {Promise<{ok: boolean, reasons: string[], oneAtATime: boolean}>}
|
|
434
452
|
*/
|
|
435
453
|
async checkSystemResources() {
|
|
@@ -446,12 +464,25 @@ export class SolveQueue {
|
|
|
446
464
|
}
|
|
447
465
|
}
|
|
448
466
|
|
|
449
|
-
// Check CPU
|
|
467
|
+
// Check CPU using 5-minute load average (more stable than 1-minute)
|
|
468
|
+
// Cache TTL is 2 minutes, which is appropriate for this metric
|
|
450
469
|
const cpuResult = await getCachedCpuInfo(this.verbose);
|
|
451
470
|
if (cpuResult.success) {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
471
|
+
// Use loadAvg5 (5-minute average) instead of usagePercentage (1-minute based)
|
|
472
|
+
// This provides a more stable metric that isn't affected by transient spikes
|
|
473
|
+
const loadAvg5 = cpuResult.cpuLoad.loadAvg5;
|
|
474
|
+
const cpuCount = cpuResult.cpuLoad.cpuCount;
|
|
475
|
+
// Calculate usage ratio: loadAvg5 / cpuCount
|
|
476
|
+
// Load average of 1.0 per CPU = 100% utilization
|
|
477
|
+
const usageRatio = loadAvg5 / cpuCount;
|
|
478
|
+
const usagePercent = Math.min(100, Math.round(usageRatio * 100));
|
|
479
|
+
|
|
480
|
+
if (this.verbose) {
|
|
481
|
+
this.log(`CPU 5m load avg: ${loadAvg5.toFixed(2)}, cpus: ${cpuCount}, usage: ${usagePercent}%`);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (usageRatio > QUEUE_CONFIG.CPU_THRESHOLD) {
|
|
485
|
+
reasons.push(formatWaitingReason('cpu', usagePercent, QUEUE_CONFIG.CPU_THRESHOLD));
|
|
455
486
|
this.recordThrottle('cpu_high');
|
|
456
487
|
}
|
|
457
488
|
}
|
|
@@ -564,18 +595,33 @@ export class SolveQueue {
|
|
|
564
595
|
* Update item message in Telegram
|
|
565
596
|
* @param {SolveQueueItem} item
|
|
566
597
|
* @param {string} text
|
|
598
|
+
* @param {boolean} trackUpdateTime - Whether to track this as a periodic update (default: true)
|
|
567
599
|
*/
|
|
568
|
-
async updateItemMessage(item, text) {
|
|
600
|
+
async updateItemMessage(item, text, trackUpdateTime = true) {
|
|
569
601
|
if (!item.messageInfo || !item.ctx) return;
|
|
570
602
|
|
|
571
603
|
try {
|
|
572
604
|
const { chatId, messageId } = item.messageInfo;
|
|
573
605
|
await item.ctx.telegram.editMessageText(chatId, messageId, undefined, text, { parse_mode: 'Markdown' });
|
|
606
|
+
if (trackUpdateTime) {
|
|
607
|
+
item.lastMessageUpdateTime = Date.now();
|
|
608
|
+
}
|
|
574
609
|
} catch (error) {
|
|
575
610
|
this.log(`Failed to update message: ${error.message}`);
|
|
576
611
|
}
|
|
577
612
|
}
|
|
578
613
|
|
|
614
|
+
/**
|
|
615
|
+
* Check if an item's message should be updated periodically
|
|
616
|
+
* @param {SolveQueueItem} item
|
|
617
|
+
* @returns {boolean}
|
|
618
|
+
*/
|
|
619
|
+
shouldUpdateMessage(item) {
|
|
620
|
+
if (!item.messageInfo || !item.ctx) return false;
|
|
621
|
+
if (!item.lastMessageUpdateTime) return true; // Never updated
|
|
622
|
+
return Date.now() - item.lastMessageUpdateTime >= QUEUE_CONFIG.MESSAGE_UPDATE_INTERVAL_MS;
|
|
623
|
+
}
|
|
624
|
+
|
|
579
625
|
/**
|
|
580
626
|
* Consumer loop - processes items from the queue
|
|
581
627
|
*/
|
|
@@ -592,14 +638,20 @@ export class SolveQueue {
|
|
|
592
638
|
|
|
593
639
|
if (!check.canStart) {
|
|
594
640
|
// Update all queued items to waiting status with reason
|
|
641
|
+
// Also periodically refresh messages to show current status
|
|
642
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1078
|
|
595
643
|
for (const item of this.queue) {
|
|
596
644
|
if (item.status === QueueItemStatus.QUEUED || item.status === QueueItemStatus.WAITING) {
|
|
597
645
|
const previousStatus = item.status;
|
|
598
646
|
const previousReason = item.waitingReason;
|
|
599
647
|
item.setWaiting(check.reason);
|
|
600
648
|
|
|
601
|
-
// Update message if
|
|
602
|
-
|
|
649
|
+
// Update message if:
|
|
650
|
+
// 1. Status or reason changed
|
|
651
|
+
// 2. OR it's time for a periodic update (every MESSAGE_UPDATE_INTERVAL_MS)
|
|
652
|
+
const shouldUpdate = previousStatus !== item.status || previousReason !== item.waitingReason || this.shouldUpdateMessage(item);
|
|
653
|
+
|
|
654
|
+
if (shouldUpdate) {
|
|
603
655
|
const position = this.queue.indexOf(item) + 1;
|
|
604
656
|
await this.updateItemMessage(item, `⏳ Waiting (position #${position})\n\n${item.infoBlock}\n\n*Reason:*\n${check.reason}`);
|
|
605
657
|
}
|
|
@@ -622,13 +674,10 @@ export class SolveQueue {
|
|
|
622
674
|
const item = this.queue.shift();
|
|
623
675
|
if (!item) continue;
|
|
624
676
|
|
|
625
|
-
//
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
await this.sleep(QUEUE_CONFIG.CONSUMER_POLL_INTERVAL_MS);
|
|
630
|
-
continue;
|
|
631
|
-
}
|
|
677
|
+
// NOTE: Running claude processes is NOT a blocking limit by itself
|
|
678
|
+
// Commands can run in parallel as long as actual limits (CPU, API, etc.) are not exceeded
|
|
679
|
+
// The MIN_START_INTERVAL_MS ensures enough time for processes to be counted
|
|
680
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1078
|
|
632
681
|
|
|
633
682
|
// Update status to Starting
|
|
634
683
|
item.setStarting();
|