@link-assistant/hive-mind 1.21.1 → 1.21.3
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 +18 -0
- package/package.json +1 -1
- package/src/agent.lib.mjs +45 -0
- package/src/solve.repository.lib.mjs +125 -41
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.21.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4426112: Fix error detection for `--tool agent` when JSON errors are pretty-printed (Issue #1258)
|
|
8
|
+
- Add fallback pattern matching for error events when NDJSON parsing fails
|
|
9
|
+
- Detect `"type": "error"` and `"type": "step_error"` patterns in raw output
|
|
10
|
+
- Detect critical error patterns like `AI_RetryError` and `UnhandledRejection`
|
|
11
|
+
- Extract error messages from output for better error reporting
|
|
12
|
+
|
|
13
|
+
## 1.21.2
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 586b84d: Add retry mechanism for GitHub 500 errors during repository clone
|
|
18
|
+
|
|
19
|
+
This change adds intelligent retry logic with exponential backoff to handle transient GitHub server errors during repository cloning operations.
|
|
20
|
+
|
|
3
21
|
## 1.21.1
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/package.json
CHANGED
package/src/agent.lib.mjs
CHANGED
|
@@ -703,6 +703,51 @@ export const executeAgentCommand = async params => {
|
|
|
703
703
|
outputError.match = streamingErrorMessage;
|
|
704
704
|
}
|
|
705
705
|
|
|
706
|
+
// Issue #1258: Fallback pattern match for error detection
|
|
707
|
+
// When JSON parsing fails (e.g., multi-line pretty-printed JSON in logs),
|
|
708
|
+
// we need to detect error patterns in the raw output string
|
|
709
|
+
if (!outputError.detected && !streamingErrorDetected) {
|
|
710
|
+
// Check for error type patterns in raw output (handles pretty-printed JSON)
|
|
711
|
+
const errorTypePatterns = [
|
|
712
|
+
{ pattern: '"type": "error"', type: 'AgentError' },
|
|
713
|
+
{ pattern: '"type":"error"', type: 'AgentError' },
|
|
714
|
+
{ pattern: '"type": "step_error"', type: 'AgentStepError' },
|
|
715
|
+
{ pattern: '"type":"step_error"', type: 'AgentStepError' },
|
|
716
|
+
];
|
|
717
|
+
|
|
718
|
+
for (const { pattern, type } of errorTypePatterns) {
|
|
719
|
+
if (fullOutput.includes(pattern)) {
|
|
720
|
+
outputError.detected = true;
|
|
721
|
+
outputError.type = type;
|
|
722
|
+
// Try to extract the error message from the output
|
|
723
|
+
const messageMatch = fullOutput.match(/"message":\s*"([^"]+)"/);
|
|
724
|
+
outputError.match = messageMatch ? messageMatch[1] : `Error event detected in output (fallback pattern match for ${pattern})`;
|
|
725
|
+
await log(`⚠️ Error event detected via fallback pattern match: ${outputError.match}`, { level: 'warning' });
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Also check for known critical error patterns that indicate failure
|
|
731
|
+
if (!outputError.detected) {
|
|
732
|
+
const criticalErrorPatterns = [
|
|
733
|
+
{ pattern: 'AI_RetryError:', extract: /AI_RetryError:\s*(.+?)(?:\n|$)/ },
|
|
734
|
+
{ pattern: 'UnhandledRejection', extract: /"errorType":\s*"UnhandledRejection"/ },
|
|
735
|
+
{ pattern: 'Failed after 3 attempts', extract: /Failed after \d+ attempts[^"]*/ },
|
|
736
|
+
];
|
|
737
|
+
|
|
738
|
+
for (const { pattern, extract } of criticalErrorPatterns) {
|
|
739
|
+
if (fullOutput.includes(pattern)) {
|
|
740
|
+
outputError.detected = true;
|
|
741
|
+
outputError.type = 'CriticalError';
|
|
742
|
+
const match = fullOutput.match(extract);
|
|
743
|
+
outputError.match = match ? match[0] : `Critical error pattern detected: ${pattern}`;
|
|
744
|
+
await log(`⚠️ Critical error pattern detected via fallback: ${outputError.match}`, { level: 'warning' });
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
706
751
|
if (exitCode !== 0 || outputError.detected) {
|
|
707
752
|
// Build JSON error structure for consistent error reporting
|
|
708
753
|
const errorInfo = {
|
|
@@ -884,56 +884,140 @@ Thank you!`;
|
|
|
884
884
|
return { repoToClone, forkedRepo, upstreamRemote, prForkOwner: forkOwner };
|
|
885
885
|
};
|
|
886
886
|
|
|
887
|
-
//
|
|
887
|
+
// Classify git clone errors to determine if they are retryable
|
|
888
|
+
export const classifyCloneError = errorOutput => {
|
|
889
|
+
const output = errorOutput.toLowerCase();
|
|
890
|
+
|
|
891
|
+
// Transient server errors (5xx) - typically retryable
|
|
892
|
+
if (output.includes('error: 500') || output.includes('internal server error') || output.includes('error: 502') || output.includes('error: 503') || output.includes('error: 504')) {
|
|
893
|
+
return { type: 'TRANSIENT', retryable: true, description: 'GitHub server error' };
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Network-related errors - typically retryable
|
|
897
|
+
if (output.includes('connection refused') || output.includes('connection timed out') || output.includes('connection reset') || output.includes('unable to connect') || output.includes('network is unreachable') || output.includes('ssl error')) {
|
|
898
|
+
return { type: 'NETWORK', retryable: true, description: 'Network connectivity issue' };
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Authentication/permission errors - not retryable
|
|
902
|
+
if (output.includes('error: 401') || output.includes('error: 403') || output.includes('authentication failed') || output.includes('permission denied')) {
|
|
903
|
+
return { type: 'PERMISSION', retryable: false, description: 'Authentication or permission error' };
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Repository not found - not retryable
|
|
907
|
+
if (output.includes('error: 404') || output.includes('not found') || output.includes('repository not found')) {
|
|
908
|
+
return { type: 'NOT_FOUND', retryable: false, description: 'Repository not found' };
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Rate limiting - retryable with backoff
|
|
912
|
+
if (output.includes('rate limit') || output.includes('too many requests') || output.includes('api rate limit exceeded')) {
|
|
913
|
+
return { type: 'RATE_LIMIT', retryable: true, description: 'Rate limit exceeded' };
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Default to retryable for unknown errors
|
|
917
|
+
return { type: 'UNKNOWN', retryable: true, description: 'Unknown error' };
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
// Clone repository and set up remotes with retry mechanism
|
|
888
921
|
export const cloneRepository = async (repoToClone, tempDir, argv, owner, repo) => {
|
|
889
|
-
|
|
890
|
-
|
|
922
|
+
const maxRetries = 3;
|
|
923
|
+
const baseDelay = 2000; // Start with 2 seconds
|
|
891
924
|
|
|
892
|
-
|
|
893
|
-
const cloneResult = await $`gh repo clone ${repoToClone} ${tempDir} 2>&1`;
|
|
925
|
+
await log(`\n${formatAligned('📥', 'Cloning repository:', repoToClone)}`);
|
|
894
926
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
await log('');
|
|
899
|
-
await log(`${formatAligned('❌', 'CLONE FAILED', '')}`, { level: 'error' });
|
|
900
|
-
await log('');
|
|
901
|
-
await log(' 🔍 What happened:');
|
|
902
|
-
await log(` Failed to clone repository ${repoToClone}`);
|
|
903
|
-
await log('');
|
|
904
|
-
await log(' 📦 Error details:');
|
|
905
|
-
for (const line of errorOutput.split('\n')) {
|
|
906
|
-
if (line.trim()) await log(` ${line}`);
|
|
927
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
928
|
+
if (attempt > 1) {
|
|
929
|
+
await log(`${formatAligned('⏳', 'Clone attempt:', `${attempt}/${maxRetries} (with retry logic)`)}`);
|
|
907
930
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
await
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
if (
|
|
914
|
-
await log('
|
|
931
|
+
|
|
932
|
+
// Use 2>&1 to capture all output and filter "Cloning into" message
|
|
933
|
+
const cloneResult = await $`gh repo clone ${repoToClone} ${tempDir} 2>&1`;
|
|
934
|
+
|
|
935
|
+
// Verify clone was successful
|
|
936
|
+
if (cloneResult.code === 0) {
|
|
937
|
+
await log(`${formatAligned('✅', 'Cloned to:', tempDir)}`);
|
|
938
|
+
|
|
939
|
+
// Verify and fix remote configuration
|
|
940
|
+
const remoteCheckResult = await $({ cwd: tempDir })`git remote -v 2>&1`;
|
|
941
|
+
if (!remoteCheckResult.stdout || !remoteCheckResult.stdout.toString().includes('origin')) {
|
|
942
|
+
await log(' Setting up git remote...', { verbose: true });
|
|
943
|
+
// Add origin remote manually
|
|
944
|
+
await $({ cwd: tempDir })`git remote add origin https://github.com/${repoToClone}.git 2>&1`;
|
|
945
|
+
}
|
|
946
|
+
return; // Success - exit function
|
|
915
947
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
948
|
+
|
|
949
|
+
// Clone failed - analyze error and determine if retry is appropriate
|
|
950
|
+
const errorOutput = (cloneResult.stderr || cloneResult.stdout || 'Unknown error').toString().trim();
|
|
951
|
+
|
|
952
|
+
const errorClassification = classifyCloneError(errorOutput);
|
|
953
|
+
|
|
954
|
+
if (!errorClassification.retryable || attempt === maxRetries) {
|
|
955
|
+
// Non-retryable error or max retries reached - fail with detailed error
|
|
956
|
+
await log('');
|
|
957
|
+
await log(`${formatAligned('❌', 'CLONE FAILED', '')}`, { level: 'error' });
|
|
958
|
+
await log('');
|
|
959
|
+
await log(' 🔍 What happened:');
|
|
960
|
+
await log(` Failed to clone repository ${repoToClone}`);
|
|
961
|
+
|
|
962
|
+
if (!errorClassification.retryable) {
|
|
963
|
+
await log(` Error type: ${errorClassification.description} (not retryable)`);
|
|
964
|
+
} else {
|
|
965
|
+
await log(` Error type: ${errorClassification.description} (max retries exceeded)`);
|
|
966
|
+
}
|
|
967
|
+
await log('');
|
|
968
|
+
await log(' 📦 Error details:');
|
|
969
|
+
for (const line of errorOutput.split('\n')) {
|
|
970
|
+
if (line.trim()) await log(` ${line}`);
|
|
971
|
+
}
|
|
972
|
+
await log('');
|
|
973
|
+
await log(' 💡 Common causes:');
|
|
974
|
+
await log(" • Repository doesn't exist or is private");
|
|
975
|
+
await log(' • No GitHub authentication');
|
|
976
|
+
await log(' • Network connectivity issues');
|
|
977
|
+
if (errorClassification.type === 'TRANSIENT') {
|
|
978
|
+
await log(' • GitHub server issues (temporary)');
|
|
979
|
+
}
|
|
980
|
+
if (errorClassification.type === 'RATE_LIMIT') {
|
|
981
|
+
await log(' • API rate limiting exceeded');
|
|
982
|
+
}
|
|
983
|
+
if (argv.fork) {
|
|
984
|
+
await log(' • Fork not ready yet (try again in a moment)');
|
|
985
|
+
}
|
|
986
|
+
await log('');
|
|
987
|
+
await log(' 🔧 How to fix:');
|
|
988
|
+
await log(' 1. Check authentication: gh auth status');
|
|
989
|
+
await log(' 2. Login if needed: gh auth login');
|
|
990
|
+
await log(` 3. Verify access: gh repo view ${owner}/${repo}`);
|
|
991
|
+
if (argv.fork) {
|
|
992
|
+
await log(` 4. Check fork: gh repo view ${repoToClone}`);
|
|
993
|
+
}
|
|
994
|
+
if (errorClassification.type === 'TRANSIENT') {
|
|
995
|
+
await log(' 5. Wait a few minutes and retry (GitHub server issue)');
|
|
996
|
+
await log(' 6. Check GitHub status: https://www.githubstatus.com');
|
|
997
|
+
}
|
|
998
|
+
if (errorClassification.type === 'RATE_LIMIT') {
|
|
999
|
+
await log(' 5. Wait for rate limit to reset (check your quota)');
|
|
1000
|
+
await log(' 6. Use --token flag with different token if available');
|
|
1001
|
+
}
|
|
1002
|
+
await log('');
|
|
1003
|
+
await safeExit(1, 'Repository setup failed');
|
|
923
1004
|
}
|
|
924
|
-
await log('');
|
|
925
|
-
await safeExit(1, 'Repository setup failed');
|
|
926
|
-
}
|
|
927
1005
|
|
|
928
|
-
|
|
1006
|
+
// Retryable error and we have attempts left
|
|
1007
|
+
const delay = baseDelay * Math.pow(2, attempt - 1); // Exponential backoff
|
|
1008
|
+
await log(`${formatAligned('⚠️', 'Clone failed:', errorClassification.description)}`);
|
|
1009
|
+
await log(`${formatAligned('⏳', 'Retrying:', `Waiting ${delay / 1000}s before attempt ${attempt + 1}/${maxRetries}...`)}`);
|
|
1010
|
+
|
|
1011
|
+
if (errorClassification.type === 'RATE_LIMIT') {
|
|
1012
|
+
await log(' 💡 Tip: Rate limiting detected - using longer delay');
|
|
1013
|
+
}
|
|
929
1014
|
|
|
930
|
-
|
|
931
|
-
const remoteCheckResult = await $({ cwd: tempDir })`git remote -v 2>&1`;
|
|
932
|
-
if (!remoteCheckResult.stdout || !remoteCheckResult.stdout.toString().includes('origin')) {
|
|
933
|
-
await log(' Setting up git remote...', { verbose: true });
|
|
934
|
-
// Add origin remote manually
|
|
935
|
-
await $({ cwd: tempDir })`git remote add origin https://github.com/${repoToClone}.git 2>&1`;
|
|
1015
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
936
1016
|
}
|
|
1017
|
+
|
|
1018
|
+
// This should never be reached due to the loop logic above
|
|
1019
|
+
await log(`${formatAligned('❌', 'UNEXPECTED ERROR:', 'Clone logic failed')}`);
|
|
1020
|
+
await safeExit(1, 'Repository setup failed');
|
|
937
1021
|
};
|
|
938
1022
|
|
|
939
1023
|
// Set up upstream remote and sync fork
|