@link-assistant/hive-mind 1.49.3 → 1.50.1
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 +26 -0
- package/package.json +1 -1
- package/src/agent.lib.mjs +2 -2
- package/src/claude.lib.mjs +2 -2
- package/src/codex.lib.mjs +2 -2
- package/src/interactive-mode.lib.mjs +103 -35
- package/src/lib.mjs +6 -1
- package/src/opencode.lib.mjs +2 -2
- package/src/solve.auto-ensure.lib.mjs +8 -0
- package/src/solve.auto-merge.lib.mjs +10 -0
- package/src/solve.mjs +4 -4
- package/src/solve.repository.lib.mjs +3 -3
- package/src/solve.results.lib.mjs +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.50.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 494989e: Add paths filter to CI/CD workflow trigger to skip unnecessary runs for non-code file changes (#1582)
|
|
8
|
+
- c4fadea: fix: prevent push failures in auto-restart and cleanup by syncing with remote (#1572)
|
|
9
|
+
- Add `git pull` before restart sessions and cleanup push to prevent stale local state
|
|
10
|
+
- Add `2>&1` to all `git push` commands so stderr is captured for proper error handling
|
|
11
|
+
- Fix multi-line log message formatting to include timestamps on each line
|
|
12
|
+
|
|
13
|
+
## 1.50.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- 4aed1c1: fix: interactive mode GitHub comments display improvements (#1576)
|
|
18
|
+
- Fix agent task comments stuck at "⏳ Running..." by propagating taskId through comment queue
|
|
19
|
+
- Fix misleading token counts by preferring modelUsage (cumulative per-model) over usage (last-iteration)
|
|
20
|
+
- Change truncation format from "[N lines truncated]" to "[X-Y lines are omitted]" showing actual line range
|
|
21
|
+
- Rename "Session Complete" to "Interactive session completed"
|
|
22
|
+
- Rename Write tool "Content" to "Change", expand by default, add line numbers to diffs
|
|
23
|
+
- Show checked/total count in TodoWrite: "Todos (2/9 items)" instead of "Todos (9 items)"
|
|
24
|
+
- Make Task prompt and Edit Change sections expanded by default
|
|
25
|
+
- Add ToolSearch-specific display with Query/Max Results fields
|
|
26
|
+
- Mark sub-agent tasks with 🤖🔀 emoji and Agent ID field
|
|
27
|
+
- Add queue flushing before waiting for comment IDs in task progress/notification handlers
|
|
28
|
+
|
|
3
29
|
## 1.49.3
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
package/package.json
CHANGED
package/src/agent.lib.mjs
CHANGED
|
@@ -1082,12 +1082,12 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
|
|
|
1082
1082
|
if (commitResult.code === 0) {
|
|
1083
1083
|
await log('✅ Changes committed successfully');
|
|
1084
1084
|
|
|
1085
|
-
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
|
|
1085
|
+
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
1086
1086
|
|
|
1087
1087
|
if (pushResult.code === 0) {
|
|
1088
1088
|
await log('✅ Changes pushed successfully');
|
|
1089
1089
|
} else {
|
|
1090
|
-
await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim()}`, {
|
|
1090
|
+
await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim() || pushResult.stdout?.toString().trim()}`, {
|
|
1091
1091
|
level: 'warning',
|
|
1092
1092
|
});
|
|
1093
1093
|
}
|
package/src/claude.lib.mjs
CHANGED
|
@@ -1449,11 +1449,11 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
|
|
|
1449
1449
|
if (commitResult.code === 0) {
|
|
1450
1450
|
await log('✅ Changes committed successfully');
|
|
1451
1451
|
await log('📤 Pushing changes to remote...');
|
|
1452
|
-
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
|
|
1452
|
+
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
1453
1453
|
if (pushResult.code === 0) {
|
|
1454
1454
|
await log('✅ Changes pushed successfully');
|
|
1455
1455
|
} else {
|
|
1456
|
-
await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim()}`, {
|
|
1456
|
+
await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim() || pushResult.stdout?.toString().trim()}`, {
|
|
1457
1457
|
level: 'warning',
|
|
1458
1458
|
});
|
|
1459
1459
|
}
|
package/src/codex.lib.mjs
CHANGED
|
@@ -492,12 +492,12 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
|
|
|
492
492
|
if (commitResult.code === 0) {
|
|
493
493
|
await log('✅ Changes committed successfully');
|
|
494
494
|
|
|
495
|
-
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
|
|
495
|
+
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
496
496
|
|
|
497
497
|
if (pushResult.code === 0) {
|
|
498
498
|
await log('✅ Changes pushed successfully');
|
|
499
499
|
} else {
|
|
500
|
-
await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim()}`, {
|
|
500
|
+
await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim() || pushResult.stdout?.toString().trim()}`, {
|
|
501
501
|
level: 'warning',
|
|
502
502
|
});
|
|
503
503
|
}
|
|
@@ -143,9 +143,11 @@ const truncateMiddle = (content, options = {}) => {
|
|
|
143
143
|
|
|
144
144
|
const startLines = lines.slice(0, keepStart);
|
|
145
145
|
const endLines = lines.slice(-keepEnd);
|
|
146
|
-
|
|
146
|
+
// Show the actual line number range that was omitted (1-based)
|
|
147
|
+
const omitStart = keepStart + 1;
|
|
148
|
+
const omitEnd = lines.length - keepEnd;
|
|
147
149
|
|
|
148
|
-
return sanitizeUnicode([...startLines, '', `... [${
|
|
150
|
+
return sanitizeUnicode([...startLines, '', `... [${omitStart}-${omitEnd} lines are omitted] ...`, '', ...endLines].join('\n'));
|
|
149
151
|
};
|
|
150
152
|
|
|
151
153
|
/**
|
|
@@ -278,6 +280,7 @@ const getToolIcon = toolName => {
|
|
|
278
280
|
WebFetch: '🌐',
|
|
279
281
|
WebSearch: '🔍',
|
|
280
282
|
TodoWrite: '📋',
|
|
283
|
+
ToolSearch: '🔍',
|
|
281
284
|
Task: '🎯',
|
|
282
285
|
Agent: '🤖',
|
|
283
286
|
NotebookEdit: '📓',
|
|
@@ -339,10 +342,11 @@ export const createInteractiveHandler = options => {
|
|
|
339
342
|
* Post a comment to the PR (with rate limiting)
|
|
340
343
|
* @param {string} body - Comment body
|
|
341
344
|
* @param {string} [toolId] - Optional tool ID for tracking pending tool calls
|
|
345
|
+
* @param {string} [taskId] - Optional task ID for tracking pending agent tasks
|
|
342
346
|
* @returns {Promise<string|null>} Comment ID if successful, null if queued or failed
|
|
343
347
|
* @private
|
|
344
348
|
*/
|
|
345
|
-
const postComment = async (body, toolId = null) => {
|
|
349
|
+
const postComment = async (body, toolId = null, taskId = null) => {
|
|
346
350
|
if (!prNumber || !owner || !repo) {
|
|
347
351
|
if (verbose) {
|
|
348
352
|
await log('⚠️ Interactive mode: Cannot post comment - missing PR info', { verbose: true });
|
|
@@ -354,10 +358,10 @@ export const createInteractiveHandler = options => {
|
|
|
354
358
|
const timeSinceLastComment = now - state.lastCommentTime;
|
|
355
359
|
|
|
356
360
|
if (timeSinceLastComment < CONFIG.MIN_COMMENT_INTERVAL) {
|
|
357
|
-
// Queue the comment for later with toolId for tracking
|
|
358
|
-
state.commentQueue.push({ body, toolId });
|
|
361
|
+
// Queue the comment for later with toolId/taskId for tracking
|
|
362
|
+
state.commentQueue.push({ body, toolId, taskId });
|
|
359
363
|
if (verbose) {
|
|
360
|
-
await log(`📝 Interactive mode: Comment queued (${state.commentQueue.length} in queue)${toolId ? ` [tool: ${toolId}]` : ''}`, { verbose: true });
|
|
364
|
+
await log(`📝 Interactive mode: Comment queued (${state.commentQueue.length} in queue)${toolId ? ` [tool: ${toolId}]` : ''}${taskId ? ` [task: ${taskId}]` : ''}`, { verbose: true });
|
|
361
365
|
}
|
|
362
366
|
return null;
|
|
363
367
|
}
|
|
@@ -461,8 +465,8 @@ export const createInteractiveHandler = options => {
|
|
|
461
465
|
|
|
462
466
|
const queueItem = state.commentQueue.shift();
|
|
463
467
|
if (queueItem) {
|
|
464
|
-
const { body, toolId } = queueItem;
|
|
465
|
-
// Post the comment (don't pass toolId to avoid re-queueing)
|
|
468
|
+
const { body, toolId, taskId } = queueItem;
|
|
469
|
+
// Post the comment (don't pass toolId/taskId to avoid re-queueing)
|
|
466
470
|
const commentId = await postComment(body);
|
|
467
471
|
|
|
468
472
|
// If this was a tool use comment, update the pending call with the comment ID
|
|
@@ -481,6 +485,25 @@ export const createInteractiveHandler = options => {
|
|
|
481
485
|
}
|
|
482
486
|
}
|
|
483
487
|
}
|
|
488
|
+
|
|
489
|
+
// If this was a task comment, update the pending task with the comment ID
|
|
490
|
+
// Fix: task comments previously lost their commentId when queued, causing
|
|
491
|
+
// task_notification edits to fail and leaving tasks stuck at "⏳ Running..."
|
|
492
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1576
|
|
493
|
+
if (taskId && commentId) {
|
|
494
|
+
const pendingTask = state.pendingTasks.get(taskId);
|
|
495
|
+
if (pendingTask) {
|
|
496
|
+
pendingTask.commentId = commentId;
|
|
497
|
+
if (pendingTask.resolveCommentId) {
|
|
498
|
+
pendingTask.resolveCommentId(commentId);
|
|
499
|
+
}
|
|
500
|
+
if (verbose) {
|
|
501
|
+
await log(`📋 Interactive mode: Updated pending task ${taskId} with comment ID ${commentId}`, {
|
|
502
|
+
verbose: true,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
484
507
|
}
|
|
485
508
|
}
|
|
486
509
|
|
|
@@ -613,26 +636,26 @@ ${createRawJsonSection(data)}`;
|
|
|
613
636
|
keepStart: 12,
|
|
614
637
|
keepEnd: 12,
|
|
615
638
|
});
|
|
616
|
-
// Format content as diff with + prefix for added lines
|
|
639
|
+
// Format content as diff with + prefix and line numbers for added lines
|
|
617
640
|
const diffContent = truncatedContent
|
|
618
641
|
.split('\n')
|
|
619
|
-
.map(line =>
|
|
642
|
+
.map((line, i) => `+${String(i + 1).padStart(4)} | ${line}`)
|
|
620
643
|
.join('\n');
|
|
621
|
-
inputDisplay += '\n\n' + createCollapsible('📄
|
|
644
|
+
inputDisplay += '\n\n' + createCollapsible('📄 Change', '```diff\n' + escapeMarkdown(diffContent) + '\n```', true);
|
|
622
645
|
}
|
|
623
646
|
} else if (toolName === 'Edit' && input.file_path) {
|
|
624
647
|
inputDisplay = `**File:** \`${input.file_path}\``;
|
|
625
648
|
if (input.old_string && input.new_string) {
|
|
626
649
|
const truncatedOld = truncateMiddle(input.old_string, { maxLines: 15, keepStart: 6, keepEnd: 6 });
|
|
627
650
|
const truncatedNew = truncateMiddle(input.new_string, { maxLines: 15, keepStart: 6, keepEnd: 6 });
|
|
628
|
-
// Format as unified diff with - for removed lines and + for added lines
|
|
651
|
+
// Format as unified diff with - for removed lines and + for added lines, with line numbers
|
|
629
652
|
const diffOld = truncatedOld
|
|
630
653
|
.split('\n')
|
|
631
|
-
.map(line =>
|
|
654
|
+
.map((line, i) => `-${String(i + 1).padStart(4)} | ${line}`)
|
|
632
655
|
.join('\n');
|
|
633
656
|
const diffNew = truncatedNew
|
|
634
657
|
.split('\n')
|
|
635
|
-
.map(line =>
|
|
658
|
+
.map((line, i) => `+${String(i + 1).padStart(4)} | ${line}`)
|
|
636
659
|
.join('\n');
|
|
637
660
|
inputDisplay += '\n\n' + createCollapsible('🔄 Change', '```diff\n' + escapeMarkdown(diffOld + '\n' + diffNew) + '\n```', true);
|
|
638
661
|
}
|
|
@@ -665,13 +688,17 @@ ${createRawJsonSection(data)}`;
|
|
|
665
688
|
todosPreview = [...startTodos, `- _...and ${skipped} more_`, ...endTodos].join('\n');
|
|
666
689
|
}
|
|
667
690
|
|
|
668
|
-
|
|
691
|
+
const completedCount = todos.filter(t => t.status === 'completed').length;
|
|
692
|
+
inputDisplay = createCollapsible(`📋 Todos (${completedCount}/${todos.length} items)`, todosPreview, true);
|
|
669
693
|
} else if (toolName === 'Task') {
|
|
670
694
|
inputDisplay = `**Description:** ${input.description || 'N/A'}`;
|
|
671
695
|
if (input.prompt) {
|
|
672
696
|
const truncatedPrompt = truncateMiddle(input.prompt, { maxLines: 20, keepStart: 8, keepEnd: 8 });
|
|
673
|
-
inputDisplay += '\n\n' + createCollapsible('📝 Prompt', truncatedPrompt);
|
|
697
|
+
inputDisplay += '\n\n' + createCollapsible('📝 Prompt', truncatedPrompt, true);
|
|
674
698
|
}
|
|
699
|
+
} else if (toolName === 'ToolSearch') {
|
|
700
|
+
inputDisplay = `**Query:** \`${input.query || 'N/A'}\``;
|
|
701
|
+
if (input.max_results) inputDisplay += `\n**Max Results:** ${input.max_results}`;
|
|
675
702
|
} else {
|
|
676
703
|
// Generic input display
|
|
677
704
|
const inputJson = truncateMiddle(safeJsonStringify(input, 2), {
|
|
@@ -885,7 +912,7 @@ ${createRawJsonSection(data)}`;
|
|
|
885
912
|
const handleResult = async data => {
|
|
886
913
|
const isError = data.is_error || false;
|
|
887
914
|
const statusIcon = isError ? '❌' : '✅';
|
|
888
|
-
const statusText = isError ? '
|
|
915
|
+
const statusText = isError ? 'Interactive session failed' : 'Interactive session completed';
|
|
889
916
|
|
|
890
917
|
// Format result text
|
|
891
918
|
const resultText = data.result || '_No result message_';
|
|
@@ -913,9 +940,22 @@ ${createRawJsonSection(data)}`;
|
|
|
913
940
|
statsTable += `| **Cost** | ${formatCost(data.total_cost_usd)} |\n`;
|
|
914
941
|
}
|
|
915
942
|
|
|
916
|
-
// Usage breakdown
|
|
943
|
+
// Usage breakdown — prefer modelUsage (cumulative per-model totals including sub-agents)
|
|
944
|
+
// over usage (which only contains last-iteration tokens and is misleading).
|
|
945
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1576
|
|
917
946
|
let usageSection = '';
|
|
918
|
-
if (data.
|
|
947
|
+
if (data.modelUsage && Object.keys(data.modelUsage).length > 0) {
|
|
948
|
+
usageSection = '\n### 📊 Token Usage (by model)\n\n';
|
|
949
|
+
for (const [model, mu] of Object.entries(data.modelUsage)) {
|
|
950
|
+
usageSection += `**${model}:**\n\n| Type | Count |\n|------|-------|\n`;
|
|
951
|
+
if (mu.inputTokens) usageSection += `| Input | ${mu.inputTokens.toLocaleString()} |\n`;
|
|
952
|
+
if (mu.outputTokens) usageSection += `| Output | ${mu.outputTokens.toLocaleString()} |\n`;
|
|
953
|
+
if (mu.cacheCreationInputTokens) usageSection += `| Cache Creation | ${mu.cacheCreationInputTokens.toLocaleString()} |\n`;
|
|
954
|
+
if (mu.cacheReadInputTokens) usageSection += `| Cache Read | ${mu.cacheReadInputTokens.toLocaleString()} |\n`;
|
|
955
|
+
if (typeof mu.costUSD === 'number') usageSection += `| Cost | ${formatCost(mu.costUSD)} |\n`;
|
|
956
|
+
usageSection += '\n';
|
|
957
|
+
}
|
|
958
|
+
} else if (data.usage) {
|
|
919
959
|
const u = data.usage;
|
|
920
960
|
usageSection = '\n### 📊 Token Usage\n\n| Type | Count |\n|------|-------|\n';
|
|
921
961
|
if (u.input_tokens) usageSection += `| Input | ${u.input_tokens.toLocaleString()} |\n`;
|
|
@@ -954,6 +994,7 @@ ${createRawJsonSection(data)}`;
|
|
|
954
994
|
const toolUseId = data.tool_use_id || '';
|
|
955
995
|
const description = data.description || 'Agent task';
|
|
956
996
|
const taskType = data.task_type || 'unknown';
|
|
997
|
+
const agentId = data.agent_id || taskId;
|
|
957
998
|
|
|
958
999
|
// Create a promise for the comment ID (handles queued comments)
|
|
959
1000
|
let resolveCommentId;
|
|
@@ -965,13 +1006,14 @@ ${createRawJsonSection(data)}`;
|
|
|
965
1006
|
let promptSection = '';
|
|
966
1007
|
if (data.prompt) {
|
|
967
1008
|
const truncatedPrompt = truncateMiddle(data.prompt, { maxLines: 15, keepStart: 6, keepEnd: 6 });
|
|
968
|
-
promptSection = '\n\n' + createCollapsible('📝 Task prompt', truncatedPrompt);
|
|
1009
|
+
promptSection = '\n\n' + createCollapsible('📝 Task prompt', truncatedPrompt, true);
|
|
969
1010
|
}
|
|
970
1011
|
|
|
971
|
-
const comment = `##
|
|
1012
|
+
const comment = `## 🤖🔀 Agent task: ${escapeMarkdown(description)}
|
|
972
1013
|
|
|
973
1014
|
| Property | Value |
|
|
974
1015
|
|----------|-------|
|
|
1016
|
+
| **Agent ID** | \`${agentId}\` |
|
|
975
1017
|
| **Task ID** | \`${taskId || 'unknown'}\` |
|
|
976
1018
|
| **Type** | \`${taskType}\` |
|
|
977
1019
|
| **Status** | ⏳ Running... |
|
|
@@ -988,12 +1030,13 @@ ${createRawJsonSection(data)}`;
|
|
|
988
1030
|
resolveCommentId,
|
|
989
1031
|
toolUseId,
|
|
990
1032
|
description,
|
|
1033
|
+
agentId,
|
|
991
1034
|
lastProgressDescription: description,
|
|
992
1035
|
progressCount: 0,
|
|
993
1036
|
allEvents: [data],
|
|
994
1037
|
});
|
|
995
1038
|
|
|
996
|
-
const commentId = await postComment(comment, null);
|
|
1039
|
+
const commentId = await postComment(comment, null, taskId);
|
|
997
1040
|
|
|
998
1041
|
if (commentId) {
|
|
999
1042
|
const pendingTask = state.pendingTasks.get(taskId);
|
|
@@ -1028,19 +1071,29 @@ ${createRawJsonSection(data)}`;
|
|
|
1028
1071
|
|
|
1029
1072
|
let commentId = pendingTask.commentId;
|
|
1030
1073
|
|
|
1031
|
-
// Wait for comment ID if not yet available
|
|
1074
|
+
// Wait for comment ID if not yet available — flush queue first to avoid timeout
|
|
1032
1075
|
if (!commentId && pendingTask.commentIdPromise) {
|
|
1033
|
-
|
|
1034
|
-
|
|
1076
|
+
if (state.commentQueue.length > 0) {
|
|
1077
|
+
const wasProcessing = state.isProcessing;
|
|
1078
|
+
state.isProcessing = false;
|
|
1079
|
+
await processQueue();
|
|
1080
|
+
state.isProcessing = wasProcessing;
|
|
1081
|
+
}
|
|
1082
|
+
commentId = pendingTask.commentId;
|
|
1083
|
+
if (!commentId) {
|
|
1084
|
+
const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(null), 15000));
|
|
1085
|
+
commentId = await Promise.race([pendingTask.commentIdPromise, timeoutPromise]);
|
|
1086
|
+
}
|
|
1035
1087
|
}
|
|
1036
1088
|
|
|
1037
1089
|
if (commentId) {
|
|
1038
|
-
// Build progress steps list from accumulated events
|
|
1090
|
+
// Build progress steps list from accumulated events, marking with agent ID
|
|
1091
|
+
const agentTag = pendingTask.agentId ? `\`[${pendingTask.agentId}]\`` : '';
|
|
1039
1092
|
const progressSteps = pendingTask.allEvents
|
|
1040
1093
|
.filter(e => e.subtype === 'task_progress')
|
|
1041
1094
|
.map(e => {
|
|
1042
1095
|
const toolIcon = e.last_tool_name ? getToolIcon(e.last_tool_name) : '🔄';
|
|
1043
|
-
return `- ${toolIcon} ${e.description || 'Working...'}`;
|
|
1096
|
+
return `- 🔀 ${agentTag} ${toolIcon} ${e.description || 'Working...'}`;
|
|
1044
1097
|
})
|
|
1045
1098
|
.join('\n');
|
|
1046
1099
|
|
|
@@ -1048,10 +1101,11 @@ ${createRawJsonSection(data)}`;
|
|
|
1048
1101
|
const toolUsesText = usage.tool_uses ? `${usage.tool_uses} tool calls` : '';
|
|
1049
1102
|
const statsText = [durationText, toolUsesText].filter(Boolean).join(' | ');
|
|
1050
1103
|
|
|
1051
|
-
const updatedComment = `##
|
|
1104
|
+
const updatedComment = `## 🤖🔀 Agent task: ${escapeMarkdown(pendingTask.description)}
|
|
1052
1105
|
|
|
1053
1106
|
| Property | Value |
|
|
1054
1107
|
|----------|-------|
|
|
1108
|
+
| **Agent ID** | \`${pendingTask.agentId || taskId}\` |
|
|
1055
1109
|
| **Task ID** | \`${taskId}\` |
|
|
1056
1110
|
| **Status** | ⏳ Running... |
|
|
1057
1111
|
| **Progress** | ${pendingTask.progressCount} updates |
|
|
@@ -1098,19 +1152,29 @@ ${createRawJsonSection(pendingTask.allEvents.slice(-3))}`;
|
|
|
1098
1152
|
|
|
1099
1153
|
let commentId = pendingTask.commentId;
|
|
1100
1154
|
|
|
1101
|
-
// Wait for comment ID if not yet available
|
|
1155
|
+
// Wait for comment ID if not yet available — flush queue first to avoid timeout
|
|
1102
1156
|
if (!commentId && pendingTask.commentIdPromise) {
|
|
1103
|
-
|
|
1104
|
-
|
|
1157
|
+
if (state.commentQueue.length > 0) {
|
|
1158
|
+
const wasProcessing = state.isProcessing;
|
|
1159
|
+
state.isProcessing = false;
|
|
1160
|
+
await processQueue();
|
|
1161
|
+
state.isProcessing = wasProcessing;
|
|
1162
|
+
}
|
|
1163
|
+
commentId = pendingTask.commentId;
|
|
1164
|
+
if (!commentId) {
|
|
1165
|
+
const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(null), 15000));
|
|
1166
|
+
commentId = await Promise.race([pendingTask.commentIdPromise, timeoutPromise]);
|
|
1167
|
+
}
|
|
1105
1168
|
}
|
|
1106
1169
|
|
|
1107
1170
|
if (commentId) {
|
|
1108
|
-
// Build final progress steps list
|
|
1171
|
+
// Build final progress steps list, marking with agent ID
|
|
1172
|
+
const agentTag = pendingTask.agentId ? `\`[${pendingTask.agentId}]\`` : '';
|
|
1109
1173
|
const progressSteps = pendingTask.allEvents
|
|
1110
1174
|
.filter(e => e.subtype === 'task_progress')
|
|
1111
1175
|
.map(e => {
|
|
1112
1176
|
const toolIcon = e.last_tool_name ? getToolIcon(e.last_tool_name) : '🔄';
|
|
1113
|
-
return `- ${toolIcon} ${e.description || 'Working...'}`;
|
|
1177
|
+
return `- 🔀 ${agentTag} ${toolIcon} ${e.description || 'Working...'}`;
|
|
1114
1178
|
})
|
|
1115
1179
|
.join('\n');
|
|
1116
1180
|
|
|
@@ -1119,10 +1183,11 @@ ${createRawJsonSection(pendingTask.allEvents.slice(-3))}`;
|
|
|
1119
1183
|
const tokensText = usage.total_tokens ? `${usage.total_tokens.toLocaleString()} tokens` : '';
|
|
1120
1184
|
const statsText = [durationText, toolUsesText, tokensText].filter(Boolean).join(' | ');
|
|
1121
1185
|
|
|
1122
|
-
const updatedComment = `##
|
|
1186
|
+
const updatedComment = `## 🤖🔀 Agent task: ${escapeMarkdown(pendingTask.description)}
|
|
1123
1187
|
|
|
1124
1188
|
| Property | Value |
|
|
1125
1189
|
|----------|-------|
|
|
1190
|
+
| **Agent ID** | \`${pendingTask.agentId || taskId}\` |
|
|
1126
1191
|
| **Task ID** | \`${taskId}\` |
|
|
1127
1192
|
| **Status** | ${statusIcon} ${statusText} |
|
|
1128
1193
|
| **Summary** | ${escapeMarkdown(summary)} |
|
|
@@ -1140,8 +1205,11 @@ ${createRawJsonSection([pendingTask.allEvents[0], data])}`;
|
|
|
1140
1205
|
state.pendingTasks.delete(taskId);
|
|
1141
1206
|
} else {
|
|
1142
1207
|
// Post as standalone if no pending task
|
|
1143
|
-
const
|
|
1208
|
+
const agentId = data.agent_id || taskId;
|
|
1209
|
+
const comment = `## 🤖🔀 Agent task ${statusIcon} ${statusText}
|
|
1144
1210
|
|
|
1211
|
+
| **Agent ID** | \`${agentId}\` |
|
|
1212
|
+
|---|---|
|
|
1145
1213
|
**Summary:** ${escapeMarkdown(summary)}
|
|
1146
1214
|
|
|
1147
1215
|
---
|
package/src/lib.mjs
CHANGED
|
@@ -83,8 +83,13 @@ export const log = async (message, options = {}) => {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// Write to file if log file is set
|
|
86
|
+
// Issue #1572: Handle multi-line messages by timestamping each line,
|
|
87
|
+
// so continuation lines don't appear without timestamps in the log file
|
|
86
88
|
if (logFile) {
|
|
87
|
-
const
|
|
89
|
+
const timestamp = new Date().toISOString();
|
|
90
|
+
const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
|
|
91
|
+
const lines = String(message).split('\n');
|
|
92
|
+
const logMessage = lines.map(line => `${prefix} ${line}`).join('\n');
|
|
88
93
|
await fs.appendFile(logFile, logMessage + '\n').catch(error => {
|
|
89
94
|
// Silent fail for file append errors to avoid infinite loop
|
|
90
95
|
// but report to Sentry in verbose mode
|
package/src/opencode.lib.mjs
CHANGED
|
@@ -546,12 +546,12 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
|
|
|
546
546
|
if (commitResult.code === 0) {
|
|
547
547
|
await log('✅ Changes committed successfully');
|
|
548
548
|
|
|
549
|
-
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
|
|
549
|
+
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
550
550
|
|
|
551
551
|
if (pushResult.code === 0) {
|
|
552
552
|
await log('✅ Changes pushed successfully');
|
|
553
553
|
} else {
|
|
554
|
-
await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim()}`, {
|
|
554
|
+
await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim() || pushResult.stdout?.toString().trim()}`, {
|
|
555
555
|
level: 'warning',
|
|
556
556
|
});
|
|
557
557
|
}
|
|
@@ -76,6 +76,14 @@ export const runAutoEnsureRequirements = async ({ issueUrl, owner, repo, issueNu
|
|
|
76
76
|
for (let ensureIteration = 1; ensureIteration <= finalizeCount; ensureIteration++) {
|
|
77
77
|
await log(`🔄 FINALIZE iteration ${ensureIteration}/${finalizeCount}: Restarting to verify requirements...`);
|
|
78
78
|
|
|
79
|
+
// Issue #1572: Sync local branch with remote before each finalize iteration
|
|
80
|
+
const pullResult = await $({ cwd: tempDir })`git pull origin ${branchName} 2>&1`;
|
|
81
|
+
if (pullResult.code === 0) {
|
|
82
|
+
await log(` Synced local branch ${branchName} from remote`, { verbose: true });
|
|
83
|
+
} else {
|
|
84
|
+
throw new Error(`git pull failed (code ${pullResult.code}): ${pullResult.stdout || pullResult.stderr || 'no output'}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
79
87
|
const ensureFeedbackLines = ['', '='.repeat(60), '🔍 FINALIZE REQUIREMENTS CHECK:', '='.repeat(60), '', 'We need to ensure all changes are correct, consistent, validated, tested, logged and fully meet all discussed requirements (check issue description and all comments in issue and in pull request). Ensure all CI/CD checks pass.', ''];
|
|
80
88
|
|
|
81
89
|
const ensureResult = await executeToolIteration({
|
|
@@ -976,6 +976,16 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
976
976
|
const prStateResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber} --jq '.mergeStateStatus'`;
|
|
977
977
|
const mergeStateStatus = prStateResult.code === 0 ? prStateResult.stdout.toString().trim() : null;
|
|
978
978
|
|
|
979
|
+
// Issue #1572: Sync local branch with remote before restarting to avoid push failures.
|
|
980
|
+
// Without this, the restarted session works on stale local state and can't push.
|
|
981
|
+
const effectiveBranch = prBranch || branchName;
|
|
982
|
+
const pullResult = await $({ cwd: tempDir })`git pull origin ${effectiveBranch} 2>&1`;
|
|
983
|
+
if (pullResult.code === 0) {
|
|
984
|
+
await log(formatAligned('🔄', 'Synced:', `Local branch ${effectiveBranch} updated from remote`));
|
|
985
|
+
} else {
|
|
986
|
+
throw new Error(`git pull failed (code ${pullResult.code}): ${pullResult.stdout || pullResult.stderr || 'no output'}`);
|
|
987
|
+
}
|
|
988
|
+
|
|
979
989
|
// Execute the AI tool using shared utility
|
|
980
990
|
await log(formatAligned('🔄', 'Restarting:', `Running ${argv.tool.toUpperCase()} to address issues...`));
|
|
981
991
|
|
package/src/solve.mjs
CHANGED
|
@@ -573,12 +573,12 @@ try {
|
|
|
573
573
|
const mergeResult = await $({ cwd: tempDir })`git merge ${defaultBranch} --no-edit`;
|
|
574
574
|
if (mergeResult.code === 0) {
|
|
575
575
|
await log(`${formatAligned('✅', 'Merge successful:', 'Pushing merged branch...')}`);
|
|
576
|
-
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
|
|
576
|
+
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
577
577
|
if (pushResult.code === 0) {
|
|
578
578
|
await log(`${formatAligned('✅', 'Push successful:', 'Branch updated with latest changes')}`);
|
|
579
579
|
} else {
|
|
580
580
|
await log(`${formatAligned('⚠️', 'Push failed:', 'Merge completed but push failed')}`, { level: 'warning' });
|
|
581
|
-
await log(` Error: ${pushResult.stderr?.toString() || 'Unknown error'}`, { level: 'warning' });
|
|
581
|
+
await log(` Error: ${pushResult.stderr?.toString() || pushResult.stdout?.toString() || 'Unknown error'}`, { level: 'warning' });
|
|
582
582
|
}
|
|
583
583
|
} else {
|
|
584
584
|
// Merge failed - likely due to conflicts
|
|
@@ -1343,13 +1343,13 @@ try {
|
|
|
1343
1343
|
await log('');
|
|
1344
1344
|
|
|
1345
1345
|
try {
|
|
1346
|
-
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
|
|
1346
|
+
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
1347
1347
|
if (pushResult.code === 0) {
|
|
1348
1348
|
await log('✅ Changes pushed successfully to remote branch');
|
|
1349
1349
|
await log(` Branch: ${branchName}`);
|
|
1350
1350
|
await log('');
|
|
1351
1351
|
} else {
|
|
1352
|
-
const errorMsg = pushResult.stderr?.toString() || 'Unknown error';
|
|
1352
|
+
const errorMsg = pushResult.stderr?.toString() || pushResult.stdout?.toString() || 'Unknown error';
|
|
1353
1353
|
await log('⚠️ Push failed:', { level: 'error' });
|
|
1354
1354
|
await log(` ${errorMsg.trim()}`, { level: 'error' });
|
|
1355
1355
|
await log(' Please push manually:', { level: 'error' });
|
|
@@ -1113,12 +1113,12 @@ export const setupUpstreamAndSync = async (tempDir, forkedRepo, upstreamRemote,
|
|
|
1113
1113
|
|
|
1114
1114
|
// Step 3: Push the updated default branch to fork to keep it in sync
|
|
1115
1115
|
await log(`${formatAligned('🔄', 'Pushing to fork:', `${upstreamDefaultBranch} branch`)}`);
|
|
1116
|
-
const pushResult = await $({ cwd: tempDir })`git push origin ${upstreamDefaultBranch}`;
|
|
1116
|
+
const pushResult = await $({ cwd: tempDir })`git push origin ${upstreamDefaultBranch} 2>&1`;
|
|
1117
1117
|
if (pushResult.code === 0) {
|
|
1118
1118
|
await log(`${formatAligned('✅', 'Fork updated:', 'Default branch pushed to fork')}`);
|
|
1119
1119
|
} else {
|
|
1120
1120
|
// Check if it's a non-fast-forward error (fork has diverged from upstream)
|
|
1121
|
-
const errorMsg = pushResult.stderr ? pushResult.stderr.toString().trim() : '';
|
|
1121
|
+
const errorMsg = (pushResult.stderr ? pushResult.stderr.toString().trim() : '') || (pushResult.stdout ? pushResult.stdout.toString().trim() : '');
|
|
1122
1122
|
const isNonFastForward = errorMsg.includes('non-fast-forward') || errorMsg.includes('rejected') || errorMsg.includes('tip of your current branch is behind');
|
|
1123
1123
|
|
|
1124
1124
|
if (isNonFastForward) {
|
|
@@ -1147,7 +1147,7 @@ export const setupUpstreamAndSync = async (tempDir, forkedRepo, upstreamRemote,
|
|
|
1147
1147
|
await log(`${formatAligned('🔄', 'Force pushing:', 'Syncing fork with upstream (--force-with-lease)')}`);
|
|
1148
1148
|
const forcePushResult = await $({
|
|
1149
1149
|
cwd: tempDir,
|
|
1150
|
-
})`git push --force-with-lease origin ${upstreamDefaultBranch}`;
|
|
1150
|
+
})`git push --force-with-lease origin ${upstreamDefaultBranch} 2>&1`;
|
|
1151
1151
|
|
|
1152
1152
|
if (forcePushResult.code === 0) {
|
|
1153
1153
|
await log(`${formatAligned('✅', 'Fork synced:', 'Successfully force-pushed to align with upstream')}`);
|
|
@@ -269,6 +269,15 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
269
269
|
await log(formatAligned('🔄', 'Cleanup:', `Reverting ${fileName} commit`));
|
|
270
270
|
await log(` Using saved commit hash: ${claudeCommitHash.substring(0, 7)}...`, { verbose: true });
|
|
271
271
|
|
|
272
|
+
// Issue #1572: Sync local branch with remote before cleanup to prevent push failures.
|
|
273
|
+
// After auto-restart sessions, the local branch may be behind the remote.
|
|
274
|
+
const pullResult = await $({ cwd: tempDir })`git pull origin ${branchName} 2>&1`;
|
|
275
|
+
if (pullResult.code === 0) {
|
|
276
|
+
await log(` Synced local branch before cleanup`, { verbose: true });
|
|
277
|
+
} else {
|
|
278
|
+
throw new Error(`git pull failed (code ${pullResult.code}): ${pullResult.stdout || pullResult.stderr || 'no output'}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
272
281
|
const commitToRevert = claudeCommitHash;
|
|
273
282
|
|
|
274
283
|
// APPROACH 3: Check for modifications before reverting (proactive detection)
|