@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 +13 -0
- package/package.json +1 -1
- package/src/claude.lib.mjs +5 -5
- package/src/isolation-runner.lib.mjs +33 -4
- package/src/tool-retry.lib.mjs +7 -0
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
package/src/claude.lib.mjs
CHANGED
|
@@ -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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
package/src/tool-retry.lib.mjs
CHANGED
|
@@ -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 /
|