@link-assistant/hive-mind 1.39.0 → 1.40.1

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,17 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.40.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 9df62ed: fix: increase activity timeout to 1hr, fix idle tracking, improve graceful kill (#1510)
8
+
9
+ ## 1.40.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 6b8465a: feat: add browsers, browser tools, and missing software to /version command
14
+
3
15
  ## 1.39.0
4
16
 
5
17
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.39.0",
3
+ "version": "1.40.1",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -853,21 +853,27 @@ export const executeClaudeCommand = async params => {
853
853
  let lastEventTime = null;
854
854
  let activityTimeoutId = null;
855
855
  let isActivityTimeout = false;
856
+ // Issue #1510: Separate SIGTERM (graceful) and SIGKILL (force) phases to allow
857
+ // capturing final output from the process during graceful shutdown
856
858
  const forceExitOnTimeout = async () => {
857
859
  if (forceExitTriggered) return;
858
860
  forceExitTriggered = true;
859
- await log(`⚠️ Stream timeout — forcing exit (Issue #1280)`, { verbose: true });
861
+ await log(`⚠️ Stream timeout — sending SIGTERM for graceful shutdown (Issue #1280, #1510)`, { verbose: true });
860
862
  try {
861
863
  if (execCommand.kill) {
862
864
  execCommand.kill('SIGTERM');
863
- // Issue #1346: Follow up with SIGKILL after 2s if still alive
865
+ // Issue #1346/#1510: Follow up with SIGKILL after 5s if still alive
866
+ // Increased from 2s to 5s to give more time for final output capture
864
867
  const t = setTimeout(() => {
865
868
  try {
866
- if (!execCommand.result?.code) execCommand.kill('SIGKILL');
869
+ if (!execCommand.result?.code) {
870
+ log(`⚠️ Process did not exit after SIGTERM, sending SIGKILL`, { verbose: true });
871
+ execCommand.kill('SIGKILL');
872
+ }
867
873
  } catch {
868
874
  /* exited */
869
875
  }
870
- }, 2000);
876
+ }, 5000);
871
877
  t.unref();
872
878
  }
873
879
  } catch (e) {
@@ -892,8 +898,8 @@ export const executeClaudeCommand = async params => {
892
898
  activityTimeoutId = setTimeout(async () => {
893
899
  if (!forceExitTriggered && !resultEventReceived) {
894
900
  isActivityTimeout = true;
895
- const idleSeconds = lastEventTime ? Math.round((Date.now() - lastEventTime) / 1000) : 'unknown';
896
- await log(`\n⚠️ No stream output for ${timeouts.streamActivityMs / 1000}s after previous activity (idle: ${idleSeconds}s) — force-killing (Issue #1472)`, { level: 'warning' });
901
+ const idleSeconds = lastEventTime ? `${Math.round((Date.now() - lastEventTime) / 1000)}s` : 'unknown';
902
+ await log(`\n⚠️ No stream output for ${timeouts.streamActivityMs / 1000}s after previous activity (idle: ${idleSeconds}) — force-killing (Issue #1472)`, { level: 'warning' });
897
903
  await forceExitOnTimeout();
898
904
  }
899
905
  }, timeouts.streamActivityMs);
@@ -901,7 +907,8 @@ export const executeClaudeCommand = async params => {
901
907
  }
902
908
  };
903
909
  for await (const chunk of execCommand.stream()) {
904
- if (forceExitTriggered) break;
910
+ // Issue #1510: Continue processing stream after SIGTERM to capture final output
911
+ // The stream will naturally end when the process exits (SIGTERM) or is force-killed (SIGKILL after 5s)
905
912
  if (!firstChunkReceived) {
906
913
  // Issue #1472/#1475: Clear startup timeout on first output
907
914
  firstChunkReceived = true;
@@ -922,12 +929,14 @@ export const executeClaudeCommand = async params => {
922
929
  if (!line.trim()) continue;
923
930
  try {
924
931
  const data = sanitizeObjectStrings(JSON.parse(line));
932
+ // Issue #1510: Track last event time for all modes (not just interactive)
933
+ // so activity timeout can report accurate idle duration
934
+ lastEventTime = Date.now();
925
935
  if (interactiveHandler) {
926
936
  if (!interactiveHandler._firstEventLogged) {
927
937
  interactiveHandler._firstEventLogged = true;
928
938
  await log(`🔌 Interactive mode: First event received (type: ${data.type || 'unknown'}) — stream is active`, { verbose: true });
929
939
  }
930
- lastEventTime = Date.now();
931
940
  try {
932
941
  await interactiveHandler.processEvent(data);
933
942
  } catch (interactiveError) {
@@ -1193,6 +1202,19 @@ export const executeClaudeCommand = async params => {
1193
1202
  const retryMode = isStartupTimeout ? ' (fresh start)' : ' (session preserved)';
1194
1203
  await log(`\n⚠️ ${errorLabel} detected. Retry ${retryCount + 1}/${maxRetries} in ${delayLabel}${retryMode}${notRetryableHint}...`, { level: 'warning' });
1195
1204
  await log(` Error: ${isStartupTimeout ? `No output from Claude CLI within ${timeouts.streamStartupMs / 1000}s` : isActivityTimeout ? `No output for ${timeouts.streamActivityMs / 1000}s after previous activity` : lastMessage.substring(0, 200)}`, { verbose: true });
1205
+ // Issue #1510: Post PR comment when force-killing and auto-resuming so reviewers can follow the session lifecycle
1206
+ if ((isActivityTimeout || isStartupTimeout) && owner && repo && prNumber && $) {
1207
+ try {
1208
+ const timeoutType = isActivityTimeout ? 'activity' : 'startup';
1209
+ const sessionInfo = sessionId ? `\nSession ID: \`${sessionId}\`` : '';
1210
+ const resumeInfo = isStartupTimeout ? 'Session will be restarted (fresh start).' : `Session will be resumed with \`--resume\` (context preserved).`;
1211
+ const commentBody = `## :warning: Session Force-Killed (${timeoutType} timeout)\n\nThe working session was force-killed due to ${timeoutType} timeout (no stream output for ${isActivityTimeout ? timeouts.streamActivityMs / 1000 : timeouts.streamStartupMs / 1000}s).\n\n**Auto-resuming**: Retry ${retryCount + 1}/${maxRetries} in ${delayLabel}. ${resumeInfo}${sessionInfo}\n\n*This is an automated notification — the session will continue automatically.*`;
1212
+ await $`gh pr comment ${prNumber} --repo ${owner}/${repo} --body ${commentBody}`;
1213
+ await log(` Posted force-kill notification to PR #${prNumber}`, { verbose: true });
1214
+ } catch (commentError) {
1215
+ await log(` Warning: Could not post force-kill comment to PR: ${commentError.message}`, { verbose: true });
1216
+ }
1217
+ }
1196
1218
  // Activity timeout preserves session (work was started), startup timeout does not (no session created)
1197
1219
  if (!isStartupTimeout && sessionId && !argv.resume) argv.resume = sessionId;
1198
1220
  await waitWithCountdown(delay, log);
@@ -63,8 +63,11 @@ export const timeouts = {
63
63
  // after at least one event was received, the process is considered hung mid-session.
64
64
  // This catches the case where Claude CLI starts producing output but then stops (e.g., the
65
65
  // original Issue #1472 where CLI was stuck for 4.5h with all output arriving only at CTRL+C).
66
- // Default: 300000ms (5 minutes). Set to 0 to disable. Configurable via environment variable.
67
- streamActivityMs: parseIntWithDefault('HIVE_MIND_STREAM_ACTIVITY_MS', 300000),
66
+ // Issue #1510: Increased from 300000ms (5 min) to 3600000ms (1 hour) because Claude Code can
67
+ // legitimately wait for long-running operations (docker builds, CI polls, large compilations).
68
+ // The 5-minute timeout was force-killing sessions during `sleep 300 && gh run view ...` commands.
69
+ // Default: 3600000ms (1 hour). Set to 0 to disable. Configurable via environment variable.
70
+ streamActivityMs: parseIntWithDefault('HIVE_MIND_STREAM_ACTIVITY_MS', 3600000),
68
71
  };
69
72
 
70
73
  // Auto-continue configurations
@@ -50,7 +50,17 @@ const VERSION_COMMANDS = [
50
50
 
51
51
  // Browser Automation
52
52
  { key: 'playwright', command: 'playwright --version 2>&1' },
53
+ { key: 'playwrightTest', command: "npm list -g @playwright/test --depth=0 2>&1 | grep @playwright/test | awk '{print $2}'" },
53
54
  { key: 'playwrightMcp', command: "npm list -g @playwright/mcp --depth=0 2>&1 | grep @playwright/mcp | awk '{print $2}'" },
55
+ { key: 'playwrightMcpStatus', command: 'timeout 5 claude mcp list 2>&1 | grep -i playwright | head -1' },
56
+ { key: 'puppeteerBrowsers', command: "npm list -g @puppeteer/browsers --depth=0 2>&1 | grep @puppeteer/browsers | awk '{print $2}'" },
57
+
58
+ // Browsers (installed via Playwright)
59
+ { key: 'chrome', command: 'google-chrome --version 2>&1' },
60
+ { key: 'chromium', command: 'chromium --version 2>&1', fallbacks: ['chromium-browser --version 2>&1'] },
61
+ { key: 'firefox', command: 'firefox --version 2>&1' },
62
+ { key: 'msedge', command: 'microsoft-edge --version 2>&1', fallbacks: ['microsoft-edge-stable --version 2>&1'] },
63
+ { key: 'webkit', command: "ls ~/.cache/ms-playwright/ 2>/dev/null | grep -oE 'webkit-[0-9]+' | head -1" },
54
64
 
55
65
  // JavaScript/Node.js ecosystem
56
66
  { key: 'bun', command: 'bun --version 2>&1' },
@@ -103,10 +113,33 @@ const VERSION_COMMANDS = [
103
113
  { key: 'make', command: 'make --version 2>&1 | head -n1' },
104
114
  { key: 'cmake', command: 'cmake --version 2>&1 | head -n1' },
105
115
 
116
+ // Ruby ecosystem
117
+ { key: 'ruby', command: 'ruby --version 2>&1' },
118
+ { key: 'rbenv', command: 'rbenv --version 2>&1' },
119
+
120
+ // Kotlin
121
+ { key: 'kotlin', command: 'kotlin -version 2>&1' },
122
+
123
+ // Swift
124
+ { key: 'swift', command: 'swift --version 2>&1 | head -n1' },
125
+
126
+ // R
127
+ { key: 'r', command: 'R --version 2>&1 | head -n1' },
128
+
106
129
  // Development Tools
107
130
  { key: 'git', command: 'git --version 2>&1' },
108
131
  { key: 'gh', command: 'gh --version 2>&1 | head -n1' },
132
+ { key: 'glab', command: 'glab --version 2>&1 | head -n1' },
109
133
  { key: 'brew', command: 'brew --version 2>&1 | head -n1' },
134
+ { key: 'nasm', command: 'nasm --version 2>&1' },
135
+ { key: 'fasm', command: 'fasm 2>&1 | head -n1' },
136
+ { key: 'curl', command: 'curl --version 2>&1 | head -n1' },
137
+ { key: 'wget', command: 'wget --version 2>&1 | head -n1' },
138
+ { key: 'zip', command: 'zip --version 2>&1 | head -n2 | tail -n1' },
139
+ { key: 'unzip', command: 'unzip -v 2>&1 | head -n1' },
140
+ { key: 'expect', command: 'expect -version 2>&1' },
141
+ { key: 'screen', command: 'screen --version 2>&1' },
142
+ { key: 'xvfb', command: 'Xvfb -version 2>&1 | head -n1', fallbacks: ['dpkg -l xvfb 2>/dev/null | grep xvfb | head -1'] },
110
143
  ];
111
144
 
112
145
  /**
@@ -202,7 +235,17 @@ export async function getVersionInfo(verbose = false, processVersion = null) {
202
235
 
203
236
  // Browser Automation
204
237
  playwright: versions.playwright,
238
+ playwrightTest: versions.playwrightTest,
205
239
  playwrightMcp: versions.playwrightMcp,
240
+ playwrightMcpStatus: versions.playwrightMcpStatus,
241
+ puppeteerBrowsers: versions.puppeteerBrowsers,
242
+
243
+ // Browsers
244
+ chrome: versions.chrome,
245
+ chromium: versions.chromium,
246
+ firefox: versions.firefox,
247
+ msedge: versions.msedge,
248
+ webkit: versions.webkit,
206
249
 
207
250
  // JavaScript/Node.js
208
251
  node: versions.node,
@@ -246,6 +289,19 @@ export async function getVersionInfo(verbose = false, processVersion = null) {
246
289
  elan: versions.elan,
247
290
  lake: versions.lake,
248
291
 
292
+ // Ruby
293
+ ruby: versions.ruby,
294
+ rbenv: versions.rbenv,
295
+
296
+ // Kotlin
297
+ kotlin: versions.kotlin,
298
+
299
+ // Swift
300
+ swift: versions.swift,
301
+
302
+ // R
303
+ r: versions.r,
304
+
249
305
  // C/C++
250
306
  gcc: versions.gcc,
251
307
  gpp: versions.gpp,
@@ -258,7 +314,17 @@ export async function getVersionInfo(verbose = false, processVersion = null) {
258
314
  // Development Tools
259
315
  git: versions.git,
260
316
  gh: versions.gh,
317
+ glab: versions.glab,
261
318
  brew: versions.brew,
319
+ nasm: versions.nasm,
320
+ fasm: versions.fasm,
321
+ curl: versions.curl,
322
+ wget: versions.wget,
323
+ zip: versions.zip,
324
+ unzip: versions.unzip,
325
+ expect: versions.expect,
326
+ screen: versions.screen,
327
+ xvfb: versions.xvfb,
262
328
 
263
329
  // Platform
264
330
  platform: versions.platform,
@@ -407,7 +473,7 @@ export function formatVersionMessage(versions) {
407
473
 
408
474
  if (perlLines.length > 0) {
409
475
  lines.push('');
410
- lines.push('*💎 Perl*');
476
+ lines.push('*🐪 Perl*');
411
477
  lines.push(...perlLines);
412
478
  }
413
479
 
@@ -435,6 +501,38 @@ export function formatVersionMessage(versions) {
435
501
  lines.push(...leanLines);
436
502
  }
437
503
 
504
+ // === Ruby ===
505
+ const rubyLines = [];
506
+ addVersionLine(rubyLines, 'Ruby', versions.ruby);
507
+ addVersionLine(rubyLines, 'Rbenv', versions.rbenv);
508
+
509
+ if (rubyLines.length > 0) {
510
+ lines.push('');
511
+ lines.push('*💎 Ruby*');
512
+ lines.push(...rubyLines);
513
+ }
514
+
515
+ // === Kotlin ===
516
+ if (versions.kotlin) {
517
+ lines.push('');
518
+ lines.push('*🟣 Kotlin*');
519
+ addVersionLine(lines, 'Kotlin', versions.kotlin);
520
+ }
521
+
522
+ // === Swift ===
523
+ if (versions.swift) {
524
+ lines.push('');
525
+ lines.push('*🦅 Swift*');
526
+ addVersionLine(lines, 'Swift', versions.swift);
527
+ }
528
+
529
+ // === R ===
530
+ if (versions.r) {
531
+ lines.push('');
532
+ lines.push('*📊 R*');
533
+ addVersionLine(lines, 'R', versions.r);
534
+ }
535
+
438
536
  // === C/C++ ===
439
537
  const cppLines = [];
440
538
  addVersionLine(cppLines, 'GCC', versions.gcc);
@@ -444,20 +542,60 @@ export function formatVersionMessage(versions) {
444
542
  addVersionLine(cppLines, 'LLD', versions.lld);
445
543
  addVersionLine(cppLines, 'Make', versions.make);
446
544
  addVersionLine(cppLines, 'CMake', versions.cmake);
545
+ addVersionLine(cppLines, 'NASM', versions.nasm);
546
+ addVersionLine(cppLines, 'FASM', versions.fasm);
447
547
 
448
548
  if (cppLines.length > 0) {
449
549
  lines.push('');
450
- lines.push('*🔨 C/C++*');
550
+ lines.push('*🔨 C/C++/Assembly*');
451
551
  lines.push(...cppLines);
452
552
  }
453
553
 
554
+ // === Browsers ===
555
+ const browserLines = [];
556
+ addVersionLine(browserLines, 'Google Chrome', versions.chrome);
557
+ addVersionLine(browserLines, 'Chromium', versions.chromium);
558
+ addVersionLine(browserLines, 'Firefox', versions.firefox);
559
+ addVersionLine(browserLines, 'Microsoft Edge', versions.msedge);
560
+ addVersionLine(browserLines, 'WebKit', versions.webkit);
561
+
562
+ if (browserLines.length > 0) {
563
+ lines.push('');
564
+ lines.push('*🌐 Browsers*');
565
+ lines.push(...browserLines);
566
+ }
567
+
568
+ // === Browser Automation ===
569
+ const browserAutoLines = [];
570
+ addVersionLine(browserAutoLines, 'Playwright', versions.playwright);
571
+ addVersionLine(browserAutoLines, 'Playwright Test', versions.playwrightTest);
572
+ addVersionLine(browserAutoLines, 'Playwright MCP', versions.playwrightMcp);
573
+ if (versions.playwrightMcpStatus) {
574
+ browserAutoLines.push(`• Playwright MCP in Claude Code: \`${versions.playwrightMcpStatus}\``);
575
+ } else if (versions.playwrightMcp) {
576
+ browserAutoLines.push('• Playwright MCP in Claude Code: `not configured`');
577
+ }
578
+ addVersionLine(browserAutoLines, 'Puppeteer Browsers', versions.puppeteerBrowsers);
579
+
580
+ if (browserAutoLines.length > 0) {
581
+ lines.push('');
582
+ lines.push('*🎭 Browser Automation*');
583
+ lines.push(...browserAutoLines);
584
+ }
585
+
454
586
  // === Development Tools ===
455
587
  const toolLines = [];
456
588
  addVersionLine(toolLines, 'Git', versions.git);
457
589
  addVersionLine(toolLines, 'GitHub CLI', versions.gh);
458
- addVersionLine(toolLines, 'Playwright', versions.playwright);
459
- addVersionLine(toolLines, 'Playwright MCP', versions.playwrightMcp);
590
+ addVersionLine(toolLines, 'GitLab CLI', versions.glab);
460
591
  addVersionLine(toolLines, 'Homebrew', versions.brew);
592
+ addVersionLine(toolLines, 'cURL', versions.curl);
593
+ addVersionLine(toolLines, 'Wget', versions.wget);
594
+ addVersionLine(toolLines, 'Zip', versions.zip);
595
+ addVersionLine(toolLines, 'Unzip', versions.unzip);
596
+ addVersionLine(toolLines, 'Expect', versions.expect);
597
+ addVersionLine(toolLines, 'Screen', versions.screen);
598
+ addVersionLine(toolLines, 'Xvfb', versions.xvfb);
461
599
 
462
600
  if (toolLines.length > 0) {
463
601
  lines.push('');