@link-assistant/hive-mind 2.1.9 → 2.1.11

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,18 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 2.1.11
4
+
5
+ ### Patch Changes
6
+
7
+ - 208e123: Fix false-positive npm releases (issue #2028). `setup-npm.mjs` now pins npm to the 11.x line and validates the result instead of installing `npm@latest` (which pulled in npm 12.0.0, whose sigstore regression crashes provenance publishes — npm/cli#9722). `publish-to-npm.mjs` no longer trusts the publish command's exit status alone: it observes the real exit code, scans output for failure patterns, and verifies the version is actually live on npm before reporting success, so a failed publish can no longer be reported as a successful release. Added `sanitize-npm-userconfig.mjs` to remove the deprecated `always-auth` npm warning from release logs.
8
+
9
+ ## 2.1.10
10
+
11
+ ### Patch Changes
12
+
13
+ - 9964e69: Retry Claude stream-json sessions with `--resume` when the stream ends without a terminal result event after tool output.
14
+ - afbc353: Avoid fetching `use-m` at isolation-runner import time when tests only use pure helper exports.
15
+
3
16
  ## 2.1.9
4
17
 
5
18
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "2.1.9",
3
+ "version": "2.1.11",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -1163,6 +1163,11 @@ export const executeClaudeCommand = async params => {
1163
1163
  queuedFeedback,
1164
1164
  };
1165
1165
  }
1166
+ if (shouldFailClaudeStreamWithoutResult({ commandFailed, streamingInput, resultEventReceived })) {
1167
+ commandFailed = true;
1168
+ lastMessage = buildMissingClaudeResultMessage({ lastToolResultError, lastMessage });
1169
+ await log(`\n\n❌ Command failed: ${lastMessage}`, { level: 'error' });
1170
+ }
1166
1171
  const retryableLastError = classifyRetryableError(lastMessage);
1167
1172
  // Issue #1834: Corrupted extended-thinking blocks → try to resume the session first, then fall
1168
1173
  // back to a fresh restart (PR #1835 feedback). When both caps are reached, tryThinkingBlockRecovery
@@ -1291,11 +1296,6 @@ export const executeClaudeCommand = async params => {
1291
1296
  .join('\n');
1292
1297
  await log(`\n\n❌ Command failed: No messages processed and errors detected in stderr\nStderr errors:\n${errorsPreview}`, { level: 'error' });
1293
1298
  }
