@link-assistant/hive-mind 2.1.2 → 2.1.4

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
+ ## 2.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - acc3ea1: Simplify the `/limits` output with subscription headings and hide unused Codex limits and credits.
8
+
9
+ ## 2.1.3
10
+
11
+ ### Patch Changes
12
+
13
+ - 5c4150b: Make live issue/PR event input available for every tool via `--auto-input-until-mergeable` (issue #2007). Claude and Agent stream events into the live process through `--input-format stream-json`; codex, opencode, gemini, qwen, and unknown tools use a universal restart/resume fallback that waits for the current turn to finish in the JSON output, stops the process, and resumes the AI session with the new events. Adds issue title/description edit detection as a restart trigger, reworks the capability matrix to report each tool's delivery mode, and records the `@link-assistant/agent` 0.24.1 live stream-json contract.
14
+
3
15
  ## 2.1.2
4
16
 
5
17
  ### Patch Changes
package/README.hi.md CHANGED
@@ -521,7 +521,9 @@ Shows:
521
521
  - RAM usage (used vs total)
522
522
  - Disk space usage
523
523
  - GitHub API rate limits
524
- - Claude usage limits (session and weekly)
524
+ - Claude and ChatGPT subscription usage windows
525
+ - Non-zero Codex weekly limits and credits
526
+ - Solve queue status
525
527
  ```
526
528
 
527
529
  #### `/terminal_watch` - Live Session Log
package/README.md CHANGED
@@ -542,7 +542,9 @@ Shows:
542
542
  - RAM usage (used vs total)
543
543
  - Disk space usage
544
544
  - GitHub API rate limits
545
- - Claude usage limits (session and weekly)
545
+ - Claude and ChatGPT subscription usage windows
546
+ - Non-zero Codex weekly limits and credits
547
+ - Solve queue status
546
548
  ```
547
549
 
548
550
  #### `/terminal_watch` - Live Session Log
package/README.ru.md CHANGED
@@ -522,7 +522,9 @@ Shows:
522
522
  - RAM usage (used vs total)
523
523
  - Disk space usage
524
524
  - GitHub API rate limits
525
- - Claude usage limits (session and weekly)
525
+ - Claude and ChatGPT subscription usage windows
526
+ - Non-zero Codex weekly limits and credits
527
+ - Solve queue status
526
528
  ```
527
529
 
528
530
  #### `/terminal_watch` — Live Session Log
package/README.zh.md CHANGED
@@ -518,7 +518,9 @@ Shows:
518
518
  - RAM usage (used vs total)
519
519
  - Disk space usage
520
520
  - GitHub API rate limits
521
- - Claude usage limits (session and weekly)
521
+ - Claude and ChatGPT subscription usage windows
522
+ - Non-zero Codex weekly limits and credits
523
+ - Solve queue status
522
524
  ```
523
525
 
524
526
  #### `/terminal_watch` - Live Session Log
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
package/src/agent.lib.mjs CHANGED
@@ -20,10 +20,12 @@ import { timeouts, retryLimits } from './config.lib.mjs';
20
20
  import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
21
21
  import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
22
22
  import Decimal from 'decimal.js-light';
23
+ import semver from 'semver';
23
24
  import { agentModels, defaultModels, freeToBaseModelMap } from './models/index.mjs';
24
25
  import { checkPlaywrightMcpPackageAvailability, getAgentPlaywrightMcpDisableEnv } from './playwright-mcp.lib.mjs';
25
26
  import { createAgentTokenUsage, accumulateAgentStepFinishUsage, parseAgentTokenUsage } from './agent-token-usage.lib.mjs';
26
27
  import { classifyRetryableError, getRetryDelayMs, maybeSwitchToFallbackModel, waitWithCountdown } from './tool-retry.lib.mjs';
28
+ import { attachStreamingInput, finalizeBidirectionalHandler, setupBidirectionalHandler } from './bidirectional-interactive.lib.mjs';
27
29
 
28
30
  export { createAgentTokenUsage, accumulateAgentStepFinishUsage, parseAgentTokenUsage };
29
31
 
@@ -248,10 +250,22 @@ export const mapModelToId = model => {
248
250
  return agentModels[model] || model;
249
251
  };
250
252
 
253
+ export const MIN_AGENT_LIVE_INPUT_VERSION = '0.24.1';
254
+
255
+ export const getAgentCliVersion = versionOutput => {
256
+ return semver.clean(versionOutput) || semver.coerce(versionOutput)?.version || null;
257
+ };
258
+
259
+ export const agentCliSupportsLiveInput = versionOutput => {
260
+ const version = getAgentCliVersion(versionOutput);
261
+ return !!version && semver.gte(version, MIN_AGENT_LIVE_INPUT_VERSION);
262
+ };
263
+
251
264
  // Function to validate Agent connection
252
- export const validateAgentConnection = async (model = defaultModels.agent) => {
265
+ export const validateAgentConnection = async (model = defaultModels.agent, options = {}) => {
253
266
  // Map model alias to full ID
254
267
  const mappedModel = mapModelToId(model);
268
+ const requireLiveInput = !!options.requireLiveInput;
255
269
 
256
270
  // Retry configuration
257
271
  const maxRetries = 3;
@@ -266,10 +280,12 @@ export const validateAgentConnection = async (model = defaultModels.agent) => {
266
280
  }
267
281
 
268
282
  // Check if Agent CLI is installed and get version
283
+ let agentVersion = null;
269
284
  try {
270
285
  const versionResult = await $`timeout ${Math.floor(timeouts.opencodeCli / 1000)} agent --version`;
271
286
  if (versionResult.code === 0) {
272
287
  const version = versionResult.stdout?.toString().trim();
288
+ agentVersion = getAgentCliVersion(version);
273
289
  if (retryCount === 0) {
274
290
  await log(`šŸ“¦ Agent CLI version: ${version}`);
275
291
  }
@@ -280,6 +296,17 @@ export const validateAgentConnection = async (model = defaultModels.agent) => {
280
296
  }
281
297
  }
282
298
 
299
+ if (requireLiveInput && (!agentVersion || !semver.gte(agentVersion, MIN_AGENT_LIVE_INPUT_VERSION))) {
300
+ await log(`āŒ Agent live stream-json input requires @link-assistant/agent >= ${MIN_AGENT_LIVE_INPUT_VERSION}`, { level: 'error' });
301
+ if (agentVersion) {
302
+ await log(` Installed Agent CLI version: ${agentVersion}`, { level: 'error' });
303
+ } else {
304
+ await log(' Could not determine the installed Agent CLI version.', { level: 'error' });
305
+ }
306
+ await log(' Update with: bun install -g @link-assistant/agent@latest', { level: 'error' });
307
+ return false;
308
+ }
309
+
283
310
  // Test basic Agent functionality with a simple "hi" message
284
311
  // Agent uses the same JSON interface as OpenCode
285
312
  const testResult = await $`printf "hi" | timeout ${Math.floor(timeouts.opencodeCli / 1000)} agent --model ${mappedModel}`;
@@ -406,13 +433,17 @@ export const executeAgent = async params => {
406
433
  getResourceSnapshot,
407
434
  forkedRepo,
408
435
  feedbackLines,
436
+ owner,
437
+ repo,
438
+ prNumber,
439
+ issueNumber,
409
440
  agentPath,
410
441
  $,
411
442
  });
412
443
  };
413
444
 
414
445
  export const executeAgentCommand = async params => {
415
- const { tempDir, branchName, prompt, systemPrompt, argv, log, formatAligned, getResourceSnapshot, forkedRepo, feedbackLines, agentPath, $, waitForRetryDelay = waitWithCountdown } = params;
446
+ const { tempDir, branchName, prompt, systemPrompt, argv, log, formatAligned, getResourceSnapshot, forkedRepo, feedbackLines, owner, repo, prNumber, issueNumber, agentPath, $, calculatePricing = calculateAgentPricing, waitForRetryDelay = waitWithCountdown } = params;
416
447
 
417
448
  // Retry configuration
418
449
  let retryCount = 0;
@@ -459,6 +490,15 @@ export const executeAgentCommand = async params => {
459
490
 
460
491
  // Build Agent command
461
492
  let execCommand;
493
+ let bidirectionalHandler = null;
494
+ let bidirectionalHandlerFinalized = false;
495
+ let queuedFeedback = [];
496
+ const finalizeAgentBidirectionalHandler = async () => {
497
+ if (bidirectionalHandlerFinalized) return queuedFeedback;
498
+ bidirectionalHandlerFinalized = true;
499
+ queuedFeedback = await finalizeBidirectionalHandler(bidirectionalHandler, log);
500
+ return queuedFeedback;
501
+ };
462
502
 
463
503
  // Map model alias to full ID
464
504
  const mappedModel = mapModelToId(argv.model);
@@ -480,27 +520,57 @@ export const executeAgentCommand = async params => {
480
520
  // We'll combine system and user prompts into a single message
481
521
  const combinedPrompt = systemPrompt ? `${systemPrompt}\n\n${prompt}` : prompt;
482
522
 
483
- // Write the combined prompt to a file for piping
484
- // Use OS temporary directory instead of repository workspace to avoid polluting the repo
485
- const promptFile = path.join(os.tmpdir(), `agent_prompt_${Date.now()}_${process.pid}.txt`);
486
- await fs.writeFile(promptFile, combinedPrompt);
487
-
488
- // Build the full command - pipe the prompt file to agent
489
- const fullCommand = `(cd "${tempDir}" && cat "${promptFile}" | ${agentPath} ${agentArgs})`;
490
-
491
- await log(`\n${formatAligned('šŸ“', 'Raw command:', '')}`);
492
- await log(`${fullCommand}`);
493
- await log('');
494
-
495
523
  try {
496
- // Pipe the prompt file to agent via stdin
497
- // Use agentArgs which includes --model and optionally --verbose
524
+ if (argv.acceptIncommingCommentsAsInput) {
525
+ bidirectionalHandler = await setupBidirectionalHandler({
526
+ argv,
527
+ owner,
528
+ repo,
529
+ prNumber,
530
+ issueNumber,
531
+ tempDir,
532
+ $,
533
+ log,
534
+ });
535
+ }
536
+ const streamingInput = !!bidirectionalHandler;
537
+ if (streamingInput) {
538
+ agentArgs += ' --input-format stream-json --output-format stream-json';
539
+ }
498
540
 
499
- execCommand = $({
500
- cwd: tempDir,
501
- mirror: false,
502
- env: agentEnv,
503
- })`cat ${promptFile} | ${agentPath} ${agentArgs}`;
541
+ let promptFile = null;
542
+ if (!streamingInput) {
543
+ // Write the combined prompt to a file for piping.
544
+ // Use OS temporary directory instead of repository workspace to avoid polluting the repo.
545
+ promptFile = path.join(os.tmpdir(), `agent_prompt_${Date.now()}_${process.pid}.txt`);
546
+ await fs.writeFile(promptFile, combinedPrompt);
547
+ }
548
+
549
+ const fullCommand = streamingInput ? `(cd "${tempDir}" && ${agentPath} ${agentArgs})` : `(cd "${tempDir}" && cat "${promptFile}" | ${agentPath} ${agentArgs})`;
550
+
551
+ await log(`\n${formatAligned('šŸ“', 'Raw command:', '')}`);
552
+ await log(`${fullCommand}`);
553
+ await log('');
554
+
555
+ if (streamingInput) {
556
+ execCommand = $({
557
+ cwd: tempDir,
558
+ stdin: 'pipe',
559
+ mirror: false,
560
+ env: agentEnv,
561
+ })`${agentPath} ${agentArgs}`;
562
+ const attached = await attachStreamingInput(bidirectionalHandler, execCommand, combinedPrompt, log, !!argv.verbose, { toolLabel: 'Agent' });
563
+ if (!attached) {
564
+ throw new Error('Agent live stream-json input requested, but stdin attachment failed');
565
+ }
566
+ } else {
567
+ // Pipe the prompt file to agent via stdin for the legacy one-shot path.
568
+ execCommand = $({
569
+ cwd: tempDir,
570
+ mirror: false,
571
+ env: agentEnv,
572
+ })`cat ${promptFile} | ${agentPath} ${agentArgs}`;
573
+ }
504
574
 
505
575
  await log(`${formatAligned('šŸ“‹', 'Command details:', '')}`);
506
576
  await log(formatAligned('šŸ“‚', 'Working directory:', tempDir, 2));
@@ -530,6 +600,30 @@ export const executeAgentCommand = async params => {
530
600
  // This fixes the issue where NDJSON lines get concatenated without newlines, breaking JSON.parse
531
601
  const streamingTokenUsage = createAgentTokenUsage();
532
602
  const accumulateTokenUsage = data => accumulateAgentStepFinishUsage(streamingTokenUsage, data);
603
+ const isAgentSuccessfulCompletionEvent = data => {
604
+ if (data.type === 'session.idle' || data.type === 'session_idle' || data.type === 'idle') return true;
605
+ if (data.type === 'log' && data.message === 'exiting loop') return true;
606
+ if (data.type === 'step_finish' && data.part?.reason === 'stop') return true;
607
+ if (data.type === 'result' && (data.status === 'success' || data.subtype === 'success')) return true;
608
+ return false;
609
+ };
610
+ const markBidirectionalStateFromAgentEvent = async data => {
611
+ if (!bidirectionalHandler) return;
612
+ if (isAgentSuccessfulCompletionEvent(data)) {
613
+ if (typeof bidirectionalHandler.markAiIdle === 'function') {
614
+ try {
615
+ await bidirectionalHandler.markAiIdle();
616
+ } catch (idleErr) {
617
+ if (argv.verbose) await log(`āš ļø Bidirectional mode: markAiIdle error: ${idleErr.message}`, { verbose: true });
618
+ }
619
+ }
620
+ return;
621
+ }
622
+ const busyEventTypes = new Set(['init', 'session_start', 'session.started', 'message', 'assistant', 'text', 'tool_use', 'tool_result', 'step_start', 'step_delta']);
623
+ if (busyEventTypes.has(data.type) && typeof bidirectionalHandler.markAiBusy === 'function') {
624
+ bidirectionalHandler.markAiBusy();
625
+ }
626
+ };
533
627
 
534
628
  for await (const chunk of execCommand.stream()) {
535
629
  if (chunk.type === 'stdout') {
@@ -548,12 +642,14 @@ export const executeAgentCommand = async params => {
548
642
  // Output formatted JSON
549
643
  await log(JSON.stringify(data, null, 2));
550
644
  // Capture session ID from the first message
551
- if (!sessionId && data.sessionID) {
552
- sessionId = data.sessionID;
645
+ const eventSessionId = data.sessionID || data.session_id || data.sessionId;
646
+ if (!sessionId && eventSessionId) {
647
+ sessionId = eventSessionId;
553
648
  await log(`šŸ“Œ Session ID: ${sessionId}`);
554
649
  }
555
650
  // Issue #1250: Accumulate token usage during streaming
556
651
  accumulateTokenUsage(data);
652
+ await markBidirectionalStateFromAgentEvent(data);
557
653
  // Issue #1201: Detect error events during streaming for reliable detection
558
654
  if (data.type === 'error' || data.type === 'step_error') {
559
655
  streamingErrorDetected = true;
@@ -590,16 +686,14 @@ export const executeAgentCommand = async params => {
590
686
  // Issue #1276: Detect successful completion events
591
687
  // When agent emits session.idle or log with "exiting loop" message, it completed successfully
592
688
  // This means any previous error events were recovered from (e.g., timeout then retry)
593
- if (data.type === 'session.idle' || (data.type === 'log' && data.message === 'exiting loop')) {
689
+ if (isAgentSuccessfulCompletionEvent(data)) {
594
690
  agentCompletedSuccessfully = true;
595
691
  }
596
692
  // Issue #1296: Detect step_finish with reason "stop" as successful completion
597
693
  // This is a clear marker of success - agent finished normally, not due to error or limit
598
694
  // When this event appears, we should ignore any error events that appeared earlier in the stream
599
695
  // (e.g., timeout errors that were recovered from via retry logic)
600
- if (data.type === 'step_finish' && data.part?.reason === 'stop') {
601
- agentCompletedSuccessfully = true;
602
- }
696
+ if (data.type === 'step_finish' && data.part?.reason === 'stop') agentCompletedSuccessfully = true;
603
697
  } catch {
604
698
  // Not JSON - log as plain text
605
699
  await log(line);
@@ -624,12 +718,14 @@ export const executeAgentCommand = async params => {
624
718
  // Output formatted JSON (same formatting as stdout)
625
719
  await log(JSON.stringify(stderrData, null, 2));
626
720
  // Capture session ID from stderr too (agent sends it via stderr)
627
- if (!sessionId && stderrData.sessionID) {
628
- sessionId = stderrData.sessionID;
721
+ const eventSessionId = stderrData.sessionID || stderrData.session_id || stderrData.sessionId;
722
+ if (!sessionId && eventSessionId) {
723
+ sessionId = eventSessionId;
629
724
  await log(`šŸ“Œ Session ID: ${sessionId}`);
630
725
  }
631
726
  // Issue #1250: Accumulate token usage during streaming (stderr)
632
727
  accumulateTokenUsage(stderrData);
728
+ await markBidirectionalStateFromAgentEvent(stderrData);
633
729
  // Issue #1201: Detect error events during streaming (stderr) for reliable detection
634
730
  if (stderrData.type === 'error' || stderrData.type === 'step_error') {
635
731
  streamingErrorDetected = true;
@@ -661,7 +757,7 @@ export const executeAgentCommand = async params => {
661
757
  }
662
758
  // Issue #1276: Detect successful completion events (stderr)
663
759
  // When agent emits session.idle or log with "exiting loop" message, it completed successfully
664
- if (stderrData.type === 'session.idle' || (stderrData.type === 'log' && stderrData.message === 'exiting loop')) {
760
+ if (isAgentSuccessfulCompletionEvent(stderrData)) {
665
761
  agentCompletedSuccessfully = true;
666
762
  }
667
763
  // Issue #1296: Detect step_finish with reason "stop" as successful completion (stderr)
@@ -811,6 +907,7 @@ export const executeAgentCommand = async params => {
811
907
  await log(`\nāš ļø ${retryableError.label} detected. Retry ${retryCount + 1}/${maxRetries} in ${delayLabel}${sessionId ? ' (session preserved)' : ''}...`, { level: 'warning' });
812
908
  if (sessionId && !argv.resume) argv.resume = sessionId;
813
909
  await maybeSwitchToFallbackModel({ tool: 'agent', argv, log, errorMessage: retryableError.message });
910
+ await finalizeAgentBidirectionalHandler();
814
911
  await waitForRetryDelay(delay, log);
815
912
  await log('\nšŸ”„ Retrying now...');
816
913
  retryCount++;
@@ -887,7 +984,8 @@ export const executeAgentCommand = async params => {
887
984
  // Issue #1250: Use streaming-accumulated token usage instead of re-parsing fullOutput
888
985
  // This fixes the issue where NDJSON lines get concatenated without newlines, breaking JSON.parse
889
986
  const tokenUsage = streamingTokenUsage;
890
- const pricingInfo = await calculateAgentPricing(mappedModel, tokenUsage);
987
+ const pricingInfo = await calculatePricing(mappedModel, tokenUsage);
988
+ await finalizeAgentBidirectionalHandler();
891
989
 
892
990
  return {
893
991
  success: false,
@@ -907,7 +1005,7 @@ export const executeAgentCommand = async params => {
907
1005
  // Issue #1250: Use streaming-accumulated token usage instead of re-parsing fullOutput
908
1006
  // This fixes the issue where NDJSON lines get concatenated without newlines, breaking JSON.parse
909
1007
  const tokenUsage = streamingTokenUsage;
910
- const pricingInfo = await calculateAgentPricing(mappedModel, tokenUsage);
1008
+ const pricingInfo = await calculatePricing(mappedModel, tokenUsage);
911
1009
 
912
1010
  // Log pricing information (similar to --tool claude breakdown)
913
1011
  if (tokenUsage.stepCount > 0) {
@@ -958,6 +1056,7 @@ export const executeAgentCommand = async params => {
958
1056
  if (lastTextContent) {
959
1057
  await log('šŸ“ Captured result summary from Agent output', { verbose: true });
960
1058
  }
1059
+ await finalizeAgentBidirectionalHandler();
961
1060
 
962
1061
  return {
963
1062
  success: true,
@@ -977,6 +1076,7 @@ export const executeAgentCommand = async params => {
977
1076
  operation: 'run_agent_command',
978
1077
  });
979
1078
 
1079
+ await finalizeAgentBidirectionalHandler();
980
1080
  await log(`\n\nāŒ Error executing Agent command: ${error.message}`, { level: 'error' });
981
1081
  return {
982
1082
  success: false,