@link-assistant/hive-mind 1.14.1 → 1.14.2
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 +19 -0
- package/package.json +1 -1
- package/src/claude.lib.mjs +25 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.14.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 69a34a6: fix: NDJSON stream buffering for Claude CLI output (Issue #1183)
|
|
8
|
+
|
|
9
|
+
Fixed issue where `total_cost_usd` and other critical fields were not being captured from Claude CLI sessions when the output JSON was split across multiple stdout chunks.
|
|
10
|
+
|
|
11
|
+
**Root Cause**: Claude CLI outputs NDJSON (newline-delimited JSON) format, but long JSON messages (like the `result` type containing `total_cost_usd`) can be split across multiple stdout buffer chunks. The code was splitting each chunk by newlines and parsing independently, causing partial JSON fragments to fail parsing.
|
|
12
|
+
|
|
13
|
+
**Solution**:
|
|
14
|
+
- Implemented line buffering to accumulate incomplete lines across chunks
|
|
15
|
+
- Lines are only parsed when they're complete (have a trailing newline)
|
|
16
|
+
- Added processing of any remaining buffer content after the stream ends
|
|
17
|
+
|
|
18
|
+
This ensures that even very long JSON output (e.g., result messages with extensive usage data) is properly parsed and cost tracking works correctly.
|
|
19
|
+
|
|
20
|
+
**Evidence from logs**: The broken session showed JSON truncated mid-word at `ephemeral_5m_input_tok` continuing on the next line with `ens":97252}}` - making both lines unparseable.
|
|
21
|
+
|
|
3
22
|
## 1.14.1
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/package.json
CHANGED
package/src/claude.lib.mjs
CHANGED
|
@@ -944,57 +944,46 @@ export const executeClaudeCommand = async params => {
|
|
|
944
944
|
await log(`\n${formatAligned('▶️', 'Streaming output:', '')}\n`);
|
|
945
945
|
// Use command-stream's async iteration for real-time streaming
|
|
946
946
|
let exitCode = 0;
|
|
947
|
+
// Issue #1183: Line buffer for NDJSON stream parsing - accumulate incomplete lines across chunks
|
|
948
|
+
// Long JSON messages (e.g., result with total_cost_usd) may be split across multiple stdout chunks
|
|
949
|
+
let stdoutLineBuffer = '';
|
|
947
950
|
for await (const chunk of execCommand.stream()) {
|
|
948
951
|
if (chunk.type === 'stdout') {
|
|
949
952
|
const output = chunk.data.toString();
|
|
950
|
-
//
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
//
|
|
955
|
-
const lines = output.split('\n');
|
|
953
|
+
// Append to buffer and split; keep last element (may be incomplete) for next chunk
|
|
954
|
+
stdoutLineBuffer += output;
|
|
955
|
+
const lines = stdoutLineBuffer.split('\n');
|
|
956
|
+
stdoutLineBuffer = lines.pop() || '';
|
|
957
|
+
// Parse each complete NDJSON line
|
|
956
958
|
for (const line of lines) {
|
|
957
959
|
if (!line.trim()) continue;
|
|
958
960
|
try {
|
|
959
961
|
const data = JSON.parse(line);
|
|
960
|
-
// Process event in interactive mode
|
|
962
|
+
// Process event in interactive mode
|
|
961
963
|
if (interactiveHandler) {
|
|
962
964
|
try {
|
|
963
965
|
await interactiveHandler.processEvent(data);
|
|
964
966
|
} catch (interactiveError) {
|
|
965
|
-
// Don't let interactive mode errors stop the main execution
|
|
966
967
|
await log(`⚠️ Interactive mode error: ${interactiveError.message}`, { verbose: true });
|
|
967
968
|
}
|
|
968
969
|
}
|
|
969
|
-
// Output formatted JSON as in v0.3.2
|
|
970
970
|
await log(JSON.stringify(data, null, 2));
|
|
971
|
-
// Capture session ID
|
|
971
|
+
// Capture session ID and rename log file
|
|
972
972
|
if (!sessionId && data.session_id) {
|
|
973
973
|
sessionId = data.session_id;
|
|
974
974
|
await log(`📌 Session ID: ${sessionId}`);
|
|
975
|
-
// Try to rename log file to include session ID
|
|
976
975
|
let sessionLogFile;
|
|
977
976
|
try {
|
|
978
977
|
const currentLogFile = getLogFile();
|
|
979
|
-
|
|
980
|
-
sessionLogFile = path.join(logDir, `${sessionId}.log`);
|
|
981
|
-
// Use fs.promises to rename the file
|
|
978
|
+
sessionLogFile = path.join(path.dirname(currentLogFile), `${sessionId}.log`);
|
|
982
979
|
await fs.rename(currentLogFile, sessionLogFile);
|
|
983
|
-
// Update the global log file reference
|
|
984
980
|
setLogFile(sessionLogFile);
|
|
985
981
|
await log(`📁 Log renamed to: ${sessionLogFile}`);
|
|
986
982
|
} catch (renameError) {
|
|
987
|
-
reportError(renameError, {
|
|
988
|
-
context: 'rename_session_log',
|
|
989
|
-
sessionId,
|
|
990
|
-
sessionLogFile,
|
|
991
|
-
operation: 'rename_log_file',
|
|
992
|
-
});
|
|
993
|
-
// If rename fails, keep original filename
|
|
983
|
+
reportError(renameError, { context: 'rename_session_log', sessionId, sessionLogFile, operation: 'rename_log_file' });
|
|
994
984
|
await log(`⚠️ Could not rename log file: ${renameError.message}`, { verbose: true });
|
|
995
985
|
}
|
|
996
986
|
}
|
|
997
|
-
// Track message and tool use counts
|
|
998
987
|
if (data.type === 'message') {
|
|
999
988
|
messageCount++;
|
|
1000
989
|
} else if (data.type === 'tool_use') {
|
|
@@ -1110,6 +1099,19 @@ export const executeClaudeCommand = async params => {
|
|
|
1110
1099
|
}
|
|
1111
1100
|
}
|
|
1112
1101
|
|
|
1102
|
+
// Issue #1183: Process remaining buffer content - extract cost from result type if present
|
|
1103
|
+
if (stdoutLineBuffer.trim()) {
|
|
1104
|
+
try {
|
|
1105
|
+
const data = JSON.parse(stdoutLineBuffer);
|
|
1106
|
+
await log(JSON.stringify(data, null, 2));
|
|
1107
|
+
if (data.type === 'result' && data.subtype === 'success' && data.total_cost_usd != null) {
|
|
1108
|
+
anthropicTotalCostUSD = data.total_cost_usd;
|
|
1109
|
+
}
|
|
1110
|
+
} catch {
|
|
1111
|
+
if (!stdoutLineBuffer.includes('node:internal')) await log(stdoutLineBuffer, { stream: 'raw' });
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1113
1115
|
// Issue #1165: Check actual exit code from command result for more reliable detection
|
|
1114
1116
|
// The .stream() method may not emit 'exit' chunks, but the command object still tracks the exit code
|
|
1115
1117
|
// Exit code 127 is the standard Unix convention for "command not found"
|