@link-assistant/hive-mind 2.12.0 → 2.12.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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 2.12.1
4
+
5
+ ### Patch Changes
6
+
7
+ - d606c77: Restore Node 24 command execution, make runtime-loaded dependencies reproducible,
8
+ and strengthen CI status, security, warning, and smoke-test enforcement.
9
+ - 098b01a: Report auto-resumed limit-reset sessions with their start marker, summary, and execution log.
10
+
3
11
  ## 2.12.0
4
12
 
5
13
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "2.12.0",
3
+ "version": "2.12.1",
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>} */
@@ -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 { log, isENOSPC, buildToolErrorMessage } from './lib.mjs';
8
+ import { isENOSPC, buildToolErrorMessage } from './lib.mjs';
10
9
  import { reportError } from './sentry.lib.mjs';
11
- import { timeouts, retryLimits, claudeCode, getClaudeEnv, getThinkingLevelToTokens, getTokensToThinkingLevel, supportsThinkingBudget, DEFAULT_MAX_THINKING_BUDGET, getMaxOutputTokensForModel } from './config.lib.mjs';
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 { buildAuthRemedyLines, isPrepareOnly, logPreparedToolCommand, resolveFormalAiToolExecution } from './formal-ai.lib.mjs';
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 const validateClaudeConnection = async (model = 'haiku') => {
44
- const mappedModel = mapModelToId(model);
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;