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.
Files changed (158) hide show
  1. package/.mcp.json.example +21 -0
  2. package/README.md +107 -80
  3. package/assets/overmind.png +0 -0
  4. package/bin/.gitkeep +0 -0
  5. package/bin/README.md +34 -0
  6. package/bin/install-overmind-unix.sh +412 -0
  7. package/bin/install-overmind-windows.bat +407 -0
  8. package/dist/bin/cli.js +438 -7
  9. package/dist/bin/cli.js.map +1 -1
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +3 -2
  13. package/dist/index.js.map +1 -1
  14. package/dist/lib/config.d.ts +31 -1
  15. package/dist/lib/config.d.ts.map +1 -1
  16. package/dist/lib/config.js +19 -27
  17. package/dist/lib/config.js.map +1 -1
  18. package/dist/lib/sessions.d.ts +3 -2
  19. package/dist/lib/sessions.d.ts.map +1 -1
  20. package/dist/lib/sessions.js +94 -25
  21. package/dist/lib/sessions.js.map +1 -1
  22. package/dist/memory/MemoryFactory.d.ts +15 -1
  23. package/dist/memory/MemoryFactory.d.ts.map +1 -1
  24. package/dist/memory/MemoryFactory.js +53 -3
  25. package/dist/memory/MemoryFactory.js.map +1 -1
  26. package/dist/memory/PostgresMemoryProvider.d.ts +7 -0
  27. package/dist/memory/PostgresMemoryProvider.d.ts.map +1 -1
  28. package/dist/memory/PostgresMemoryProvider.js +180 -105
  29. package/dist/memory/PostgresMemoryProvider.js.map +1 -1
  30. package/dist/server.d.ts +41 -0
  31. package/dist/server.d.ts.map +1 -1
  32. package/dist/server.js +152 -26
  33. package/dist/server.js.map +1 -1
  34. package/dist/services/AgentManager.d.ts.map +1 -1
  35. package/dist/services/AgentManager.js +67 -11
  36. package/dist/services/AgentManager.js.map +1 -1
  37. package/dist/services/ClaudeRunner.d.ts +8 -0
  38. package/dist/services/ClaudeRunner.d.ts.map +1 -1
  39. package/dist/services/ClaudeRunner.js +514 -213
  40. package/dist/services/ClaudeRunner.js.map +1 -1
  41. package/dist/services/ClineRunner.d.ts +7 -0
  42. package/dist/services/ClineRunner.d.ts.map +1 -1
  43. package/dist/services/ClineRunner.js +80 -11
  44. package/dist/services/ClineRunner.js.map +1 -1
  45. package/dist/services/GeminiRunner.d.ts +6 -0
  46. package/dist/services/GeminiRunner.d.ts.map +1 -1
  47. package/dist/services/GeminiRunner.js +285 -58
  48. package/dist/services/GeminiRunner.js.map +1 -1
  49. package/dist/services/KiloRunner.d.ts +12 -0
  50. package/dist/services/KiloRunner.d.ts.map +1 -1
  51. package/dist/services/KiloRunner.js +441 -57
  52. package/dist/services/KiloRunner.js.map +1 -1
  53. package/dist/services/OpenClawRunner.d.ts +7 -0
  54. package/dist/services/OpenClawRunner.d.ts.map +1 -1
  55. package/dist/services/OpenClawRunner.js +79 -11
  56. package/dist/services/OpenClawRunner.js.map +1 -1
  57. package/dist/services/OpenCodeRunner.d.ts +7 -0
  58. package/dist/services/OpenCodeRunner.d.ts.map +1 -1
  59. package/dist/services/OpenCodeRunner.js +79 -11
  60. package/dist/services/OpenCodeRunner.js.map +1 -1
  61. package/dist/tools/config_example.d.ts +7 -4
  62. package/dist/tools/config_example.d.ts.map +1 -1
  63. package/dist/tools/config_example.js +191 -86
  64. package/dist/tools/config_example.js.map +1 -1
  65. package/dist/tools/create_agent.d.ts +15 -9
  66. package/dist/tools/create_agent.d.ts.map +1 -1
  67. package/dist/tools/create_agent.js +8 -9
  68. package/dist/tools/create_agent.js.map +1 -1
  69. package/dist/tools/get_agent_configs.d.ts +10 -4
  70. package/dist/tools/get_agent_configs.d.ts.map +1 -1
  71. package/dist/tools/get_agent_configs.js.map +1 -1
  72. package/dist/tools/manage_agents.d.ts +36 -18
  73. package/dist/tools/manage_agents.d.ts.map +1 -1
  74. package/dist/tools/manage_agents.js +2 -2
  75. package/dist/tools/manage_agents.js.map +1 -1
  76. package/dist/tools/manage_prompts.d.ts +13 -8
  77. package/dist/tools/manage_prompts.d.ts.map +1 -1
  78. package/dist/tools/manage_prompts.js.map +1 -1
  79. package/dist/tools/memory_runs.d.ts +3 -4
  80. package/dist/tools/memory_runs.d.ts.map +1 -1
  81. package/dist/tools/memory_runs.js.map +1 -1
  82. package/dist/tools/memory_search.d.ts +3 -4
  83. package/dist/tools/memory_search.d.ts.map +1 -1
  84. package/dist/tools/memory_search.js.map +1 -1
  85. package/dist/tools/memory_store.d.ts +11 -5
  86. package/dist/tools/memory_store.d.ts.map +1 -1
  87. package/dist/tools/memory_store.js.map +1 -1
  88. package/dist/tools/run_agent.d.ts +15 -16
  89. package/dist/tools/run_agent.d.ts.map +1 -1
  90. package/dist/tools/run_agent.js +128 -160
  91. package/dist/tools/run_agent.js.map +1 -1
  92. package/dist/tools/run_claude.d.ts +8 -3
  93. package/dist/tools/run_claude.d.ts.map +1 -1
  94. package/dist/tools/run_claude.js +59 -41
  95. package/dist/tools/run_claude.js.map +1 -1
  96. package/dist/tools/run_cline.d.ts +13 -4
  97. package/dist/tools/run_cline.d.ts.map +1 -1
  98. package/dist/tools/run_cline.js +55 -43
  99. package/dist/tools/run_cline.js.map +1 -1
  100. package/dist/tools/run_gemini.d.ts +13 -4
  101. package/dist/tools/run_gemini.d.ts.map +1 -1
  102. package/dist/tools/run_gemini.js +53 -33
  103. package/dist/tools/run_gemini.js.map +1 -1
  104. package/dist/tools/run_kilo.d.ts +17 -7
  105. package/dist/tools/run_kilo.d.ts.map +1 -1
  106. package/dist/tools/run_kilo.js +68 -41
  107. package/dist/tools/run_kilo.js.map +1 -1
  108. package/dist/tools/run_openclaw.d.ts +13 -4
  109. package/dist/tools/run_openclaw.d.ts.map +1 -1
  110. package/dist/tools/run_openclaw.js +52 -44
  111. package/dist/tools/run_openclaw.js.map +1 -1
  112. package/dist/tools/run_opencode.d.ts +13 -4
  113. package/dist/tools/run_opencode.d.ts.map +1 -1
  114. package/dist/tools/run_opencode.js +52 -39
  115. package/dist/tools/run_opencode.js.map +1 -1
  116. package/docs/README.md +128 -0
  117. package/docs/agent_control.md +656 -0
  118. package/docs/index.html +493 -0
  119. package/docs/library.html +239 -0
  120. package/docs/prompt.html +1212 -0
  121. package/docs/script.js +428 -0
  122. package/docs/styles.css +2816 -0
  123. package/package.json +55 -25
  124. package/scripts/auto-changelog.mjs +132 -0
  125. package/scripts/auto-install.mjs +322 -0
  126. package/scripts/install-dependencies.mjs +462 -0
  127. package/scripts/postgres-manager.mjs +219 -0
  128. package/scripts/postinstall.mjs +538 -0
  129. package/scripts/setup-overmind-db.mjs +199 -0
  130. package/scripts/setup-windows.js +266 -0
  131. package/scripts/setup.mjs +397 -0
  132. package/scripts/test-installation.mjs +158 -0
  133. package/scripts/uninstall.mjs +238 -0
  134. package/assets/overmind_mcp_pro_banner_v3.png +0 -0
  135. package/dist/services/QwenRunner.d.ts +0 -19
  136. package/dist/services/QwenRunner.d.ts.map +0 -1
  137. package/dist/services/QwenRunner.js +0 -87
  138. package/dist/services/QwenRunner.js.map +0 -1
  139. package/dist/services/TraeRunner.d.ts +0 -19
  140. package/dist/services/TraeRunner.d.ts.map +0 -1
  141. package/dist/services/TraeRunner.js +0 -88
  142. package/dist/services/TraeRunner.js.map +0 -1
  143. package/dist/tools/run_qwen.d.ts +0 -15
  144. package/dist/tools/run_qwen.d.ts.map +0 -1
  145. package/dist/tools/run_qwen.js +0 -61
  146. package/dist/tools/run_qwen.js.map +0 -1
  147. package/dist/tools/run_trae.d.ts +0 -15
  148. package/dist/tools/run_trae.d.ts.map +0 -1
  149. package/dist/tools/run_trae.js +0 -66
  150. package/dist/tools/run_trae.js.map +0 -1
  151. package/dist/tools/shell_execute.d.ts +0 -10
  152. package/dist/tools/shell_execute.d.ts.map +0 -1
  153. package/dist/tools/shell_execute.js +0 -24
  154. package/dist/tools/shell_execute.js.map +0 -1
  155. package/dist/tools/test_agent_control.d.ts +0 -2
  156. package/dist/tools/test_agent_control.d.ts.map +0 -1
  157. package/dist/tools/test_agent_control.js +0 -92
  158. 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 { spawn } from 'child_process';
