@link-assistant/hive-mind 2.12.0 → 2.12.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 +14 -0
- package/package.json +4 -1
- package/src/claude.connection.lib.mjs +209 -0
- package/src/claude.lib.mjs +6 -202
- package/src/codex.lib.mjs +0 -128
- package/src/github-rate-limit.lib.mjs +3 -0
- package/src/github-url-parser.lib.mjs +255 -0
- package/src/github.lib.mjs +22 -343
- package/src/hive.mjs +0 -152
- package/src/interactive-mode.lib.mjs +0 -43
- package/src/isolation-runner.lib.mjs +14 -174
- package/src/limits.lib.mjs +0 -89
- package/src/models/index.mjs +0 -15
- package/src/session-monitor.lib.mjs +14 -172
- package/src/solve.auto-merge.lib.mjs +70 -164
- package/src/solve.mjs +31 -193
- package/src/solve.repository.lib.mjs +0 -83
- package/src/solve.results.lib.mjs +0 -90
- package/src/solve.session.lib.mjs +52 -19
- package/src/solve.tool-uncommitted.lib.mjs +22 -0
- package/src/telegram-bot.mjs +0 -66
- package/src/telegram-merge-queue.lib.mjs +3 -155
- package/src/telegram-solve-queue.lib.mjs +9 -168
- package/src/use-m-bootstrap.lib.mjs +6 -5
- package/src/use-with-retry.lib.mjs +128 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 2.12.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a33befc: Document native multi-architecture container builds, build caching, release independence, and published-manifest verification in the CI/CD guide.
|
|
8
|
+
|
|
9
|
+
## 2.12.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- d606c77: Restore Node 24 command execution, make runtime-loaded dependencies reproducible,
|
|
14
|
+
and strengthen CI status, security, warning, and smoke-test enforcement.
|
|
15
|
+
- 098b01a: Report auto-resumed limit-reset sessions with their start marker, summary, and execution log.
|
|
16
|
+
|
|
3
17
|
## 2.12.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/hive-mind",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.2",
|
|
4
4
|
"description": "AI-powered issue solver and hive mind for collaborative problem solving",
|
|
5
5
|
"main": "src/hive.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -88,5 +88,8 @@
|
|
|
88
88
|
"*.{js,mjs,json,md}": [
|
|
89
89
|
"prettier --write"
|
|
90
90
|
]
|
|
91
|
+
},
|
|
92
|
+
"allowScripts": {
|
|
93
|
+
"@sentry/node-cpu-profiler@2.4.2": true
|
|
91
94
|
}
|
|
92
95
|
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
2
|
+
if (typeof globalThis.use === 'undefined') {
|
|
3
|
+
await ensureUseM();
|
|
4
|
+
}
|
|
5
|
+
const { $ } = await use('command-stream');
|
|
6
|
+
import { log } from './lib.mjs';
|
|
7
|
+
import { reportError } from './sentry.lib.mjs';
|
|
8
|
+
import { timeouts, getThinkingLevelToTokens, getTokensToThinkingLevel, supportsThinkingBudget, DEFAULT_MAX_THINKING_BUDGET } from './config.lib.mjs';
|
|
9
|
+
import { buildAuthRemedyLines } from './formal-ai.lib.mjs';
|
|
10
|
+
import { stringifyErrorValue } from './error-text.lib.mjs';
|
|
11
|
+
import { mapModelToId } from './claude.model-utils.lib.mjs';
|
|
12
|
+
|
|
13
|
+
export const validateClaudeConnection = async (model = 'haiku') => {
|
|
14
|
+
const mappedModel = mapModelToId(model);
|
|
15
|
+
// Issue #2130: "run claude login" is wrong advice for a Formal-AI-served model.
|
|
16
|
+
const authRemedyLines = buildAuthRemedyLines({ model, vendorRemedy: 'Please run: claude login' });
|
|
17
|
+
const maxRetries = 3;
|
|
18
|
+
const baseDelay = timeouts.retryBaseDelay;
|
|
19
|
+
let retryCount = 0;
|
|
20
|
+
const attemptValidation = async () => {
|
|
21
|
+
try {
|
|
22
|
+
if (retryCount === 0) {
|
|
23
|
+
await log('🔍 Validating Claude CLI connection...');
|
|
24
|
+
} else {
|
|
25
|
+
await log(`🔄 Retry attempt ${retryCount}/${maxRetries} for Claude CLI validation...`);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const versionResult = await $`timeout ${Math.floor(timeouts.claudeCli / 6000)} claude --version`;
|
|
29
|
+
if (versionResult.code === 0) {
|
|
30
|
+
const version = versionResult.stdout?.toString().trim();
|
|
31
|
+
detectedClaudeVersion = version; // issue #1146
|
|
32
|
+
if (retryCount === 0) {
|
|
33
|
+
await log(`📦 Claude CLI version: ${version}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} catch (versionError) {
|
|
37
|
+
if (retryCount === 0) {
|
|
38
|
+
await log(`⚠️ Claude CLI version check failed (${versionError.code}), proceeding with connection test...`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
let result;
|
|
42
|
+
try {
|
|
43
|
+
result = await $`printf hi | claude --model ${mappedModel} -p`;
|
|
44
|
+
} catch (pipeError) {
|
|
45
|
+
await log(`⚠️ Pipe validation failed (${pipeError.code}), trying timeout approach...`);
|
|
46
|
+
try {
|
|
47
|
+
result = await $`timeout ${Math.floor(timeouts.claudeCli / 1000)} claude --model ${mappedModel} -p hi`;
|
|
48
|
+
} catch (timeoutError) {
|
|
49
|
+
if (timeoutError.code === 124) {
|
|
50
|
+
await log(`❌ Claude CLI timed out after ${Math.floor(timeouts.claudeCli / 1000)} seconds`, {
|
|
51
|
+
level: 'error',
|
|
52
|
+
});
|
|
53
|
+
await log(' 💡 This may indicate Claude CLI is taking too long to respond', { level: 'error' });
|
|
54
|
+
await log(` 💡 Try running 'claude --model ${mappedModel} -p hi' manually to verify it works`, {
|
|
55
|
+
level: 'error',
|
|
56
|
+
});
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
throw timeoutError;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const stdout = result.stdout?.toString() || '';
|
|
63
|
+
const stderr = result.stderr?.toString() || '';
|
|
64
|
+
const checkForJsonError = text => {
|
|
65
|
+
try {
|
|
66
|
+
if (text.includes('"error"') && text.includes('"type"')) {
|
|
67
|
+
const jsonMatch = text.match(/\{.*"error".*\}/);
|
|
68
|
+
if (jsonMatch) {
|
|
69
|
+
const errorObj = JSON.parse(jsonMatch[0]);
|
|
70
|
+
return stringifyErrorValue(errorObj.error, { fallback: jsonMatch[0] }); // Issue #2141: the `error` field is usually an object
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch (e) {
|
|
74
|
+
if (global.verboseMode) {
|
|
75
|
+
reportError(e, {
|
|
76
|
+
context: 'claude_json_error_parse',
|
|
77
|
+
level: 'debug',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
};
|
|
83
|
+
const jsonError = checkForJsonError(stdout) || checkForJsonError(stderr);
|
|
84
|
+
const isOverloadError = (stdout.includes('API Error: 500') && stdout.includes('Overloaded')) || (stdout.includes('API Error: 529') && stdout.includes('Overloaded')) || (stderr.includes('API Error: 500') && stderr.includes('Overloaded')) || (stderr.includes('API Error: 529') && stderr.includes('Overloaded')) || (jsonError && (jsonError.type === 'api_error' || jsonError.type === 'overloaded_error') && jsonError.message === 'Overloaded');
|
|
85
|
+
if (isOverloadError) {
|
|
86
|
+
if (retryCount < maxRetries) {
|
|
87
|
+
const delay = baseDelay * Math.pow(2, retryCount);
|
|
88
|
+
await log(`⚠️ API overload error during validation. Retrying in ${delay / 1000} seconds...`, {
|
|
89
|
+
level: 'warning',
|
|
90
|
+
});
|
|
91
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
92
|
+
retryCount++;
|
|
93
|
+
return await attemptValidation();
|
|
94
|
+
} else {
|
|
95
|
+
await log(`❌ API overload error persisted after ${maxRetries} retries during validation`, {
|
|
96
|
+
level: 'error',
|
|
97
|
+
});
|
|
98
|
+
await log(' The API appears to be heavily loaded. Please try again later.', { level: 'error' });
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const exitCode = result.code ?? result.exitCode ?? 0; // Bun shell compat
|
|
103
|
+
if (exitCode !== 0) {
|
|
104
|
+
if (jsonError) {
|
|
105
|
+
await log(`❌ Claude CLI authentication failed: ${jsonError.type} - ${jsonError.message}`, {
|
|
106
|
+
level: 'error',
|
|
107
|
+
});
|
|
108
|
+
} else {
|
|
109
|
+
await log(`❌ Claude CLI failed with exit code ${exitCode}`, { level: 'error' });
|
|
110
|
+
if (stderr) await log(` Error: ${stderr.trim()}`, { level: 'error' });
|
|
111
|
+
}
|
|
112
|
+
if (stderr.includes('Please run /login') || (jsonError && jsonError.type === 'forbidden')) {
|
|
113
|
+
for (const line of authRemedyLines) await log(line, { level: 'error' });
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (jsonError) {
|
|
118
|
+
if ((jsonError.type === 'api_error' || jsonError.type === 'overloaded_error') && jsonError.message === 'Overloaded') {
|
|
119
|
+
if (retryCount < maxRetries) {
|
|
120
|
+
const delay = baseDelay * Math.pow(2, retryCount);
|
|
121
|
+
await log(`⚠️ API overload error in response. Retrying in ${delay / 1000} seconds...`, {
|
|
122
|
+
level: 'warning',
|
|
123
|
+
});
|
|
124
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
125
|
+
retryCount++;
|
|
126
|
+
return await attemptValidation();
|
|
127
|
+
} else {
|
|
128
|
+
await log(`❌ API overload error persisted after ${maxRetries} retries`, { level: 'error' });
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
await log(`❌ Claude CLI returned error: ${jsonError.type} - ${jsonError.message}`, { level: 'error' });
|
|
133
|
+
if (jsonError.type === 'forbidden') {
|
|
134
|
+
for (const line of authRemedyLines) await log(line, { level: 'error' });
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
await log('✅ Claude CLI connection validated successfully');
|
|
139
|
+
return true;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const errorStr = error.message || error.toString();
|
|
142
|
+
if ((errorStr.includes('API Error: 500') && errorStr.includes('Overloaded')) || (errorStr.includes('API Error: 529') && errorStr.includes('Overloaded')) || (errorStr.includes('api_error') && errorStr.includes('Overloaded')) || (errorStr.includes('overloaded_error') && errorStr.includes('Overloaded'))) {
|
|
143
|
+
if (retryCount < maxRetries) {
|
|
144
|
+
const delay = baseDelay * Math.pow(2, retryCount);
|
|
145
|
+
await log(`⚠️ API overload error during validation. Retrying in ${delay / 1000} seconds...`, {
|
|
146
|
+
level: 'warning',
|
|
147
|
+
});
|
|
148
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
149
|
+
retryCount++;
|
|
150
|
+
return await attemptValidation();
|
|
151
|
+
} else {
|
|
152
|
+
await log(`❌ API overload error persisted after ${maxRetries} retries`, { level: 'error' });
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
await log(`❌ Failed to validate Claude CLI connection: ${error.message}`, { level: 'error' });
|
|
157
|
+
await log(' 💡 Make sure Claude CLI is installed and accessible', { level: 'error' });
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
return await attemptValidation();
|
|
162
|
+
};
|
|
163
|
+
// Store Claude Code version globally (set during validation)
|
|
164
|
+
let detectedClaudeVersion = null;
|
|
165
|
+
/** Get the detected Claude Code version @returns {string|null} */
|
|
166
|
+
export const getClaudeVersion = () => detectedClaudeVersion;
|
|
167
|
+
/** Set the detected Claude Code version (called during validation) @param {string} version */
|
|
168
|
+
export const setClaudeVersion = version => {
|
|
169
|
+
detectedClaudeVersion = version;
|
|
170
|
+
};
|
|
171
|
+
/** Resolve thinking settings based on --think and --thinking-budget options */
|
|
172
|
+
export const resolveThinkingSettings = async (argv, log) => {
|
|
173
|
+
const minVersion = argv.thinkingBudgetClaudeMinimumVersion || '2.1.12';
|
|
174
|
+
const version = detectedClaudeVersion || '0.0.0';
|
|
175
|
+
const isNewVersion = supportsThinkingBudget(version, minVersion);
|
|
176
|
+
const maxBudget = argv.maxThinkingBudget ?? DEFAULT_MAX_THINKING_BUDGET;
|
|
177
|
+
const thinkingLevelToTokens = getThinkingLevelToTokens(maxBudget);
|
|
178
|
+
const tokensToThinkingLevel = getTokensToThinkingLevel(maxBudget);
|
|
179
|
+
let thinkingBudget = argv.thinkingBudget;
|
|
180
|
+
let thinkLevel = argv.think;
|
|
181
|
+
let translation = null;
|
|
182
|
+
if (isNewVersion) {
|
|
183
|
+
// Issue #2038: `adaptive` is provider-managed and has no explicit token
|
|
184
|
+
// budget; skip the budget translation and let the model manage thinking.
|
|
185
|
+
if (thinkLevel !== undefined && thinkLevel !== 'adaptive' && thinkingBudget === undefined) {
|
|
186
|
+
thinkingBudget = thinkingLevelToTokens[thinkLevel];
|
|
187
|
+
translation = `--think ${thinkLevel} → --thinking-budget ${thinkingBudget}`;
|
|
188
|
+
if (argv.verbose) {
|
|
189
|
+
await log(`📊 Translating for Claude Code ${version} (>= ${minVersion}):`, { verbose: true });
|
|
190
|
+
await log(` ${translation}`, { verbose: true });
|
|
191
|
+
if (maxBudget !== DEFAULT_MAX_THINKING_BUDGET) {
|
|
192
|
+
await log(` Using custom --max-thinking-budget: ${maxBudget}`, { verbose: true });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
if (thinkingBudget !== undefined && thinkLevel === undefined) {
|
|
198
|
+
thinkLevel = tokensToThinkingLevel(thinkingBudget);
|
|
199
|
+
translation = `--thinking-budget ${thinkingBudget} → --think ${thinkLevel}`;
|
|
200
|
+
if (argv.verbose) {
|
|
201
|
+
await log(`📊 Translating for Claude Code ${version} (< ${minVersion}):`, { verbose: true });
|
|
202
|
+
await log(` ${translation}`, { verbose: true });
|
|
203
|
+
}
|
|
204
|
+
thinkingBudget = undefined;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return { thinkingBudget, thinkLevel, translation, isNewVersion, maxBudget };
|
|
208
|
+
};
|
|
209
|
+
/** Check if Playwright MCP is available and connected to Claude @returns {Promise<boolean>} */
|
package/src/claude.lib.mjs
CHANGED
|
@@ -3,12 +3,11 @@ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
|
3
3
|
if (typeof globalThis.use === 'undefined') {
|
|
4
4
|
await ensureUseM();
|
|
5
5
|
}
|
|
6
|
-
const { $ } = await use('command-stream');
|
|
7
6
|
const fs = (await use('fs')).promises;
|
|
8
7
|
const path = (await use('path')).default;
|
|
9
|
-
import {
|
|
8
|
+
import { isENOSPC, buildToolErrorMessage } from './lib.mjs';
|
|
10
9
|
import { reportError } from './sentry.lib.mjs';
|
|
11
|
-
import { timeouts, retryLimits, claudeCode, getClaudeEnv,
|
|
10
|
+
import { timeouts, retryLimits, claudeCode, getClaudeEnv, getMaxOutputTokensForModel } from './config.lib.mjs';
|
|
12
11
|
import { detectUsageLimit, formatUsageLimitMessage, isUsageLimitError } from './usage-limit.lib.mjs';
|
|
13
12
|
import { createInteractiveHandler } from './interactive-mode.lib.mjs';
|
|
14
13
|
import { setupBidirectionalHandler, finalizeBidirectionalHandler, validateBidirectionalModeConfig, attachStreamingInput } from './bidirectional-interactive.lib.mjs';
|
|
@@ -23,7 +22,7 @@ import { SESSION_FORCE_KILLED_MARKER, postTrackedComment } from './tool-comments
|
|
|
23
22
|
import { handleClaudeRuntimeSwitch } from './claude.runtime-switch.lib.mjs'; // see issue #1141
|
|
24
23
|
import { CLAUDE_MODELS as availableModels, mapClaudeSubAgentModelToEnvValue } from './models/index.mjs'; // Issue #1221, #1978
|
|
25
24
|
import { applyFormalAiPricingOverride } from './formal-ai-pricing.lib.mjs'; // Issue #2119
|
|
26
|
-
import {
|
|
25
|
+
import { isPrepareOnly, logPreparedToolCommand, resolveFormalAiToolExecution } from './formal-ai.lib.mjs';
|
|
27
26
|
import { buildMcpConfigWithoutPlaywright, ensureClaudePlaywrightMcpServer } from './playwright-mcp.lib.mjs';
|
|
28
27
|
import { resolveClaudeSessionToolFlags } from './useless-tools.lib.mjs';
|
|
29
28
|
import { ensureClaudeQuietConfig } from './claude-quiet-config.lib.mjs';
|
|
@@ -38,206 +37,11 @@ import { formatNumber, mapModelToId, checkModelVisionCapability } from './claude
|
|
|
38
37
|
import { showResumeCommand } from './claude.resume-output.lib.mjs';
|
|
39
38
|
import { stringifyErrorValue } from './error-text.lib.mjs'; // Issue #2141
|
|
40
39
|
import { createPullRequestBaseBranchCommandIntervention } from './solve.pr-base-command-intervention.lib.mjs';
|
|
40
|
+
import { getClaudeVersion, resolveThinkingSettings, setClaudeVersion, validateClaudeConnection } from './claude.connection.lib.mjs';
|
|
41
41
|
export { availableModels, fetchModelInfo }; // Re-export for backward compatibility
|
|
42
42
|
export { formatNumber, mapModelToId, checkModelVisionCapability };
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
// Issue #2130: "run claude login" is wrong advice for a Formal-AI-served model.
|
|
46
|
-
const authRemedyLines = buildAuthRemedyLines({ model, vendorRemedy: 'Please run: claude login' });
|
|
47
|
-
const maxRetries = 3;
|
|
48
|
-
const baseDelay = timeouts.retryBaseDelay;
|
|
49
|
-
let retryCount = 0;
|
|
50
|
-
const attemptValidation = async () => {
|
|
51
|
-
try {
|
|
52
|
-
if (retryCount === 0) {
|
|
53
|
-
await log('🔍 Validating Claude CLI connection...');
|
|
54
|
-
} else {
|
|
55
|
-
await log(`🔄 Retry attempt ${retryCount}/${maxRetries} for Claude CLI validation...`);
|
|
56
|
-
}
|
|
57
|
-
try {
|
|
58
|
-
const versionResult = await $`timeout ${Math.floor(timeouts.claudeCli / 6000)} claude --version`;
|
|
59
|
-
if (versionResult.code === 0) {
|
|
60
|
-
const version = versionResult.stdout?.toString().trim();
|
|
61
|
-
detectedClaudeVersion = version; // issue #1146
|
|
62
|
-
if (retryCount === 0) {
|
|
63
|
-
await log(`📦 Claude CLI version: ${version}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
} catch (versionError) {
|
|
67
|
-
if (retryCount === 0) {
|
|
68
|
-
await log(`⚠️ Claude CLI version check failed (${versionError.code}), proceeding with connection test...`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
let result;
|
|
72
|
-
try {
|
|
73
|
-
result = await $`printf hi | claude --model ${mappedModel} -p`;
|
|
74
|
-
} catch (pipeError) {
|
|
75
|
-
await log(`⚠️ Pipe validation failed (${pipeError.code}), trying timeout approach...`);
|
|
76
|
-
try {
|
|
77
|
-
result = await $`timeout ${Math.floor(timeouts.claudeCli / 1000)} claude --model ${mappedModel} -p hi`;
|
|
78
|
-
} catch (timeoutError) {
|
|
79
|
-
if (timeoutError.code === 124) {
|
|
80
|
-
await log(`❌ Claude CLI timed out after ${Math.floor(timeouts.claudeCli / 1000)} seconds`, {
|
|
81
|
-
level: 'error',
|
|
82
|
-
});
|
|
83
|
-
await log(' 💡 This may indicate Claude CLI is taking too long to respond', { level: 'error' });
|
|
84
|
-
await log(` 💡 Try running 'claude --model ${mappedModel} -p hi' manually to verify it works`, {
|
|
85
|
-
level: 'error',
|
|
86
|
-
});
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
throw timeoutError;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
const stdout = result.stdout?.toString() || '';
|
|
93
|
-
const stderr = result.stderr?.toString() || '';
|
|
94
|
-
const checkForJsonError = text => {
|
|
95
|
-
try {
|
|
96
|
-
if (text.includes('"error"') && text.includes('"type"')) {
|
|
97
|
-
const jsonMatch = text.match(/\{.*"error".*\}/);
|
|
98
|
-
if (jsonMatch) {
|
|
99
|
-
const errorObj = JSON.parse(jsonMatch[0]);
|
|
100
|
-
return stringifyErrorValue(errorObj.error, { fallback: jsonMatch[0] }); // Issue #2141: the `error` field is usually an object
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
} catch (e) {
|
|
104
|
-
if (global.verboseMode) {
|
|
105
|
-
reportError(e, {
|
|
106
|
-
context: 'claude_json_error_parse',
|
|
107
|
-
level: 'debug',
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
};
|
|
113
|
-
const jsonError = checkForJsonError(stdout) || checkForJsonError(stderr);
|
|
114
|
-
const isOverloadError = (stdout.includes('API Error: 500') && stdout.includes('Overloaded')) || (stdout.includes('API Error: 529') && stdout.includes('Overloaded')) || (stderr.includes('API Error: 500') && stderr.includes('Overloaded')) || (stderr.includes('API Error: 529') && stderr.includes('Overloaded')) || (jsonError && (jsonError.type === 'api_error' || jsonError.type === 'overloaded_error') && jsonError.message === 'Overloaded');
|
|
115
|
-
if (isOverloadError) {
|
|
116
|
-
if (retryCount < maxRetries) {
|
|
117
|
-
const delay = baseDelay * Math.pow(2, retryCount);
|
|
118
|
-
await log(`⚠️ API overload error during validation. Retrying in ${delay / 1000} seconds...`, {
|
|
119
|
-
level: 'warning',
|
|
120
|
-
});
|
|
121
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
122
|
-
retryCount++;
|
|
123
|
-
return await attemptValidation();
|
|
124
|
-
} else {
|
|
125
|
-
await log(`❌ API overload error persisted after ${maxRetries} retries during validation`, {
|
|
126
|
-
level: 'error',
|
|
127
|
-
});
|
|
128
|
-
await log(' The API appears to be heavily loaded. Please try again later.', { level: 'error' });
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const exitCode = result.code ?? result.exitCode ?? 0; // Bun shell compat
|
|
133
|
-
if (exitCode !== 0) {
|
|
134
|
-
if (jsonError) {
|
|
135
|
-
await log(`❌ Claude CLI authentication failed: ${jsonError.type} - ${jsonError.message}`, {
|
|
136
|
-
level: 'error',
|
|
137
|
-
});
|
|
138
|
-
} else {
|
|
139
|
-
await log(`❌ Claude CLI failed with exit code ${exitCode}`, { level: 'error' });
|
|
140
|
-
if (stderr) await log(` Error: ${stderr.trim()}`, { level: 'error' });
|
|
141
|
-
}
|
|
142
|
-
if (stderr.includes('Please run /login') || (jsonError && jsonError.type === 'forbidden')) {
|
|
143
|
-
for (const line of authRemedyLines) await log(line, { level: 'error' });
|
|
144
|
-
}
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
if (jsonError) {
|
|
148
|
-
if ((jsonError.type === 'api_error' || jsonError.type === 'overloaded_error') && jsonError.message === 'Overloaded') {
|
|
149
|
-
if (retryCount < maxRetries) {
|
|
150
|
-
const delay = baseDelay * Math.pow(2, retryCount);
|
|
151
|
-
await log(`⚠️ API overload error in response. Retrying in ${delay / 1000} seconds...`, {
|
|
152
|
-
level: 'warning',
|
|
153
|
-
});
|
|
154
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
155
|
-
retryCount++;
|
|
156
|
-
return await attemptValidation();
|
|
157
|
-
} else {
|
|
158
|
-
await log(`❌ API overload error persisted after ${maxRetries} retries`, { level: 'error' });
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
await log(`❌ Claude CLI returned error: ${jsonError.type} - ${jsonError.message}`, { level: 'error' });
|
|
163
|
-
if (jsonError.type === 'forbidden') {
|
|
164
|
-
for (const line of authRemedyLines) await log(line, { level: 'error' });
|
|
165
|
-
}
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
await log('✅ Claude CLI connection validated successfully');
|
|
169
|
-
return true;
|
|
170
|
-
} catch (error) {
|
|
171
|
-
const errorStr = error.message || error.toString();
|
|
172
|
-
if ((errorStr.includes('API Error: 500') && errorStr.includes('Overloaded')) || (errorStr.includes('API Error: 529') && errorStr.includes('Overloaded')) || (errorStr.includes('api_error') && errorStr.includes('Overloaded')) || (errorStr.includes('overloaded_error') && errorStr.includes('Overloaded'))) {
|
|
173
|
-
if (retryCount < maxRetries) {
|
|
174
|
-
const delay = baseDelay * Math.pow(2, retryCount);
|
|
175
|
-
await log(`⚠️ API overload error during validation. Retrying in ${delay / 1000} seconds...`, {
|
|
176
|
-
level: 'warning',
|
|
177
|
-
});
|
|
178
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
179
|
-
retryCount++;
|
|
180
|
-
return await attemptValidation();
|
|
181
|
-
} else {
|
|
182
|
-
await log(`❌ API overload error persisted after ${maxRetries} retries`, { level: 'error' });
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
await log(`❌ Failed to validate Claude CLI connection: ${error.message}`, { level: 'error' });
|
|
187
|
-
await log(' 💡 Make sure Claude CLI is installed and accessible', { level: 'error' });
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
return await attemptValidation();
|
|
192
|
-
};
|
|
193
|
-
export { handleClaudeRuntimeSwitch }; // Re-export from ./claude.runtime-switch.lib.mjs
|
|
194
|
-
// Store Claude Code version globally (set during validation)
|
|
195
|
-
let detectedClaudeVersion = null;
|
|
196
|
-
/** Get the detected Claude Code version @returns {string|null} */
|
|
197
|
-
export const getClaudeVersion = () => detectedClaudeVersion;
|
|
198
|
-
/** Set the detected Claude Code version (called during validation) @param {string} version */
|
|
199
|
-
export const setClaudeVersion = version => {
|
|
200
|
-
detectedClaudeVersion = version;
|
|
201
|
-
};
|
|
202
|
-
/** Resolve thinking settings based on --think and --thinking-budget options */
|
|
203
|
-
export const resolveThinkingSettings = async (argv, log) => {
|
|
204
|
-
const minVersion = argv.thinkingBudgetClaudeMinimumVersion || '2.1.12';
|
|
205
|
-
const version = detectedClaudeVersion || '0.0.0';
|
|
206
|
-
const isNewVersion = supportsThinkingBudget(version, minVersion);
|
|
207
|
-
const maxBudget = argv.maxThinkingBudget ?? DEFAULT_MAX_THINKING_BUDGET;
|
|
208
|
-
const thinkingLevelToTokens = getThinkingLevelToTokens(maxBudget);
|
|
209
|
-
const tokensToThinkingLevel = getTokensToThinkingLevel(maxBudget);
|
|
210
|
-
let thinkingBudget = argv.thinkingBudget;
|
|
211
|
-
let thinkLevel = argv.think;
|
|
212
|
-
let translation = null;
|
|
213
|
-
if (isNewVersion) {
|
|
214
|
-
// Issue #2038: `adaptive` is provider-managed and has no explicit token
|
|
215
|
-
// budget; skip the budget translation and let the model manage thinking.
|
|
216
|
-
if (thinkLevel !== undefined && thinkLevel !== 'adaptive' && thinkingBudget === undefined) {
|
|
217
|
-
thinkingBudget = thinkingLevelToTokens[thinkLevel];
|
|
218
|
-
translation = `--think ${thinkLevel} → --thinking-budget ${thinkingBudget}`;
|
|
219
|
-
if (argv.verbose) {
|
|
220
|
-
await log(`📊 Translating for Claude Code ${version} (>= ${minVersion}):`, { verbose: true });
|
|
221
|
-
await log(` ${translation}`, { verbose: true });
|
|
222
|
-
if (maxBudget !== DEFAULT_MAX_THINKING_BUDGET) {
|
|
223
|
-
await log(` Using custom --max-thinking-budget: ${maxBudget}`, { verbose: true });
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
} else {
|
|
228
|
-
if (thinkingBudget !== undefined && thinkLevel === undefined) {
|
|
229
|
-
thinkLevel = tokensToThinkingLevel(thinkingBudget);
|
|
230
|
-
translation = `--thinking-budget ${thinkingBudget} → --think ${thinkLevel}`;
|
|
231
|
-
if (argv.verbose) {
|
|
232
|
-
await log(`📊 Translating for Claude Code ${version} (< ${minVersion}):`, { verbose: true });
|
|
233
|
-
await log(` ${translation}`, { verbose: true });
|
|
234
|
-
}
|
|
235
|
-
thinkingBudget = undefined;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return { thinkingBudget, thinkLevel, translation, isNewVersion, maxBudget };
|
|
239
|
-
};
|
|
240
|
-
/** Check if Playwright MCP is available and connected to Claude @returns {Promise<boolean>} */
|
|
43
|
+
export { getClaudeVersion, resolveThinkingSettings, setClaudeVersion, validateClaudeConnection };
|
|
44
|
+
export { handleClaudeRuntimeSwitch };
|
|
241
45
|
export const checkPlaywrightMcpAvailability = ensureClaudePlaywrightMcpServer;
|
|
242
46
|
export const executeClaude = async params => {
|
|
243
47
|
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, setLogFile, getLogFile, formatAligned, getResourceSnapshot, claudePath, $ } = params;
|