1294
- if (shouldFailClaudeStreamWithoutResult({ commandFailed, streamingInput, resultEventReceived })) {
1295
- commandFailed = true;
1296
- lastMessage = buildMissingClaudeResultMessage({ lastToolResultError, lastMessage });
1297
- await log(`\n\n❌ Command failed: ${lastMessage}`, { level: 'error' });
1298
- }
1299
1299
  if (commandFailed) {
1300
1300
  // Take resource snapshot after failure
1301
1301
  const resourcesAfter = await getResourceSnapshot();
@@ -20,11 +20,26 @@ import os from 'node:os';
20
20
  import path from 'node:path';
21
21
  import { isExecutingSessionStatus, isTerminalSessionStatus } from './session-status.lib.mjs';
22
22
 
23
- if (typeof use === 'undefined') {
24
- await ensureUseM();
25
- }
23
+ let commandStreamDollarPromise = null;
24
+
25
+ async function getCommandStreamDollar() {
26
+ if (!commandStreamDollarPromise) {
27
+ commandStreamDollarPromise = (async () => {
28
+ if (typeof globalThis.use === 'undefined') {
29
+ await ensureUseM();
30
+ }
31
+ const { $ } = await globalThis.use('command-stream');
32
+ return $;
33
+ })();
34
+ }
26
35
 
27
- const { $ } = await use('command-stream');
36
+ try {
37
+ return await commandStreamDollarPromise;
38
+ } catch (error) {
39
+ commandStreamDollarPromise = null;
40
+ throw error;
41
+ }
42
+ }
28
43
 
29
44
  // Re-export the shared status predicates so existing callers that reach them via
30
45
  // the isolation-runner module (e.g. session-monitor's `runner.isExecutingSessionStatus`)
@@ -516,6 +531,7 @@ export function readSessionExitFromLog(logPath, options = {}) {
516
531
  */
517
532
  async function findStartCommandBinary() {
518
533
  try {
534
+ const $ = await getCommandStreamDollar();
519
535
  const result = await $({ mirror: false })`which $`;
520
536
  const path = result.stdout?.toString().trim() || '';
521
537
  return path || null;
@@ -677,6 +693,7 @@ export async function querySessionStatus(sessionId, verbose = false) {
677
693
  }
678
694
 
679
695
  try {
696
+ const $ = await getCommandStreamDollar();
680
697
  const result = await $({ mirror: false })`${binPath} --status ${sessionId} --output-format json`;
681
698
 
682
699
  const stdout = result.stdout?.toString().trim() || '';
@@ -755,6 +772,7 @@ export async function listIsolationSessions(verbose = false) {
755
772
  return [];
756
773
  }
757
774
  try {
775
+ const $ = await getCommandStreamDollar();
758
776
  const result = await $({ mirror: false })`${binPath} --list --output-format json`;
759
777
  const stdout = result.stdout?.toString().trim() || '';
760
778
  const sessions = parseSessionListOutput(stdout);
@@ -791,6 +809,7 @@ export async function stopIsolatedSession(sessionId, verbose = false) {
791
809
  }
792
810
 
793
811
  try {
812
+ const $ = await getCommandStreamDollar();
794
813
  const result = await $({ mirror: false })`${binPath} --stop ${sessionId}`;
795
814
  const stdout = result.stdout?.toString() || '';
796
815
  const stderr = result.stderr?.toString() || '';
@@ -827,6 +846,7 @@ export async function stopIsolatedSession(sessionId, verbose = false) {
827
846
  */
828
847
  export async function checkScreenSessionRunning(sessionName, verbose = false) {
829
848
  try {
849
+ const $ = await getCommandStreamDollar();
830
850
  const result = await $({ mirror: false })`screen -ls`;
831
851
  const output = result.stdout?.toString() || '';
832
852
  const exists = output.includes(sessionName);
@@ -857,6 +877,7 @@ export async function checkScreenSessionRunning(sessionName, verbose = false) {
857
877
  */
858
878
  export async function checkDockerContainerRunning(containerName, verbose = false) {
859
879
  try {
880
+ const $ = await getCommandStreamDollar();
860
881
  const result = await $({ mirror: false })`docker inspect -f ${'{{.State.Running}}'} ${containerName}`;
861
882
  const running = (result.stdout?.toString() || '').trim() === 'true';
862
883
  if (verbose) {
@@ -890,6 +911,7 @@ export function parseDockerContainerWritableLayerSizeOutput(output) {
890
911
  export async function getDockerContainerWritableLayerSize(containerName, verbose = false) {
891
912
  if (!containerName) return null;
892
913
  try {
914
+ const $ = await getCommandStreamDollar();
893
915
  const result = await $({ mirror: false })`docker inspect --size -f ${'{{.SizeRw}}'} ${containerName}`;
894
916
  const bytes = parseDockerContainerWritableLayerSizeOutput(result.stdout?.toString() || '');
895
917
  if (verbose) {
@@ -922,6 +944,7 @@ export async function releaseDockerContainerStartGate(containerName, verbose = f
922
944
 
923
945
  for (let attempt = 1; attempt <= 5; attempt++) {
924
946
  try {
947
+ const $ = await getCommandStreamDollar();
925
948
  await $({ mirror: false })`docker exec ${containerName} sh -c ${releaseCommand}`;
926
949
  if (verbose) {
927
950
  console.log(`[VERBOSE] isolation-runner: released docker start gate for '${containerName}'`);
@@ -960,6 +983,7 @@ export async function removeDockerContainer(containerName, verbose = false) {
960
983
  }
961
984
 
962
985
  try {
986
+ const $ = await getCommandStreamDollar();
963
987
  const result = await $({ mirror: false })`docker rm -f ${containerName}`;
964
988
  const stdout = result.stdout?.toString() || '';
965
989
  const stderr = result.stderr?.toString() || '';
@@ -992,6 +1016,7 @@ export async function removeDockerContainer(containerName, verbose = false) {
992
1016
  */
993
1017
  export async function checkTmuxSessionRunning(sessionName, verbose = false) {
994
1018
  try {
1019
+ const $ = await getCommandStreamDollar();
995
1020
  await $({ mirror: false })`tmux has-session -t ${sessionName}`;
996
1021
  if (verbose) console.log(`[VERBOSE] isolation-runner: tmux has-session '${sessionName}': running`);
997
1022
  return true;
@@ -1035,6 +1060,7 @@ export async function checkBackendSessionAlive(sessionId, backend, verbose = fal
1035
1060
  */
1036
1061
  export async function checkDockerImagePresent(image, verbose = false) {
1037
1062
  try {
1063
+ const $ = await getCommandStreamDollar();
1038
1064
  await $({ mirror: false })`docker image inspect ${image}`;
1039
1065
  if (verbose) console.log(`[VERBOSE] isolation-runner: docker image inspect '${image}': present`);
1040
1066
  return true;
@@ -1062,6 +1088,7 @@ export async function checkDockerImagePresent(image, verbose = false) {
1062
1088
  */
1063
1089
  export async function checkDockerStorageDriver(verbose = false) {
1064
1090
  try {
1091
+ const $ = await getCommandStreamDollar();
1065
1092
  const result = await $({ mirror: false })`docker info --format ${'{{.Driver}}'}`;
1066
1093
  const driver = (result.stdout?.toString() || '').trim().toLowerCase() || null;
1067
1094
  if (verbose) console.log(`[VERBOSE] isolation-runner: docker storage driver: ${driver || '(unknown)'}`);
@@ -1091,6 +1118,7 @@ export async function checkDockerDiskSpace(verbose = false) {
1091
1118
  try {
1092
1119
  let dataRoot = '/var/lib/docker';
1093
1120
  try {
1121
+ const $ = await getCommandStreamDollar();
1094
1122
  const info = await $({ mirror: false })`docker info --format ${'{{.DockerRootDir}}'}`;
1095
1123
  const root = (info.stdout?.toString() || '').trim();
1096
1124
  if (root) dataRoot = root;
@@ -1099,6 +1127,7 @@ export async function checkDockerDiskSpace(verbose = false) {
1099
1127
  // fails on it (e.g. the path does not exist) we return null below.
1100
1128
  }
1101
1129
 
1130
+ const $ = await getCommandStreamDollar();
1102
1131
  const df = await $({ mirror: false })`df -Pk ${dataRoot}`;
1103
1132
  // `df -P` guarantees one logical line per filesystem (no wrapping). The last
1104
1133
  // line is the data row: Filesystem 1024-blocks Used Available Capacity Mount
@@ -59,6 +59,13 @@ export const classifyRetryableError = value => {
59
59
  return { message, isRetryable: true, isCapacity: false, label: 'Stream disconnected before completion' };
60
60
  }
61
61
 
62
+ // Issue #2023: Claude Code can close a stream-json process after tool output
63
+ // (for example "Exit code 144") without emitting the terminal result event.
64
+ // The session is still resumable; failing immediately loses in-progress work.
65
+ if (lower.includes('claude stream ended without a terminal result event')) {
66
+ return { message, isRetryable: true, isCapacity: false, label: 'Claude stream ended without terminal result' };
67
+ }
68
+
62
69
  // Issue #1937: Stream idle timeout. When the Anthropic streaming response stalls
63
70
  // (no bytes for the SDK's idle window) after the model has already emitted part of
64
71
  // its answer, the Claude CLI aborts the turn and surfaces a synthetic assistant /