@link-assistant/hive-mind 1.35.5 → 1.35.6
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 +6 -0
- package/package.json +1 -1
- package/src/interactive-mode.lib.mjs +83 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.35.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4b0beaf: Fix interactive mode PR comment output: use stdin for GitHub API calls to prevent shell quoting corruption, flush comment queue before tool result timeout to prevent stuck "Waiting for result..." comments, and guard against duplicate session started comments from late system.init events
|
|
8
|
+
|
|
3
9
|
## 1.35.5
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -52,6 +52,13 @@ const CONFIG = {
|
|
|
52
52
|
// See: https://github.com/link-assistant/hive-mind/issues/1324
|
|
53
53
|
import { sanitizeUnicode } from './unicode-sanitization.lib.mjs';
|
|
54
54
|
|
|
55
|
+
// Use child_process for stdin-based API calls to avoid shell quoting issues
|
|
56
|
+
// with large/complex comment bodies containing backticks, quotes, etc.
|
|
57
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
58
|
+
import { execFile } from 'node:child_process';
|
|
59
|
+
import { promisify } from 'node:util';
|
|
60
|
+
const execFileAsync = promisify(execFile);
|
|
61
|
+
|
|
55
62
|
/**
|
|
56
63
|
* Truncate content in the middle, keeping start and end
|
|
57
64
|
* This helps show context while reducing size for large outputs
|
|
@@ -237,7 +244,9 @@ const getToolIcon = toolName => {
|
|
|
237
244
|
* @returns {Object} Handler object with event processing methods
|
|
238
245
|
*/
|
|
239
246
|
export const createInteractiveHandler = options => {
|
|
240
|
-
const { owner, repo, prNumber,
|
|
247
|
+
const { owner, repo, prNumber, log, verbose = false, execFile: execFileFn } = options;
|
|
248
|
+
// Use injected execFile for testability, or the real one by default
|
|
249
|
+
const runGhApi = execFileFn || execFileAsync;
|
|
241
250
|
|
|
242
251
|
// State tracking for the handler
|
|
243
252
|
const state = {
|
|
@@ -291,24 +300,35 @@ export const createInteractiveHandler = options => {
|
|
|
291
300
|
}
|
|
292
301
|
|
|
293
302
|
try {
|
|
294
|
-
// Post comment
|
|
295
|
-
|
|
303
|
+
// Post comment via gh api with stdin to avoid shell quoting issues
|
|
304
|
+
// with complex markdown bodies containing backticks, quotes, etc.
|
|
305
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
306
|
+
const apiUrl = `repos/${owner}/${repo}/issues/${prNumber}/comments`;
|
|
307
|
+
const jsonPayload = JSON.stringify({ body });
|
|
308
|
+
const { stdout } = await runGhApi('gh', ['api', apiUrl, '-X', 'POST', '--input', '-'], {
|
|
309
|
+
input: jsonPayload,
|
|
310
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
311
|
+
});
|
|
296
312
|
state.lastCommentTime = Date.now();
|
|
297
313
|
|
|
298
|
-
// Extract comment ID from the
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
314
|
+
// Extract comment ID from the API response JSON
|
|
315
|
+
let commentId = null;
|
|
316
|
+
try {
|
|
317
|
+
const response = JSON.parse(stdout);
|
|
318
|
+
commentId = response.id ? String(response.id) : null;
|
|
319
|
+
} catch {
|
|
320
|
+
// Fallback: try to extract from URL pattern
|
|
321
|
+
const match = stdout.match(/issuecomment-(\d+)|"id":\s*(\d+)/);
|
|
322
|
+
commentId = match ? match[1] || match[2] : null;
|
|
323
|
+
}
|
|
304
324
|
|
|
305
325
|
if (verbose) {
|
|
306
|
-
await log(`✅ Interactive mode: Comment posted${commentId ? ` (ID: ${commentId})` : ''}`, { verbose: true });
|
|
326
|
+
await log(`✅ Interactive mode: Comment posted${commentId ? ` (ID: ${commentId})` : ''} (body: ${body.length} chars)`, { verbose: true });
|
|
307
327
|
}
|
|
308
328
|
return commentId;
|
|
309
329
|
} catch (error) {
|
|
310
330
|
if (verbose) {
|
|
311
|
-
await log(`⚠️ Interactive mode: Failed to post comment: ${error.message}`, { verbose: true });
|
|
331
|
+
await log(`⚠️ Interactive mode: Failed to post comment: ${error.message} (body: ${body.length} chars)`, { verbose: true });
|
|
312
332
|
}
|
|
313
333
|
return null;
|
|
314
334
|
}
|
|
@@ -330,14 +350,22 @@ export const createInteractiveHandler = options => {
|
|
|
330
350
|
}
|
|
331
351
|
|
|
332
352
|
try {
|
|
333
|
-
|
|
353
|
+
// Edit comment via gh api with stdin to avoid shell quoting issues
|
|
354
|
+
// with complex markdown bodies containing backticks, quotes, etc.
|
|
355
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
356
|
+
const apiUrl = `repos/${owner}/${repo}/issues/comments/${commentId}`;
|
|
357
|
+
const jsonPayload = JSON.stringify({ body });
|
|
358
|
+
await runGhApi('gh', ['api', apiUrl, '-X', 'PATCH', '--input', '-'], {
|
|
359
|
+
input: jsonPayload,
|
|
360
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
361
|
+
});
|
|
334
362
|
if (verbose) {
|
|
335
|
-
await log(`✅ Interactive mode: Comment ${commentId} updated`, { verbose: true });
|
|
363
|
+
await log(`✅ Interactive mode: Comment ${commentId} updated (body: ${body.length} chars, payload: ${jsonPayload.length} chars)`, { verbose: true });
|
|
336
364
|
}
|
|
337
365
|
return true;
|
|
338
366
|
} catch (error) {
|
|
339
367
|
if (verbose) {
|
|
340
|
-
await log(`⚠️ Interactive mode: Failed to edit comment: ${error.message}`, { verbose: true });
|
|
368
|
+
await log(`⚠️ Interactive mode: Failed to edit comment ${commentId}: ${error.message} (body: ${body.length} chars)`, { verbose: true });
|
|
341
369
|
}
|
|
342
370
|
return false;
|
|
343
371
|
}
|
|
@@ -398,6 +426,16 @@ export const createInteractiveHandler = options => {
|
|
|
398
426
|
* @param {Object} data - Event data
|
|
399
427
|
*/
|
|
400
428
|
const handleSystemInit = async data => {
|
|
429
|
+
// Guard against duplicate init events (e.g., when a late task_notification
|
|
430
|
+
// arrives after the result event and triggers a new conversation turn)
|
|
431
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
432
|
+
if (state.sessionId) {
|
|
433
|
+
if (verbose) {
|
|
434
|
+
await log(`⚠️ Interactive mode: Ignoring duplicate system.init event (session already initialized: ${state.sessionId})`, { verbose: true });
|
|
435
|
+
}
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
401
439
|
state.sessionId = data.session_id;
|
|
402
440
|
state.startTime = Date.now();
|
|
403
441
|
|
|
@@ -672,21 +710,44 @@ ${createRawJsonSection(data)}`;
|
|
|
672
710
|
// If comment ID is not yet available (comment was queued), wait for it
|
|
673
711
|
// But use a timeout to avoid blocking forever
|
|
674
712
|
if (!commentId && commentIdPromise) {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
713
|
+
// First, try to flush the queue — the tool_use comment may still be
|
|
714
|
+
// waiting for rate-limit clearance. Processing it here avoids the 30s
|
|
715
|
+
// timeout that previously caused many comments to stay stuck on
|
|
716
|
+
// "Waiting for result...".
|
|
717
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
718
|
+
if (state.commentQueue.length > 0) {
|
|
719
|
+
if (verbose) {
|
|
720
|
+
await log(`🔄 Interactive mode: Flushing comment queue (${state.commentQueue.length} items) before waiting for tool use comment`, {
|
|
721
|
+
verbose: true,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
// Temporarily reset isProcessing to allow processQueue to run
|
|
725
|
+
const wasProcessing = state.isProcessing;
|
|
726
|
+
state.isProcessing = false;
|
|
727
|
+
await processQueue();
|
|
728
|
+
state.isProcessing = wasProcessing;
|
|
679
729
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
commentId =
|
|
730
|
+
|
|
731
|
+
// Check again after queue flush
|
|
732
|
+
commentId = pendingCall.commentId;
|
|
683
733
|
|
|
684
734
|
if (!commentId) {
|
|
685
735
|
if (verbose) {
|
|
686
|
-
await log(
|
|
736
|
+
await log(`⏳ Interactive mode: Waiting for tool use comment to be posted (tool: ${toolUseId})`, {
|
|
687
737
|
verbose: true,
|
|
688
738
|
});
|
|
689
739
|
}
|
|
740
|
+
// Wait for the comment to be posted (with 30 second timeout)
|
|
741
|
+
const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(null), 30000));
|
|
742
|
+
commentId = await Promise.race([commentIdPromise, timeoutPromise]);
|
|
743
|
+
|
|
744
|
+
if (!commentId) {
|
|
745
|
+
if (verbose) {
|
|
746
|
+
await log('⚠️ Interactive mode: Timeout waiting for tool use comment, posting result separately', {
|
|
747
|
+
verbose: true,
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
}
|
|
690
751
|
}
|
|
691
752
|
}
|
|
692
753
|
|