@link-assistant/hive-mind 1.23.12 → 1.23.14

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,26 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.23.14
4
+
5
+ ### Patch Changes
6
+
7
+ - 069d437: Parallelize version gathering with Promise.all for 6-30x performance improvement
8
+ - Replaced sequential `execSync` calls with parallel `execAsync` using `Promise.all`
9
+ - Reduced execution time from 30-150s to ~2-5s for version info gathering
10
+ - Added support for all `--tool` options: agent, codex, opencode, qwen-code, gemini, copilot
11
+ - Reorganized Telegram output to group tools by programming language instead of generic categories
12
+ - Consolidated hive-mind version display to show single version with restart warning when process version differs from installed
13
+ - Added `gatherTimeMs` metric to track performance
14
+
15
+ ## 1.23.13
16
+
17
+ ### Patch Changes
18
+
19
+ - af1f456: fix: suppress dotenvx MISSING_ENV_FILE warnings in hive-telegram-bot --version
20
+ - Add early --version handling before loading dotenvx to avoid warnings
21
+ - Add ignore: ['MISSING_ENV_FILE'] option to make .env file optional
22
+ - Add tests for version output in tests/test-telegram-bot-version.mjs
23
+
3
24
  ## 1.23.12
4
25
 
5
26
  ### Patch Changes
package/README.md CHANGED
@@ -149,11 +149,14 @@ Run the Hive Mind using Docker for safer local installation - no manual setup re
149
149
  # Pull the latest image from Docker Hub
150
150
  docker pull konard/hive-mind:latest
151
151
 
152
- # Run an interactive session
153
- docker run -it konard/hive-mind:latest
152
+ # Start hive-mind container
153
+ docker run -dit --name hive-mind konard/hive-mind:latest
154
154
 
155
- # IMPORTANT: Authenticate AFTER the Docker image is installed
156
- # This avoids build timeouts and allows the installation to complete successfully
155
+ # Verify container started
156
+ docker ps -a
157
+
158
+ # Enter additional terminal process to do installation
159
+ docker exec -it hive-mind /bin/bash
157
160
 
158
161
  # Inside the container, authenticate with GitHub
159
162
  gh-setup-git-identity
@@ -163,6 +166,15 @@ claude
163
166
 
164
167
  # Now you can use hive and solve commands
165
168
  solve https://github.com/owner/repo/issues/123
