overmind-mcp 2.5.0 → 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/InstallHelper.d.ts +14 -0
- package/dist/lib/InstallHelper.d.ts.map +1 -0
- package/dist/lib/InstallHelper.js +113 -0
- package/dist/lib/InstallHelper.js.map +1 -0
- 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/envUtils.d.ts +10 -0
- package/dist/lib/envUtils.d.ts.map +1 -0
- package/dist/lib/envUtils.js +24 -0
- package/dist/lib/envUtils.js.map +1 -0
- package/dist/lib/loadEnv.d.ts +2 -0
- package/dist/lib/loadEnv.d.ts.map +1 -0
- package/dist/lib/loadEnv.js +26 -0
- package/dist/lib/loadEnv.js.map +1 -0
- package/dist/lib/logger.d.ts +8 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +81 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/modelMapping.d.ts +18 -0
- package/dist/lib/modelMapping.d.ts.map +1 -0
- package/dist/lib/modelMapping.js +90 -0
- package/dist/lib/modelMapping.js.map +1 -0
- package/dist/lib/orchestration/dispatcher.d.ts +32 -0
- package/dist/lib/orchestration/dispatcher.d.ts.map +1 -0
- package/dist/lib/orchestration/dispatcher.js +113 -0
- package/dist/lib/orchestration/dispatcher.js.map +1 -0
- package/dist/lib/orchestration/swarm.d.ts +75 -0
- package/dist/lib/orchestration/swarm.d.ts.map +1 -0
- package/dist/lib/orchestration/swarm.js +238 -0
- package/dist/lib/orchestration/swarm.js.map +1 -0
- 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/lib/telemetry.d.ts +21 -0
- package/dist/lib/telemetry.d.ts.map +1 -0
- package/dist/lib/telemetry.js +30 -0
- package/dist/lib/telemetry.js.map +1 -0
- 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 +145 -41
- 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 +510 -281
- 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 +76 -22
- 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 +283 -69
- 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 +439 -70
- package/dist/services/KiloRunner.js.map +1 -1
- package/dist/services/{QwenRunner.d.ts → NousHermesRunner.d.ts} +12 -3
- package/dist/services/NousHermesRunner.d.ts.map +1 -0
- package/dist/services/NousHermesRunner.js +460 -0
- package/dist/services/NousHermesRunner.js.map +1 -0
- package/dist/services/OpenClawRunner.d.ts +7 -0
- package/dist/services/OpenClawRunner.d.ts.map +1 -1
- package/dist/services/OpenClawRunner.js +75 -22
- 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 +75 -22
- package/dist/services/OpenCodeRunner.js.map +1 -1
- package/dist/services/{TraeRunner.d.ts → QwenCliRunner.d.ts} +9 -2
- package/dist/services/QwenCliRunner.d.ts.map +1 -0
- package/dist/services/QwenCliRunner.js +155 -0
- package/dist/services/QwenCliRunner.js.map +1 -0
- package/dist/tools/agent_control.d.ts +2 -2
- 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 +13 -7
- 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/initialization_check.d.ts +2 -0
- package/dist/tools/initialization_check.d.ts.map +1 -0
- package/dist/tools/initialization_check.js +23 -0
- package/dist/tools/initialization_check.js.map +1 -0
- package/dist/tools/manage_agents.d.ts +34 -16
- 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 +13 -14
- 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_agent_cli.d.ts +21 -0
- package/dist/tools/run_agent_cli.d.ts.map +1 -0
- package/dist/tools/run_agent_cli.js +129 -0
- package/dist/tools/run_agent_cli.js.map +1 -0
- package/dist/tools/run_agents_parallel.d.ts +34 -0
- package/dist/tools/run_agents_parallel.d.ts.map +1 -0
- package/dist/tools/run_agents_parallel.js +28 -0
- package/dist/tools/run_agents_parallel.js.map +1 -0
- 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_hermes.d.ts +25 -0
- package/dist/tools/run_hermes.d.ts.map +1 -0
- package/dist/tools/run_hermes.js +93 -0
- package/dist/tools/run_hermes.js.map +1 -0
- 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/dist/tools/run_qwencli.d.ts +24 -0
- package/dist/tools/run_qwencli.d.ts.map +1 -0
- package/dist/tools/run_qwencli.js +59 -0
- package/dist/tools/run_qwencli.js.map +1 -0
- 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 +114 -83
- 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.map +0 -1
- package/dist/services/QwenRunner.js +0 -102
- package/dist/services/QwenRunner.js.map +0 -1
- package/dist/services/TraeRunner.d.ts.map +0 -1
- package/dist/services/TraeRunner.js +0 -103
- 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,108 +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 {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
+
});
|
|
27
36
|
}
|
|
28
37
|
else {
|
|
29
|
-
|
|
30
|
-
|
|
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);
|
|
31
54
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
+
};
|
|
35
60
|
export class ClaudeRunner {
|
|
36
61
|
config;
|
|
37
62
|
timeoutMs;
|
|
38
63
|
constructor() {
|
|
39
64
|
this.config = CONFIG.CLAUDE;
|
|
40
|
-
this.timeoutMs = CONFIG.TIMEOUT_MS ||
|
|
65
|
+
this.timeoutMs = CONFIG.TIMEOUT_MS || 900000;
|
|
41
66
|
}
|
|
42
67
|
async runAgent(options) {
|
|
43
68
|
const { prompt, agentName, autoResume } = options;
|
|
44
69
|
let { sessionId } = options;
|
|
45
70
|
const { CORE, PERMISSIONS, PATHS } = this.config;
|
|
46
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
|
+
}
|
|
47
88
|
// --- Auto Resume ---
|
|
48
89
|
if (autoResume && agentName && !sessionId) {
|
|
49
|
-
const lastId = await getLastSessionId(agentName);
|
|
90
|
+
const lastId = await getLastSessionId(agentName, options.configPath, 'claude');
|
|
50
91
|
if (lastId) {
|
|
51
92
|
sessionId = lastId;
|
|
93
|
+
if (!options.silent) {
|
|
94
|
+
console.log(`[ClaudeRunner] Auto-resume session: ${sessionId}`);
|
|
95
|
+
}
|
|
52
96
|
}
|
|
53
97
|
}
|
|
54
|
-
let settingsPath = resolveConfigPath(PATHS.SETTINGS);
|
|
98
|
+
let settingsPath = resolveConfigPath(PATHS.SETTINGS, options.configPath);
|
|
55
99
|
if (agentName) {
|
|
56
100
|
const settingsDir = path.dirname(PATHS.SETTINGS);
|
|
57
|
-
const specificSettingsPath = resolveConfigPath(path.join(settingsDir, `settings_${agentName}.json`));
|
|
101
|
+
const specificSettingsPath = resolveConfigPath(path.join(settingsDir, `settings_${agentName}.json`), options.configPath);
|
|
58
102
|
if (!fs.existsSync(specificSettingsPath)) {
|
|
59
103
|
return {
|
|
60
104
|
result: '',
|
|
61
|
-
error: `INVALID_AGENT
|
|
105
|
+
error: `INVALID_AGENT: Agent "${agentName}" non trouvé.`,
|
|
62
106
|
};
|
|
63
107
|
}
|
|
64
108
|
settingsPath = specificSettingsPath;
|
|
65
109
|
}
|
|
66
|
-
|
|
67
|
-
const relativeSettings = path.relative(cwd, settingsPath);
|
|
68
|
-
if (!relativeSettings.startsWith('..') && !path.isAbsolute(relativeSettings)) {
|
|
69
|
-
settingsPath = relativeSettings.startsWith('./') ? relativeSettings : `./${relativeSettings}`;
|
|
70
|
-
}
|
|
71
|
-
let mcpPath = resolveConfigPath(PATHS.MCP);
|
|
110
|
+
let mcpPath = resolveConfigPath(PATHS.MCP, options.configPath);
|
|
72
111
|
let tmpMcpPathToDelete = null;
|
|
112
|
+
let tmpSettingsPathToDelete = null;
|
|
73
113
|
let customTimeoutMs = this.timeoutMs;
|
|
74
|
-
// --- Isolation ---
|
|
75
114
|
if (agentName) {
|
|
76
115
|
try {
|
|
77
|
-
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);
|
|
78
117
|
if (fs.existsSync(agentSettingsPath)) {
|
|
79
|
-
|
|
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;
|
|
80
137
|
if (settings.env) {
|
|
81
|
-
|
|
82
|
-
const interpolatedEnv = interpolateEnvVars(settings.env);
|
|
83
|
-
Object.assign(agentCustomEnv, interpolatedEnv);
|
|
84
|
-
// --- SMART NICKNAME FALLBACK ---
|
|
85
|
-
const currentModel = settings.env.ANTHROPIC_MODEL;
|
|
86
|
-
const isTechnicalModelId = currentModel && (currentModel.includes('claude') ||
|
|
87
|
-
currentModel.includes('gpt') ||
|
|
88
|
-
currentModel.includes('glm') ||
|
|
89
|
-
currentModel.includes('minimax') ||
|
|
90
|
-
currentModel.includes('deepseek') ||
|
|
91
|
-
currentModel.includes('moonshot'));
|
|
92
|
-
if (currentModel && !isTechnicalModelId) {
|
|
93
|
-
// 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
|
|
94
|
-
agentCustomEnv.AGENT_NICKNAME = currentModel;
|
|
95
|
-
// On utilise le modèle Sonnet par défaut ou la valeur configurée si elle semble valide
|
|
96
|
-
agentCustomEnv.ANTHROPIC_MODEL = (settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL && settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL.includes('claude'))
|
|
97
|
-
? settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL
|
|
98
|
-
: 'claude-3-5-sonnet-20241022';
|
|
99
|
-
}
|
|
138
|
+
Object.assign(agentCustomEnv, settings.env);
|
|
100
139
|
if (settings.env.AGENT_TIMEOUT_MS || settings.env.API_TIMEOUT_MS) {
|
|
101
140
|
const timeoutValue = settings.env.AGENT_TIMEOUT_MS || settings.env.API_TIMEOUT_MS;
|
|
102
141
|
customTimeoutMs = parseInt(timeoutValue, 10) || customTimeoutMs;
|
|
103
142
|
}
|
|
143
|
+
if (!options.model && settings.env.MODEL) {
|
|
144
|
+
agentCustomEnv.ANTHROPIC_MODEL = settings.env.MODEL;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const agentMcpPath = resolveConfigPath(path.join(path.dirname(PATHS.SETTINGS), `.mcp.${agentName}.json`));
|
|
148
|
+
if (fs.existsSync(agentMcpPath)) {
|
|
149
|
+
mcpPath = agentMcpPath;
|
|
104
150
|
}
|
|
105
|
-
if (settings.enableAllProjectMcpServers === false &&
|
|
151
|
+
else if (settings.enableAllProjectMcpServers === false &&
|
|
106
152
|
Array.isArray(settings.enabledMcpjsonServers)) {
|
|
107
153
|
if (fs.existsSync(mcpPath)) {
|
|
108
154
|
const fullMcp = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
@@ -120,40 +166,8 @@ export class ClaudeRunner {
|
|
|
120
166
|
}
|
|
121
167
|
}
|
|
122
168
|
}
|
|
123
|
-
catch (_e) {
|
|
124
|
-
// Warning
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
const relativeMcp = path.relative(cwd, mcpPath);
|
|
128
|
-
if (!relativeMcp.startsWith('..') && !path.isAbsolute(relativeMcp)) {
|
|
129
|
-
mcpPath = relativeMcp.startsWith('./') ? relativeMcp : `./${relativeMcp}`;
|
|
130
|
-
}
|
|
131
|
-
let tmpSettingsPathToDelete = null;
|
|
132
|
-
let finalSettingsPath = settingsPath;
|
|
133
|
-
if (agentCustomEnv.AGENT_NICKNAME) {
|
|
134
|
-
try {
|
|
135
|
-
// On crée un fichier settings temporaire pour substituer le surnom par un vrai modèle
|
|
136
|
-
// car le CLI Claude ne valide pas les surnoms dynamiques en interne
|
|
137
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
138
|
-
const tempSettings = JSON.parse(JSON.stringify(settings));
|
|
139
|
-
// IMPORTANT: Mettre à jour toutes les variables env avec les valeurs interpolées
|
|
140
|
-
// pour éviter que le CLI Claude ne reçoive les valeurs $VAR littérales
|
|
141
|
-
if (tempSettings.env) {
|
|
142
|
-
for (const [key, value] of Object.entries(agentCustomEnv)) {
|
|
143
|
-
if (key !== 'AGENT_NICKNAME' && key !== 'AGENT_TIMEOUT_MS' && key !== 'API_TIMEOUT_MS') {
|
|
144
|
-
tempSettings.env[key] = value;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
tempSettings.env.ANTHROPIC_MODEL = agentCustomEnv.ANTHROPIC_MODEL;
|
|
149
|
-
const tmpSettingsPath = path.join(os.tmpdir(), `settings-${agentName || 'agent'}-${Date.now()}.json`);
|
|
150
|
-
fs.writeFileSync(tmpSettingsPath, JSON.stringify(tempSettings, null, 2));
|
|
151
|
-
finalSettingsPath = tmpSettingsPath;
|
|
152
|
-
tmpSettingsPathToDelete = tmpSettingsPath;
|
|
153
|
-
console.error(`[ClaudeRunner] 📝 Settings temporaire créé avec variables interpolées`);
|
|
154
|
-
}
|
|
155
169
|
catch (e) {
|
|
156
|
-
console.error(`[ClaudeRunner]
|
|
170
|
+
console.error(`[ClaudeRunner] [WARN] Error processing agent settings: ${e}`);
|
|
157
171
|
}
|
|
158
172
|
}
|
|
159
173
|
const argsSpawn = [];
|
|
@@ -161,215 +175,430 @@ export class ClaudeRunner {
|
|
|
161
175
|
argsSpawn.push(...CORE.split(' ').filter(Boolean));
|
|
162
176
|
if (PERMISSIONS)
|
|
163
177
|
argsSpawn.push(...PERMISSIONS.split(' ').filter(Boolean));
|
|
164
|
-
|
|
165
|
-
argsSpawn.push('--settings', finalSettingsPath);
|
|
178
|
+
argsSpawn.push('--settings', settingsPath);
|
|
166
179
|
argsSpawn.push('--mcp-config', mcpPath);
|
|
167
|
-
|
|
168
|
-
|
|
180
|
+
argsSpawn.push('--output-format', 'json');
|
|
181
|
+
let modelUsed = options.model;
|
|
182
|
+
if (!modelUsed && agentCustomEnv.ANTHROPIC_MODEL) {
|
|
183
|
+
modelUsed = agentCustomEnv.ANTHROPIC_MODEL;
|
|
169
184
|
}
|
|
170
|
-
//
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
console.error(`[ClaudeRunner] 👤 Nickname: ${agentCustomEnv.AGENT_NICKNAME}`);
|
|
176
|
-
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);
|
|
177
190
|
}
|
|
178
|
-
|
|
191
|
+
if (sessionId)
|
|
192
|
+
argsSpawn.push('--resume', sessionId);
|
|
193
|
+
if (modelUsed)
|
|
194
|
+
argsSpawn.push('--model', modelUsed);
|
|
195
|
+
if (agentName)
|
|
179
196
|
argsSpawn.push('--name', agentName);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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 });
|
|
210
250
|
}
|
|
211
251
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
'Bypass',
|
|
230
|
-
'-File',
|
|
231
|
-
claudePath,
|
|
232
|
-
...argsSpawn,
|
|
233
|
-
'-p',
|
|
234
|
-
finalPrompt,
|
|
235
|
-
];
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
command = 'cmd.exe';
|
|
239
|
-
spawnArgs = ['/c', 'claude', ...argsSpawn, '-p', finalPrompt];
|
|
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 };
|
|
240
269
|
}
|
|
241
270
|
}
|
|
271
|
+
// Aucun primary token trouvé — retourner null plutôt que de tomber dans les fallbacks
|
|
272
|
+
return null;
|
|
242
273
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
console.error(`[ClaudeRunner] 🚀 Démarrage de l'agent ${id}...`);
|
|
249
|
-
// Debug: Log the prompt size
|
|
250
|
-
console.error(`[ClaudeRunner] 📏 Prompt Size: ${finalPrompt.length} chars`);
|
|
251
|
-
}
|
|
252
|
-
const child = spawn(command, spawnArgs, {
|
|
253
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
254
|
-
cwd: process.cwd(),
|
|
255
|
-
// shell: false explicitly (handled by command selection)
|
|
256
|
-
windowsHide: true,
|
|
257
|
-
env: {
|
|
258
|
-
...process.env,
|
|
259
|
-
...agentCustomEnv,
|
|
260
|
-
// Compatibilité Anthropic/Z.ai pour Claude Code standard
|
|
261
|
-
...(agentCustomEnv.ANTHROPIC_AUTH_TOKEN && !agentCustomEnv.ANTHROPIC_API_KEY
|
|
262
|
-
? { ANTHROPIC_API_KEY: agentCustomEnv.ANTHROPIC_AUTH_TOKEN }
|
|
263
|
-
: {}),
|
|
264
|
-
...(agentName ? { OVERMIND_AGENT_NAME: agentName } : {}),
|
|
265
|
-
},
|
|
266
|
-
});
|
|
267
|
-
// Register this process in the registry for agent_control visibility
|
|
268
|
-
if (child.pid) {
|
|
269
|
-
registerProcess(child.pid, {
|
|
270
|
-
agentName: agentName || 'unknown',
|
|
271
|
-
runner: 'claude',
|
|
272
|
-
}).catch(() => { });
|
|
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 };
|
|
273
279
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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);
|
|
283
309
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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);
|
|
287
324
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const chunk = d.toString();
|
|
293
|
-
stderr += chunk;
|
|
294
|
-
if (agentName) {
|
|
295
|
-
const id = agentCustomEnv.AGENT_NICKNAME || agentName;
|
|
296
|
-
process.stderr.write(`[ClaudeRunner:${id}:ERR] ${chunk}`);
|
|
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`);
|
|
297
329
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
child.on('close', async (code) => {
|
|
309
|
-
clearTimeout(timeout);
|
|
310
|
-
cleanupTmpFiles();
|
|
311
|
-
// Update process registry status
|
|
312
|
-
if (child.pid) {
|
|
313
|
-
const status = code === 0 ? 'done' : 'failed';
|
|
314
|
-
updateProcessStatus(child.pid, status, code).catch(() => { });
|
|
315
|
-
}
|
|
316
|
-
const detectError = (text) => {
|
|
317
|
-
const lower = text.toLowerCase();
|
|
318
|
-
if (lower.includes('api key') || lower.includes('auth') || lower.includes('401')) {
|
|
319
|
-
return '🔑 Erreur Auth/API Key (Clé invalide ou manquante)';
|
|
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
|
+
}
|
|
320
340
|
}
|
|
321
|
-
if (
|
|
322
|
-
|
|
341
|
+
if (tmpSettingsPathToDelete && fs.existsSync(tmpSettingsPathToDelete)) {
|
|
342
|
+
try {
|
|
343
|
+
fs.unlinkSync(tmpSettingsPathToDelete);
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// Ignored
|
|
347
|
+
}
|
|
323
348
|
}
|
|
324
|
-
|
|
325
|
-
|
|
349
|
+
};
|
|
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;
|
|
326
361
|
}
|
|
327
|
-
if (
|
|
328
|
-
|
|
362
|
+
if (killTimer) {
|
|
363
|
+
clearTimeout(killTimer);
|
|
364
|
+
killTimer = null;
|
|
329
365
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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,
|
|
338
406
|
});
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
+
});
|
|
346
414
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
+
});
|
|
429
|
+
}
|
|
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();
|
|
353
448
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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: '' });
|
|
358
479
|
});
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
+
}
|
|
366
592
|
});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
cleanupTmpFiles();
|
|
371
|
-
resolve({ result: '', error: err.message, rawOutput: '' });
|
|
593
|
+
};
|
|
594
|
+
// ─── Démarrage initial avec le primary token ───
|
|
595
|
+
spawnWithToken(getTokenForIndex(0));
|
|
372
596
|
});
|
|
597
|
+
};
|
|
598
|
+
return withSpan('claude.runAgent', runImpl, {
|
|
599
|
+
agentName: agentName || '',
|
|
600
|
+
model: modelUsed || '',
|
|
601
|
+
runner: 'claude',
|
|
373
602
|
});
|
|
374
603
|
}
|
|
375
604
|
}
|