5
- import { CONFIG, resolveConfigPath } from '../lib/config.js';
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 || 120000;
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
- 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);
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
- const settings = JSON.parse(fs.readFileSync(agentSettingsPath, 'utf8'));
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
- if (settings.enableAllProjectMcpServers === false &&
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] ⚠️ Erreur lors de la création du settings temporaire: ${e}`);
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
- // DÉCISION IMPORTANTE: On n'ajoute pas de guillemets manuels ici, spawn s'en occupe
126
- argsSpawn.push('--settings', finalSettingsPath);
178
+ argsSpawn.push('--settings', settingsPath);
127
179
  argsSpawn.push('--mcp-config', mcpPath);
128
- if (sessionId) {
129
- argsSpawn.push('--resume', sessionId);
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
- // --- 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);
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
- else if (agentName) {
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
- 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 */ }
149
- }
150
- if (tmpSettingsPathToDelete && fs.existsSync(tmpSettingsPathToDelete)) {
151
- try {
152
- fs.unlinkSync(tmpSettingsPathToDelete);
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}`;
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
- 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];
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
- 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`);
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
- 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}`);
226
- }
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}`);
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
- 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)';
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
- if (lower.includes('quota') || lower.includes('exceeded') || lower.includes('429')) {
253
- return '📊 Quota dépassé (API Key épuisée)';
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
- if (lower.includes('rate limit')) {
256
- return '⏳ Rate limit atteint';
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 (lower.includes('model') && lower.includes('404')) {
259
- return '🤖 Modèle introuvable';
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
- if (code !== 0 && !stdout) {
264
- const specificError = detectError(stderr);
265
- return resolve({
266
- result: '',
267
- error: specificError || `EXIT_CODE_${code}`,
268
- rawOutput: stderr,
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
- 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);
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
- const response = JSON.parse(jsonStr || '{}');
279
- if (agentName && response.session_id) {
280
- await saveSessionId(agentName, response.session_id);
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
- resolve({
283
- result: response.result || JSON.stringify(response),
284
- sessionId: response.session_id,
285
- rawOutput: stdout,
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
- catch (_error) {
289
- const specificError = detectError(stdout) || detectError(stderr);
290
- resolve({
291
- result: '',
292
- error: specificError || 'JSON_PARSE_ERROR',
293
- rawOutput: stdout,
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
- child.on('error', (err) => {
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
  }