@link-assistant/hive-mind 1.34.7 → 1.35.0
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,21 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.35.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f3de781: Add handlers for agent task lifecycle events (task_started, task_progress, task_notification) and rate_limit_event in interactive mode, reducing PR comment noise by ~30%
|
|
8
|
+
|
|
9
|
+
## 1.34.8
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- c95a472: Add test timeout guidelines to system prompt and case study documentation
|
|
14
|
+
- Added guidelines for setting reasonable test timeouts in CI/CD pipelines
|
|
15
|
+
- Created comprehensive case study in docs/case-studies/issue-1197/
|
|
16
|
+
- Recommendations include: 5-30s for unit tests, 30-60s for E2E tests
|
|
17
|
+
- Guidelines for job-level workflow timeouts and fail-fast patterns
|
|
18
|
+
|
|
3
19
|
## 1.34.7
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -172,6 +172,8 @@ Solution development and testing.
|
|
|
172
172
|
write unit tests with mocks for easy and quick start.
|
|
173
173
|
- When you test integrations, use existing framework.
|
|
174
174
|
- When you test solution draft, include automated checks in pr.
|
|
175
|
+
- When you write or modify tests, consider setting reasonable timeouts at test, suite, and CI job levels so failures surface quickly instead of hanging.
|
|
176
|
+
- When you see repeated test timeout patterns in CI, investigate the root cause rather than increasing timeouts.
|
|
175
177
|
- When issue is unclear, write comment on issue asking questions.
|
|
176
178
|
- When you encounter any problems that you unable to solve yourself, write a comment to the pull request asking for help.
|
|
177
179
|
- When you need human help, use gh pr comment ${prNumber} --body "your message" to comment on existing PR.
|
|
@@ -215,6 +215,8 @@ Solution development and testing.
|
|
|
215
215
|
write unit tests with mocks for easy and quick start.
|
|
216
216
|
- When you test integrations, use existing framework.
|
|
217
217
|
- When you test solution draft, include automated checks in pr.
|
|
218
|
+
- When you write or modify tests, consider setting reasonable timeouts at test, suite, and CI job levels so failures surface quickly instead of hanging.
|
|
219
|
+
- When you see repeated test timeout patterns in CI, investigate the root cause rather than increasing timeouts.
|
|
218
220
|
- When issue is unclear, write comment on issue asking questions.
|
|
219
221
|
- When you encounter any problems that you unable to solve yourself (any human feedback or help), write a comment to the pull request asking for help.
|
|
220
222
|
- When you need human help, use gh pr comment ${prNumber} --body "your message" to comment on existing PR.
|
|
@@ -181,6 +181,8 @@ Solution development and testing.
|
|
|
181
181
|
write unit tests with mocks for easy and quick start.
|
|
182
182
|
- When you test integrations, use existing framework.
|
|
183
183
|
- When you test solution draft, include automated checks in pr.
|
|
184
|
+
- When you write or modify tests, consider setting reasonable timeouts at test, suite, and CI job levels so failures surface quickly instead of hanging.
|
|
185
|
+
- When you see repeated test timeout patterns in CI, investigate the root cause rather than increasing timeouts.
|
|
184
186
|
- When issue is unclear, write comment on issue asking questions.
|
|
185
187
|
- When you encounter any problems that you unable to solve yourself (any human feedback or help), write a comment to the pull request asking for help.
|
|
186
188
|
- When you need human help, use gh pr comment ${prNumber} --body "your message" to comment on existing PR.
|
|
@@ -7,10 +7,14 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Supported JSON event types:
|
|
9
9
|
* - system.init: Session initialization
|
|
10
|
+
* - system.task_started: Agent subtask started (Issue #1450)
|
|
11
|
+
* - system.task_progress: Agent subtask progress update (Issue #1450)
|
|
12
|
+
* - system.task_notification: Agent subtask completed/failed (Issue #1450)
|
|
10
13
|
* - assistant (text): AI text responses
|
|
11
14
|
* - assistant (tool_use): Tool invocations
|
|
12
15
|
* - user (tool_result): Tool execution results
|
|
13
16
|
* - result: Session completion
|
|
17
|
+
* - rate_limit_event: Rate limit info (silently logged, Issue #1450)
|
|
14
18
|
* - unrecognized: Any unknown event types
|
|
15
19
|
*
|
|
16
20
|
* Features:
|
|
@@ -213,6 +217,7 @@ const getToolIcon = toolName => {
|
|
|
213
217
|
WebSearch: '🔍',
|
|
214
218
|
TodoWrite: '📋',
|
|
215
219
|
Task: '🎯',
|
|
220
|
+
Agent: '🤖',
|
|
216
221
|
NotebookEdit: '📓',
|
|
217
222
|
default: '🔧',
|
|
218
223
|
};
|
|
@@ -253,6 +258,9 @@ export const createInteractiveHandler = options => {
|
|
|
253
258
|
// Simple map of tool_use_id -> { toolName, toolIcon } for standalone tool results
|
|
254
259
|
// This is preserved even after pendingToolCalls entry is deleted
|
|
255
260
|
toolUseRegistry: new Map(),
|
|
261
|
+
// Track active agent tasks for progress update deduplication
|
|
262
|
+
// Map of task_id -> { commentId, toolUseId, description, commentIdPromise, resolveCommentId }
|
|
263
|
+
pendingTasks: new Map(),
|
|
256
264
|
};
|
|
257
265
|
|
|
258
266
|
/**
|
|
@@ -809,6 +817,232 @@ ${createRawJsonSection(data)}`;
|
|
|
809
817
|
}
|
|
810
818
|
};
|
|
811
819
|
|
|
820
|
+
/**
|
|
821
|
+
* Handle system.task_started event (Agent subtask started)
|
|
822
|
+
* Creates a progress comment that will be updated by task_progress events
|
|
823
|
+
* @param {Object} data - Event data
|
|
824
|
+
*/
|
|
825
|
+
const handleTaskStarted = async data => {
|
|
826
|
+
const taskId = data.task_id;
|
|
827
|
+
const toolUseId = data.tool_use_id || '';
|
|
828
|
+
const description = data.description || 'Agent task';
|
|
829
|
+
const taskType = data.task_type || 'unknown';
|
|
830
|
+
|
|
831
|
+
// Create a promise for the comment ID (handles queued comments)
|
|
832
|
+
let resolveCommentId;
|
|
833
|
+
const commentIdPromise = new Promise(resolve => {
|
|
834
|
+
resolveCommentId = resolve;
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
// Build prompt preview if available
|
|
838
|
+
let promptSection = '';
|
|
839
|
+
if (data.prompt) {
|
|
840
|
+
const truncatedPrompt = truncateMiddle(data.prompt, { maxLines: 15, keepStart: 6, keepEnd: 6 });
|
|
841
|
+
promptSection = '\n\n' + createCollapsible('📝 Task prompt', truncatedPrompt);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const comment = `## 🤖 Agent task: ${escapeMarkdown(description)}
|
|
845
|
+
|
|
846
|
+
| Property | Value |
|
|
847
|
+
|----------|-------|
|
|
848
|
+
| **Task ID** | \`${taskId || 'unknown'}\` |
|
|
849
|
+
| **Type** | \`${taskType}\` |
|
|
850
|
+
| **Status** | ⏳ Running... |
|
|
851
|
+
${promptSection}
|
|
852
|
+
|
|
853
|
+
---
|
|
854
|
+
|
|
855
|
+
${createRawJsonSection(data)}`;
|
|
856
|
+
|
|
857
|
+
// Track this task BEFORE posting
|
|
858
|
+
state.pendingTasks.set(taskId, {
|
|
859
|
+
commentId: null,
|
|
860
|
+
commentIdPromise,
|
|
861
|
+
resolveCommentId,
|
|
862
|
+
toolUseId,
|
|
863
|
+
description,
|
|
864
|
+
lastProgressDescription: description,
|
|
865
|
+
progressCount: 0,
|
|
866
|
+
allEvents: [data],
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
const commentId = await postComment(comment, null);
|
|
870
|
+
|
|
871
|
+
if (commentId) {
|
|
872
|
+
const pendingTask = state.pendingTasks.get(taskId);
|
|
873
|
+
if (pendingTask) {
|
|
874
|
+
pendingTask.commentId = commentId;
|
|
875
|
+
resolveCommentId(commentId);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (verbose) {
|
|
880
|
+
await log(`🤖 Interactive mode: Agent task started - ${description} (task: ${taskId})`, { verbose: true });
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Handle system.task_progress event (Agent subtask progress update)
|
|
886
|
+
* Updates the existing task comment instead of creating a new one
|
|
887
|
+
* @param {Object} data - Event data
|
|
888
|
+
*/
|
|
889
|
+
const handleTaskProgress = async data => {
|
|
890
|
+
const taskId = data.task_id;
|
|
891
|
+
const description = data.description || 'Working...';
|
|
892
|
+
const lastToolName = data.last_tool_name || '';
|
|
893
|
+
const usage = data.usage || {};
|
|
894
|
+
|
|
895
|
+
const pendingTask = state.pendingTasks.get(taskId);
|
|
896
|
+
|
|
897
|
+
if (pendingTask) {
|
|
898
|
+
pendingTask.progressCount++;
|
|
899
|
+
pendingTask.lastProgressDescription = description;
|
|
900
|
+
pendingTask.allEvents.push(data);
|
|
901
|
+
|
|
902
|
+
let commentId = pendingTask.commentId;
|
|
903
|
+
|
|
904
|
+
// Wait for comment ID if not yet available
|
|
905
|
+
if (!commentId && pendingTask.commentIdPromise) {
|
|
906
|
+
const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(null), 15000));
|
|
907
|
+
commentId = await Promise.race([pendingTask.commentIdPromise, timeoutPromise]);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
if (commentId) {
|
|
911
|
+
// Build progress steps list from accumulated events
|
|
912
|
+
const progressSteps = pendingTask.allEvents
|
|
913
|
+
.filter(e => e.subtype === 'task_progress')
|
|
914
|
+
.map(e => {
|
|
915
|
+
const toolIcon = e.last_tool_name ? getToolIcon(e.last_tool_name) : '🔄';
|
|
916
|
+
return `- ${toolIcon} ${e.description || 'Working...'}`;
|
|
917
|
+
})
|
|
918
|
+
.join('\n');
|
|
919
|
+
|
|
920
|
+
const durationText = usage.duration_ms ? formatDuration(usage.duration_ms) : '';
|
|
921
|
+
const toolUsesText = usage.tool_uses ? `${usage.tool_uses} tool calls` : '';
|
|
922
|
+
const statsText = [durationText, toolUsesText].filter(Boolean).join(' | ');
|
|
923
|
+
|
|
924
|
+
const updatedComment = `## 🤖 Agent task: ${escapeMarkdown(pendingTask.description)}
|
|
925
|
+
|
|
926
|
+
| Property | Value |
|
|
927
|
+
|----------|-------|
|
|
928
|
+
| **Task ID** | \`${taskId}\` |
|
|
929
|
+
| **Status** | ⏳ Running... |
|
|
930
|
+
| **Progress** | ${pendingTask.progressCount} updates |
|
|
931
|
+
${statsText ? `| **Stats** | ${statsText} |\n` : ''}
|
|
932
|
+
${createCollapsible(`📋 Progress steps (${pendingTask.progressCount})`, progressSteps, true)}
|
|
933
|
+
|
|
934
|
+
---
|
|
935
|
+
|
|
936
|
+
${createRawJsonSection(pendingTask.allEvents.slice(-3))}`;
|
|
937
|
+
|
|
938
|
+
await editComment(commentId, updatedComment);
|
|
939
|
+
}
|
|
940
|
+
} else {
|
|
941
|
+
// No pending task found - this can happen if task_started was missed
|
|
942
|
+
// Just log it silently rather than creating an unrecognized comment
|
|
943
|
+
if (verbose) {
|
|
944
|
+
await log(`🤖 Interactive mode: Task progress for unknown task ${taskId}: ${description}`, { verbose: true });
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (verbose) {
|
|
949
|
+
await log(`🤖 Interactive mode: Task progress - ${description} (task: ${taskId}, tool: ${lastToolName})`, { verbose: true });
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Handle system.task_notification event (Agent subtask completed/failed)
|
|
955
|
+
* Updates the existing task comment with final status
|
|
956
|
+
* @param {Object} data - Event data
|
|
957
|
+
*/
|
|
958
|
+
const handleTaskNotification = async data => {
|
|
959
|
+
const taskId = data.task_id;
|
|
960
|
+
const status = data.status || 'unknown';
|
|
961
|
+
const summary = data.summary || data.description || 'Task finished';
|
|
962
|
+
const usage = data.usage || {};
|
|
963
|
+
const isCompleted = status === 'completed';
|
|
964
|
+
const statusIcon = isCompleted ? '✅' : '❌';
|
|
965
|
+
const statusText = isCompleted ? 'Completed' : status.charAt(0).toUpperCase() + status.slice(1);
|
|
966
|
+
|
|
967
|
+
const pendingTask = state.pendingTasks.get(taskId);
|
|
968
|
+
|
|
969
|
+
if (pendingTask) {
|
|
970
|
+
pendingTask.allEvents.push(data);
|
|
971
|
+
|
|
972
|
+
let commentId = pendingTask.commentId;
|
|
973
|
+
|
|
974
|
+
// Wait for comment ID if not yet available
|
|
975
|
+
if (!commentId && pendingTask.commentIdPromise) {
|
|
976
|
+
const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(null), 15000));
|
|
977
|
+
commentId = await Promise.race([pendingTask.commentIdPromise, timeoutPromise]);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (commentId) {
|
|
981
|
+
// Build final progress steps list
|
|
982
|
+
const progressSteps = pendingTask.allEvents
|
|
983
|
+
.filter(e => e.subtype === 'task_progress')
|
|
984
|
+
.map(e => {
|
|
985
|
+
const toolIcon = e.last_tool_name ? getToolIcon(e.last_tool_name) : '🔄';
|
|
986
|
+
return `- ${toolIcon} ${e.description || 'Working...'}`;
|
|
987
|
+
})
|
|
988
|
+
.join('\n');
|
|
989
|
+
|
|
990
|
+
const durationText = usage.duration_ms ? formatDuration(usage.duration_ms) : '';
|
|
991
|
+
const toolUsesText = usage.tool_uses ? `${usage.tool_uses} tool calls` : '';
|
|
992
|
+
const tokensText = usage.total_tokens ? `${usage.total_tokens.toLocaleString()} tokens` : '';
|
|
993
|
+
const statsText = [durationText, toolUsesText, tokensText].filter(Boolean).join(' | ');
|
|
994
|
+
|
|
995
|
+
const updatedComment = `## 🤖 Agent task: ${escapeMarkdown(pendingTask.description)}
|
|
996
|
+
|
|
997
|
+
| Property | Value |
|
|
998
|
+
|----------|-------|
|
|
999
|
+
| **Task ID** | \`${taskId}\` |
|
|
1000
|
+
| **Status** | ${statusIcon} ${statusText} |
|
|
1001
|
+
| **Summary** | ${escapeMarkdown(summary)} |
|
|
1002
|
+
${statsText ? `| **Stats** | ${statsText} |\n` : ''}
|
|
1003
|
+
${progressSteps ? createCollapsible(`📋 Progress steps (${pendingTask.progressCount})`, progressSteps) : ''}
|
|
1004
|
+
|
|
1005
|
+
---
|
|
1006
|
+
|
|
1007
|
+
${createRawJsonSection([pendingTask.allEvents[0], data])}`;
|
|
1008
|
+
|
|
1009
|
+
await editComment(commentId, updatedComment);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// Clean up
|
|
1013
|
+
state.pendingTasks.delete(taskId);
|
|
1014
|
+
} else {
|
|
1015
|
+
// Post as standalone if no pending task
|
|
1016
|
+
const comment = `## 🤖 Agent task ${statusIcon} ${statusText}
|
|
1017
|
+
|
|
1018
|
+
**Summary:** ${escapeMarkdown(summary)}
|
|
1019
|
+
|
|
1020
|
+
---
|
|
1021
|
+
|
|
1022
|
+
${createRawJsonSection(data)}`;
|
|
1023
|
+
|
|
1024
|
+
await postComment(comment);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
if (verbose) {
|
|
1028
|
+
await log(`🤖 Interactive mode: Task ${statusText.toLowerCase()} - ${summary} (task: ${taskId})`, {
|
|
1029
|
+
verbose: true,
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Handle rate_limit_event (silently logged, no comment created)
|
|
1036
|
+
* @param {Object} data - Event data
|
|
1037
|
+
*/
|
|
1038
|
+
const handleRateLimitEvent = async data => {
|
|
1039
|
+
// Rate limit events are internal/informational - log but don't create a PR comment
|
|
1040
|
+
if (verbose) {
|
|
1041
|
+
const info = data.rate_limit_info || {};
|
|
1042
|
+
await log(`⏱️ Interactive mode: Rate limit event - status: ${info.status || 'unknown'}, type: ${info.rateLimitType || 'unknown'}`, { verbose: true });
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
|
|
812
1046
|
/**
|
|
813
1047
|
* Handle unrecognized event types
|
|
814
1048
|
* @param {Object} data - Event data
|
|
@@ -851,12 +1085,22 @@ ${createRawJsonSection(data)}`;
|
|
|
851
1085
|
case 'system':
|
|
852
1086
|
if (data.subtype === 'init') {
|
|
853
1087
|
await handleSystemInit(data);
|
|
1088
|
+
} else if (data.subtype === 'task_started') {
|
|
1089
|
+
await handleTaskStarted(data);
|
|
1090
|
+
} else if (data.subtype === 'task_progress') {
|
|
1091
|
+
await handleTaskProgress(data);
|
|
1092
|
+
} else if (data.subtype === 'task_notification') {
|
|
1093
|
+
await handleTaskNotification(data);
|
|
854
1094
|
} else {
|
|
855
1095
|
// Unknown system subtype
|
|
856
1096
|
await handleUnrecognized(data);
|
|
857
1097
|
}
|
|
858
1098
|
break;
|
|
859
1099
|
|
|
1100
|
+
case 'rate_limit_event':
|
|
1101
|
+
await handleRateLimitEvent(data);
|
|
1102
|
+
break;
|
|
1103
|
+
|
|
860
1104
|
case 'assistant':
|
|
861
1105
|
if (data.message && data.message.content) {
|
|
862
1106
|
const content = Array.isArray(data.message.content) ? data.message.content : [data.message.content];
|
|
@@ -923,6 +1167,10 @@ ${createRawJsonSection(data)}`;
|
|
|
923
1167
|
handleToolUse,
|
|
924
1168
|
handleToolResult,
|
|
925
1169
|
handleResult,
|
|
1170
|
+
handleTaskStarted,
|
|
1171
|
+
handleTaskProgress,
|
|
1172
|
+
handleTaskNotification,
|
|
1173
|
+
handleRateLimitEvent,
|
|
926
1174
|
handleUnrecognized,
|
|
927
1175
|
},
|
|
928
1176
|
};
|
|
@@ -175,6 +175,8 @@ Solution development and testing.
|
|
|
175
175
|
write unit tests with mocks for easy and quick start.
|
|
176
176
|
- When you test integrations, use existing framework.
|
|
177
177
|
- When you test solution draft, include automated checks in pr.
|
|
178
|
+
- When you write or modify tests, consider setting reasonable timeouts at test, suite, and CI job levels so failures surface quickly instead of hanging.
|
|
179
|
+
- When you see repeated test timeout patterns in CI, investigate the root cause rather than increasing timeouts.
|
|
178
180
|
- When issue is unclear, write comment on issue asking questions.
|
|
179
181
|
- When you encounter any problems that you unable to solve yourself, write a comment to the pull request asking for help.
|
|
180
182
|
- When you need human help, use gh pr comment ${prNumber} --body "your message" to comment on existing PR.
|