@link-assistant/hive-mind 1.26.1 → 1.26.3

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,22 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.26.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 864023d: Add case study and regression test for issue #1389: no `ready to merge` comment when `--auto-restart-until-mergeable` is enabled
8
+
9
+ Documents root cause (checkForExistingComment searching all-time PR history in v1.25.7),
10
+ timeline reconstruction from log b623ee9f, and confirms the fix from issue #1371 (in-memory
11
+ readyToMergeCommentPosted flag) resolves the cross-session notification suppression.
12
+ Adds test-ready-to-merge-cross-session-1389.mjs to prevent regression to the old approach.
13
+
14
+ ## 1.26.2
15
+
16
+ ### Patch Changes
17
+
18
+ - 72c933c: Skip empty Claude subsection headers when auth error occurs in /limits output
19
+
3
20
  ## 1.26.1
4
21
 
5
22
  ### Patch Changes
package/README.md CHANGED
@@ -46,7 +46,7 @@ For detailed features and comparisons, see [docs/FEATURES.md](./docs/FEATURES.md
46
46
 
47
47
  It is UNSAFE to run this software on your developer machine.
48
48
 
49
- It is recommended to use SEPARATE Ubuntu 24.04 installation (installation script is prepared for you).
49
+ It is recommended to use Docker for installation (both locally and on servers). See the [Docker installation](#using-docker) section below.
50
50
 
51
51
  This software uses full autonomous mode of Claude Code, that means it is free to execute any commands it sees fit.
52
52
 
@@ -292,121 +292,26 @@ See [docs/HELM.md](./docs/HELM.md) for detailed Helm configuration options.
292
292
 
293
293
  **Note:** The Helm chart is published to [ArtifactHub](https://artifacthub.io/packages/helm/link-assistant/hive-mind) for easy discovery.
294
294
 
295
- ### Installation on Ubuntu 24.04 server
295
+ ### Installation on Ubuntu 24.04 server (Deprecated)
296
296
 
297
- 1. Reset/install VPS/VDS server with fresh Ubuntu 24.04
298
- 2. Login to `root` user.
299
- 3. Execute main installation script
300
-
301
- ```bash
302
- curl -fsSL -o- https://github.com/link-assistant/hive-mind/raw/refs/heads/main/scripts/ubuntu-24-server-install.sh | bash
303
- ```
304
-
305
- **Note:** The installation script will NOT run `gh auth login` automatically. This is intentional to support Docker builds without timeouts. Authentication is performed in the next steps.
306
-
307
- 4. Login to `hive` user
308
-
309
- ```bash
310
- su - hive
311
- ```
312
-
313
- 5. **IMPORTANT:** Authenticate with GitHub CLI AFTER installation is complete
314
-
315
- ```bash
316
- gh-setup-git-identity
317
- ```
318
-
319
- Note: Follow the prompts to authenticate with your GitHub account. This is required for the gh tool to work, and the system will perform all actions using this GitHub account. This step must be done AFTER the installation script completes to avoid build timeouts in Docker environments.
320
-
321
- 6. Claude Code CLI, OpenCode AI CLI, and @link-assistant/agent are preinstalled with the previous script. Now you need to make sure claude is authorized. Execute claude command, and follow all steps to authorize the local claude
322
-
323
- ```bash
324
- claude
325
- ```
326
-
327
- Note: Both opencode and agent come with free Grok Code Fast 1 model by default - so no authorization is required for these tools.
328
-
329
- 7. Launch the Hive Mind telegram bot:
330
-
331
- **Using Links Notation (recommended):**
332
-
333
- ```
334
- screen -R bot # Enter new screen for bot
335
-
336
- hive-telegram-bot --configuration "
337
- TELEGRAM_BOT_TOKEN: '849...355:AAG...rgk_YZk...aPU'
338
- TELEGRAM_ALLOWED_CHATS:
339
- -1002975819706
340
- -1002861722681
341
- TELEGRAM_HIVE_OVERRIDES:
342
- --all-issues
343
- --once
344
- --skip-issues-with-prs
345
- --attach-logs
346
- --verbose
347
- --no-tool-check
348
- TELEGRAM_SOLVE_OVERRIDES:
349
- --attach-logs
350
- --verbose
351
- --no-tool-check
352
- TELEGRAM_BOT_VERBOSE: true
353
- "
354
-
355
- # Press CTRL + A + D for detach from screen
356
- ```
357
-
358
- **Using individual command-line options:**
359
-
360
- ```
361
- screen -R bot # Enter new screen for bot
362
-
363
- hive-telegram-bot --token 849...355:AAG...rgk_YZk...aPU --allowed-chats "(
364
- -1002975819706
365
- -1002861722681
366
- )" --hive-overrides "(
367
- --all-issues
368
- --once
369
- --skip-issues-with-prs
370
- --attach-logs
371
- --verbose
372
- --no-tool-check
373
- )" --solve-overrides "(
374
- --attach-logs
375
- --verbose
376
- --no-tool-check
377
- )" --verbose
378
-
379
- # Press CTRL + A + D for detach from screen
380
- ```
381
-
382
- Note: You may need to register you own bot with https://t.me/BotFather to get the bot token.
383
-
384
- #### Codex sign-in
385
-
386
- 1. Connect to your instance of VPS with Hive Mind installed, using SSH with tunnel opened
387
-
388
- ```bash
389
- ssh -L 1455:localhost:1455 root@123.123.123.123
390
- ```
391
-
392
- 2. Start codex login oAuth server:
393
-
394
- ```bash
395
- codex login
396
- ```
397
-
398
- The oAuth callback server on 1455 port will be started, and the link to oAuth will be printed, copy the link.
399
-
400
- 3. Use your browser on machine where you started the tunnel from, paste there the link from `codex login` command, and go there using your browser. Once redirected to localhost:1455 you will see successful login page, and in `codex login` you will see `Successfully logged in`. After that `codex login` command will complete, and you can use `codex` command as usual to verify. It should also be working with `--tool codex` in `solve` and `hive` commands.
297
+ > ⚠️ **DEPRECATED:** This installation method is no longer recommended.
298
+ >
299
+ > **We now recommend using Docker for all installations**, both on developer machines and servers.
300
+ > Docker provides better isolation, easier management, and consistent environments.
301
+ >
302
+ > Please use the [Docker installation method](#using-docker) above.
303
+ > For Kubernetes deployments, see the [Helm installation](#helm-installation-kubernetes) section.
304
+ >
305
+ > The legacy bare-metal installation instructions have been moved to [docs/UBUNTU-SERVER.md](./docs/UBUNTU-SERVER.md) for reference.
401
306
 
402
307
  ### Core Operations
403
308
 
404
309
  ```bash
405
310
  # Solve using maximum power
406
- solve https://github.com/Veronika89-lang/index.html/issues/1 --auto-continue --attach-logs --verbose --model opus --auto-fork --think max
311
+ solve https://github.com/Veronika89-lang/index.html/issues/1 --auto-continue --attach-logs --verbose --model opus --think max
407
312
 
408
- # Solve GitHub issues automatically (auto-fork if no write access)
409
- solve https://github.com/owner/repo/issues/123 --auto-fork --model sonnet
313
+ # Solve GitHub issues automatically
314
+ solve https://github.com/owner/repo/issues/123 --model sonnet
410
315
 
411
316
  # Solve issue with PR to custom branch (manual fork mode)
412
317
  solve https://github.com/owner/repo/issues/123 --base-branch develop --fork
@@ -420,8 +325,8 @@ solve https://github.com/owner/repo/issues/123 --resume session-id
420
325
  # Start hive orchestration (monitor and solve issues automatically)
421
326
  hive https://github.com/owner/repo --monitor-tag "help wanted" --concurrency 3
422
327
 
423
- # Monitor all issues in organization with auto-fork
424
- hive https://github.com/microsoft --all-issues --max-issues 10 --auto-fork
328
+ # Monitor all issues in organization
329
+ hive https://github.com/microsoft --all-issues --max-issues 10
425
330
 
426
331
  # Run collaborative review process
427
332
  review --repo owner/repo --pr 456
@@ -440,8 +345,6 @@ review --repo owner/repo --pr 456
440
345
  | `reviewers-hive.mjs` (alpha / experimental) | Review team management | Multi-agent consensus, reviewer assignment |
441
346
  | `telegram-bot.mjs` (stable) | Telegram bot interface | Remote command execution, group chat support, diagnostic tools |
442
347
 
443
- > **Note**: For a comprehensive analysis of the "Could not process image" error in AI issue solvers, see the [Case Study: Issue #597](docs/case-studies/issue-597/README.md). The case study includes root cause analysis, timeline reconstruction, and evidence of GitHub's time-limited S3 URLs causing image processing failures. Separate tools for downloading GitHub issues and PRs with embedded images are being developed at [gh-download-issue](https://github.com/link-foundation/gh-download-issue) and [gh-download-pull-request](https://github.com/link-foundation/gh-download-pull-request).
444
-
445
348
  ## 🔧 solve Options
446
349
 
447
350
  ```bash
@@ -715,8 +618,8 @@ sequenceDiagram
715
618
  ### Automated Issue Resolution
716
619
 
717
620
  ```bash
718
- # Auto-fork and solve issue (automatic fork detection for public repos)
719
- solve https://github.com/owner/repo/issues/123 --auto-fork --model opus
621
+ # Solve issue (automatically forks if no write access)
622
+ solve https://github.com/owner/repo/issues/123 --model opus
720
623
 
721
624
  # Manual fork and solve issue (works for both public and private repos)
722
625
  solve https://github.com/owner/repo/issues/123 --fork --model opus
@@ -737,17 +640,17 @@ solve https://github.com/owner/repo/issues/123 --dry-run
737
640
  # Monitor single repository with specific label
738
641
  hive https://github.com/owner/repo --monitor-tag "bug" --concurrency 4
739
642
 
740
- # Monitor all issues in an organization with auto-fork
741
- hive https://github.com/microsoft --all-issues --max-issues 20 --once --auto-fork
643
+ # Monitor all issues in an organization
644
+ hive https://github.com/microsoft --all-issues --max-issues 20 --once
742
645
 
743
646
  # Monitor user repositories with high concurrency
744
- hive https://github.com/username --all-issues --concurrency 8 --interval 120 --auto-fork
647
+ hive https://github.com/username --all-issues --concurrency 8 --interval 120
745
648
 
746
649
  # Skip issues that already have PRs
747
650
  hive https://github.com/org/repo --skip-issues-with-prs --verbose
748
651
 
749
- # Auto-cleanup temporary files and auto-fork if needed
750
- hive https://github.com/org/repo --auto-cleanup --auto-fork --concurrency 5
652
+ # Auto-cleanup temporary files
653
+ hive https://github.com/org/repo --auto-cleanup --concurrency 5
751
654
  ```
752
655
 
753
656
  ### Session Management
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.26.1",
3
+ "version": "1.26.3",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -700,177 +700,184 @@ export function calculateTimePassedPercentage(resetsAt, periodHours) {
700
700
  * @param {Object} cpuLoad - Optional CPU load info from getCpuLoadInfo
701
701
  * @param {Object} memory - Optional memory info from getMemoryInfo
702
702
  * @param {string|null} claudeError - Optional error message to show in Claude sections (e.g., auth expired)
703
- * @returns {string} Formatted message
703
+ * @param {string[]} extraSections - Optional extra sections to append inside the code block (e.g. queue status)
704
+ * @returns {string} Formatted message wrapped in a single code block
704
705
  * @see https://github.com/link-assistant/hive-mind/issues/1242
705
706
  */
706
- export function formatUsageMessage(usage, diskSpace = null, githubRateLimit = null, cpuLoad = null, memory = null, claudeError = null) {
707
- // Use code block for monospace font to align progress bars properly
708
- let message = '```\n';
707
+ export function formatUsageMessage(usage, diskSpace = null, githubRateLimit = null, cpuLoad = null, memory = null, claudeError = null, extraSections = []) {
708
+ // Build sections as individual text blocks; they will all be joined and wrapped in a
709
+ // single code block at the end. This avoids fragile string-searching to inject content.
710
+
711
+ const sections = [];
709
712
 
710
713
  // Show current time
711
- message += `Current time: ${formatCurrentTime()}\n\n`;
714
+ sections.push(`Current time: ${formatCurrentTime()}\n`);
712
715
 
713
716
  // CPU load section (if provided)
714
717
  // Threshold: Blocks new commands when usage >= 65%
715
718
  if (cpuLoad) {
716
- message += 'CPU\n';
719
+ let section = 'CPU\n';
717
720
  const usedBar = getProgressBar(cpuLoad.usagePercentage, DISPLAY_THRESHOLDS.CPU);
718
721
  // Show 'used' label when below threshold, warning emoji when at/above threshold
719
722
  // See: https://github.com/link-assistant/hive-mind/issues/1267
720
723
  const suffix = cpuLoad.usagePercentage >= DISPLAY_THRESHOLDS.CPU ? ' ⚠️' : ' used';
721
- message += `${usedBar} ${cpuLoad.usagePercentage}%${suffix}\n`;
724
+ section += `${usedBar} ${cpuLoad.usagePercentage}%${suffix}\n`;
722
725
  // Show cores used based on 5m load average (e.g., "0.04/6 CPU cores used" or "3/6 CPU cores used")
723
726
  // Use parseFloat to strip unnecessary trailing zeros (3.00 -> 3, 0.10 -> 0.1, 0.04 -> 0.04)
724
- message += `${parseFloat(cpuLoad.loadAvg5.toFixed(2))}/${cpuLoad.cpuCount} CPU cores\n\n`;
727
+ section += `${parseFloat(cpuLoad.loadAvg5.toFixed(2))}/${cpuLoad.cpuCount} CPU cores\n`;
728
+ sections.push(section);
725
729
  }
726
730
 
727
731
  // Memory section (if provided)
728
732
  // Threshold: Blocks new commands when usage >= 65%
729
733
  if (memory) {
730
- message += 'RAM\n';
734
+ let section = 'RAM\n';
731
735
  const usedBar = getProgressBar(memory.usedPercentage, DISPLAY_THRESHOLDS.RAM);
732
736
  const suffix = memory.usedPercentage >= DISPLAY_THRESHOLDS.RAM ? ' ⚠️' : ' used';
733
- message += `${usedBar} ${memory.usedPercentage}%${suffix}\n`;
734
- message += `${formatBytesRange(memory.usedBytes, memory.totalBytes)}\n\n`;
737
+ section += `${usedBar} ${memory.usedPercentage}%${suffix}\n`;
738
+ section += `${formatBytesRange(memory.usedBytes, memory.totalBytes)}\n`;
739
+ sections.push(section);
735
740
  }
736
741
 
737
742
  // Disk space section (if provided)
738
743
  // Threshold: One-at-a-time mode when usage >= 90%
739
744
  if (diskSpace) {
740
- message += 'Disk space\n';
745
+ let section = 'Disk space\n';
741
746
  // Show used percentage with progress bar and threshold marker
742
747
  const usedBar = getProgressBar(diskSpace.usedPercentage, DISPLAY_THRESHOLDS.DISK);
743
748
  const suffix = diskSpace.usedPercentage >= DISPLAY_THRESHOLDS.DISK ? ' ⚠️' : ' used';
744
- message += `${usedBar} ${diskSpace.usedPercentage}%${suffix}\n`;
745
- message += `${formatBytesRange(diskSpace.usedBytes, diskSpace.totalBytes)}\n\n`;
749
+ section += `${usedBar} ${diskSpace.usedPercentage}%${suffix}\n`;
750
+ section += `${formatBytesRange(diskSpace.usedBytes, diskSpace.totalBytes)}\n`;
751
+ sections.push(section);
746
752
  }
747
753
 
748
754
  // GitHub API rate limits section (if provided)
749
755
  // Threshold: Blocks parallel claude commands when >= 75%
750
756
  if (githubRateLimit) {
751
- message += 'GitHub API\n';
757
+ let section = 'GitHub API\n';
752
758
  // Show used percentage with progress bar and threshold marker
753
759
  const usedBar = getProgressBar(githubRateLimit.usedPercentage, DISPLAY_THRESHOLDS.GITHUB_API);
754
760
  const suffix = githubRateLimit.usedPercentage >= DISPLAY_THRESHOLDS.GITHUB_API ? ' ⚠️' : ' used';
755
- message += `${usedBar} ${githubRateLimit.usedPercentage}%${suffix}\n`;
756
- message += `${githubRateLimit.used}/${githubRateLimit.limit} requests\n`;
761
+ section += `${usedBar} ${githubRateLimit.usedPercentage}%${suffix}\n`;
762
+ section += `${githubRateLimit.used}/${githubRateLimit.limit} requests\n`;
757
763
  if (githubRateLimit.relativeReset) {
758
- message += `Resets in ${githubRateLimit.relativeReset} (${githubRateLimit.resetTime})\n`;
764
+ section += `Resets in ${githubRateLimit.relativeReset} (${githubRateLimit.resetTime})\n`;
759
765
  } else if (githubRateLimit.resetTime) {
760
- message += `Resets ${githubRateLimit.resetTime}\n`;
766
+ section += `Resets ${githubRateLimit.resetTime}\n`;
761
767
  }
762
- message += '\n';
768
+ sections.push(section);
763
769
  }
764
770
 
765
771
  // Claude limits section
766
- // When there's an error (e.g., auth expired), show it once here instead of repeating in each subsection
767
- if (claudeError) {
768
- message += `Claude limits\n${claudeError}\n\n`;
769
- }
770
-
771
- // Claude 5 hour session (five_hour)
772
- // Threshold: One-at-a-time mode when usage >= 65%
773
- message += 'Claude 5 hour session\n';
772
+ // When there's an error (e.g., auth expired), show it once and skip empty subsections
774
773
  if (claudeError) {
775
- // Error already shown above; skip subsection content
776
- } else if (usage && usage.currentSession.percentage !== null) {
777
- // Add time passed progress bar first (no threshold marker for time)
778
- const timePassed = calculateTimePassedPercentage(usage.currentSession.resetsAt, 5);
779
- if (timePassed !== null) {
780
- const timeBar = getProgressBar(timePassed);
781
- message += `${timeBar} ${timePassed}% passed\n`;
782
- }
774
+ sections.push(`Claude limits\n${claudeError}\n`);
775
+ } else {
776
+ // Claude 5 hour session (five_hour)
777
+ // Threshold: One-at-a-time mode when usage >= 65%
778
+ let sessionSection = 'Claude 5 hour session\n';
779
+ if (usage && usage.currentSession.percentage !== null) {
780
+ // Add time passed progress bar first (no threshold marker for time)
781
+ const timePassed = calculateTimePassedPercentage(usage.currentSession.resetsAt, 5);
782
+ if (timePassed !== null) {
783
+ const timeBar = getProgressBar(timePassed);
784
+ sessionSection += `${timeBar} ${timePassed}% passed\n`;
785
+ }
783
786
 
784
- // Add usage progress bar second with threshold marker
785
- // Use Math.floor so 100% only appears when usage is exactly 100%
786
- // See: https://github.com/link-assistant/hive-mind/issues/1133
787
- const pct = Math.floor(usage.currentSession.percentage);
788
- const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION);
789
- const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION ? ' ⚠️' : ' used';
790
- message += `${bar} ${pct}%${suffix}\n`;
791
-
792
- if (usage.currentSession.resetTime) {
793
- const relativeTime = formatRelativeTime(usage.currentSession.resetsAt);
794
- if (relativeTime) {
795
- message += `Resets in ${relativeTime} (${usage.currentSession.resetTime})\n`;
796
- } else {
797
- message += `Resets ${usage.currentSession.resetTime}\n`;
787
+ // Add usage progress bar second with threshold marker
788
+ // Use Math.floor so 100% only appears when usage is exactly 100%
789
+ // See: https://github.com/link-assistant/hive-mind/issues/1133
790
+ const pct = Math.floor(usage.currentSession.percentage);
791
+ const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION);
792
+ const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION ? ' ⚠️' : ' used';
793
+ sessionSection += `${bar} ${pct}%${suffix}\n`;
794
+
795
+ if (usage.currentSession.resetTime) {
796
+ const relativeTime = formatRelativeTime(usage.currentSession.resetsAt);
797
+ if (relativeTime) {
798
+ sessionSection += `Resets in ${relativeTime} (${usage.currentSession.resetTime})\n`;
799
+ } else {
800
+ sessionSection += `Resets ${usage.currentSession.resetTime}\n`;
801
+ }
798
802
  }
803
+ } else {
804
+ sessionSection += 'N/A\n';
799
805
  }
800
- } else {
801
- message += 'N/A\n';
802
- }
803
- message += '\n';
806
+ sections.push(sessionSection);
807
+
808
+ // Current week (all models / seven_day)
809
+ // Threshold: One-at-a-time mode when usage >= 97%
810
+ let allModelsSection = 'Current week (all models)\n';
811
+ if (usage && usage.allModels.percentage !== null) {
812
+ // Add time passed progress bar first (no threshold marker for time)
813
+ const timePassed = calculateTimePassedPercentage(usage.allModels.resetsAt, 168);
814
+ if (timePassed !== null) {
815
+ const timeBar = getProgressBar(timePassed);
816
+ allModelsSection += `${timeBar} ${timePassed}% passed\n`;
817
+ }
804
818
 
805
- // Current week (all models / seven_day)
806
- // Threshold: One-at-a-time mode when usage >= 97%
807
- message += 'Current week (all models)\n';
808
- if (claudeError) {
809
- // Error already shown above; skip subsection content
810
- } else if (usage && usage.allModels.percentage !== null) {
811
- // Add time passed progress bar first (no threshold marker for time)
812
- const timePassed = calculateTimePassedPercentage(usage.allModels.resetsAt, 168);
813
- if (timePassed !== null) {
814
- const timeBar = getProgressBar(timePassed);
815
- message += `${timeBar} ${timePassed}% passed\n`;
819
+ // Add usage progress bar second with threshold marker
820
+ // Use Math.floor so 100% only appears when usage is exactly 100%
821
+ // See: https://github.com/link-assistant/hive-mind/issues/1133
822
+ const pct = Math.floor(usage.allModels.percentage);
823
+ const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_WEEKLY);
824
+ const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_WEEKLY ? ' ⚠️' : ' used';
825
+ allModelsSection += `${bar} ${pct}%${suffix}\n`;
826
+
827
+ if (usage.allModels.resetTime) {
828
+ const relativeTime = formatRelativeTime(usage.allModels.resetsAt);
829
+ if (relativeTime) {
830
+ allModelsSection += `Resets in ${relativeTime} (${usage.allModels.resetTime})\n`;
831
+ } else {
832
+ allModelsSection += `Resets ${usage.allModels.resetTime}\n`;
833
+ }
834
+ }
835
+ } else {
836
+ allModelsSection += 'N/A\n';
816
837
  }
838
+ sections.push(allModelsSection);
839
+
840
+ // Current week (Sonnet only / seven_day_sonnet)
841
+ // Threshold: One-at-a-time mode when usage >= 97% (same as all models)
842
+ let sonnetSection = 'Current week (Sonnet only)\n';
843
+ if (usage && usage.sonnetOnly.percentage !== null) {
844
+ // Add time passed progress bar first (no threshold marker for time)
845
+ const timePassed = calculateTimePassedPercentage(usage.sonnetOnly.resetsAt, 168);
846
+ if (timePassed !== null) {
847
+ const timeBar = getProgressBar(timePassed);
848
+ sonnetSection += `${timeBar} ${timePassed}% passed\n`;
849
+ }
817
850
 
818
- // Add usage progress bar second with threshold marker
819
- // Use Math.floor so 100% only appears when usage is exactly 100%
820
- // See: https://github.com/link-assistant/hive-mind/issues/1133
821
- const pct = Math.floor(usage.allModels.percentage);
822
- const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_WEEKLY);
823
- const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_WEEKLY ? ' ⚠️' : ' used';
824
- message += `${bar} ${pct}%${suffix}\n`;
825
-
826
- if (usage.allModels.resetTime) {
827
- const relativeTime = formatRelativeTime(usage.allModels.resetsAt);
828
- if (relativeTime) {
829
- message += `Resets in ${relativeTime} (${usage.allModels.resetTime})\n`;
830
- } else {
831
- message += `Resets ${usage.allModels.resetTime}\n`;
851
+ // Add usage progress bar second with threshold marker
852
+ // Use Math.floor so 100% only appears when usage is exactly 100%
853
+ // See: https://github.com/link-assistant/hive-mind/issues/1133
854
+ const pct = Math.floor(usage.sonnetOnly.percentage);
855
+ const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_WEEKLY);
856
+ const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_WEEKLY ? ' ⚠️' : ' used';
857
+ sonnetSection += `${bar} ${pct}%${suffix}\n`;
858
+
859
+ if (usage.sonnetOnly.resetTime) {
860
+ const relativeTime = formatRelativeTime(usage.sonnetOnly.resetsAt);
861
+ if (relativeTime) {
862
+ sonnetSection += `Resets in ${relativeTime} (${usage.sonnetOnly.resetTime})\n`;
863
+ } else {
864
+ sonnetSection += `Resets ${usage.sonnetOnly.resetTime}\n`;
865
+ }
832
866
  }
867
+ } else {
868
+ sonnetSection += 'N/A\n';
833
869
  }
834
- } else {
835
- message += 'N/A\n';
870
+ sections.push(sonnetSection);
836
871
  }
837
- message += '\n';
838
872
 
839
- // Current week (Sonnet only / seven_day_sonnet)
840
- // Threshold: One-at-a-time mode when usage >= 97% (same as all models)
841
- message += 'Current week (Sonnet only)\n';
842
- if (claudeError) {
843
- // Error already shown above; skip subsection content
844
- } else if (usage && usage.sonnetOnly.percentage !== null) {
845
- // Add time passed progress bar first (no threshold marker for time)
846
- const timePassed = calculateTimePassedPercentage(usage.sonnetOnly.resetsAt, 168);
847
- if (timePassed !== null) {
848
- const timeBar = getProgressBar(timePassed);
849
- message += `${timeBar} ${timePassed}% passed\n`;
850
- }
851
-
852
- // Add usage progress bar second with threshold marker
853
- // Use Math.floor so 100% only appears when usage is exactly 100%
854
- // See: https://github.com/link-assistant/hive-mind/issues/1133
855
- const pct = Math.floor(usage.sonnetOnly.percentage);
856
- const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_WEEKLY);
857
- const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_WEEKLY ? ' ⚠️' : ' used';
858
- message += `${bar} ${pct}%${suffix}\n`;
859
-
860
- if (usage.sonnetOnly.resetTime) {
861
- const relativeTime = formatRelativeTime(usage.sonnetOnly.resetsAt);
862
- if (relativeTime) {
863
- message += `Resets in ${relativeTime} (${usage.sonnetOnly.resetTime})\n`;
864
- } else {
865
- message += `Resets ${usage.sonnetOnly.resetTime}\n`;
866
- }
867
- }
868
- } else {
869
- message += 'N/A\n';
873
+ // Append any caller-provided extra sections (e.g. queue status) inside the code block
874
+ for (const extra of extraSections) {
875
+ sections.push(extra);
870
876
  }
871
877
 
872
- message += '```';
873
- return message;
878
+ // Wrap all sections in a single code block for monospace font / aligned progress bars.
879
+ // Sections are separated by blank lines; the trailing newline on each section provides spacing.
880
+ return '```\n' + sections.join('\n') + '```';
874
881
  }
875
882
 
876
883
  // ============================================================================
@@ -785,17 +785,14 @@ bot.command('limits', async ctx => {
785
785
  // while still displaying all other limits sections (disk, GitHub, CPU, memory)
786
786
  // See: https://github.com/link-assistant/hive-mind/issues/1343
787
787
  const claudeError = limits.claude.success ? null : limits.claude.error;
788
- let message = '📊 *Usage Limits*\n\n' + formatUsageMessage(limits.claude.success ? limits.claude.usage : null, limits.disk.success ? limits.disk.diskSpace : null, limits.github.success ? limits.github.githubRateLimit : null, limits.cpu.success ? limits.cpu.cpuLoad : null, limits.memory.success ? limits.memory.memory : null, claudeError);
789
788
  const solveQueue = getSolveQueue({ verbose: VERBOSE });
790
- // Insert per-queue status into the code block
791
- // Shows each queue (claude, agent) with pending/processing counts
792
- // Processing counts are actual running system processes (via pgrep)
789
+ // Fetch queue status and pass it as an extra section to formatUsageMessage so that all
790
+ // sections are assembled before the code block is formed — no fragile string-searching needed.
791
+ // Shows each queue (claude, agent) with pending/processing counts.
792
+ // Processing counts are actual running system processes (via pgrep).
793
793
  // See: https://github.com/link-assistant/hive-mind/issues/1267
794
- const codeBlockEnd = message.lastIndexOf('```');
795
- if (codeBlockEnd !== -1) {
796
- const queueStatus = await solveQueue.formatStatus();
797
- message = message.slice(0, codeBlockEnd) + `\n${queueStatus}` + message.slice(codeBlockEnd);
798
- }
794
+ const queueStatus = await solveQueue.formatStatus();
795
+ const message = '📊 *Usage Limits*\n\n' + formatUsageMessage(limits.claude.success ? limits.claude.usage : null, limits.disk.success ? limits.disk.diskSpace : null, limits.github.success ? limits.github.githubRateLimit : null, limits.cpu.success ? limits.cpu.cpuLoad : null, limits.memory.success ? limits.memory.memory : null, claudeError, [queueStatus]);
799
796
  await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, message, { parse_mode: 'Markdown' });
800
797
  });
801
798
  bot.command('version', async ctx => {