agentxchain 2.155.66 → 2.155.68
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/package.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
import { spawn } from 'child_process';
|
|
21
21
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
22
|
-
import { join } from 'path';
|
|
22
|
+
import { delimiter, join } from 'path';
|
|
23
23
|
import {
|
|
24
24
|
getDispatchContextPath,
|
|
25
25
|
getDispatchLogPath,
|
|
@@ -33,7 +33,9 @@ import { hasMeaningfulStagedResult } from '../staged-result-proof.js';
|
|
|
33
33
|
import {
|
|
34
34
|
getClaudeSubprocessAuthIssue,
|
|
35
35
|
hasClaudeAuthenticationFailureText,
|
|
36
|
+
hasClaudeNodeIncompatibilityText,
|
|
36
37
|
isClaudeLocalCliRuntime,
|
|
38
|
+
resolveClaudeCompatibleNodeBinary,
|
|
37
39
|
} from '../claude-local-auth.js';
|
|
38
40
|
|
|
39
41
|
const DIAGNOSTIC_ENV_KEYS = [
|
|
@@ -135,6 +137,7 @@ export async function dispatchLocalCli(root, state, config, options = {}) {
|
|
|
135
137
|
const spawnEnv = { ...process.env, AGENTXCHAIN_TURN_ID: turn.turn_id };
|
|
136
138
|
const stdinBytes = transport === 'stdin' ? Buffer.byteLength(fullPrompt, 'utf8') : 0;
|
|
137
139
|
const diagnosticArgs = redactPromptArgs(args, fullPrompt, transport);
|
|
140
|
+
const spawnSpec = resolveClaudeSpawnSpec(runtime, command, args, spawnEnv);
|
|
138
141
|
const claudeAuthIssue = await getClaudeSubprocessAuthIssue(runtime, spawnEnv);
|
|
139
142
|
|
|
140
143
|
if (claudeAuthIssue) {
|
|
@@ -163,14 +166,17 @@ export async function dispatchLocalCli(root, state, config, options = {}) {
|
|
|
163
166
|
appendDiagnostic(logs, 'spawn_prepare', {
|
|
164
167
|
runtime_id: runtimeId,
|
|
165
168
|
turn_id: turn.turn_id,
|
|
166
|
-
command,
|
|
167
|
-
args: diagnosticArgs,
|
|
169
|
+
command: spawnSpec.command,
|
|
170
|
+
args: spawnSpec.args === args ? diagnosticArgs : redactPromptArgs(spawnSpec.args, fullPrompt, transport),
|
|
171
|
+
configured_command: spawnSpec.command === command ? undefined : command,
|
|
172
|
+
configured_args: spawnSpec.command === command ? undefined : diagnosticArgs,
|
|
173
|
+
spawn_wrapper: spawnSpec.wrapper,
|
|
168
174
|
cwd: runtimeCwd,
|
|
169
175
|
prompt_transport: transport,
|
|
170
176
|
stdin_bytes: stdinBytes,
|
|
171
177
|
env: pickDiagnosticEnv(spawnEnv),
|
|
172
178
|
});
|
|
173
|
-
child = spawn(command, args, {
|
|
179
|
+
child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
174
180
|
cwd: runtimeCwd,
|
|
175
181
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
176
182
|
env: spawnEnv,
|
|
@@ -447,6 +453,22 @@ export async function dispatchLocalCli(root, state, config, options = {}) {
|
|
|
447
453
|
error: `Claude local_cli authentication failed. ${recovery}`,
|
|
448
454
|
logs,
|
|
449
455
|
});
|
|
456
|
+
} else if (isClaudeLocalCliRuntime(runtime) && hasClaudeNodeRuntimeIncompatibilityOutput(logs)) {
|
|
457
|
+
const recovery = 'Run AgentXchain with Node.js 20.5+ available to the Claude local_cli runtime, then resume continuous mode.';
|
|
458
|
+
settle({
|
|
459
|
+
ok: false,
|
|
460
|
+
blocked: true,
|
|
461
|
+
exitCode,
|
|
462
|
+
timedOut: false,
|
|
463
|
+
aborted: false,
|
|
464
|
+
firstOutputAt,
|
|
465
|
+
classified: {
|
|
466
|
+
error_class: 'claude_node_incompatible',
|
|
467
|
+
recovery,
|
|
468
|
+
},
|
|
469
|
+
error: `Claude local_cli runtime is using an incompatible Node.js version. ${recovery}`,
|
|
470
|
+
logs,
|
|
471
|
+
});
|
|
450
472
|
} else if (startupTimedOut) {
|
|
451
473
|
settle({
|
|
452
474
|
ok: false,
|
|
@@ -665,6 +687,55 @@ function hasClaudeAuthFailureOutput(logs) {
|
|
|
665
687
|
return logs.some((line) => hasClaudeAuthenticationFailureText(line));
|
|
666
688
|
}
|
|
667
689
|
|
|
690
|
+
function hasClaudeNodeRuntimeIncompatibilityOutput(logs) {
|
|
691
|
+
if (!Array.isArray(logs)) return false;
|
|
692
|
+
return hasClaudeNodeIncompatibilityText(logs.join('\n'));
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function resolveCommandPath(command, pathValue) {
|
|
696
|
+
if (!command || command.includes('/')) {
|
|
697
|
+
return existsSync(command) ? command : null;
|
|
698
|
+
}
|
|
699
|
+
const parts = String(pathValue || '').split(delimiter).filter(Boolean);
|
|
700
|
+
for (const dir of parts) {
|
|
701
|
+
const candidate = join(dir, command);
|
|
702
|
+
if (existsSync(candidate)) return candidate;
|
|
703
|
+
}
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function resolveClaudeSpawnSpec(runtime, command, args, env) {
|
|
708
|
+
if (command !== 'claude' || !isClaudeLocalCliRuntime(runtime)) {
|
|
709
|
+
return { command, args, wrapper: null };
|
|
710
|
+
}
|
|
711
|
+
const nodeBinary = resolveClaudeCompatibleNodeBinary(env);
|
|
712
|
+
if (!nodeBinary) {
|
|
713
|
+
return { command, args, wrapper: null };
|
|
714
|
+
}
|
|
715
|
+
const claudeEntry = resolveCommandPath(command, env?.PATH);
|
|
716
|
+
if (!claudeEntry) {
|
|
717
|
+
return { command, args, wrapper: null };
|
|
718
|
+
}
|
|
719
|
+
if (!isNodeEntrypoint(claudeEntry)) {
|
|
720
|
+
return { command, args, wrapper: null };
|
|
721
|
+
}
|
|
722
|
+
return {
|
|
723
|
+
command: nodeBinary,
|
|
724
|
+
args: [claudeEntry, ...args],
|
|
725
|
+
wrapper: 'claude_compatible_node',
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function isNodeEntrypoint(filePath) {
|
|
730
|
+
try {
|
|
731
|
+
const head = readFileSync(filePath, 'utf8').slice(0, 256);
|
|
732
|
+
const firstLine = head.split(/\r?\n/, 1)[0] || '';
|
|
733
|
+
return /^#!.*(?:^|[\/\s])(?:env\s+)?node(?:\s|$)/.test(firstLine);
|
|
734
|
+
} catch {
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
668
739
|
function pickDiagnosticEnv(env) {
|
|
669
740
|
return Object.fromEntries(
|
|
670
741
|
DIAGNOSTIC_ENV_KEYS
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { accessSync, constants } from 'node:fs';
|
|
2
3
|
|
|
3
4
|
const CLAUDE_ENV_AUTH_KEYS = [
|
|
4
5
|
'ANTHROPIC_API_KEY',
|
|
@@ -11,6 +12,8 @@ const CLAUDE_ENV_AUTH_KEYS = [
|
|
|
11
12
|
const DEFAULT_SMOKE_PROBE_TIMEOUT_MS = 10_000;
|
|
12
13
|
const DEFAULT_SMOKE_PROBE_STDIN = 'ok';
|
|
13
14
|
const CLAUDE_AUTH_FAILURE_RE = /authentication_failed|authentication_error|invalid authentication credentials|unauthorized|API Error:\s*401/i;
|
|
15
|
+
const CLAUDE_NODE_INCOMPATIBILITY_RE = /TypeError:\s*Object not disposable|Object not disposable[\s\S]{0,2000}Node\.js v(?:1[0-9]|20\.[0-4]\.)/i;
|
|
16
|
+
const CLAUDE_COMPATIBLE_NODE_MIN = { major: 20, minor: 5, patch: 0 };
|
|
14
17
|
|
|
15
18
|
function normalizeCommandTokens(runtime) {
|
|
16
19
|
if (Array.isArray(runtime?.command)) {
|
|
@@ -37,10 +40,77 @@ export function hasClaudeAuthenticationFailureText(text) {
|
|
|
37
40
|
return typeof text === 'string' && CLAUDE_AUTH_FAILURE_RE.test(text);
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
export function hasClaudeNodeIncompatibilityText(text) {
|
|
44
|
+
return typeof text === 'string' && CLAUDE_NODE_INCOMPATIBILITY_RE.test(text);
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
export function hasClaudeBareFlag(runtime) {
|
|
41
48
|
return normalizeCommandTokens(runtime).includes('--bare');
|
|
42
49
|
}
|
|
43
50
|
|
|
51
|
+
function parseNodeVersion(raw) {
|
|
52
|
+
const match = String(raw || '').trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
53
|
+
if (!match) return null;
|
|
54
|
+
return {
|
|
55
|
+
major: Number.parseInt(match[1], 10),
|
|
56
|
+
minor: Number.parseInt(match[2], 10),
|
|
57
|
+
patch: Number.parseInt(match[3], 10),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isClaudeCompatibleNodeVersion(raw) {
|
|
62
|
+
const version = parseNodeVersion(raw);
|
|
63
|
+
if (!version) return false;
|
|
64
|
+
if (version.major !== CLAUDE_COMPATIBLE_NODE_MIN.major) {
|
|
65
|
+
return version.major > CLAUDE_COMPATIBLE_NODE_MIN.major;
|
|
66
|
+
}
|
|
67
|
+
if (version.minor !== CLAUDE_COMPATIBLE_NODE_MIN.minor) {
|
|
68
|
+
return version.minor > CLAUDE_COMPATIBLE_NODE_MIN.minor;
|
|
69
|
+
}
|
|
70
|
+
return version.patch >= CLAUDE_COMPATIBLE_NODE_MIN.patch;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isExecutable(filePath) {
|
|
74
|
+
if (!filePath) return false;
|
|
75
|
+
try {
|
|
76
|
+
accessSync(filePath, constants.X_OK);
|
|
77
|
+
return true;
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function candidateNodeVersion(filePath) {
|
|
84
|
+
if (!isExecutable(filePath)) return null;
|
|
85
|
+
const result = spawnSync(filePath, ['--version'], {
|
|
86
|
+
encoding: 'utf8',
|
|
87
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
88
|
+
timeout: 2000,
|
|
89
|
+
});
|
|
90
|
+
if (result.status !== 0) return null;
|
|
91
|
+
return String(result.stdout || '').trim();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function resolveClaudeCompatibleNodeBinary(env = process.env) {
|
|
95
|
+
if (env?.AGENTXCHAIN_CLAUDE_NODE && isExecutable(env.AGENTXCHAIN_CLAUDE_NODE)) {
|
|
96
|
+
return env.AGENTXCHAIN_CLAUDE_NODE;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const candidates = [
|
|
100
|
+
process.execPath,
|
|
101
|
+
'/opt/homebrew/opt/node@20/bin/node',
|
|
102
|
+
'/opt/homebrew/bin/node',
|
|
103
|
+
'/usr/local/bin/node',
|
|
104
|
+
];
|
|
105
|
+
for (const candidate of candidates) {
|
|
106
|
+
const version = candidateNodeVersion(candidate);
|
|
107
|
+
if (isClaudeCompatibleNodeVersion(version)) {
|
|
108
|
+
return candidate;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
44
114
|
export function getClaudeEnvAuthPresence(env = process.env) {
|
|
45
115
|
return Object.fromEntries(
|
|
46
116
|
CLAUDE_ENV_AUTH_KEYS.map((key) => [key, Boolean(env?.[key])]),
|
|
@@ -55,12 +55,15 @@ import {
|
|
|
55
55
|
import { checkpointAcceptedTurn } from './turn-checkpoint.js';
|
|
56
56
|
import {
|
|
57
57
|
hasClaudeAuthenticationFailureText,
|
|
58
|
+
hasClaudeNodeIncompatibilityText,
|
|
58
59
|
isClaudeLocalCliRuntime,
|
|
60
|
+
resolveClaudeCompatibleNodeBinary,
|
|
59
61
|
} from './claude-local-auth.js';
|
|
60
62
|
|
|
61
63
|
const CONTINUOUS_SESSION_PATH = '.agentxchain/continuous-session.json';
|
|
62
64
|
const PRODUCTIVE_TIMEOUT_RETRY_MAX_PER_RUN = 1;
|
|
63
65
|
const PRODUCTIVE_TIMEOUT_RETRY_DEADLINE_MINUTES = 60;
|
|
66
|
+
const PROVIDER_REQUEST_TIMEOUT_RE = /request timed out|timed out waiting for (?:provider|api)|provider request timed out/i;
|
|
64
67
|
|
|
65
68
|
function getRoadmapReplenishmentTriageHints(root) {
|
|
66
69
|
const context = loadProjectContext(root);
|
|
@@ -315,6 +318,8 @@ function getBlockedCategory(state) {
|
|
|
315
318
|
|
|
316
319
|
const CLAUDE_AUTH_RECOVERY_ACTION =
|
|
317
320
|
'Refresh Claude credentials before resuming: export a valid ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN, then run agentxchain step --resume.';
|
|
321
|
+
const CLAUDE_NODE_RECOVERY_ACTION =
|
|
322
|
+
'Run AgentXchain with Node.js 20.5+ available to the Claude local_cli runtime, then resume continuous mode.';
|
|
318
323
|
|
|
319
324
|
function findRetainedClaudeAuthEscalation(root, state, config) {
|
|
320
325
|
if (!state || state.status !== 'blocked') return null;
|
|
@@ -394,6 +399,104 @@ function maybeReclassifyRetainedClaudeAuthEscalation(context, session, state, lo
|
|
|
394
399
|
return nextState;
|
|
395
400
|
}
|
|
396
401
|
|
|
402
|
+
function findRetainedClaudeNodeIncompatGhost(root, state, config) {
|
|
403
|
+
if (!state || state.status !== 'blocked') return null;
|
|
404
|
+
if (state.blocked_reason?.category !== 'ghost_turn') return null;
|
|
405
|
+
const activeTurns = state.active_turns || {};
|
|
406
|
+
const turnId = state.blocked_reason?.turn_id || null;
|
|
407
|
+
const candidateIds = turnId && activeTurns[turnId] ? [turnId] : Object.keys(activeTurns);
|
|
408
|
+
for (const candidateId of candidateIds) {
|
|
409
|
+
const turn = activeTurns[candidateId];
|
|
410
|
+
if (!turn || turn.status !== 'failed_start') continue;
|
|
411
|
+
if (turn.failed_start_reason !== 'stdout_attach_failed') continue;
|
|
412
|
+
const runtime = config?.runtimes?.[turn.runtime_id];
|
|
413
|
+
if (!isClaudeLocalCliRuntime(runtime)) continue;
|
|
414
|
+
const logPath = join(root, getDispatchLogPath(candidateId));
|
|
415
|
+
if (!existsSync(logPath)) continue;
|
|
416
|
+
let logText = '';
|
|
417
|
+
try {
|
|
418
|
+
logText = readFileSync(logPath, 'utf8');
|
|
419
|
+
} catch {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (!hasClaudeNodeIncompatibilityText(logText)) continue;
|
|
423
|
+
return { turn_id: candidateId, turn, previous_blocked_on: state.blocked_on || null };
|
|
424
|
+
}
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function reclassifyRetainedClaudeNodeIncompatGhost(context, session, state, candidate, nodeBinary, log = console.log) {
|
|
429
|
+
const { root } = context;
|
|
430
|
+
if (nodeBinary) {
|
|
431
|
+
const reissued = reissueTurn(root, context.config, {
|
|
432
|
+
turnId: candidate.turn_id,
|
|
433
|
+
reason: 'auto_retry_claude_node_runtime',
|
|
434
|
+
});
|
|
435
|
+
if (!reissued.ok) {
|
|
436
|
+
log(`Claude Node runtime auto-retry skipped: ${reissued.error}`);
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
const runId = session.current_run_id || state.run_id || reissued.state?.run_id || null;
|
|
440
|
+
const nextState = clearGhostBlockerAfterReissue(root, reissued.state);
|
|
441
|
+
delete session.ghost_retry;
|
|
442
|
+
Object.assign(session, {
|
|
443
|
+
status: 'running',
|
|
444
|
+
current_run_id: runId,
|
|
445
|
+
});
|
|
446
|
+
writeContinuousSession(root, session);
|
|
447
|
+
emitRunEvent(root, 'auto_retried_ghost', {
|
|
448
|
+
run_id: runId,
|
|
449
|
+
phase: nextState.phase || state.phase || null,
|
|
450
|
+
status: 'active',
|
|
451
|
+
turn: { turn_id: reissued.newTurn.turn_id, role_id: reissued.newTurn.assigned_role || null },
|
|
452
|
+
payload: {
|
|
453
|
+
old_turn_id: candidate.turn_id,
|
|
454
|
+
new_turn_id: reissued.newTurn.turn_id,
|
|
455
|
+
failure_type: 'stdout_attach_failed',
|
|
456
|
+
recovery_class: 'claude_node_runtime_recovered',
|
|
457
|
+
runtime_id: candidate.turn.runtime_id || null,
|
|
458
|
+
node_binary: nodeBinary,
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
log(`Claude Node runtime recovered; auto-retried ${candidate.turn_id} -> ${reissued.newTurn.turn_id}.`);
|
|
462
|
+
return {
|
|
463
|
+
ok: true,
|
|
464
|
+
status: 'running',
|
|
465
|
+
action: 'auto_retried_claude_node_runtime',
|
|
466
|
+
run_id: runId,
|
|
467
|
+
old_turn_id: candidate.turn_id,
|
|
468
|
+
new_turn_id: reissued.newTurn.turn_id,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const blockedAt = new Date().toISOString();
|
|
473
|
+
const nextState = {
|
|
474
|
+
...state,
|
|
475
|
+
status: 'blocked',
|
|
476
|
+
blocked_on: 'dispatch:claude_node_incompatible',
|
|
477
|
+
blocked_reason: {
|
|
478
|
+
category: 'dispatch_error',
|
|
479
|
+
blocked_at: blockedAt,
|
|
480
|
+
turn_id: candidate.turn_id,
|
|
481
|
+
reclassified_from: {
|
|
482
|
+
blocked_on: state.blocked_on || null,
|
|
483
|
+
category: state.blocked_reason?.category || null,
|
|
484
|
+
},
|
|
485
|
+
recovery: {
|
|
486
|
+
typed_reason: 'dispatch_error',
|
|
487
|
+
owner: 'human',
|
|
488
|
+
recovery_action: CLAUDE_NODE_RECOVERY_ACTION,
|
|
489
|
+
turn_retained: true,
|
|
490
|
+
detail: `claude_node_incompatible: ${CLAUDE_NODE_RECOVERY_ACTION}`,
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
escalation: null,
|
|
494
|
+
};
|
|
495
|
+
writeGovernedState(root, nextState);
|
|
496
|
+
log(`Reclassified retained Claude Node runtime blocker for ${candidate.turn_id} as dispatch:claude_node_incompatible.`);
|
|
497
|
+
return nextState;
|
|
498
|
+
}
|
|
499
|
+
|
|
397
500
|
const RECOVERABLE_ACTIVE_TURN_STATUSES = new Set(['assigned', 'dispatched', 'starting', 'running']);
|
|
398
501
|
|
|
399
502
|
function hasOnlyRecoverableActiveTurns(activeTurns = {}) {
|
|
@@ -537,7 +640,16 @@ function findPrimaryProductiveTimeoutTurn(root, state) {
|
|
|
537
640
|
...(Array.isArray(turn.last_rejection?.validation_errors) ? turn.last_rejection.validation_errors : []),
|
|
538
641
|
].join('\n');
|
|
539
642
|
const looksDeadlineKilled = /code 143|dispatch timed out|timed out/i.test(reason);
|
|
540
|
-
|
|
643
|
+
let looksProviderTimedOut = false;
|
|
644
|
+
const logPath = join(root, getDispatchLogPath(candidateId));
|
|
645
|
+
if (!looksDeadlineKilled && existsSync(logPath)) {
|
|
646
|
+
try {
|
|
647
|
+
looksProviderTimedOut = PROVIDER_REQUEST_TIMEOUT_RE.test(readFileSync(logPath, 'utf8'));
|
|
648
|
+
} catch {
|
|
649
|
+
looksProviderTimedOut = false;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (!looksDeadlineKilled && !looksProviderTimedOut) continue;
|
|
541
653
|
if (!turn.first_output_at) continue;
|
|
542
654
|
const stagingPath = join(root, getTurnStagingResultPath(candidateId));
|
|
543
655
|
if (existsSync(stagingPath)) continue;
|
|
@@ -1800,7 +1912,20 @@ export async function advanceContinuousRunOnce(context, session, contOpts, execu
|
|
|
1800
1912
|
const startupGovernedState = loadProjectState(root, context.config);
|
|
1801
1913
|
if (startupGovernedState?.status === 'blocked') {
|
|
1802
1914
|
const reclassifiedState = maybeReclassifyRetainedClaudeAuthEscalation(context, session, startupGovernedState, log);
|
|
1803
|
-
|
|
1915
|
+
let effectiveBlockedState = reclassifiedState || startupGovernedState;
|
|
1916
|
+
const nodeCandidate = findRetainedClaudeNodeIncompatGhost(root, effectiveBlockedState, context.config);
|
|
1917
|
+
if (nodeCandidate) {
|
|
1918
|
+
const nodeRecovery = reclassifyRetainedClaudeNodeIncompatGhost(
|
|
1919
|
+
context,
|
|
1920
|
+
session,
|
|
1921
|
+
effectiveBlockedState,
|
|
1922
|
+
nodeCandidate,
|
|
1923
|
+
resolveClaudeCompatibleNodeBinary(process.env),
|
|
1924
|
+
log,
|
|
1925
|
+
);
|
|
1926
|
+
if (nodeRecovery?.status === 'running') return nodeRecovery;
|
|
1927
|
+
if (nodeRecovery) effectiveBlockedState = nodeRecovery;
|
|
1928
|
+
}
|
|
1804
1929
|
const retried = await maybeAutoRetryContinuousBlocker(context, session, contOpts, effectiveBlockedState, log);
|
|
1805
1930
|
if (retried) return retried;
|
|
1806
1931
|
session.status = 'paused';
|