169
+
170
+ # Or you can run telegram bot:
171
+
172
+ # Run an to main process
173
+ docker attach hive-mind
174
+
175
+ # Run bot here
176
+
177
+ # Press Ctrl + P, Ctrl + Q to detach without destroying the container (no stopping of main bash process)
166
178
  ```
167
179
 
168
180
  **Benefits of Docker:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.23.12",
3
+ "version": "1.23.14",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ // Early exit for --version (issue #1318: avoid dotenvx MISSING_ENV_FILE warnings)
3
+ if (process.argv.includes('--version')) {
4
+ const v = await import('./version.lib.mjs').then(m => m.getVersion()).catch(() => 'unknown');
5
+ console.log(v);
6
+ process.exit(v === 'unknown' ? 1 : 0);
7
+ }
2
8
 
3
9
  import { spawn } from 'child_process';
4
10
  import { promisify } from 'util';
@@ -21,7 +27,9 @@ const dotenvx = dotenvxModule.default || dotenvxModule;
21
27
  const getenv = await use('getenv');
22
28
 
23
29
  // Load .env configuration as base
24
- dotenvx.config({ quiet: true });
30
+ // quiet: true suppresses info messages, ignore: ['MISSING_ENV_FILE'] suppresses error when .env doesn't exist
31
+ // This makes .env file optional (issue #1318)
32
+ dotenvx.config({ quiet: true, ignore: ['MISSING_ENV_FILE'] });
25
33
 
26
34
  // Load .lenv configuration (if exists)
27
35
  // .lenv overrides .env
@@ -30,20 +38,15 @@ loadLenvConfig({ override: true, quiet: true });
30
38
  const yargsModule = await use('yargs@17.7.2');
31
39
  const yargs = yargsModule.default || yargsModule;
32
40
  const { hideBin } = await use('yargs@17.7.2/helpers');
33
-
34
- // Import solve and hive yargs configurations for validation
41
+ // Import yargs configurations, GitHub utilities, and telegram helpers
35
42
  const { createYargsConfig: createSolveYargsConfig, detectMalformedFlags } = await import('./solve.config.lib.mjs');
36
43
  const { createYargsConfig: createHiveYargsConfig } = await import('./hive.config.lib.mjs');
37
- // Import GitHub URL parser for extracting URLs from messages
38
44
  const { parseGitHubUrl } = await import('./github.lib.mjs');
39
- // Import model validation for early validation with helpful error messages
40
45
  const { validateModelName } = await import('./model-validation.lib.mjs');
41
- // Import libraries for /limits, /version, and markdown escaping
42
46
  const { formatUsageMessage, getAllCachedLimits } = await import('./limits.lib.mjs');
43
47
  const { getVersionInfo, formatVersionMessage } = await import('./version-info.lib.mjs');
44
48
  const { escapeMarkdown, escapeMarkdownV2, cleanNonPrintableChars, makeSpecialCharsVisible } = await import('./telegram-markdown.lib.mjs');
45
49
  const { getSolveQueue, createQueueExecuteCallback } = await import('./telegram-solve-queue.lib.mjs');
46
- // Import extracted message filter functions for testability (issue #1207)
47
50
  const { isOldMessage: _isOldMessage, isGroupChat: _isGroupChat, isChatAuthorized: _isChatAuthorized, isForwardedOrReply: _isForwardedOrReply, extractCommandFromText } = await import('./telegram-message-filters.lib.mjs');
48
51
  // Import bot launcher with exponential backoff retry (issue #1240)
49
52
  const { launchBotWithRetry } = await import('./telegram-bot-launcher.lib.mjs');
@@ -2,42 +2,152 @@
2
2
 
3
3
  // Version information library for hive-mind project
4
4
  // Provides comprehensive version information for bot, commands, and runtime
5
+ //
6
+ // Performance optimization (issue #1320):
7
+ // Uses Promise.all for parallel command execution instead of sequential execSync.
8
+ // This reduces version gathering time from ~30-150s to ~5s (limited by slowest command).
5
9
 
6
10
  import { getVersion } from './version.lib.mjs';
7
- import { execSync } from 'child_process';
11
+ import { exec } from 'child_process';
12
+ import { promisify } from 'util';
13
+
14
+ const execAsync = promisify(exec);
8
15
 
9
16
  /**
10
- * Execute a command and return its output, or null if it fails
17
+ * Execute a command asynchronously and return its output, or null if it fails
11
18
  * @param {string} command - Command to execute
12
- * @param {boolean} verbose - Enable verbose logging
13
- * @returns {string|null} Command output or null
19
+ * @param {number} timeout - Timeout in milliseconds (default: 5000ms)
20
+ * @returns {Promise<string|null>} Command output or null
14
21
  */
15
- function execCommand(command, verbose = false) {
22
+ async function execCommandAsync(command, timeout = 5000) {
16
23
  try {
17
- const result = execSync(command, { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'ignore'] });
18
- const trimmed = result.trim();
24
+ const { stdout } = await execAsync(command, { timeout, maxBuffer: 1024 * 1024 });
25
+ const trimmed = stdout.trim();
19
26
  // Return null if the output looks like an error message
20
27
  if (trimmed.includes('not found') || trimmed.includes('command not found') || trimmed === '') {
21
28
  return null;
22
29
  }
23
30
  return trimmed;
24
- } catch (error) {
25
- if (verbose) {
26
- console.log(`[VERBOSE] Command failed: ${command}`, error.message);
27
- }
31
+ } catch {
28
32
  return null;
29
33
  }
30
34
  }
31
35
 
36
+ /**
37
+ * Command definitions for version checking
38
+ * Each entry has: key, command, and optional fallbacks
39
+ * @type {Array<{key: string, command: string, fallbacks?: string[]}>}
40
+ */
41
+ const VERSION_COMMANDS = [
42
+ // AI Agents and Tools (--tool options)
43
+ { key: 'claudeCode', command: 'claude --version 2>&1' },
44
+ { key: 'agent', command: 'agent --version 2>&1' },
45
+ { key: 'codex', command: 'codex --version 2>&1' },
46
+ { key: 'opencode', command: 'opencode --version 2>&1' },
47
+ { key: 'qwenCode', command: 'qwen-code --version 2>&1' },
48
+ { key: 'gemini', command: 'gemini --version 2>&1' },
49
+ { key: 'copilot', command: 'copilot --version 2>&1' },
50
+
51
+ // Browser Automation
52
+ { key: 'playwright', command: 'playwright --version 2>&1' },
53
+ { key: 'playwrightMcp', command: "npm list -g @playwright/mcp --depth=0 2>&1 | grep @playwright/mcp | awk '{print $2}'" },
54
+
55
+ // JavaScript/Node.js ecosystem
56
+ { key: 'bun', command: 'bun --version 2>&1' },
57
+ { key: 'deno', command: 'deno --version 2>&1 | head -n1' },
58
+ { key: 'npm', command: 'npm --version 2>&1' },
59
+ { key: 'nvm', command: 'nvm --version 2>&1' },
60
+
61
+ // Python ecosystem
62
+ { key: 'python', command: 'python --version 2>&1' },
63
+ { key: 'pyenv', command: 'pyenv --version 2>&1' },
64
+
65
+ // Rust ecosystem
66
+ { key: 'rust', command: 'rustc --version 2>&1' },
67
+ { key: 'cargo', command: 'cargo --version 2>&1' },
68
+
69
+ // Java ecosystem
70
+ { key: 'java', command: 'java -version 2>&1 | head -n1' },
71
+ { key: 'sdkman', command: "sdk version 2>&1 | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+'" },
72
+
73
+ // Go
74
+ { key: 'go', command: 'go version 2>&1' },
75
+
76
+ // PHP
77
+ { key: 'php', command: 'php --version 2>&1 | head -n1' },
78
+
79
+ // .NET
80
+ { key: 'dotnet', command: 'dotnet --version 2>&1' },
81
+
82
+ // Perl ecosystem
83
+ { key: 'perl', command: "perl -v 2>&1 | grep -oE 'v[0-9]+\\.[0-9]+\\.[0-9]+'" },
84
+ { key: 'perlbrew', command: 'perlbrew --version 2>&1' },
85
+
86
+ // OCaml/Rocq ecosystem
87
+ { key: 'ocaml', command: 'ocaml --version 2>&1' },
88
+ { key: 'opam', command: 'opam --version 2>&1' },
89
+ // Rocq has fallback commands (rocq -> rocqc -> coqc)
90
+ { key: 'rocq', command: 'rocq -v 2>&1 | head -n1', fallbacks: ['rocqc --version 2>&1 | head -n1', 'coqc --version 2>&1 | head -n1'] },
91
+
92
+ // Lean ecosystem
93
+ { key: 'lean', command: 'lean --version 2>&1' },
94
+ { key: 'elan', command: 'elan --version 2>&1' },
95
+ { key: 'lake', command: 'lake --version 2>&1' },
96
+
97
+ // C/C++ Development Tools
98
+ { key: 'gcc', command: 'gcc --version 2>&1 | head -n1' },
99
+ { key: 'gpp', command: 'g++ --version 2>&1 | head -n1' },
100
+ { key: 'clang', command: 'clang --version 2>&1 | head -n1' },
101
+ { key: 'llvm', command: 'llvm-config --version 2>&1' },
102
+ { key: 'lld', command: 'lld --version 2>&1 | head -n1' },
103
+ { key: 'make', command: 'make --version 2>&1 | head -n1' },
104
+ { key: 'cmake', command: 'cmake --version 2>&1 | head -n1' },
105
+
106
+ // Development Tools
107
+ { key: 'git', command: 'git --version 2>&1' },
108
+ { key: 'gh', command: 'gh --version 2>&1 | head -n1' },
109
+ { key: 'brew', command: 'brew --version 2>&1 | head -n1' },
110
+ ];
111
+
112
+ /**
113
+ * Execute a version command with optional fallbacks
114
+ * @param {{key: string, command: string, fallbacks?: string[]}} cmdDef - Command definition
115
+ * @param {boolean} verbose - Enable verbose logging
116
+ * @returns {Promise<{key: string, value: string|null}>}
117
+ */
118
+ async function executeVersionCommand(cmdDef, verbose) {
119
+ let result = await execCommandAsync(cmdDef.command);
120
+
121
+ // Try fallbacks if primary command failed
122
+ if (!result && cmdDef.fallbacks) {
123
+ for (const fallback of cmdDef.fallbacks) {
124
+ result = await execCommandAsync(fallback);
125
+ if (result) break;
126
+ }
127
+ }
128
+
129
+ if (verbose && result) {
130
+ console.log(`[VERBOSE] ${cmdDef.key}: ${result}`);
131
+ } else if (verbose && !result) {
132
+ console.log(`[VERBOSE] ${cmdDef.key}: not found`);
133
+ }
134
+
135
+ return { key: cmdDef.key, value: result };
136
+ }
137
+
32
138
  /**
33
139
  * Get comprehensive version information for all components
140
+ * Uses Promise.all for parallel execution (issue #1320)
34
141
  * @param {boolean} verbose - Enable verbose logging
142
+ * @param {string} [processVersion] - Optional: version from the running process (for restart warning)
35
143
  * @returns {Promise<Object>} Version information object
36
144
  */
37
- export async function getVersionInfo(verbose = false) {
145
+ export async function getVersionInfo(verbose = false, processVersion = null) {
146
+ const startTime = Date.now();
147
+
38
148
  try {
39
149
  if (verbose) {
40
- console.log('[VERBOSE] Gathering version information...');
150
+ console.log('[VERBOSE] Gathering version information (parallel execution)...');
41
151
  }
42
152
 
43
153
  // Get hive-mind package version
@@ -46,297 +156,120 @@ export async function getVersionInfo(verbose = false) {
46
156
  console.log(`[VERBOSE] Package version: ${packageVersion}`);
47
157
  }
48
158
 
49
- // === Agents ===
159
+ // Execute all version commands in parallel
160
+ const results = await Promise.all(VERSION_COMMANDS.map(cmd => executeVersionCommand(cmd, verbose)));
50
161
 
51
- // Claude Code
52
- const claudeVersion = execCommand('claude --version 2>&1', verbose);
53
- if (verbose && claudeVersion) {
54
- console.log(`[VERBOSE] Claude Code version: ${claudeVersion}`);
162
+ // Convert results array to object
163
+ const versions = {};
164
+ for (const { key, value } of results) {
165
+ versions[key] = value;
55
166
  }
56
167
 
57
- // Playwright
58
- const playwrightVersion = execCommand('playwright --version 2>&1', verbose);
59
- if (verbose && playwrightVersion) {
60
- console.log(`[VERBOSE] Playwright version: ${playwrightVersion}`);
61
- }
62
-
63
- // Playwright MCP (check if installed via npm)
64
- const playwrightMcpVersion = execCommand("npm list -g @playwright/mcp --depth=0 2>&1 | grep @playwright/mcp | awk '{print $2}'", verbose);
65
- if (verbose && playwrightMcpVersion) {
66
- console.log(`[VERBOSE] Playwright MCP version: ${playwrightMcpVersion}`);
67
- }
68
-
69
- // === Language Runtimes ===
70
-
71
- // Node.js (from process, always available)
72
- const nodeVersion = process.version;
168
+ // Add Node.js version (always available from process)
169
+ versions.node = process.version;
73
170
  if (verbose) {
74
- console.log(`[VERBOSE] Node.js version: ${nodeVersion}`);
75
- }
76
-
77
- // Python
78
- const pythonVersion = execCommand('python --version 2>&1', verbose);
79
- if (verbose && pythonVersion) {
80
- console.log(`[VERBOSE] Python version: ${pythonVersion}`);
81
- }
82
-
83
- // Pyenv
84
- const pyenvVersion = execCommand('pyenv --version 2>&1', verbose);
85
- if (verbose && pyenvVersion) {
86
- console.log(`[VERBOSE] Pyenv version: ${pyenvVersion}`);
87
- }
88
-
89
- // Rust
90
- const rustVersion = execCommand('rustc --version 2>&1', verbose);
91
- if (verbose && rustVersion) {
92
- console.log(`[VERBOSE] Rust version: ${rustVersion}`);
93
- }
94
-
95
- // Cargo
96
- const cargoVersion = execCommand('cargo --version 2>&1', verbose);
97
- if (verbose && cargoVersion) {
98
- console.log(`[VERBOSE] Cargo version: ${cargoVersion}`);
99
- }
100
-
101
- // PHP
102
- const phpVersion = execCommand('php --version 2>&1 | head -n1', verbose);
103
- if (verbose && phpVersion) {
104
- console.log(`[VERBOSE] PHP version: ${phpVersion}`);
105
- }
106
-
107
- // Bun
108
- const bunVersion = execCommand('bun --version 2>&1', verbose);
109
- if (verbose && bunVersion) {
110
- console.log(`[VERBOSE] Bun version: ${bunVersion}`);
111
- }
112
-
113
- // .NET
114
- const dotnetVersion = execCommand('dotnet --version 2>&1', verbose);
115
- if (verbose && dotnetVersion) {
116
- console.log(`[VERBOSE] .NET version: ${dotnetVersion}`);
117
- }
118
-
119
- // Deno
120
- const denoVersion = execCommand('deno --version 2>&1 | head -n1', verbose);
121
- if (verbose && denoVersion) {
122
- console.log(`[VERBOSE] Deno version: ${denoVersion}`);
123
- }
124
-
125
- // Go (Golang)
126
- const goVersion = execCommand('go version 2>&1', verbose);
127
- if (verbose && goVersion) {
128
- console.log(`[VERBOSE] Go version: ${goVersion}`);
129
- }
130
-
131
- // Java
132
- const javaVersion = execCommand('java -version 2>&1 | head -n1', verbose);
133
- if (verbose && javaVersion) {
134
- console.log(`[VERBOSE] Java version: ${javaVersion}`);
135
- }
136
-
137
- // Lean (theorem prover)
138
- const leanVersion = execCommand('lean --version 2>&1', verbose);
139
- if (verbose && leanVersion) {
140
- console.log(`[VERBOSE] Lean version: ${leanVersion}`);
141
- }
142
-
143
- // Perl
144
- const perlVersion = execCommand("perl -v 2>&1 | grep -oE 'v[0-9]+\\.[0-9]+\\.[0-9]+'", verbose);
145
- if (verbose && perlVersion) {
146
- console.log(`[VERBOSE] Perl version: ${perlVersion}`);
147
- }
148
-
149
- // OCaml (via opam)
150
- const ocamlVersion = execCommand('ocaml --version 2>&1', verbose);
151
- if (verbose && ocamlVersion) {
152
- console.log(`[VERBOSE] OCaml version: ${ocamlVersion}`);
153
- }
154
-
155
- // Rocq/Coq (theorem prover)
156
- // Try rocq first (Rocq 9.0+), then fall back to coqc (legacy)
157
- let rocqVersion = execCommand('rocq -v 2>&1 | head -n1', verbose);
158
- if (!rocqVersion) {
159
- rocqVersion = execCommand('rocqc --version 2>&1 | head -n1', verbose);
160
- }
161
- if (!rocqVersion) {
162
- rocqVersion = execCommand('coqc --version 2>&1 | head -n1', verbose);
163
- }
164
- if (verbose && rocqVersion) {
165
- console.log(`[VERBOSE] Rocq/Coq version: ${rocqVersion}`);
166
- }
167
-
168
- // === Development Tools ===
169
-
170
- // Git
171
- const gitVersion = execCommand('git --version 2>&1', verbose);
172
- if (verbose && gitVersion) {
173
- console.log(`[VERBOSE] Git version: ${gitVersion}`);
174
- }
175
-
176
- // GitHub CLI
177
- const ghVersion = execCommand('gh --version 2>&1 | head -n1', verbose);
178
- if (verbose && ghVersion) {
179
- console.log(`[VERBOSE] GitHub CLI version: ${ghVersion}`);
180
- }
181
-
182
- // NVM
183
- const nvmVersion = execCommand('nvm --version 2>&1', verbose);
184
- if (verbose && nvmVersion) {
185
- console.log(`[VERBOSE] NVM version: ${nvmVersion}`);
186
- }
187
-
188
- // Homebrew
189
- const brewVersion = execCommand('brew --version 2>&1 | head -n1', verbose);
190
- if (verbose && brewVersion) {
191
- console.log(`[VERBOSE] Homebrew version: ${brewVersion}`);
192
- }
193
-
194
- // NPM
195
- const npmVersion = execCommand('npm --version 2>&1', verbose);
196
- if (verbose && npmVersion) {
197
- console.log(`[VERBOSE] NPM version: ${npmVersion}`);
198
- }
199
-
200
- // SDKMAN (Java version manager)
201
- const sdkmanVersion = execCommand("sdk version 2>&1 | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+'", verbose);
202
- if (verbose && sdkmanVersion) {
203
- console.log(`[VERBOSE] SDKMAN version: ${sdkmanVersion}`);
204
- }
205
-
206
- // Elan (Lean version manager)
207
- const elanVersion = execCommand('elan --version 2>&1', verbose);
208
- if (verbose && elanVersion) {
209
- console.log(`[VERBOSE] Elan version: ${elanVersion}`);
210
- }
211
-
212
- // Lake (Lean package manager)
213
- const lakeVersion = execCommand('lake --version 2>&1', verbose);
214
- if (verbose && lakeVersion) {
215
- console.log(`[VERBOSE] Lake version: ${lakeVersion}`);
216
- }
217
-
218
- // Perlbrew (Perl version manager)
219
- const perlbrewVersion = execCommand('perlbrew --version 2>&1', verbose);
220
- if (verbose && perlbrewVersion) {
221
- console.log(`[VERBOSE] Perlbrew version: ${perlbrewVersion}`);
222
- }
223
-
224
- // Opam (OCaml package manager)
225
- const opamVersion = execCommand('opam --version 2>&1', verbose);
226
- if (verbose && opamVersion) {
227
- console.log(`[VERBOSE] Opam version: ${opamVersion}`);
228
- }
229
-
230
- // === C/C++ Development Tools ===
231
-
232
- // Make
233
- const makeVersion = execCommand('make --version 2>&1 | head -n1', verbose);
234
- if (verbose && makeVersion) {
235
- console.log(`[VERBOSE] Make version: ${makeVersion}`);
236
- }
237
-
238
- // CMake
239
- const cmakeVersion = execCommand('cmake --version 2>&1 | head -n1', verbose);
240
- if (verbose && cmakeVersion) {
241
- console.log(`[VERBOSE] CMake version: ${cmakeVersion}`);
242
- }
243
-
244
- // GCC
245
- const gccVersion = execCommand('gcc --version 2>&1 | head -n1', verbose);
246
- if (verbose && gccVersion) {
247
- console.log(`[VERBOSE] GCC version: ${gccVersion}`);
248
- }
249
-
250
- // G++
251
- const gppVersion = execCommand('g++ --version 2>&1 | head -n1', verbose);
252
- if (verbose && gppVersion) {
253
- console.log(`[VERBOSE] G++ version: ${gppVersion}`);
254
- }
255
-
256
- // Clang
257
- const clangVersion = execCommand('clang --version 2>&1 | head -n1', verbose);
258
- if (verbose && clangVersion) {
259
- console.log(`[VERBOSE] Clang version: ${clangVersion}`);
260
- }
261
-
262
- // LLVM
263
- const llvmVersion = execCommand('llvm-config --version 2>&1', verbose);
264
- if (verbose && llvmVersion) {
265
- console.log(`[VERBOSE] LLVM version: ${llvmVersion}`);
266
- }
267
-
268
- // LLD (LLVM linker)
269
- const lldVersion = execCommand('lld --version 2>&1 | head -n1', verbose);
270
- if (verbose && lldVersion) {
271
- console.log(`[VERBOSE] LLD version: ${lldVersion}`);
171
+ console.log(`[VERBOSE] Node.js version: ${versions.node}`);
272
172
  }
273
173
 
274
- // === Platform Information ===
174
+ // Platform information
275
175
  const platform = process.platform;
276
176
  const arch = process.arch;
177
+ versions.platform = `${platform} (${arch})`;
277
178
  if (verbose) {
278
- console.log(`[VERBOSE] Platform: ${platform} (${arch})`);
179
+ console.log(`[VERBOSE] Platform: ${versions.platform}`);
279
180
  }
280
181
 
182
+ // Check if process version differs from installed version (restart warning)
183
+ const needsRestart = processVersion && processVersion !== packageVersion;
184
+
281
185
  // Build version info object
282
186
  const versionInfo = {
283
187
  success: true,
284
188
  versions: {
285
- // Bot components
286
- bot: packageVersion,
287
- solve: packageVersion,
288
- hive: packageVersion,
289
-
290
- // Agents
291
- claudeCode: claudeVersion,
292
- playwright: playwrightVersion,
293
- playwrightMcp: playwrightMcpVersion,
294
-
295
- // Language runtimes
296
- node: nodeVersion,
297
- python: pythonVersion,
298
- rust: rustVersion,
299
- php: phpVersion,
300
- bun: bunVersion,
301
- dotnet: dotnetVersion,
302
- deno: denoVersion,
303
- go: goVersion,
304
- java: javaVersion,
305
- lean: leanVersion,
306
- perl: perlVersion,
307
- ocaml: ocamlVersion,
308
- rocq: rocqVersion,
309
-
310
- // Development tools
311
- git: gitVersion,
312
- gh: ghVersion,
313
- npm: npmVersion,
314
- nvm: nvmVersion,
315
- pyenv: pyenvVersion,
316
- cargo: cargoVersion,
317
- brew: brewVersion,
318
- sdkman: sdkmanVersion,
319
- elan: elanVersion,
320
- lake: lakeVersion,
321
- perlbrew: perlbrewVersion,
322
- opam: opamVersion,
323
-
324
- // C/C++ Development Tools
325
- make: makeVersion,
326
- cmake: cmakeVersion,
327
- gcc: gccVersion,
328
- gpp: gppVersion,
329
- clang: clangVersion,
330
- llvm: llvmVersion,
331
- lld: lldVersion,
189
+ // Hive-mind package (single entry, not duplicated)
190
+ hiveMind: packageVersion,
191
+ processVersion: processVersion || packageVersion,
192
+ needsRestart,
193
+
194
+ // AI Agents (--tool options)
195
+ claudeCode: versions.claudeCode,
196
+ agent: versions.agent,
197
+ codex: versions.codex,
198
+ opencode: versions.opencode,
199
+ qwenCode: versions.qwenCode,
200
+ gemini: versions.gemini,
201
+ copilot: versions.copilot,
202
+
203
+ // Browser Automation
204
+ playwright: versions.playwright,
205
+ playwrightMcp: versions.playwrightMcp,
206
+
207
+ // JavaScript/Node.js
208
+ node: versions.node,
209
+ bun: versions.bun,
210
+ deno: versions.deno,
211
+ npm: versions.npm,
212
+ nvm: versions.nvm,
213
+
214
+ // Python
215
+ python: versions.python,
216
+ pyenv: versions.pyenv,
217
+
218
+ // Rust
219
+ rust: versions.rust,
220
+ cargo: versions.cargo,
221
+
222
+ // Java
223
+ java: versions.java,
224
+ sdkman: versions.sdkman,
225
+
226
+ // Go
227
+ go: versions.go,
228
+
229
+ // PHP
230
+ php: versions.php,
231
+
232
+ // .NET
233
+ dotnet: versions.dotnet,
234
+
235
+ // Perl
236
+ perl: versions.perl,
237
+ perlbrew: versions.perlbrew,
238
+
239
+ // OCaml/Rocq
240
+ ocaml: versions.ocaml,
241
+ opam: versions.opam,
242
+ rocq: versions.rocq,
243
+
244
+ // Lean
245
+ lean: versions.lean,
246
+ elan: versions.elan,
247
+ lake: versions.lake,
248
+
249
+ // C/C++
250
+ gcc: versions.gcc,
251
+ gpp: versions.gpp,
252
+ clang: versions.clang,
253
+ llvm: versions.llvm,
254
+ lld: versions.lld,
255
+ make: versions.make,
256
+ cmake: versions.cmake,
257
+
258
+ // Development Tools
259
+ git: versions.git,
260
+ gh: versions.gh,
261
+ brew: versions.brew,
332
262
 
333
263
  // Platform
334
- platform: `${platform} (${arch})`,
264
+ platform: versions.platform,
335
265
  },
266
+ // Performance metrics
267
+ gatherTimeMs: Date.now() - startTime,
336
268
  };
337
269
 
338
270
  if (verbose) {
339
- console.log('[VERBOSE] Version info gathered successfully:', JSON.stringify(versionInfo, null, 2));
271
+ console.log(`[VERBOSE] Version info gathered in ${versionInfo.gatherTimeMs}ms`);
272
+ console.log('[VERBOSE] Version info:', JSON.stringify(versionInfo, null, 2));
340
273
  }
341
274
 
342
275
  return versionInfo;
@@ -348,134 +281,183 @@ export async function getVersionInfo(verbose = false) {
348
281
  return {
349
282
  success: false,
350
283
  error: error.message || 'Failed to gather version information',
284
+ gatherTimeMs: Date.now() - startTime,
351
285
  };
352
286
  }
353
287
  }
354
288
 
289
+ /**
290
+ * Helper to add version line if version exists
291
+ * @param {string[]} lines - Array to push to
292
+ * @param {string} label - Display label
293
+ * @param {string|null} version - Version string or null
294
+ */
295
+ function addVersionLine(lines, label, version) {
296
+ if (version) {
297
+ lines.push(`• ${label}: \`${version}\``);
298
+ }
299
+ }
300
+
355
301
  /**
356
302
  * Format version information as a Telegram message
303
+ * Groups tools by programming language for better readability (issue #1320)
357
304
  * @param {Object} versions - Version information object
358
305
  * @returns {string} Formatted message
359
306
  */
