overmind-mcp 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -107
- package/assets/overmind_mcp_pro_banner_v3.png +0 -0
- package/dist/bin/cli.js +7 -438
- 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 +2 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +1 -31
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +27 -19
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/sessions.d.ts +2 -3
- package/dist/lib/sessions.d.ts.map +1 -1
- package/dist/lib/sessions.js +25 -94
- package/dist/lib/sessions.js.map +1 -1
- package/dist/memory/MemoryFactory.d.ts +1 -15
- package/dist/memory/MemoryFactory.d.ts.map +1 -1
- package/dist/memory/MemoryFactory.js +3 -53
- package/dist/memory/MemoryFactory.js.map +1 -1
- package/dist/memory/PostgresMemoryProvider.d.ts +0 -7
- package/dist/memory/PostgresMemoryProvider.d.ts.map +1 -1
- package/dist/memory/PostgresMemoryProvider.js +105 -180
- package/dist/memory/PostgresMemoryProvider.js.map +1 -1
- package/dist/server.d.ts +0 -41
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +26 -152
- package/dist/server.js.map +1 -1
- package/dist/services/AgentManager.d.ts.map +1 -1
- package/dist/services/AgentManager.js +11 -67
- package/dist/services/AgentManager.js.map +1 -1
- package/dist/services/ClaudeRunner.d.ts +0 -8
- package/dist/services/ClaudeRunner.d.ts.map +1 -1
- package/dist/services/ClaudeRunner.js +213 -514
- package/dist/services/ClaudeRunner.js.map +1 -1
- package/dist/services/ClineRunner.d.ts +0 -7
- package/dist/services/ClineRunner.d.ts.map +1 -1
- package/dist/services/ClineRunner.js +11 -80
- package/dist/services/ClineRunner.js.map +1 -1
- package/dist/services/GeminiRunner.d.ts +0 -6
- package/dist/services/GeminiRunner.d.ts.map +1 -1
- package/dist/services/GeminiRunner.js +58 -285
- package/dist/services/GeminiRunner.js.map +1 -1
- package/dist/services/KiloRunner.d.ts +0 -12
- package/dist/services/KiloRunner.d.ts.map +1 -1
- package/dist/services/KiloRunner.js +57 -441
- package/dist/services/KiloRunner.js.map +1 -1
- package/dist/services/OpenClawRunner.d.ts +0 -7
- package/dist/services/OpenClawRunner.d.ts.map +1 -1
- package/dist/services/OpenClawRunner.js +11 -79
- package/dist/services/OpenClawRunner.js.map +1 -1
- package/dist/services/OpenCodeRunner.d.ts +0 -7
- package/dist/services/OpenCodeRunner.d.ts.map +1 -1
- package/dist/services/OpenCodeRunner.js +11 -79
- package/dist/services/OpenCodeRunner.js.map +1 -1
- package/dist/services/QwenRunner.d.ts +19 -0
- package/dist/services/QwenRunner.d.ts.map +1 -0
- package/dist/services/QwenRunner.js +87 -0
- package/dist/services/QwenRunner.js.map +1 -0
- package/dist/services/TraeRunner.d.ts +19 -0
- package/dist/services/TraeRunner.d.ts.map +1 -0
- package/dist/services/TraeRunner.js +88 -0
- package/dist/services/TraeRunner.js.map +1 -0
- package/dist/tools/config_example.d.ts +4 -7
- package/dist/tools/config_example.d.ts.map +1 -1
- package/dist/tools/config_example.js +86 -191
- package/dist/tools/config_example.js.map +1 -1
- package/dist/tools/create_agent.d.ts +9 -15
- package/dist/tools/create_agent.d.ts.map +1 -1
- package/dist/tools/create_agent.js +9 -8
- package/dist/tools/create_agent.js.map +1 -1
- package/dist/tools/get_agent_configs.d.ts +4 -10
- 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 +18 -36
- 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 +8 -13
- 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 +4 -3
- 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 +4 -3
- 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 +5 -11
- 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 +16 -15
- package/dist/tools/run_agent.d.ts.map +1 -1
- package/dist/tools/run_agent.js +160 -128
- package/dist/tools/run_agent.js.map +1 -1
- package/dist/tools/run_claude.d.ts +3 -8
- package/dist/tools/run_claude.d.ts.map +1 -1
- package/dist/tools/run_claude.js +41 -59
- package/dist/tools/run_claude.js.map +1 -1
- package/dist/tools/run_cline.d.ts +4 -13
- package/dist/tools/run_cline.d.ts.map +1 -1
- package/dist/tools/run_cline.js +43 -55
- package/dist/tools/run_cline.js.map +1 -1
- package/dist/tools/run_gemini.d.ts +4 -13
- package/dist/tools/run_gemini.d.ts.map +1 -1
- package/dist/tools/run_gemini.js +33 -53
- package/dist/tools/run_gemini.js.map +1 -1
- package/dist/tools/run_kilo.d.ts +7 -17
- package/dist/tools/run_kilo.d.ts.map +1 -1
- package/dist/tools/run_kilo.js +41 -68
- package/dist/tools/run_kilo.js.map +1 -1
- package/dist/tools/run_openclaw.d.ts +4 -13
- package/dist/tools/run_openclaw.d.ts.map +1 -1
- package/dist/tools/run_openclaw.js +44 -52
- package/dist/tools/run_openclaw.js.map +1 -1
- package/dist/tools/run_opencode.d.ts +4 -13
- package/dist/tools/run_opencode.d.ts.map +1 -1
- package/dist/tools/run_opencode.js +39 -52
- package/dist/tools/run_opencode.js.map +1 -1
- package/dist/tools/run_qwen.d.ts +15 -0
- package/dist/tools/run_qwen.d.ts.map +1 -0
- package/dist/tools/run_qwen.js +61 -0
- package/dist/tools/run_qwen.js.map +1 -0
- package/dist/tools/run_trae.d.ts +15 -0
- package/dist/tools/run_trae.d.ts.map +1 -0
- package/dist/tools/run_trae.js +66 -0
- package/dist/tools/run_trae.js.map +1 -0
- package/dist/tools/shell_execute.d.ts +10 -0
- package/dist/tools/shell_execute.d.ts.map +1 -0
- package/dist/tools/shell_execute.js +24 -0
- package/dist/tools/shell_execute.js.map +1 -0
- package/package.json +25 -55
- package/.mcp.json.example +0 -21
- package/assets/overmind.png +0 -0
- package/bin/.gitkeep +0 -0
- package/bin/README.md +0 -34
- package/bin/install-overmind-unix.sh +0 -412
- package/bin/install-overmind-windows.bat +0 -407
- package/docs/README.md +0 -128
- package/docs/agent_control.md +0 -656
- package/docs/index.html +0 -493
- package/docs/library.html +0 -239
- package/docs/prompt.html +0 -1212
- package/docs/script.js +0 -428
- package/docs/styles.css +0 -2816
- package/scripts/auto-changelog.mjs +0 -132
- package/scripts/auto-install.mjs +0 -322
- package/scripts/install-dependencies.mjs +0 -462
- package/scripts/postgres-manager.mjs +0 -219
- package/scripts/postinstall.mjs +0 -538
- package/scripts/setup-overmind-db.mjs +0 -199
- package/scripts/setup-windows.js +0 -266
- package/scripts/setup.mjs +0 -397
- package/scripts/test-installation.mjs +0 -158
- package/scripts/uninstall.mjs +0 -238
|
@@ -1,154 +1,79 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
2
3
|
import path from 'path';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { CONFIG, resolveConfigPath, getWorkspaceDir } from '../lib/config.js';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { CONFIG, resolveConfigPath } 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
|
-
};
|
|
60
7
|
export class ClaudeRunner {
|
|
61
8
|
config;
|
|
62
9
|
timeoutMs;
|
|
63
10
|
constructor() {
|
|
64
11
|
this.config = CONFIG.CLAUDE;
|
|
65
|
-
this.timeoutMs = CONFIG.TIMEOUT_MS ||
|
|
12
|
+
this.timeoutMs = CONFIG.TIMEOUT_MS || 120000;
|
|
66
13
|
}
|
|
67
14
|
async runAgent(options) {
|
|
68
15
|
const { prompt, agentName, autoResume } = options;
|
|
69
16
|
let { sessionId } = options;
|
|
70
17
|
const { CORE, PERMISSIONS, PATHS } = this.config;
|
|
71
18
|
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
|
-
}
|
|
88
19
|
// --- Auto Resume ---
|
|
89
20
|
if (autoResume && agentName && !sessionId) {
|
|
90
|
-
const lastId = await getLastSessionId(agentName
|
|
21
|
+
const lastId = await getLastSessionId(agentName);
|
|
91
22
|
if (lastId) {
|
|
92
23
|
sessionId = lastId;
|
|
93
|
-
if (!options.silent) {
|
|
94
|
-
console.log(`[ClaudeRunner] Auto-resume session: ${sessionId}`);
|
|
95
|
-
}
|
|
96
24
|
}
|
|
97
25
|
}
|
|
98
|
-
let settingsPath = resolveConfigPath(PATHS.SETTINGS
|
|
26
|
+
let settingsPath = resolveConfigPath(PATHS.SETTINGS);
|
|
99
27
|
if (agentName) {
|
|
100
28
|
const settingsDir = path.dirname(PATHS.SETTINGS);
|
|
101
|
-
const specificSettingsPath = resolveConfigPath(path.join(settingsDir, `settings_${agentName}.json`)
|
|
29
|
+
const specificSettingsPath = resolveConfigPath(path.join(settingsDir, `settings_${agentName}.json`));
|
|
102
30
|
if (!fs.existsSync(specificSettingsPath)) {
|
|
103
31
|
return {
|
|
104
32
|
result: '',
|
|
105
|
-
error: `INVALID_AGENT
|
|
33
|
+
error: `INVALID_AGENT`,
|
|
106
34
|
};
|
|
107
35
|
}
|
|
108
36
|
settingsPath = specificSettingsPath;
|
|
109
37
|
}
|
|
110
|
-
|
|
38
|
+
const cwd = process.cwd();
|
|
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);
|
|
111
44
|
let tmpMcpPathToDelete = null;
|
|
112
|
-
let tmpSettingsPathToDelete = null;
|
|
113
45
|
let customTimeoutMs = this.timeoutMs;
|
|
46
|
+
// --- Isolation ---
|
|
114
47
|
if (agentName) {
|
|
115
48
|
try {
|
|
116
|
-
const agentSettingsPath = resolveConfigPath(path.join(path.dirname(PATHS.SETTINGS), `settings_${agentName}.json`)
|
|
49
|
+
const agentSettingsPath = resolveConfigPath(path.join(path.dirname(PATHS.SETTINGS), `settings_${agentName}.json`));
|
|
117
50
|
if (fs.existsSync(agentSettingsPath)) {
|
|
118
|
-
|
|
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;
|
|
51
|
+
const settings = JSON.parse(fs.readFileSync(agentSettingsPath, 'utf8'));
|
|
137
52
|
if (settings.env) {
|
|
53
|
+
// Mémoriser l'env configuré pour l'injection
|
|
138
54
|
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
|
+
}
|
|
139
71
|
if (settings.env.AGENT_TIMEOUT_MS || settings.env.API_TIMEOUT_MS) {
|
|
140
72
|
const timeoutValue = settings.env.AGENT_TIMEOUT_MS || settings.env.API_TIMEOUT_MS;
|
|
141
73
|
customTimeoutMs = parseInt(timeoutValue, 10) || customTimeoutMs;
|
|
142
74
|
}
|
|
143
|
-
if (!options.model && settings.env.MODEL) {
|
|
144
|
-
agentCustomEnv.ANTHROPIC_MODEL = settings.env.MODEL;
|
|
145
|
-
}
|
|
146
75
|
}
|
|
147
|
-
|
|
148
|
-
if (fs.existsSync(agentMcpPath)) {
|
|
149
|
-
mcpPath = agentMcpPath;
|
|
150
|
-
}
|
|
151
|
-
else if (settings.enableAllProjectMcpServers === false &&
|
|
76
|
+
if (settings.enableAllProjectMcpServers === false &&
|
|
152
77
|
Array.isArray(settings.enabledMcpjsonServers)) {
|
|
153
78
|
if (fs.existsSync(mcpPath)) {
|
|
154
79
|
const fullMcp = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
@@ -166,8 +91,30 @@ export class ClaudeRunner {
|
|
|
166
91
|
}
|
|
167
92
|
}
|
|
168
93
|
}
|
|
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
|
+
}
|
|
169
116
|
catch (e) {
|
|
170
|
-
console.error(`[ClaudeRunner]
|
|
117
|
+
console.error(`[ClaudeRunner] ⚠️ Erreur lors de la création du settings temporaire: ${e}`);
|
|
171
118
|
}
|
|
172
119
|
}
|
|
173
120
|
const argsSpawn = [];
|
|
@@ -175,430 +122,182 @@ export class ClaudeRunner {
|
|
|
175
122
|
argsSpawn.push(...CORE.split(' ').filter(Boolean));
|
|
176
123
|
if (PERMISSIONS)
|
|
177
124
|
argsSpawn.push(...PERMISSIONS.split(' ').filter(Boolean));
|
|
178
|
-
|
|
125
|
+
// DÉCISION IMPORTANTE: On n'ajoute pas de guillemets manuels ici, spawn s'en occupe
|
|
126
|
+
argsSpawn.push('--settings', finalSettingsPath);
|
|
179
127
|
argsSpawn.push('--mcp-config', mcpPath);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (!modelUsed && agentCustomEnv.ANTHROPIC_MODEL) {
|
|
183
|
-
modelUsed = agentCustomEnv.ANTHROPIC_MODEL;
|
|
128
|
+
if (sessionId) {
|
|
129
|
+
argsSpawn.push('--resume', sessionId);
|
|
184
130
|
}
|
|
185
|
-
//
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
131
|
+
// --- MODEL & NICKNAME FLAGS ---
|
|
132
|
+
const modelToUse = agentCustomEnv.ANTHROPIC_MODEL || 'claude-3-5-sonnet-20241022';
|
|
133
|
+
console.error(`[ClaudeRunner] 🛠️ Model override: ${modelToUse}`);
|
|
134
|
+
argsSpawn.push('--model', modelToUse);
|
|
135
|
+
if (agentCustomEnv.AGENT_NICKNAME) {
|
|
136
|
+
console.error(`[ClaudeRunner] 👤 Nickname: ${agentCustomEnv.AGENT_NICKNAME}`);
|
|
137
|
+
argsSpawn.push('--name', agentCustomEnv.AGENT_NICKNAME);
|
|
190
138
|
}
|
|
191
|
-
if (
|
|
192
|
-
argsSpawn.push('--resume', sessionId);
|
|
193
|
-
if (modelUsed)
|
|
194
|
-
argsSpawn.push('--model', modelUsed);
|
|
195
|
-
if (agentName)
|
|
139
|
+
else if (agentName) {
|
|
196
140
|
argsSpawn.push('--name', agentName);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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 });
|
|
141
|
+
}
|
|
142
|
+
return new Promise((resolve) => {
|
|
143
|
+
const cleanupTmpFiles = () => {
|
|
144
|
+
if (tmpMcpPathToDelete && fs.existsSync(tmpMcpPathToDelete)) {
|
|
145
|
+
try {
|
|
146
|
+
fs.unlinkSync(tmpMcpPathToDelete);
|
|
147
|
+
}
|
|
148
|
+
catch (_e) { /* intentionally empty */ }
|
|
250
149
|
}
|
|
251
|
-
|
|
252
|
-
|
|
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 };
|
|
150
|
+
if (tmpSettingsPathToDelete && fs.existsSync(tmpSettingsPathToDelete)) {
|
|
151
|
+
try {
|
|
152
|
+
fs.unlinkSync(tmpSettingsPathToDelete);
|
|
269
153
|
}
|
|
154
|
+
catch (_e) { /* intentionally empty */ }
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const isWin = process.platform === 'win32';
|
|
158
|
+
let command = 'claude';
|
|
159
|
+
let spawnArgs;
|
|
160
|
+
// Prepend persona if defined
|
|
161
|
+
let finalPrompt = prompt;
|
|
162
|
+
if (agentName) {
|
|
163
|
+
let agentPromptPath = resolveConfigPath(path.join(path.dirname(PATHS.SETTINGS), 'agents', `${agentName}.md`));
|
|
164
|
+
if (!fs.existsSync(agentPromptPath)) {
|
|
165
|
+
// Fallback: Check agents/ folder at the root level of settingsDir
|
|
166
|
+
agentPromptPath = resolveConfigPath(path.join(path.dirname(path.dirname(PATHS.SETTINGS)), 'agents', `${agentName}.md`));
|
|
167
|
+
}
|
|
168
|
+
if (fs.existsSync(agentPromptPath)) {
|
|
169
|
+
const systemPrompt = fs.readFileSync(agentPromptPath, 'utf8');
|
|
170
|
+
finalPrompt = `${systemPrompt}\n\n[USER QUERY]:\n${prompt}`;
|
|
270
171
|
}
|
|
271
|
-
// Aucun primary token trouvé — retourner null plutôt que de tomber dans les fallbacks
|
|
272
|
-
return null;
|
|
273
172
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
173
|
+
if (isWin) {
|
|
174
|
+
const claudePath = 'C:\\Users\\Deamon\\AppData\\Roaming\\npm\\claude.ps1';
|
|
175
|
+
if (fs.existsSync(claudePath)) {
|
|
176
|
+
command = 'powershell.exe';
|
|
177
|
+
spawnArgs = [
|
|
178
|
+
'-NoProfile',
|
|
179
|
+
'-ExecutionPolicy',
|
|
180
|
+
'Bypass',
|
|
181
|
+
'-File',
|
|
182
|
+
claudePath,
|
|
183
|
+
...argsSpawn,
|
|
184
|
+
'-p',
|
|
185
|
+
finalPrompt,
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
command = 'cmd.exe';
|
|
190
|
+
spawnArgs = ['/c', 'claude', ...argsSpawn, '-p', finalPrompt];
|
|
191
|
+
}
|
|
279
192
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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);
|
|
324
|
-
}
|
|
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`);
|
|
329
|
-
}
|
|
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
|
-
}
|
|
193
|
+
else {
|
|
194
|
+
spawnArgs = [...argsSpawn, '-p', finalPrompt];
|
|
195
|
+
}
|
|
196
|
+
if (agentName) {
|
|
197
|
+
const id = agentCustomEnv.AGENT_NICKNAME || agentName;
|
|
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`);
|
|
201
|
+
}
|
|
202
|
+
const child = spawn(command, spawnArgs, {
|
|
203
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
204
|
+
cwd: process.cwd(),
|
|
205
|
+
// shell: false explicitly (handled by command selection)
|
|
206
|
+
windowsHide: true,
|
|
207
|
+
env: {
|
|
208
|
+
...process.env,
|
|
209
|
+
...agentCustomEnv,
|
|
210
|
+
// Compatibilité Anthropic/Z.ai pour Claude Code standard
|
|
211
|
+
...(agentCustomEnv.ANTHROPIC_AUTH_TOKEN && !agentCustomEnv.ANTHROPIC_API_KEY
|
|
212
|
+
? { ANTHROPIC_API_KEY: agentCustomEnv.ANTHROPIC_AUTH_TOKEN }
|
|
213
|
+
: {}),
|
|
214
|
+
...(agentName ? { OVERMIND_AGENT_NAME: agentName } : {}),
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
let stdout = '';
|
|
218
|
+
let stderr = '';
|
|
219
|
+
if (child.stdout) {
|
|
220
|
+
child.stdout.on('data', (d) => {
|
|
221
|
+
const chunk = d.toString();
|
|
222
|
+
stdout += chunk;
|
|
223
|
+
if (agentName) {
|
|
224
|
+
const id = agentCustomEnv.AGENT_NICKNAME || agentName;
|
|
225
|
+
process.stderr.write(`[ClaudeRunner:${id}] ${chunk}`);
|
|
340
226
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (child.stderr) {
|
|
230
|
+
child.stderr.on('data', (d) => {
|
|
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}`);
|
|
348
236
|
}
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const timeout = setTimeout(() => {
|
|
240
|
+
child.kill();
|
|
241
|
+
cleanupTmpFiles();
|
|
242
|
+
resolve({ result: '', error: `TIMEOUT`, rawOutput: stdout });
|
|
243
|
+
}, customTimeoutMs);
|
|
244
|
+
child.on('close', async (code) => {
|
|
245
|
+
clearTimeout(timeout);
|
|
246
|
+
cleanupTmpFiles();
|
|
247
|
+
const detectError = (text) => {
|
|
248
|
+
const lower = text.toLowerCase();
|
|
249
|
+
if (lower.includes('api key') || lower.includes('auth') || lower.includes('401')) {
|
|
250
|
+
return '🔑 Erreur Auth/API Key (Clé invalide ou manquante)';
|
|
361
251
|
}
|
|
362
|
-
if (
|
|
363
|
-
|
|
364
|
-
killTimer = null;
|
|
252
|
+
if (lower.includes('quota') || lower.includes('exceeded') || lower.includes('429')) {
|
|
253
|
+
return '📊 Quota dépassé (API Key épuisée)';
|
|
365
254
|
}
|
|
366
|
-
|
|
367
|
-
|
|
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;
|
|
255
|
+
if (lower.includes('rate limit')) {
|
|
256
|
+
return '⏳ Rate limit atteint';
|
|
389
257
|
}
|
|
390
|
-
|
|
391
|
-
|
|
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`);
|
|
258
|
+
if (lower.includes('model') && lower.includes('404')) {
|
|
259
|
+
return '🤖 Modèle introuvable';
|
|
399
260
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
261
|
+
return null;
|
|
262
|
+
};
|
|
263
|
+
if (code !== 0 && !stdout) {
|
|
264
|
+
const specificError = detectError(stderr);
|
|
265
|
+
return resolve({
|
|
266
|
+
result: '',
|
|
267
|
+
error: specificError || `EXIT_CODE_${code}`,
|
|
268
|
+
rawOutput: stderr,
|
|
406
269
|
});
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
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
|
-
});
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
let jsonStr = stdout.trim();
|
|
273
|
+
const jsonStartIndex = jsonStr.indexOf('{');
|
|
274
|
+
const jsonLastIndex = jsonStr.lastIndexOf('}');
|
|
275
|
+
if (jsonStartIndex >= 0 && jsonLastIndex > jsonStartIndex) {
|
|
276
|
+
jsonStr = jsonStr.substring(jsonStartIndex, jsonLastIndex + 1);
|
|
444
277
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
278
|
+
const response = JSON.parse(jsonStr || '{}');
|
|
279
|
+
if (agentName && response.session_id) {
|
|
280
|
+
await saveSessionId(agentName, response.session_id);
|
|
448
281
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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: '' });
|
|
282
|
+
resolve({
|
|
283
|
+
result: response.result || JSON.stringify(response),
|
|
284
|
+
sessionId: response.session_id,
|
|
285
|
+
rawOutput: stdout,
|
|
479
286
|
});
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
}
|
|
287
|
+
}
|
|
288
|
+
catch (_error) {
|
|
289
|
+
const specificError = detectError(stdout) || detectError(stderr);
|
|
290
|
+
resolve({
|
|
291
|
+
result: '',
|
|
292
|
+
error: specificError || 'JSON_PARSE_ERROR',
|
|
293
|
+
rawOutput: stdout,
|
|
592
294
|
});
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
child.on('error', (err) => {
|
|
298
|
+
cleanupTmpFiles();
|
|
299
|
+
resolve({ result: '', error: err.message, rawOutput: '' });
|
|
596
300
|
});
|
|
597
|
-
};
|
|
598
|
-
return withSpan('claude.runAgent', runImpl, {
|
|
599
|
-
agentName: agentName || '',
|
|
600
|
-
model: modelUsed || '',
|
|
601
|
-
runner: 'claude',
|
|
602
301
|
});
|
|
603
302
|
}
|
|
604
303
|
}
|