overmind-mcp 2.4.2 → 2.5.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/.mcp.json.example +21 -0
- package/README.md +107 -80
- package/assets/overmind.png +0 -0
- package/bin/.gitkeep +0 -0
- package/bin/README.md +34 -0
- package/bin/install-overmind-unix.sh +412 -0
- package/bin/install-overmind-windows.bat +407 -0
- package/dist/bin/cli.js +438 -7
- package/dist/bin/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +31 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +19 -27
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/sessions.d.ts +3 -2
- package/dist/lib/sessions.d.ts.map +1 -1
- package/dist/lib/sessions.js +94 -25
- package/dist/lib/sessions.js.map +1 -1
- package/dist/memory/MemoryFactory.d.ts +15 -1
- package/dist/memory/MemoryFactory.d.ts.map +1 -1
- package/dist/memory/MemoryFactory.js +53 -3
- package/dist/memory/MemoryFactory.js.map +1 -1
- package/dist/memory/PostgresMemoryProvider.d.ts +7 -0
- package/dist/memory/PostgresMemoryProvider.d.ts.map +1 -1
- package/dist/memory/PostgresMemoryProvider.js +180 -105
- package/dist/memory/PostgresMemoryProvider.js.map +1 -1
- package/dist/server.d.ts +41 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +152 -26
- package/dist/server.js.map +1 -1
- package/dist/services/AgentManager.d.ts.map +1 -1
- package/dist/services/AgentManager.js +67 -11
- package/dist/services/AgentManager.js.map +1 -1
- package/dist/services/ClaudeRunner.d.ts +8 -0
- package/dist/services/ClaudeRunner.d.ts.map +1 -1
- package/dist/services/ClaudeRunner.js +514 -213
- package/dist/services/ClaudeRunner.js.map +1 -1
- package/dist/services/ClineRunner.d.ts +7 -0
- package/dist/services/ClineRunner.d.ts.map +1 -1
- package/dist/services/ClineRunner.js +80 -11
- package/dist/services/ClineRunner.js.map +1 -1
- package/dist/services/GeminiRunner.d.ts +6 -0
- package/dist/services/GeminiRunner.d.ts.map +1 -1
- package/dist/services/GeminiRunner.js +285 -58
- package/dist/services/GeminiRunner.js.map +1 -1
- package/dist/services/KiloRunner.d.ts +12 -0
- package/dist/services/KiloRunner.d.ts.map +1 -1
- package/dist/services/KiloRunner.js +441 -57
- package/dist/services/KiloRunner.js.map +1 -1
- package/dist/services/OpenClawRunner.d.ts +7 -0
- package/dist/services/OpenClawRunner.d.ts.map +1 -1
- package/dist/services/OpenClawRunner.js +79 -11
- package/dist/services/OpenClawRunner.js.map +1 -1
- package/dist/services/OpenCodeRunner.d.ts +7 -0
- package/dist/services/OpenCodeRunner.d.ts.map +1 -1
- package/dist/services/OpenCodeRunner.js +79 -11
- package/dist/services/OpenCodeRunner.js.map +1 -1
- package/dist/tools/config_example.d.ts +7 -4
- package/dist/tools/config_example.d.ts.map +1 -1
- package/dist/tools/config_example.js +191 -86
- package/dist/tools/config_example.js.map +1 -1
- package/dist/tools/create_agent.d.ts +15 -9
- package/dist/tools/create_agent.d.ts.map +1 -1
- package/dist/tools/create_agent.js +8 -9
- package/dist/tools/create_agent.js.map +1 -1
- package/dist/tools/get_agent_configs.d.ts +10 -4
- package/dist/tools/get_agent_configs.d.ts.map +1 -1
- package/dist/tools/get_agent_configs.js.map +1 -1
- package/dist/tools/manage_agents.d.ts +36 -18
- package/dist/tools/manage_agents.d.ts.map +1 -1
- package/dist/tools/manage_agents.js +2 -2
- package/dist/tools/manage_agents.js.map +1 -1
- package/dist/tools/manage_prompts.d.ts +13 -8
- package/dist/tools/manage_prompts.d.ts.map +1 -1
- package/dist/tools/manage_prompts.js.map +1 -1
- package/dist/tools/memory_runs.d.ts +3 -4
- package/dist/tools/memory_runs.d.ts.map +1 -1
- package/dist/tools/memory_runs.js.map +1 -1
- package/dist/tools/memory_search.d.ts +3 -4
- package/dist/tools/memory_search.d.ts.map +1 -1
- package/dist/tools/memory_search.js.map +1 -1
- package/dist/tools/memory_store.d.ts +11 -5
- package/dist/tools/memory_store.d.ts.map +1 -1
- package/dist/tools/memory_store.js.map +1 -1
- package/dist/tools/run_agent.d.ts +15 -16
- package/dist/tools/run_agent.d.ts.map +1 -1
- package/dist/tools/run_agent.js +128 -160
- package/dist/tools/run_agent.js.map +1 -1
- package/dist/tools/run_claude.d.ts +8 -3
- package/dist/tools/run_claude.d.ts.map +1 -1
- package/dist/tools/run_claude.js +59 -41
- package/dist/tools/run_claude.js.map +1 -1
- package/dist/tools/run_cline.d.ts +13 -4
- package/dist/tools/run_cline.d.ts.map +1 -1
- package/dist/tools/run_cline.js +55 -43
- package/dist/tools/run_cline.js.map +1 -1
- package/dist/tools/run_gemini.d.ts +13 -4
- package/dist/tools/run_gemini.d.ts.map +1 -1
- package/dist/tools/run_gemini.js +53 -33
- package/dist/tools/run_gemini.js.map +1 -1
- package/dist/tools/run_kilo.d.ts +17 -7
- package/dist/tools/run_kilo.d.ts.map +1 -1
- package/dist/tools/run_kilo.js +68 -41
- package/dist/tools/run_kilo.js.map +1 -1
- package/dist/tools/run_openclaw.d.ts +13 -4
- package/dist/tools/run_openclaw.d.ts.map +1 -1
- package/dist/tools/run_openclaw.js +52 -44
- package/dist/tools/run_openclaw.js.map +1 -1
- package/dist/tools/run_opencode.d.ts +13 -4
- package/dist/tools/run_opencode.d.ts.map +1 -1
- package/dist/tools/run_opencode.js +52 -39
- package/dist/tools/run_opencode.js.map +1 -1
- package/docs/README.md +128 -0
- package/docs/agent_control.md +656 -0
- package/docs/index.html +493 -0
- package/docs/library.html +239 -0
- package/docs/prompt.html +1212 -0
- package/docs/script.js +428 -0
- package/docs/styles.css +2816 -0
- package/package.json +55 -25
- package/scripts/auto-changelog.mjs +132 -0
- package/scripts/auto-install.mjs +322 -0
- package/scripts/install-dependencies.mjs +462 -0
- package/scripts/postgres-manager.mjs +219 -0
- package/scripts/postinstall.mjs +538 -0
- package/scripts/setup-overmind-db.mjs +199 -0
- package/scripts/setup-windows.js +266 -0
- package/scripts/setup.mjs +397 -0
- package/scripts/test-installation.mjs +158 -0
- package/scripts/uninstall.mjs +238 -0
- package/assets/overmind_mcp_pro_banner_v3.png +0 -0
- package/dist/services/QwenRunner.d.ts +0 -19
- package/dist/services/QwenRunner.d.ts.map +0 -1
- package/dist/services/QwenRunner.js +0 -87
- package/dist/services/QwenRunner.js.map +0 -1
- package/dist/services/TraeRunner.d.ts +0 -19
- package/dist/services/TraeRunner.d.ts.map +0 -1
- package/dist/services/TraeRunner.js +0 -88
- package/dist/services/TraeRunner.js.map +0 -1
- package/dist/tools/run_qwen.d.ts +0 -15
- package/dist/tools/run_qwen.d.ts.map +0 -1
- package/dist/tools/run_qwen.js +0 -61
- package/dist/tools/run_qwen.js.map +0 -1
- package/dist/tools/run_trae.d.ts +0 -15
- package/dist/tools/run_trae.d.ts.map +0 -1
- package/dist/tools/run_trae.js +0 -66
- package/dist/tools/run_trae.js.map +0 -1
- package/dist/tools/shell_execute.d.ts +0 -10
- package/dist/tools/shell_execute.d.ts.map +0 -1
- package/dist/tools/shell_execute.js +0 -24
- package/dist/tools/shell_execute.js.map +0 -1
- package/dist/tools/test_agent_control.d.ts +0 -2
- package/dist/tools/test_agent_control.d.ts.map +0 -1
- package/dist/tools/test_agent_control.js +0 -92
- package/dist/tools/test_agent_control.js.map +0 -1
|
@@ -1,79 +1,154 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
2
|
import path from 'path';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { spawn, exec } from 'child_process';
|
|
5
|
+
import { CONFIG, resolveConfigPath, getWorkspaceDir } from '../lib/config.js';
|
|
6
6
|
import { getLastSessionId, saveSessionId } from '../lib/sessions.js';
|
|
7
|
+
import { interpolateEnvVars } from '../lib/envUtils.js';
|
|
8
|
+
import { resolveModel } from '../lib/modelMapping.js';
|
|
9
|
+
import { withSpan } from '../lib/telemetry.js';
|
|
10
|
+
import { loadEnvQuietly } from '../lib/loadEnv.js';
|
|
11
|
+
import { registerProcess, linkSessionToPid, appendOutput, updateProcessStatus, } from '../lib/processRegistry.js';
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
// Sur Windows, `child.kill()` ne tue que cmd.exe (le wrapper) — claude.exe
|
|
15
|
+
// devient orphelin et garde la session bound au token initial côté provider.
|
|
16
|
+
// On utilise `taskkill /F /T /PID` pour propager le kill au sous-arbre,
|
|
17
|
+
// puis on attend l'event 'exit' avant de respawn.
|
|
18
|
+
const killProcessTree = (child) => {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
if (!child || child.exitCode !== null || child.killed) {
|
|
21
|
+
resolve();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
let settled = false;
|
|
25
|
+
const finish = () => {
|
|
26
|
+
if (settled)
|
|
27
|
+
return;
|
|
28
|
+
settled = true;
|
|
29
|
+
resolve();
|
|
30
|
+
};
|
|
31
|
+
child.once('exit', finish);
|
|
32
|
+
if (process.platform === 'win32' && child.pid) {
|
|
33
|
+
exec(`taskkill /F /T /PID ${child.pid}`, () => {
|
|
34
|
+
// taskkill peut échouer si le process est déjà mort — on s'appuie sur 'exit'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
try {
|
|
39
|
+
child.kill('SIGTERM');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Ignored
|
|
43
|
+
}
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
if (child.exitCode === null && !child.killed) {
|
|
46
|
+
try {
|
|
47
|
+
child.kill('SIGKILL');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Ignored
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, 2000);
|
|
54
|
+
}
|
|
55
|
+
// Filet de sécurité : si 'exit' ne se déclenche pas (process zombie),
|
|
56
|
+
// on débloque le respawn après 5s plutôt que de bloquer indéfiniment.
|
|
57
|
+
setTimeout(finish, 5000);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
7
60
|
export class ClaudeRunner {
|
|
8
61
|
config;
|
|
9
62
|
timeoutMs;
|
|
10
63
|
constructor() {
|
|
11
64
|
this.config = CONFIG.CLAUDE;
|
|
12
|
-
this.timeoutMs = CONFIG.TIMEOUT_MS ||
|
|
65
|
+
this.timeoutMs = CONFIG.TIMEOUT_MS || 900000;
|
|
13
66
|
}
|
|
14
67
|
async runAgent(options) {
|
|
15
68
|
const { prompt, agentName, autoResume } = options;
|
|
16
69
|
let { sessionId } = options;
|
|
17
70
|
const { CORE, PERMISSIONS, PATHS } = this.config;
|
|
18
71
|
const agentCustomEnv = {};
|
|
72
|
+
// Load environment variables FIRST before any processing
|
|
73
|
+
const workspaceEnvPath = path.resolve(options.configPath || getWorkspaceDir(), '.env');
|
|
74
|
+
loadEnvQuietly(workspaceEnvPath);
|
|
75
|
+
// Also load from Workflow directory as fallback
|
|
76
|
+
const workflowEnvPath = path.resolve(__dirname, '../../.env');
|
|
77
|
+
loadEnvQuietly(workflowEnvPath);
|
|
78
|
+
// Debug: check if variables are loaded
|
|
79
|
+
if (!options.silent) {
|
|
80
|
+
console.error(`[ClaudeRunner] Env check - ANTHROPIC_MODEL_Z present: ${!!process.env.ANTHROPIC_MODEL_Z}`);
|
|
81
|
+
console.error(`[ClaudeRunner] Auth tokens present: ${!!process.env.ANTHROPIC_AUTH_TOKEN_Y || !!process.env.ANTHROPIC_AUTH_TOKEN_E}`);
|
|
82
|
+
console.error(`[ClaudeRunner] workspaceEnvPath: ${workspaceEnvPath}`);
|
|
83
|
+
console.error(`[ClaudeRunner] workflowEnvPath: ${workflowEnvPath}`);
|
|
84
|
+
}
|
|
85
|
+
if (agentName) {
|
|
86
|
+
agentCustomEnv.OVERMIND_AGENT_NAME = agentName;
|
|
87
|
+
}
|
|
19
88
|
// --- Auto Resume ---
|
|
20
89
|
if (autoResume && agentName && !sessionId) {
|
|
21
|
-
const lastId = await getLastSessionId(agentName);
|
|
90
|
+
const lastId = await getLastSessionId(agentName, options.configPath, 'claude');
|
|
22
91
|
if (lastId) {
|
|
23
92
|
sessionId = lastId;
|
|
93
|
+
if (!options.silent) {
|
|
94
|
+
console.log(`[ClaudeRunner] Auto-resume session: ${sessionId}`);
|
|
95
|
+
}
|
|
24
96
|
}
|
|
25
97
|
}
|
|
26
|
-
let settingsPath = resolveConfigPath(PATHS.SETTINGS);
|
|
98
|
+
let settingsPath = resolveConfigPath(PATHS.SETTINGS, options.configPath);
|
|
27
99
|
if (agentName) {
|
|
28
100
|
const settingsDir = path.dirname(PATHS.SETTINGS);
|
|
29
|
-
const specificSettingsPath = resolveConfigPath(path.join(settingsDir, `settings_${agentName}.json`));
|
|
101
|
+
const specificSettingsPath = resolveConfigPath(path.join(settingsDir, `settings_${agentName}.json`), options.configPath);
|
|
30
102
|
if (!fs.existsSync(specificSettingsPath)) {
|
|
31
103
|
return {
|
|
32
104
|
result: '',
|
|
33
|
-
error: `INVALID_AGENT
|
|
105
|
+
error: `INVALID_AGENT: Agent "${agentName}" non trouvé.`,
|
|
34
106
|
};
|
|
35
107
|
}
|
|
36
108
|
settingsPath = specificSettingsPath;
|
|
37
109
|
}
|
|
38
|
-
|
|
39
|
-
const relativeSettings = path.relative(cwd, settingsPath);
|
|
40
|
-
if (!relativeSettings.startsWith('..') && !path.isAbsolute(relativeSettings)) {
|
|
41
|
-
settingsPath = relativeSettings.startsWith('./') ? relativeSettings : `./${relativeSettings}`;
|
|
42
|
-
}
|
|
43
|
-
let mcpPath = resolveConfigPath(PATHS.MCP);
|
|
110
|
+
let mcpPath = resolveConfigPath(PATHS.MCP, options.configPath);
|
|
44
111
|
let tmpMcpPathToDelete = null;
|
|
112
|
+
let tmpSettingsPathToDelete = null;
|
|
45
113
|
let customTimeoutMs = this.timeoutMs;
|
|
46
|
-
// --- Isolation ---
|
|
47
114
|
if (agentName) {
|
|
48
115
|
try {
|
|
49
|
-
const agentSettingsPath = resolveConfigPath(path.join(path.dirname(PATHS.SETTINGS), `settings_${agentName}.json`));
|
|
116
|
+
const agentSettingsPath = resolveConfigPath(path.join(path.dirname(PATHS.SETTINGS), `settings_${agentName}.json`), options.configPath);
|
|
50
117
|
if (fs.existsSync(agentSettingsPath)) {
|
|
51
|
-
|
|
118
|
+
let settings = JSON.parse(fs.readFileSync(agentSettingsPath, 'utf8'));
|
|
119
|
+
// Debug: log environment variables
|
|
120
|
+
if (!options.silent) {
|
|
121
|
+
console.error(`[ClaudeRunner] ANTHROPIC_MODEL_Z present: ${!!process.env.ANTHROPIC_MODEL_Z}`);
|
|
122
|
+
console.error(`[ClaudeRunner] Auth tokens present: ${!!process.env.ANTHROPIC_AUTH_TOKEN_Y || !!process.env.ANTHROPIC_AUTH_TOKEN_E}`);
|
|
123
|
+
console.error(`[ClaudeRunner] ANTHROPIC_BASE_URL_Z present: ${!!process.env.ANTHROPIC_BASE_URL_Z}`);
|
|
124
|
+
}
|
|
125
|
+
// --- New interpolation logic ---
|
|
126
|
+
settings = interpolateEnvVars(settings);
|
|
127
|
+
// Debug: log interpolated values
|
|
128
|
+
if (!options.silent) {
|
|
129
|
+
console.error(`[ClaudeRunner] Interpolated ANTHROPIC_MODEL = ${settings.env?.ANTHROPIC_MODEL}`);
|
|
130
|
+
console.error(`[ClaudeRunner] Interpolated ANTHROPIC_BASE_URL = ${settings.env?.ANTHROPIC_BASE_URL}`);
|
|
131
|
+
}
|
|
132
|
+
// 1. Create a temporary settings file with interpolated values
|
|
133
|
+
const tmpSettingsPath = path.join(path.dirname(agentSettingsPath), `settings_${agentName}_tmp.json`);
|
|
134
|
+
fs.writeFileSync(tmpSettingsPath, JSON.stringify(settings, null, 2));
|
|
135
|
+
settingsPath = tmpSettingsPath;
|
|
136
|
+
tmpSettingsPathToDelete = tmpSettingsPath;
|
|
52
137
|
if (settings.env) {
|
|
53
|
-
// Mémoriser l'env configuré pour l'injection
|
|
54
138
|
Object.assign(agentCustomEnv, settings.env);
|
|
55
|
-
// --- SMART NICKNAME FALLBACK ---
|
|
56
|
-
const currentModel = settings.env.ANTHROPIC_MODEL;
|
|
57
|
-
const isTechnicalModelId = currentModel && (currentModel.includes('claude') ||
|
|
58
|
-
currentModel.includes('gpt') ||
|
|
59
|
-
currentModel.includes('glm') ||
|
|
60
|
-
currentModel.includes('minimax') ||
|
|
61
|
-
currentModel.includes('deepseek') ||
|
|
62
|
-
currentModel.includes('moonshot'));
|
|
63
|
-
if (currentModel && !isTechnicalModelId) {
|
|
64
|
-
// Si le modèle est un surnom, on l'utilise pour l'affichage mais on remet un vrai ID de modèle pour l'API
|
|
65
|
-
agentCustomEnv.AGENT_NICKNAME = currentModel;
|
|
66
|
-
// On utilise le modèle Sonnet par défaut ou la valeur configurée si elle semble valide
|
|
67
|
-
agentCustomEnv.ANTHROPIC_MODEL = (settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL && settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL.includes('claude'))
|
|
68
|
-
? settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL
|
|
69
|
-
: 'claude-3-5-sonnet-20241022';
|
|
70
|
-
}
|
|
71
139
|
if (settings.env.AGENT_TIMEOUT_MS || settings.env.API_TIMEOUT_MS) {
|
|
72
140
|
const timeoutValue = settings.env.AGENT_TIMEOUT_MS || settings.env.API_TIMEOUT_MS;
|
|
73
141
|
customTimeoutMs = parseInt(timeoutValue, 10) || customTimeoutMs;
|
|
74
142
|
}
|
|
143
|
+
if (!options.model && settings.env.MODEL) {
|
|
144
|
+
agentCustomEnv.ANTHROPIC_MODEL = settings.env.MODEL;
|
|
145
|
+
}
|
|
75
146
|
}
|
|
76
|
-
|
|
147
|
+
const agentMcpPath = resolveConfigPath(path.join(path.dirname(PATHS.SETTINGS), `.mcp.${agentName}.json`));
|
|
148
|
+
if (fs.existsSync(agentMcpPath)) {
|
|
149
|
+
mcpPath = agentMcpPath;
|
|
150
|
+
}
|
|
151
|
+
else if (settings.enableAllProjectMcpServers === false &&
|
|
77
152
|
Array.isArray(settings.enabledMcpjsonServers)) {
|
|
78
153
|
if (fs.existsSync(mcpPath)) {
|
|
79
154
|
const fullMcp = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
@@ -91,30 +166,8 @@ export class ClaudeRunner {
|
|
|
91
166
|
}
|
|
92
167
|
}
|
|
93
168
|
}
|
|
94
|
-
catch (_e) {
|
|
95
|
-
// Warning
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const relativeMcp = path.relative(cwd, mcpPath);
|
|
99
|
-
if (!relativeMcp.startsWith('..') && !path.isAbsolute(relativeMcp)) {
|
|
100
|
-
mcpPath = relativeMcp.startsWith('./') ? relativeMcp : `./${relativeMcp}`;
|
|
101
|
-
}
|
|
102
|
-
let tmpSettingsPathToDelete = null;
|
|
103
|
-
let finalSettingsPath = settingsPath;
|
|
104
|
-
if (agentCustomEnv.AGENT_NICKNAME) {
|
|
105
|
-
try {
|
|
106
|
-
// On crée un fichier settings temporaire pour substituer le surnom par un vrai modèle
|
|
107
|
-
// car le CLI Claude ne valide pas les surnoms dynamiques en interne
|
|
108
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
109
|
-
const tempSettings = JSON.parse(JSON.stringify(settings));
|
|
110
|
-
tempSettings.env.ANTHROPIC_MODEL = agentCustomEnv.ANTHROPIC_MODEL;
|
|
111
|
-
const tmpSettingsPath = path.join(os.tmpdir(), `settings-${agentName || 'agent'}-${Date.now()}.json`);
|
|
112
|
-
fs.writeFileSync(tmpSettingsPath, JSON.stringify(tempSettings, null, 2));
|
|
113
|
-
finalSettingsPath = tmpSettingsPath;
|
|
114
|
-
tmpSettingsPathToDelete = tmpSettingsPath;
|
|
115
|
-
}
|
|
116
169
|
catch (e) {
|
|
117
|
-
console.error(`[ClaudeRunner]
|
|
170
|
+
console.error(`[ClaudeRunner] [WARN] Error processing agent settings: ${e}`);
|
|
118
171
|
}
|
|
119
172
|
}
|
|
120
173
|
const argsSpawn = [];
|
|
@@ -122,182 +175,430 @@ export class ClaudeRunner {
|
|
|
122
175
|
argsSpawn.push(...CORE.split(' ').filter(Boolean));
|
|
123
176
|
if (PERMISSIONS)
|
|
124
177
|
argsSpawn.push(...PERMISSIONS.split(' ').filter(Boolean));
|
|
125
|
-
|
|
126
|
-
argsSpawn.push('--settings', finalSettingsPath);
|
|
178
|
+
argsSpawn.push('--settings', settingsPath);
|
|
127
179
|
argsSpawn.push('--mcp-config', mcpPath);
|
|
128
|
-
|
|
129
|
-
|
|
180
|
+
argsSpawn.push('--output-format', 'json');
|
|
181
|
+
let modelUsed = options.model;
|
|
182
|
+
if (!modelUsed && agentCustomEnv.ANTHROPIC_MODEL) {
|
|
183
|
+
modelUsed = agentCustomEnv.ANTHROPIC_MODEL;
|
|
130
184
|
}
|
|
131
|
-
//
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
console.error(`[ClaudeRunner] 👤 Nickname: ${agentCustomEnv.AGENT_NICKNAME}`);
|
|
137
|
-
argsSpawn.push('--name', agentCustomEnv.AGENT_NICKNAME);
|
|
185
|
+
// Remember original value (nickname or raw model) for display
|
|
186
|
+
const originalModel = modelUsed ?? '';
|
|
187
|
+
// Resolve nickname → real model ID before calling the API
|
|
188
|
+
if (modelUsed) {
|
|
189
|
+
modelUsed = resolveModel(modelUsed);
|
|
138
190
|
}
|
|
139
|
-
|
|
191
|
+
if (sessionId)
|
|
192
|
+
argsSpawn.push('--resume', sessionId);
|
|
193
|
+
if (modelUsed)
|
|
194
|
+
argsSpawn.push('--model', modelUsed);
|
|
195
|
+
if (agentName)
|
|
140
196
|
argsSpawn.push('--name', agentName);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
197
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
198
|
+
// 🔄 FALLBACK TOKEN RETRY LOGIC
|
|
199
|
+
//
|
|
200
|
+
// Overmind lit les tokens fallback depuis agentCustomEnv (résolus depuis $VAR).
|
|
201
|
+
// Si une erreur 401 (auth) survient, on tente chaque fallback séquentiellement :
|
|
202
|
+
// AUTH_FALLBACK_1 → AUTH_FALLBACK_2 → AUTH_FALLBACK_3
|
|
203
|
+
//
|
|
204
|
+
// Settings exemple :
|
|
205
|
+
// { "env": { "ANTHROPIC_AUTH_TOKEN": "$ANTHROPIC_AUTH_FALLBACK_1" } }
|
|
206
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
207
|
+
const FALLBACK_KEYS = ['AUTH_FALLBACK_1', 'AUTH_FALLBACK_2', 'AUTH_FALLBACK_3'];
|
|
208
|
+
const TOKEN_KEYS = ['ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_AUTH_TOKEN_E'];
|
|
209
|
+
/**
|
|
210
|
+
* Vérifie si une erreur est retryable (fallback recommended).
|
|
211
|
+
* 401 = auth error (token invalide/expiré)
|
|
212
|
+
* 429 = rate limit / quota exhausted
|
|
213
|
+
* 500/502/503 = server error
|
|
214
|
+
*/
|
|
215
|
+
const isRetryableError = (stderr, jsonEnv) => {
|
|
216
|
+
const lower = stderr.toLowerCase();
|
|
217
|
+
const status = jsonEnv?.api_error_status;
|
|
218
|
+
if (status === 401)
|
|
219
|
+
return true;
|
|
220
|
+
if (status === 429)
|
|
221
|
+
return true;
|
|
222
|
+
if (status === 500 || status === 502 || status === 503)
|
|
223
|
+
return true;
|
|
224
|
+
return (lower.includes('401') ||
|
|
225
|
+
lower.includes('unauthorized') ||
|
|
226
|
+
lower.includes('invalid api key') ||
|
|
227
|
+
lower.includes('api key invalid') ||
|
|
228
|
+
lower.includes('authentication failed') ||
|
|
229
|
+
lower.includes('auth error') ||
|
|
230
|
+
lower.includes('invalid authentication') ||
|
|
231
|
+
lower.includes('429') ||
|
|
232
|
+
lower.includes('rate limit') ||
|
|
233
|
+
lower.includes('quota exhausted') ||
|
|
234
|
+
lower.includes('limit exhausted') ||
|
|
235
|
+
lower.includes('503') ||
|
|
236
|
+
lower.includes('service unavailable') ||
|
|
237
|
+
lower.includes('500') ||
|
|
238
|
+
lower.includes('internal server error'));
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* Extrait les tokens fallback disponibles depuis agentCustomEnv.
|
|
242
|
+
* Retourne un tableau de { key, value } pour chaque fallback non vide.
|
|
243
|
+
*/
|
|
244
|
+
const getAvailableFallbacks = () => {
|
|
245
|
+
const fallbacks = [];
|
|
246
|
+
for (const key of FALLBACK_KEYS) {
|
|
247
|
+
const val = agentCustomEnv[key];
|
|
248
|
+
if (val && typeof val === 'string' && val.length > 0) {
|
|
249
|
+
fallbacks.push({ key, value: val });
|
|
171
250
|
}
|
|
172
251
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
252
|
+
return fallbacks;
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Détermine quel token utiliser selon l'index de retry.
|
|
256
|
+
* - index 0 = tentative initiale → use primary token (ANTHROPIC_AUTH_TOKEN)
|
|
257
|
+
* - index 1+ = retry → skip primary, use fallbacks directly
|
|
258
|
+
*/
|
|
259
|
+
const getTokenForIndex = (index) => {
|
|
260
|
+
if (index === 0) {
|
|
261
|
+
// Tentative initiale : utiliser le primary token
|
|
262
|
+
// NOTE: si la valeur est un $VAR non résolu (interpolateEnvVars n'a pas trouvé
|
|
263
|
+
// la variable dans process.env à ce moment), on le passe quand même à spawnWithToken
|
|
264
|
+
// qui fera la résolution finale via process.env.
|
|
265
|
+
for (const tk of TOKEN_KEYS) {
|
|
266
|
+
const val = agentCustomEnv[tk];
|
|
267
|
+
if (val && typeof val === 'string' && val.length > 0) {
|
|
268
|
+
return { tokenEnvKey: tk, tokenValue: val };
|
|
269
|
+
}
|
|
191
270
|
}
|
|
271
|
+
// Aucun primary token trouvé — retourner null plutôt que de tomber dans les fallbacks
|
|
272
|
+
return null;
|
|
192
273
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
console.error(`[ClaudeRunner] 🚀 Démarrage de l'agent ${id}...`);
|
|
199
|
-
// Debug: Log the prompt size
|
|
200
|
-
console.error(`[ClaudeRunner] 📏 Prompt Size: ${finalPrompt.length} chars`);
|
|
274
|
+
// Retry (index >= 1) : skip primary, use fallbacks directly
|
|
275
|
+
const fallbacks = getAvailableFallbacks();
|
|
276
|
+
const fallbackIndex = index - 1; // index 1 → fallback[0] (AUTH_FALLBACK_1)
|
|
277
|
+
if (fallbackIndex < fallbacks.length) {
|
|
278
|
+
return { tokenEnvKey: fallbacks[fallbackIndex].key, tokenValue: fallbacks[fallbackIndex].value };
|
|
201
279
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const chunk = d.toString();
|
|
232
|
-
stderr += chunk;
|
|
233
|
-
if (agentName) {
|
|
234
|
-
const id = agentCustomEnv.AGENT_NICKNAME || agentName;
|
|
235
|
-
process.stderr.write(`[ClaudeRunner:${id}:ERR] ${chunk}`);
|
|
280
|
+
return null;
|
|
281
|
+
};
|
|
282
|
+
// ─── AbortSignal support ─────────────────────────────────────────────────────
|
|
283
|
+
let currentChildRef = null;
|
|
284
|
+
if (options.signal?.aborted) {
|
|
285
|
+
return Promise.reject(new Error('ABORTED'));
|
|
286
|
+
}
|
|
287
|
+
options.signal?.addEventListener('abort', () => {
|
|
288
|
+
if (currentChildRef)
|
|
289
|
+
void killProcessTree(currentChildRef);
|
|
290
|
+
});
|
|
291
|
+
const runImpl = async (span) => {
|
|
292
|
+
span.setAttribute('agentName', agentName || '');
|
|
293
|
+
span.setAttribute('model', modelUsed || '');
|
|
294
|
+
span.setAttribute('runner', 'claude');
|
|
295
|
+
return new Promise((resolve) => {
|
|
296
|
+
let resolved = false;
|
|
297
|
+
let retryCount = 0;
|
|
298
|
+
const maxRetries = getAvailableFallbacks().length + 1; // primary + fallbacks
|
|
299
|
+
currentChildRef = null;
|
|
300
|
+
let currentStderr = '';
|
|
301
|
+
let currentStdout = '';
|
|
302
|
+
const MAX_BUF = 10 * 1024 * 1024;
|
|
303
|
+
let currentSessionId = sessionId;
|
|
304
|
+
let earlyExitTriggered = false; // Prevent double-exit on early retry
|
|
305
|
+
const safeResolve = (value) => {
|
|
306
|
+
if (!resolved) {
|
|
307
|
+
resolved = true;
|
|
308
|
+
resolve(value);
|
|
236
309
|
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
|
|
310
|
+
};
|
|
311
|
+
const triggerRetry = async (targetRetryCount) => {
|
|
312
|
+
if (earlyExitTriggered)
|
|
313
|
+
return;
|
|
314
|
+
earlyExitTriggered = true;
|
|
315
|
+
if (hardTimeoutTimer)
|
|
316
|
+
clearTimeout(hardTimeoutTimer);
|
|
317
|
+
if (killTimer)
|
|
318
|
+
clearTimeout(killTimer);
|
|
319
|
+
// Attendre la mort effective de l'arbre (cmd.exe + claude.exe sur Win)
|
|
320
|
+
// avant de respawn, sinon le nouveau process tape sur la même session
|
|
321
|
+
// encore "vivante" côté provider → 429 fantôme.
|
|
322
|
+
if (currentChildRef) {
|
|
323
|
+
await killProcessTree(currentChildRef);
|
|
251
324
|
}
|
|
252
|
-
|
|
253
|
-
|
|
325
|
+
retryCount = targetRetryCount;
|
|
326
|
+
const tokenInfo = getTokenForIndex(retryCount);
|
|
327
|
+
if (!options.silent) {
|
|
328
|
+
process.stderr.write(`\n\x1b[41m\x1b[37m[ClaudeRunner] 🔄 Retry ${retryCount}/${maxRetries} avec ${tokenInfo?.tokenEnvKey || 'UNKNOWN'}...\x1b[0m\n`);
|
|
254
329
|
}
|
|
255
|
-
|
|
256
|
-
|
|
330
|
+
setImmediate(() => spawnWithToken(tokenInfo));
|
|
331
|
+
};
|
|
332
|
+
const cleanupTmpFiles = () => {
|
|
333
|
+
if (tmpMcpPathToDelete && fs.existsSync(tmpMcpPathToDelete)) {
|
|
334
|
+
try {
|
|
335
|
+
fs.unlinkSync(tmpMcpPathToDelete);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
// Ignored
|
|
339
|
+
}
|
|
257
340
|
}
|
|
258
|
-
if (
|
|
259
|
-
|
|
341
|
+
if (tmpSettingsPathToDelete && fs.existsSync(tmpSettingsPathToDelete)) {
|
|
342
|
+
try {
|
|
343
|
+
fs.unlinkSync(tmpSettingsPathToDelete);
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// Ignored
|
|
347
|
+
}
|
|
260
348
|
}
|
|
261
|
-
return null;
|
|
262
349
|
};
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
350
|
+
let killTimer = null;
|
|
351
|
+
let hardTimeoutTimer = null;
|
|
352
|
+
/**
|
|
353
|
+
* Fonction centrale qui spawn le processus Claude avec le bon token.
|
|
354
|
+
* Appelé initialement et après chaque retry.
|
|
355
|
+
*/
|
|
356
|
+
const spawnWithToken = (tokenInfo) => {
|
|
357
|
+
// Nettoyer les listeners/timers de la tentative précédente
|
|
358
|
+
if (hardTimeoutTimer) {
|
|
359
|
+
clearTimeout(hardTimeoutTimer);
|
|
360
|
+
hardTimeoutTimer = null;
|
|
361
|
+
}
|
|
362
|
+
if (killTimer) {
|
|
363
|
+
clearTimeout(killTimer);
|
|
364
|
+
killTimer = null;
|
|
365
|
+
}
|
|
366
|
+
// Construire l'env avec le bon token
|
|
367
|
+
// NOTE: Overmind gère la substitution des variables $VAR dans les settings.
|
|
368
|
+
// Les fallback tokens (AUTH_FALLBACK_1/2/3) sont résolus ici pour le retry 401.
|
|
369
|
+
const spawnEnv = {
|
|
370
|
+
...process.env,
|
|
371
|
+
...agentCustomEnv,
|
|
372
|
+
};
|
|
373
|
+
if (tokenInfo) {
|
|
374
|
+
// Remplacer le token actif par celui du fallback
|
|
375
|
+
// NOTE: Les tokens peuvent encore contenir des $VAR non résolus
|
|
376
|
+
// (interpolateEnvVars n'a pas trouvé ces vars dans process.env au moment du load).
|
|
377
|
+
// On résout ici via process.env (qui a été peuplé par loadEnvQuietly).
|
|
378
|
+
for (const tk of TOKEN_KEYS) {
|
|
379
|
+
delete spawnEnv[tk];
|
|
380
|
+
}
|
|
381
|
+
let resolvedToken = tokenInfo.tokenValue;
|
|
382
|
+
if (resolvedToken.startsWith('$')) {
|
|
383
|
+
const envKey = resolvedToken.slice(1);
|
|
384
|
+
resolvedToken = process.env[envKey] || resolvedToken;
|
|
385
|
+
}
|
|
386
|
+
// Le Claude CLI lit ANTHROPIC_AUTH_TOKEN — on injecte toujours sous ce nom,
|
|
387
|
+
// peu importe que tokenInfo vienne du primary ou d'un AUTH_FALLBACK_n.
|
|
388
|
+
spawnEnv['ANTHROPIC_AUTH_TOKEN'] = resolvedToken;
|
|
389
|
+
}
|
|
390
|
+
currentStderr = '';
|
|
391
|
+
currentStdout = '';
|
|
392
|
+
const command = process.platform === 'win32' ? 'cmd.exe' : 'claude';
|
|
393
|
+
const spawnArgs = process.platform === 'win32'
|
|
394
|
+
? ['/c', 'claude', ...argsSpawn, '-p']
|
|
395
|
+
: ['claude', ...argsSpawn, '-p'];
|
|
396
|
+
if (!options.silent) {
|
|
397
|
+
const tokenLabel = tokenInfo ? ` (token: ${tokenInfo.tokenEnvKey})` : '';
|
|
398
|
+
process.stderr.write(`\n\x1b[33m[ClaudeRunner]${tokenLabel} Spawning Claude CLI...\x1b[0m\n`);
|
|
399
|
+
}
|
|
400
|
+
currentChildRef = spawn(command, spawnArgs, {
|
|
401
|
+
cwd: options.cwd || process.cwd(),
|
|
402
|
+
windowsHide: true,
|
|
403
|
+
env: spawnEnv,
|
|
404
|
+
shell: false,
|
|
405
|
+
signal: options.signal,
|
|
269
406
|
});
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
407
|
+
// Register process immediately after spawn
|
|
408
|
+
if (currentChildRef.pid) {
|
|
409
|
+
void registerProcess(currentChildRef.pid, {
|
|
410
|
+
agentName: agentName || '',
|
|
411
|
+
runner: 'claude',
|
|
412
|
+
configPath: options.configPath,
|
|
413
|
+
});
|
|
277
414
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
415
|
+
if (currentChildRef.stdout) {
|
|
416
|
+
currentChildRef.stdout.on('data', (d) => {
|
|
417
|
+
const chunk = d.toString();
|
|
418
|
+
if (currentChildRef && currentChildRef.pid && chunk) {
|
|
419
|
+
void appendOutput(currentChildRef.pid, chunk, options.configPath);
|
|
420
|
+
}
|
|
421
|
+
if (currentStdout.length + chunk.length > MAX_BUF)
|
|
422
|
+
currentStdout = currentStdout.slice(-MAX_BUF);
|
|
423
|
+
else
|
|
424
|
+
currentStdout += chunk;
|
|
425
|
+
if (agentName && !options.silent) {
|
|
426
|
+
process.stderr.write(`[ClaudeRunner:${agentName}] ${chunk}`);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
281
429
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
430
|
+
if (currentChildRef.stderr) {
|
|
431
|
+
currentChildRef.stderr.on('data', (d) => {
|
|
432
|
+
const chunk = d.toString();
|
|
433
|
+
if (currentChildRef && currentChildRef.pid && chunk) {
|
|
434
|
+
void appendOutput(currentChildRef.pid, chunk, options.configPath);
|
|
435
|
+
}
|
|
436
|
+
if (currentStderr.length + chunk.length > MAX_BUF)
|
|
437
|
+
currentStderr = currentStderr.slice(-MAX_BUF);
|
|
438
|
+
else
|
|
439
|
+
currentStderr += chunk;
|
|
440
|
+
if (agentName && !options.silent) {
|
|
441
|
+
process.stderr.write(`[ClaudeRunner:${agentName}:ERR] ${chunk}`);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
if (currentChildRef.stdin) {
|
|
446
|
+
currentChildRef.stdin.write(prompt);
|
|
447
|
+
currentChildRef.stdin.end();
|
|
448
|
+
}
|
|
449
|
+
const timeout = setTimeout(() => {
|
|
450
|
+
if (currentChildRef && currentChildRef.stdin && !currentChildRef.stdin.destroyed) {
|
|
451
|
+
try {
|
|
452
|
+
currentChildRef.stdin.write('\n');
|
|
453
|
+
if (!options.silent) {
|
|
454
|
+
process.stderr.write(`\n\x1b[33m[ClaudeRunner] [WARN] Agent stagnant (${customTimeoutMs}ms). Envoi d'un keep-alive (\\n)...\x1b[0m\n`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
catch (_e) {
|
|
458
|
+
// Ignore
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
const hardTimeoutDelay = CONFIG.HARD_TIMEOUT_MS || 60000;
|
|
462
|
+
hardTimeoutTimer = setTimeout(() => {
|
|
463
|
+
if (currentChildRef)
|
|
464
|
+
void killProcessTree(currentChildRef);
|
|
465
|
+
cleanupTmpFiles();
|
|
466
|
+
safeResolve({
|
|
467
|
+
result: '',
|
|
468
|
+
error: 'HARD_TIMEOUT',
|
|
469
|
+
rawOutput: currentStdout + currentStderr,
|
|
470
|
+
});
|
|
471
|
+
}, hardTimeoutDelay);
|
|
472
|
+
}, customTimeoutMs);
|
|
473
|
+
currentChildRef.on('error', (err) => {
|
|
474
|
+
clearTimeout(timeout);
|
|
475
|
+
if (hardTimeoutTimer)
|
|
476
|
+
clearTimeout(hardTimeoutTimer);
|
|
477
|
+
cleanupTmpFiles();
|
|
478
|
+
safeResolve({ result: '', error: `SPAWN_ERROR: ${err.message}`, rawOutput: '' });
|
|
286
479
|
});
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
480
|
+
currentChildRef.on('close', async (code) => {
|
|
481
|
+
clearTimeout(timeout);
|
|
482
|
+
if (hardTimeoutTimer)
|
|
483
|
+
clearTimeout(hardTimeoutTimer);
|
|
484
|
+
const fullRaw = currentStdout + (currentStderr ? `\n\n--- STDERR ---\n${currentStderr}` : '');
|
|
485
|
+
// ─── Parser le JSON en premier (pour extraire api_error_status) ───
|
|
486
|
+
let jsonEnvelope = null;
|
|
487
|
+
const trimmedStdout = currentStdout.trim();
|
|
488
|
+
try {
|
|
489
|
+
jsonEnvelope = JSON.parse(trimmedStdout);
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
const lastBrace = trimmedStdout.lastIndexOf('}');
|
|
493
|
+
const firstBrace = trimmedStdout.lastIndexOf('{', lastBrace);
|
|
494
|
+
if (firstBrace !== -1 && lastBrace !== -1) {
|
|
495
|
+
try {
|
|
496
|
+
jsonEnvelope = JSON.parse(trimmedStdout.substring(firstBrace, lastBrace + 1));
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
// Ignored
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// ─── Fallback retry ─────────────────────────────────────────────────
|
|
504
|
+
// Sur 401/429/5xx, on tue l'arbre du process courant via killProcessTree
|
|
505
|
+
// (taskkill /F /T sur Windows pour ne pas laisser claude.exe orphelin),
|
|
506
|
+
// puis on respawn un nouveau noeud avec --resume + le token fallback
|
|
507
|
+
// suivant (AUTH_FALLBACK_1 → 2 → 3).
|
|
508
|
+
// ───────────────────────────────────────────────────────────────────
|
|
509
|
+
const FALLBACK_RETRY_ENABLED = true;
|
|
510
|
+
const isRetryable = isRetryableError(currentStderr, jsonEnvelope);
|
|
511
|
+
const hasRetryableStatus = jsonEnvelope !== null &&
|
|
512
|
+
(jsonEnvelope.api_error_status === 401 ||
|
|
513
|
+
jsonEnvelope.api_error_status === 429 ||
|
|
514
|
+
jsonEnvelope.api_error_status === 500 ||
|
|
515
|
+
jsonEnvelope.api_error_status === 502 ||
|
|
516
|
+
jsonEnvelope.api_error_status === 503);
|
|
517
|
+
const isFailure = FALLBACK_RETRY_ENABLED && ((code !== 0 && isRetryable) || hasRetryableStatus);
|
|
518
|
+
if (isFailure) {
|
|
519
|
+
if (retryCount < maxRetries) {
|
|
520
|
+
triggerRetry(retryCount + 1);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
if (!options.silent) {
|
|
525
|
+
process.stderr.write(`\n\x1b[41m\x1b[37m[ClaudeRunner] ❌ Tous les tokens fallback épuisés. Error retryable finale.\x1b[0m\n`);
|
|
526
|
+
}
|
|
527
|
+
if (currentChildRef?.pid) {
|
|
528
|
+
void updateProcessStatus(currentChildRef.pid, 'failed', code, options.configPath);
|
|
529
|
+
}
|
|
530
|
+
cleanupTmpFiles();
|
|
531
|
+
safeResolve({
|
|
532
|
+
result: '',
|
|
533
|
+
error: 'RETRYABLE_ERROR_ALL_FALLBACKS_EXHAUSTED',
|
|
534
|
+
rawOutput: fullRaw,
|
|
535
|
+
});
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
cleanupTmpFiles();
|
|
540
|
+
try {
|
|
541
|
+
if (jsonEnvelope) {
|
|
542
|
+
let foundSessionId = currentSessionId;
|
|
543
|
+
if (jsonEnvelope.session_id && agentName) {
|
|
544
|
+
foundSessionId = jsonEnvelope.session_id;
|
|
545
|
+
currentSessionId = foundSessionId;
|
|
546
|
+
await saveSessionId(agentName, jsonEnvelope.session_id, options.configPath, 'claude');
|
|
547
|
+
if (currentChildRef?.pid) {
|
|
548
|
+
void linkSessionToPid(jsonEnvelope.session_id, currentChildRef.pid, options.configPath);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (currentChildRef?.pid) {
|
|
552
|
+
void updateProcessStatus(currentChildRef.pid, code === 0 ? 'done' : 'failed', code ?? null, options.configPath);
|
|
553
|
+
}
|
|
554
|
+
return safeResolve({
|
|
555
|
+
result: jsonEnvelope.reply ||
|
|
556
|
+
jsonEnvelope.result ||
|
|
557
|
+
currentStdout.trim(),
|
|
558
|
+
sessionId: foundSessionId,
|
|
559
|
+
rawOutput: currentStdout,
|
|
560
|
+
model: modelUsed ?? undefined,
|
|
561
|
+
nickname: originalModel !== modelUsed ? originalModel : undefined,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
if (code === 0) {
|
|
565
|
+
if (currentChildRef?.pid) {
|
|
566
|
+
void updateProcessStatus(currentChildRef.pid, 'done', code, options.configPath);
|
|
567
|
+
}
|
|
568
|
+
return safeResolve({
|
|
569
|
+
result: currentStdout.trim(),
|
|
570
|
+
sessionId: currentSessionId,
|
|
571
|
+
rawOutput: currentStdout,
|
|
572
|
+
model: modelUsed ?? undefined,
|
|
573
|
+
nickname: originalModel !== modelUsed ? originalModel : undefined,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
if (currentChildRef?.pid) {
|
|
577
|
+
void updateProcessStatus(currentChildRef.pid, 'failed', code, options.configPath);
|
|
578
|
+
}
|
|
579
|
+
safeResolve({
|
|
580
|
+
result: '',
|
|
581
|
+
error: code !== 0 ? `EXIT_CODE_${code}` : 'JSON_PARSE_ERROR',
|
|
582
|
+
rawOutput: fullRaw,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
safeResolve({
|
|
587
|
+
result: '',
|
|
588
|
+
error: `INTERNAL_ERROR: ${error instanceof Error ? error.message : String(error)}`,
|
|
589
|
+
rawOutput: fullRaw,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
294
592
|
});
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
cleanupTmpFiles();
|
|
299
|
-
resolve({ result: '', error: err.message, rawOutput: '' });
|
|
593
|
+
};
|
|
594
|
+
// ─── Démarrage initial avec le primary token ───
|
|
595
|
+
spawnWithToken(getTokenForIndex(0));
|
|
300
596
|
});
|
|
597
|
+
};
|
|
598
|
+
return withSpan('claude.runAgent', runImpl, {
|
|
599
|
+
agentName: agentName || '',
|
|
600
|
+
model: modelUsed || '',
|
|
601
|
+
runner: 'claude',
|
|
301
602
|
});
|
|
302
603
|
}
|
|
303
604
|
}
|