360
307
  export function formatVersionMessage(versions) {
361
308
  const lines = [];
362
309
 
363
- // === Bot Components ===
364
- lines.push('*🤖 Bot Components*');
365
- if (versions.bot) {
366
- lines.push(`• Bot: \`${versions.bot}\``);
367
- }
368
- if (versions.solve) {
369
- lines.push(`• solve: \`${versions.solve}\``);
370
- }
371
- if (versions.hive) {
372
- lines.push(`• hive: \`${versions.hive}\``);
310
+ // === Hive-Mind Package (single entry with restart warning) ===
311
+ lines.push('*🤖 Hive-Mind*');
312
+ if (versions.hiveMind) {
313
+ lines.push(`• Version: \`${versions.hiveMind}\``);
314
+ if (versions.needsRestart) {
315
+ lines.push(`⚠️ _Process running: \`${versions.processVersion}\` (restart needed)_`);
316
+ }
373
317
  }
374
318
 
375
- // === Agents ===
319
+ // === AI Agents (--tool options) ===
376
320
  const agentLines = [];
377
- if (versions.claudeCode) {
378
- agentLines.push(`• Claude Code: \`${versions.claudeCode}\``);
379
- }
380
- if (versions.playwright) {
381
- agentLines.push(`• Playwright: \`${versions.playwright}\``);
382
- }
383
- if (versions.playwrightMcp) {
384
- agentLines.push(`• Playwright MCP: \`${versions.playwrightMcp}\``);
385
- }
321
+ addVersionLine(agentLines, 'Claude Code', versions.claudeCode);
322
+ addVersionLine(agentLines, 'Agent CLI', versions.agent);
323
+ addVersionLine(agentLines, 'OpenAI Codex', versions.codex);
324
+ addVersionLine(agentLines, 'OpenCode', versions.opencode);
325
+ addVersionLine(agentLines, 'Qwen Code', versions.qwenCode);
326
+ addVersionLine(agentLines, 'Gemini CLI', versions.gemini);
327
+ addVersionLine(agentLines, 'GitHub Copilot', versions.copilot);
386
328
 
387
329
  if (agentLines.length > 0) {
388
330
  lines.push('');
389
- lines.push('*🎭 Agents*');
331
+ lines.push('*🎭 AI Agents*');
390
332
  lines.push(...agentLines);
391
333
  }
392
334
 
393
- // === Language Runtimes ===
394
- const runtimeLines = [];
395
- if (versions.node) {
396
- runtimeLines.push(`• Node.js: \`${versions.node}\``);
397
- }
398
- if (versions.python) {
399
- runtimeLines.push(`• Python: \`${versions.python}\``);
400
- }
401
- if (versions.rust) {
402
- runtimeLines.push(`• Rust: \`${versions.rust}\``);
403
- }
404
- if (versions.php) {
405
- runtimeLines.push(`• PHP: \`${versions.php}\``);
335
+ // === JavaScript/Node.js ===
336
+ const jsLines = [];
337
+ addVersionLine(jsLines, 'Node.js', versions.node);
338
+ addVersionLine(jsLines, 'Bun', versions.bun);
339
+ addVersionLine(jsLines, 'Deno', versions.deno);
340
+ addVersionLine(jsLines, 'NPM', versions.npm);
341
+ addVersionLine(jsLines, 'NVM', versions.nvm);
342
+
343
+ if (jsLines.length > 0) {
344
+ lines.push('');
345
+ lines.push('*📦 JavaScript/Node.js*');
346
+ lines.push(...jsLines);
406
347
  }
407
- if (versions.bun) {
408
- runtimeLines.push(`• Bun: \`${versions.bun}\``);
348
+
349
+ // === Python ===
350
+ const pythonLines = [];
351
+ addVersionLine(pythonLines, 'Python', versions.python);
352
+ addVersionLine(pythonLines, 'Pyenv', versions.pyenv);
353
+
354
+ if (pythonLines.length > 0) {
355
+ lines.push('');
356
+ lines.push('*🐍 Python*');
357
+ lines.push(...pythonLines);
409
358
  }
410
- if (versions.dotnet) {
411
- runtimeLines.push(`• .NET: \`${versions.dotnet}\``);
359
+
360
+ // === Rust ===
361
+ const rustLines = [];
362
+ addVersionLine(rustLines, 'Rustc', versions.rust);
363
+ addVersionLine(rustLines, 'Cargo', versions.cargo);
364
+
365
+ if (rustLines.length > 0) {
366
+ lines.push('');
367
+ lines.push('*🦀 Rust*');
368
+ lines.push(...rustLines);
412
369
  }
413
- if (versions.deno) {
414
- runtimeLines.push(`• Deno: \`${versions.deno}\``);
370
+
371
+ // === Java ===
372
+ const javaLines = [];
373
+ addVersionLine(javaLines, 'Java', versions.java);
374
+ addVersionLine(javaLines, 'SDKMAN', versions.sdkman);
375
+
376
+ if (javaLines.length > 0) {
377
+ lines.push('');
378
+ lines.push('*☕ Java*');
379
+ lines.push(...javaLines);
415
380
  }
381
+
382
+ // === Go ===
416
383
  if (versions.go) {
417
- runtimeLines.push(`• Go: \`${versions.go}\``);
384
+ lines.push('');
385
+ lines.push('*🔷 Go*');
386
+ addVersionLine(lines, 'Go', versions.go);
418
387
  }
419
- if (versions.java) {
420
- runtimeLines.push(`• Java: \`${versions.java}\``);
388
+
389
+ // === PHP ===
390
+ if (versions.php) {
391
+ lines.push('');
392
+ lines.push('*🐘 PHP*');
393
+ addVersionLine(lines, 'PHP', versions.php);
421
394
  }
422
- if (versions.lean) {
423
- runtimeLines.push(`• Lean: \`${versions.lean}\``);
395
+
396
+ // === .NET ===
397
+ if (versions.dotnet) {
398
+ lines.push('');
399
+ lines.push('*📦 .NET*');
400
+ addVersionLine(lines, '.NET SDK', versions.dotnet);
424
401
  }
425
- if (versions.perl) {
426
- runtimeLines.push(`• Perl: \`${versions.perl}\``);
402
+
403
+ // === Perl ===
404
+ const perlLines = [];
405
+ addVersionLine(perlLines, 'Perl', versions.perl);
406
+ addVersionLine(perlLines, 'Perlbrew', versions.perlbrew);
407
+
408
+ if (perlLines.length > 0) {
409
+ lines.push('');
410
+ lines.push('*💎 Perl*');
411
+ lines.push(...perlLines);
427
412
  }
428
- if (versions.ocaml) {
429
- runtimeLines.push(`• OCaml: \`${versions.ocaml}\``);
413
+
414
+ // === OCaml/Rocq ===
415
+ const ocamlLines = [];
416
+ addVersionLine(ocamlLines, 'OCaml', versions.ocaml);
417
+ addVersionLine(ocamlLines, 'Opam', versions.opam);
418
+ addVersionLine(ocamlLines, 'Rocq/Coq', versions.rocq);
419
+
420
+ if (ocamlLines.length > 0) {
421
+ lines.push('');
422
+ lines.push('*🐫 OCaml/Rocq*');
423
+ lines.push(...ocamlLines);
430
424
  }
431
- if (versions.rocq) {
432
- runtimeLines.push(`• Rocq/Coq: \`${versions.rocq}\``);
425
+
426
+ // === Lean ===
427
+ const leanLines = [];
428
+ addVersionLine(leanLines, 'Lean', versions.lean);
429
+ addVersionLine(leanLines, 'Elan', versions.elan);
430
+ addVersionLine(leanLines, 'Lake', versions.lake);
431
+
432
+ if (leanLines.length > 0) {
433
+ lines.push('');
434
+ lines.push('*📐 Lean*');
435
+ lines.push(...leanLines);
433
436
  }
434
437
 
435
- if (runtimeLines.length > 0) {
438
+ // === C/C++ ===
439
+ const cppLines = [];
440
+ addVersionLine(cppLines, 'GCC', versions.gcc);
441
+ addVersionLine(cppLines, 'G++', versions.gpp);
442
+ addVersionLine(cppLines, 'Clang', versions.clang);
443
+ addVersionLine(cppLines, 'LLVM', versions.llvm);
444
+ addVersionLine(cppLines, 'LLD', versions.lld);
445
+ addVersionLine(cppLines, 'Make', versions.make);
446
+ addVersionLine(cppLines, 'CMake', versions.cmake);
447
+
448
+ if (cppLines.length > 0) {
436
449
  lines.push('');
437
- lines.push('*⚙️ Language Runtimes*');
438
- lines.push(...runtimeLines);
450
+ lines.push('*🔨 C/C++*');
451
+ lines.push(...cppLines);
439
452
  }
440
453
 
441
454
  // === Development Tools ===
442
455
  const toolLines = [];
443
- if (versions.git) {
444
- toolLines.push(`• Git: \`${versions.git}\``);
445
- }
446
- if (versions.gh) {
447
- toolLines.push(`• GitHub CLI: \`${versions.gh}\``);
448
- }
449
- if (versions.npm) {
450
- toolLines.push(`• NPM: \`${versions.npm}\``);
451
- }
452
- if (versions.nvm) {
453
- toolLines.push(`• NVM: \`${versions.nvm}\``);
454
- }
455
- if (versions.pyenv) {
456
- toolLines.push(`• Pyenv: \`${versions.pyenv}\``);
457
- }
458
- if (versions.cargo) {
459
- toolLines.push(`• Cargo: \`${versions.cargo}\``);
460
- }
461
- if (versions.brew) {
462
- toolLines.push(`• Homebrew: \`${versions.brew}\``);
463
- }
464
- if (versions.sdkman) {
465
- toolLines.push(`• SDKMAN: \`${versions.sdkman}\``);
466
- }
467
- if (versions.elan) {
468
- toolLines.push(`• Elan: \`${versions.elan}\``);
469
- }
470
- if (versions.lake) {
471
- toolLines.push(`• Lake: \`${versions.lake}\``);
472
- }
473
- if (versions.perlbrew) {
474
- toolLines.push(`• Perlbrew: \`${versions.perlbrew}\``);
475
- }
476
- if (versions.opam) {
477
- toolLines.push(`• Opam: \`${versions.opam}\``);
478
- }
456
+ addVersionLine(toolLines, 'Git', versions.git);
457
+ addVersionLine(toolLines, 'GitHub CLI', versions.gh);
458
+ addVersionLine(toolLines, 'Playwright', versions.playwright);
459
+ addVersionLine(toolLines, 'Playwright MCP', versions.playwrightMcp);
460
+ addVersionLine(toolLines, 'Homebrew', versions.brew);
479
461
 
480
462
  if (toolLines.length > 0) {
481
463
  lines.push('');
@@ -483,36 +465,6 @@ export function formatVersionMessage(versions) {
483
465
  lines.push(...toolLines);
484
466
  }
485
467
 
486
- // === C/C++ Development Tools ===
487
- const cppToolLines = [];
488
- if (versions.make) {
489
- cppToolLines.push(`• Make: \`${versions.make}\``);
490
- }
491
- if (versions.cmake) {
492
- cppToolLines.push(`• CMake: \`${versions.cmake}\``);
493
- }
494
- if (versions.gcc) {
495
- cppToolLines.push(`• GCC: \`${versions.gcc}\``);
496
- }
497
- if (versions.gpp) {
498
- cppToolLines.push(`• G++: \`${versions.gpp}\``);
499
- }
500
- if (versions.clang) {
501
- cppToolLines.push(`• Clang: \`${versions.clang}\``);
502
- }
503
- if (versions.llvm) {
504
- cppToolLines.push(`• LLVM: \`${versions.llvm}\``);
505
- }
506
- if (versions.lld) {
507
- cppToolLines.push(`• LLD: \`${versions.lld}\``);
508
- }
509
-
510
- if (cppToolLines.length > 0) {
511
- lines.push('');
512
- lines.push('*🔧 C/C++ Development Tools*');
513
- lines.push(...cppToolLines);
514
- }
515
-
516
468
  // === Platform ===
517
469
  if (versions.platform) {
518
470
  lines.push('');
@@ -5,6 +5,11 @@ import { dirname, join } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { getGitVersion } from './git.lib.mjs';
7
7
 
8
+ // Cache for version (immutable after first read)
9
+ // This ensures the version remains consistent even if package.json changes during runtime
10
+ // See issue #1318: version should be cached in RAM at startup
11
+ let cachedVersion = null;
12
+
8
13
  async function isRunningAsScript() {
9
14
  const __filename = fileURLToPath(import.meta.url);
10
15
  const __dirname = dirname(__filename);
@@ -18,6 +23,11 @@ async function isRunningAsScript() {
18
23
  }
19
24
 
20
25
  export async function getVersion() {
26
+ // Return cached version if already computed (immutable after first read)
27
+ if (cachedVersion !== null) {
28
+ return cachedVersion;
29
+ }
30
+
21
31
  const __filename = fileURLToPath(import.meta.url);
22
32
  const __dirname = dirname(__filename);
23
33
  const packagePath = join(__dirname, '..', 'package.json');
@@ -28,13 +38,15 @@ export async function getVersion() {
28
38
  const currentVersion = packageJson.version;
29
39
 
30
40
  if (await isRunningAsScript()) {
31
- const version = await getGitVersion(undefined, currentVersion);
32
- return version;
41
+ cachedVersion = await getGitVersion(undefined, currentVersion);
42
+ } else {
43
+ cachedVersion = currentVersion;
33
44
  }
34
45
 
35
- return currentVersion;
46
+ return cachedVersion;
36
47
  } catch {
37
- return 'unknown';
48
+ cachedVersion = 'unknown';
49
+ return cachedVersion;
38
50
  }
39
51
  }
40
52