overmind-mcp 2.8.3 → 2.8.7
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 +20 -20
- package/README.md +143 -143
- package/bin/launch.bat +40 -0
- package/bin/launch.js +78 -0
- package/bin/launch.sh +46 -0
- package/bin/overmind-pool.mjs +248 -248
- package/bin/restart_mcp.bat +3 -0
- package/bin/start_server.bat +3 -0
- package/bin/test_mcp.bat +4 -0
- package/dist/bin/cli.js +13 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/launch.js +78 -0
- package/dist/bridge/BridgeProxy.d.ts +52 -0
- package/dist/bridge/BridgeProxy.d.ts.map +1 -0
- package/dist/bridge/BridgeProxy.js +265 -0
- package/dist/bridge/BridgeProxy.js.map +1 -0
- package/dist/bridge/OverBridgeService.d.ts +96 -0
- package/dist/bridge/OverBridgeService.d.ts.map +1 -0
- package/dist/bridge/OverBridgeService.js +334 -0
- package/dist/bridge/OverBridgeService.js.map +1 -0
- package/dist/bridge/index.d.ts +11 -0
- package/dist/bridge/index.d.ts.map +1 -0
- package/dist/bridge/index.js +11 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge/types.d.ts +132 -0
- package/dist/bridge/types.d.ts.map +1 -0
- package/dist/bridge/types.js +19 -0
- package/dist/bridge/types.js.map +1 -0
- package/dist/bridge/utils.d.ts +48 -0
- package/dist/bridge/utils.d.ts.map +1 -0
- package/dist/bridge/utils.js +128 -0
- package/dist/bridge/utils.js.map +1 -0
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +13 -5
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/envUtils.d.ts +2 -0
- package/dist/lib/envUtils.d.ts.map +1 -1
- package/dist/lib/envUtils.js +13 -2
- package/dist/lib/envUtils.js.map +1 -1
- package/dist/lib/logger.js +1 -1
- package/dist/lib/orchestration/dispatcher.d.ts.map +1 -1
- package/dist/lib/orchestration/dispatcher.js +0 -1
- package/dist/lib/orchestration/dispatcher.js.map +1 -1
- package/dist/lib/processRegistry.d.ts.map +1 -1
- package/dist/lib/processRegistry.js +34 -21
- package/dist/lib/processRegistry.js.map +1 -1
- package/dist/memory/PostgresMemoryProvider.d.ts.map +1 -1
- package/dist/memory/PostgresMemoryProvider.js +14 -3
- package/dist/memory/PostgresMemoryProvider.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/dist/services/AgentManager.d.ts.map +1 -1
- package/dist/services/AgentManager.js +473 -97
- package/dist/services/AgentManager.js.map +1 -1
- package/dist/services/ClaudeRunner.d.ts.map +1 -1
- package/dist/services/ClaudeRunner.js.map +1 -1
- package/dist/services/GeminiRunner.d.ts +26 -2
- package/dist/services/GeminiRunner.d.ts.map +1 -1
- package/dist/services/GeminiRunner.js +146 -136
- package/dist/services/GeminiRunner.js.map +1 -1
- package/dist/services/KiloRunner.d.ts.map +1 -1
- package/dist/services/KiloRunner.js +6 -1
- package/dist/services/KiloRunner.js.map +1 -1
- package/dist/services/NousHermesRunner.d.ts +1 -0
- package/dist/services/NousHermesRunner.d.ts.map +1 -1
- package/dist/services/NousHermesRunner.js +476 -549
- package/dist/services/NousHermesRunner.js.map +1 -1
- package/dist/tools/agent_control.d.ts +1 -1
- package/dist/tools/agent_control.d.ts.map +1 -1
- package/dist/tools/agent_control.js +4 -3
- package/dist/tools/agent_control.js.map +1 -1
- package/dist/tools/config_example.d.ts +1 -0
- package/dist/tools/config_example.d.ts.map +1 -1
- package/dist/tools/config_example.js +45 -2
- package/dist/tools/config_example.js.map +1 -1
- package/dist/tools/create_agent.d.ts +1 -1
- package/dist/tools/manage_agents.d.ts +2 -2
- package/dist/tools/run_agent.d.ts +2 -1
- package/dist/tools/run_agent.d.ts.map +1 -1
- package/dist/tools/run_agent.js +30 -3
- package/dist/tools/run_agent.js.map +1 -1
- package/dist/tools/run_agents_parallel.d.ts +2 -1
- package/dist/tools/run_agents_parallel.d.ts.map +1 -1
- package/dist/tools/run_gemini.d.ts +13 -0
- package/dist/tools/run_gemini.d.ts.map +1 -1
- package/dist/tools/run_gemini.js +6 -2
- package/dist/tools/run_gemini.js.map +1 -1
- package/dist/tools/run_hermes.d.ts +1 -0
- package/dist/tools/run_hermes.d.ts.map +1 -1
- package/dist/tools/run_hermes.js +22 -15
- package/dist/tools/run_hermes.js.map +1 -1
- package/docs/agent-http-tutorial.md +524 -524
- package/docs/provider-config-map.md +444 -0
- package/package.json +8 -10
- package/scripts/_db_check.py +10 -0
- package/scripts/status_check.py +20 -0
|
@@ -3,43 +3,54 @@ import path from 'path';
|
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
4
|
import { exec } from 'child_process';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
|
-
import { CONFIG, resolveConfigPath } from '../lib/config.js';
|
|
6
|
+
import { CONFIG, resolveConfigPath, getWorkspaceDir } from '../lib/config.js';
|
|
7
7
|
import { getLastSessionId, saveSessionId } from '../lib/sessions.js';
|
|
8
|
+
import { linkSessionToPid } from '../lib/processRegistry.js';
|
|
8
9
|
import { interpolateEnvVars } from '../lib/envUtils.js';
|
|
9
10
|
import { withSpan } from '../lib/telemetry.js';
|
|
10
11
|
import { loadEnvQuietly } from '../lib/loadEnv.js';
|
|
11
12
|
import pino from 'pino';
|
|
12
13
|
import { registerProcess, appendOutput, updateProcessStatus, } from '../lib/processRegistry.js';
|
|
14
|
+
import { registerLiveAgent, appendLiveOutput, setLiveStatus, unregisterLiveAgent, } from '../lib/agent_lifecycle.js';
|
|
13
15
|
const execAsync = promisify(exec);
|
|
14
16
|
const logger = pino({ name: 'NousHermesRunner' });
|
|
15
17
|
// Sur Windows, child.kill() ne tue que le wrapper cmd.exe — le child réel devient
|
|
16
|
-
// orphelin. On utilise taskkill /F /T pour propager au sous-arbre complet.
|
|
18
|
+
// orphelin. On utilise taskkill /F /T pour propager le kill au sous-arbre complet.
|
|
17
19
|
const killProcessTree = (child) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// taskkill peut échouer si déjà mort — ignoré
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
try {
|
|
27
|
-
child.kill('SIGTERM');
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
if (!child || child.exitCode !== null || child.killed) {
|
|
22
|
+
resolve();
|
|
23
|
+
return;
|
|
28
24
|
}
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
let settled = false;
|
|
26
|
+
const finish = () => {
|
|
27
|
+
if (settled)
|
|
28
|
+
return;
|
|
29
|
+
settled = true;
|
|
30
|
+
resolve();
|
|
31
|
+
};
|
|
32
|
+
child.once('exit', finish);
|
|
33
|
+
if (process.platform === 'win32' && child.pid) {
|
|
34
|
+
exec(`taskkill /F /T /PID ${child.pid}`, () => {
|
|
35
|
+
// taskkill peut échouer si le process est déjà mort
|
|
36
|
+
});
|
|
31
37
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
child.kill('SIGKILL');
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
// Ignored
|
|
39
|
-
}
|
|
38
|
+
else {
|
|
39
|
+
try {
|
|
40
|
+
child.kill('SIGTERM');
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
catch { /* ignored */ }
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
if (child.exitCode === null && !child.killed) {
|
|
45
|
+
try {
|
|
46
|
+
child.kill('SIGKILL');
|
|
47
|
+
}
|
|
48
|
+
catch { /* ignored */ }
|
|
49
|
+
}
|
|
50
|
+
}, 2000);
|
|
51
|
+
}
|
|
52
|
+
setTimeout(finish, 5000);
|
|
53
|
+
});
|
|
43
54
|
};
|
|
44
55
|
/**
|
|
45
56
|
* Find hermes binary across platforms (Windows, Linux, macOS)
|
|
@@ -149,586 +160,502 @@ export class NousHermesRunner {
|
|
|
149
160
|
this.tempFiles = [];
|
|
150
161
|
}
|
|
151
162
|
async runAgent(options) {
|
|
152
|
-
|
|
163
|
+
try {
|
|
164
|
+
const result = await withSpan('hermes.runAgent', async (span) => {
|
|
165
|
+
span.setAttribute('agentName', options.agentName || '');
|
|
166
|
+
span.setAttribute('model', options.model || '');
|
|
167
|
+
span.setAttribute('runner', 'hermes');
|
|
168
|
+
return await this.runAgentInternal(options);
|
|
169
|
+
}, {
|
|
170
|
+
agentName: options.agentName || '',
|
|
171
|
+
model: options.model || '',
|
|
172
|
+
runner: 'hermes',
|
|
173
|
+
});
|
|
174
|
+
this.cleanupTempFiles();
|
|
175
|
+
if (options.agentName && result.sessionId) {
|
|
176
|
+
await saveSessionId(options.agentName, result.sessionId, options.configPath, 'hermes');
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
this.cleanupTempFiles();
|
|
182
|
+
logger.error({ error: error instanceof Error ? error.message : String(error), agentName: options.agentName }, 'Hermes runner failed');
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
153
185
|
}
|
|
154
186
|
async runAgentInternal(options) {
|
|
155
187
|
const { prompt, agentName, autoResume, silent } = options;
|
|
156
188
|
let { sessionId } = options;
|
|
157
|
-
// --- Load .env files first (before anything else) ---
|
|
158
189
|
const cwd = options.cwd || process.cwd();
|
|
190
|
+
const configPath = options.configPath || getWorkspaceDir();
|
|
191
|
+
// Load .env files FIRST
|
|
159
192
|
loadEnvQuietly(path.join(cwd, '.env'));
|
|
160
193
|
loadEnvQuietly(path.join(cwd, '../Workflow/.env'));
|
|
161
|
-
//
|
|
194
|
+
// Auto Resume
|
|
162
195
|
if (autoResume && agentName && !sessionId) {
|
|
163
|
-
const lastId = await getLastSessionId(agentName,
|
|
196
|
+
const lastId = await getLastSessionId(agentName, configPath, 'hermes');
|
|
164
197
|
if (lastId) {
|
|
165
198
|
sessionId = lastId;
|
|
199
|
+
if (!silent)
|
|
200
|
+
console.error(`[NousHermesRunner] Auto-resume session: ${sessionId}`);
|
|
166
201
|
}
|
|
167
202
|
}
|
|
203
|
+
const MAX_BUF = 10 * 1024 * 1024;
|
|
204
|
+
const timeoutMs = this.timeoutMs;
|
|
205
|
+
const HARD_TIMEOUT_MS = 60000;
|
|
206
|
+
// HERMES_HOME setup
|
|
207
|
+
const overmindHermesPath = path.resolve(cwd, '.overmind', 'hermes', agentName ? `agent_${agentName}` : 'central');
|
|
208
|
+
const overmindHermesSubPath = path.join(overmindHermesPath, '.hermes');
|
|
209
|
+
if (agentName && !fs.existsSync(overmindHermesPath)) {
|
|
210
|
+
return { result: '', error: `INVALID_AGENT: Agent Hermes "${agentName}" non trouvé.` };
|
|
211
|
+
}
|
|
212
|
+
// Load agent settings + MCP config (same pattern as ClaudeRunner)
|
|
213
|
+
let systemPrompt = '';
|
|
214
|
+
let resolvedModel;
|
|
215
|
+
let resolvedProvider;
|
|
168
216
|
const agentCustomEnv = {
|
|
169
217
|
...process.env,
|
|
170
|
-
PYTHONIOENCODING: 'utf-8',
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
TERM: 'emacs',
|
|
175
|
-
PROMPT_TOOLKIT_NO_INTERACTIVE: '1',
|
|
176
|
-
// Force non-interactive for prompt_toolkit
|
|
177
|
-
ANSICON: '1',
|
|
178
|
-
// Map OpenRouter key if needed
|
|
179
|
-
OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY || process.env.OVERMIND_EMBEDDING_KEY,
|
|
180
|
-
// Map NVIDIA NIM key
|
|
218
|
+
PYTHONIOENCODING: 'utf-8', PYTHONUTF8: '1', PYTHONUNBUFFERED: '1',
|
|
219
|
+
PYTHONLEGACYWINDOWSSTDIO: '1', TERM: 'emacs',
|
|
220
|
+
PROMPT_TOOLKIT_NO_INTERACTIVE: '1', ANSICON: '1',
|
|
221
|
+
OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY || '',
|
|
181
222
|
NVIDIA_API_KEY: process.env.NVIDIA_API_KEY || process.env.NVAPI_KEY,
|
|
182
223
|
NVIDIA_API_BASE: process.env.NVIDIA_API_BASE || 'https://integrate.api.nvidia.com/v1',
|
|
183
224
|
...(agentName ? { OVERMIND_AGENT_NAME: agentName } : {}),
|
|
225
|
+
// OVERMIND_AGENT_HOME tells Hermes (v0.13.0+) to read agent-specific .env FIRST
|
|
226
|
+
// get_env_value() in Hermes checks OVERMIND_AGENT_HOME/.hermes/.env before HERMES_HOME/.env
|
|
227
|
+
// This allows $VAR expansion done by Overmind to take precedence over gateway .env
|
|
228
|
+
...(agentName ? { OVERMIND_AGENT_HOME: path.resolve(cwd, '.overmind', 'hermes', `agent_${agentName}`) } : {}),
|
|
229
|
+
// GLM_API_KEY in spawn env — zai provider resolves credentials via os.environ.get("GLM_API_KEY")
|
|
230
|
+
// before checking .env files. This is the most reliable path for Z.AI tokens.
|
|
231
|
+
...(agentName ? { GLM_API_KEY: '' } : {}),
|
|
184
232
|
};
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const overmindHermesSubPath = path.join(overmindHermesPath, '.hermes');
|
|
188
|
-
if (!fs.existsSync(overmindHermesSubPath)) {
|
|
189
|
-
fs.mkdirSync(overmindHermesSubPath, { recursive: true });
|
|
190
|
-
}
|
|
191
|
-
// On définit l'environnement pour Hermes
|
|
192
|
-
// IMPORTANT: HERMES_HOME doit pointer vers le dossier contenant config.yaml
|
|
193
|
-
agentCustomEnv.HERMES_HOME = overmindHermesSubPath;
|
|
194
|
-
if (process.platform === 'win32') {
|
|
195
|
-
agentCustomEnv.USERPROFILE = overmindHermesPath;
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
agentCustomEnv.HOME = overmindHermesPath;
|
|
199
|
-
}
|
|
200
|
-
// ─── Pre-write API keys to HERMES_HOME/.env ───────────────────────────────
|
|
201
|
-
// Hermes (et son credential pool) lisent ~/.hermes/.env très tôt au démarrage,
|
|
202
|
-
// avant même que le credential pool soit initialisé. On écrit les clés
|
|
203
|
-
// critiques dans:
|
|
204
|
-
// 1. HERMES_HOME/.env (notre isolation)
|
|
205
|
-
// 2. ~/.hermes/.env (fallback pour l'init Hermes avant lecture HERMES_HOME)
|
|
206
|
-
const writeHermesDotEnv = (dotEnvPath) => {
|
|
207
|
-
const dotEnvEntries = [];
|
|
208
|
-
const dotEnvKeys = [
|
|
209
|
-
'MINIMAXI_API_KEY',
|
|
210
|
-
'MINIMAX_API_KEY',
|
|
211
|
-
'ANTHROPIC_AUTH_TOKEN',
|
|
212
|
-
'ANTHROPIC_AUTH_TOKEN_1',
|
|
213
|
-
'ANTHROPIC_AUTH_TOKEN_2',
|
|
214
|
-
'ANTHROPIC_AUTH_TOKEN_3',
|
|
215
|
-
'ANTHROPIC_AUTH_TOKEN_4',
|
|
216
|
-
'MINIMAX_CN_API_KEY',
|
|
217
|
-
'OPENROUTER_API_KEY',
|
|
218
|
-
'OPENAI_API_KEY',
|
|
219
|
-
'Z_AI_API_KEY',
|
|
220
|
-
'GLM_API_KEY',
|
|
221
|
-
'Z_AI_API_KEY_2',
|
|
222
|
-
'MISTRAL_API_KEY',
|
|
223
|
-
'NVIDIA_API_KEY',
|
|
224
|
-
];
|
|
225
|
-
for (const key of dotEnvKeys) {
|
|
226
|
-
if (agentCustomEnv[key]) {
|
|
227
|
-
dotEnvEntries.push(`${key}=${agentCustomEnv[key]}`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
if (dotEnvEntries.length > 0) {
|
|
231
|
-
const existingContent = fs.existsSync(dotEnvPath)
|
|
232
|
-
? fs.readFileSync(dotEnvPath, 'utf8')
|
|
233
|
-
: '';
|
|
234
|
-
const newContent = dotEnvEntries.join('\n') + '\n';
|
|
235
|
-
const finalContent = existingContent ? newContent + existingContent : newContent;
|
|
236
|
-
fs.writeFileSync(dotEnvPath, finalContent, 'utf8');
|
|
237
|
-
if (!silent)
|
|
238
|
-
console.error(`[NousHermesRunner] Wrote ${dotEnvEntries.length} keys to ${dotEnvPath}`);
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
let systemPrompt = '';
|
|
233
|
+
let tmpSettingsPath = null;
|
|
234
|
+
let tmpMcpPath = null;
|
|
242
235
|
if (agentName) {
|
|
236
|
+
const agentPromptPath = path.join(overmindHermesSubPath, 'SOUL.md');
|
|
237
|
+
if (fs.existsSync(agentPromptPath)) {
|
|
238
|
+
systemPrompt = fs.readFileSync(agentPromptPath, 'utf8');
|
|
239
|
+
}
|
|
240
|
+
// Load environment variables from .claude/settings_<agentName>.json
|
|
243
241
|
try {
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
return {
|
|
259
|
-
result: '',
|
|
260
|
-
error: `INVALID_AGENT: Agent Hermes "${agentName}" non trouvé.
|
|
261
|
-
Veuillez utiliser 'create_agent' au préalable.
|
|
262
|
-
Fichier attendu: ${agentSettingsPath}
|
|
263
|
-
${availableAgents.length > 0 ? `Agents disponibles: ${availableAgents.join(', ')}` : 'Aucun agent disponible'}
|
|
264
|
-
`
|
|
265
|
-
.replace(/\s+/g, ' ')
|
|
266
|
-
.trim(),
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
const rawSettings = JSON.parse(fs.readFileSync(agentSettingsPath, 'utf8'));
|
|
270
|
-
const settings = interpolateEnvVars(rawSettings);
|
|
271
|
-
// Create a temporary settings file with interpolated values (same approach as ClaudeRunner)
|
|
272
|
-
// This ensures $VAR placeholders are resolved before Hermes reads them
|
|
273
|
-
const tmpSettingsPath = path.join(path.dirname(agentSettingsPath), `settings_${agentName}_tmp.json`);
|
|
274
|
-
fs.writeFileSync(tmpSettingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
275
|
-
this.tempFiles.push(tmpSettingsPath);
|
|
276
|
-
const interpolatedSettingsPath = tmpSettingsPath;
|
|
277
|
-
// Only use settings.model if it's a string (not a config object like {provider:"custom",...})
|
|
278
|
-
if (!options.model && typeof settings.model === 'string') {
|
|
279
|
-
options.model = settings.model;
|
|
280
|
-
}
|
|
281
|
-
// Extract ANTHROPIC_MODEL from env (used by some agents like sniperbot_analyst)
|
|
282
|
-
if (!options.model && settings.env?.ANTHROPIC_MODEL && !settings.env.ANTHROPIC_MODEL.startsWith('$')) {
|
|
283
|
-
options.model = settings.env.ANTHROPIC_MODEL;
|
|
284
|
-
}
|
|
285
|
-
// Extract ANTHROPIC_PROVIDER from env if present
|
|
286
|
-
if (!options.provider && settings.env?.ANTHROPIC_PROVIDER && !settings.env.ANTHROPIC_PROVIDER.startsWith('$')) {
|
|
287
|
-
options.provider = settings.env.ANTHROPIC_PROVIDER;
|
|
288
|
-
}
|
|
289
|
-
if (settings.env) {
|
|
290
|
-
// Fusion intelligente : préserver les clés critiques (API keys)
|
|
291
|
-
const criticalKeys = [
|
|
292
|
-
// OpenAI
|
|
293
|
-
'OPENAI_API_KEY',
|
|
294
|
-
'OPENAI_API_BASE',
|
|
295
|
-
'OPENAI_BASE_URL',
|
|
296
|
-
// Mistral
|
|
297
|
-
'MISTRAL_API_KEY',
|
|
298
|
-
'MISTRAL_API_KEY_2',
|
|
299
|
-
'MISTRAL_API_KEY_3',
|
|
300
|
-
'MISTRAL_API_KEY_4',
|
|
301
|
-
'MISTRAL_API_KEY_5',
|
|
302
|
-
'MISTRAL_API_KEY_6',
|
|
303
|
-
'MISTRAL_API_KEY_7',
|
|
304
|
-
// NVIDIA
|
|
305
|
-
'NVIDIA_API_KEY',
|
|
306
|
-
'NVAPI_KEY',
|
|
307
|
-
'NVIDIA_API_BASE',
|
|
308
|
-
// OpenRouter / Overmind
|
|
309
|
-
'OPENROUTER_API_KEY',
|
|
310
|
-
'OVERMIND_EMBEDDING_KEY',
|
|
311
|
-
// MiniMax
|
|
312
|
-
'MINIMAXI_API_KEY',
|
|
313
|
-
// ZhipuAI / GLM
|
|
314
|
-
'Z_AI_API_KEY',
|
|
315
|
-
// Google / Gemini
|
|
316
|
-
'GOOGLE_API_KEY',
|
|
317
|
-
'GEMINI_API_KEY',
|
|
318
|
-
// Anthropic
|
|
319
|
-
'ANTHROPIC_API_KEY',
|
|
320
|
-
'ANTHROPIC_AUTH_TOKEN',
|
|
321
|
-
];
|
|
322
|
-
const envCopy = { ...settings.env };
|
|
323
|
-
for (const key of criticalKeys) {
|
|
324
|
-
if (agentCustomEnv[key] && !envCopy[key]) {
|
|
325
|
-
envCopy[key] = agentCustomEnv[key];
|
|
242
|
+
const agentSettingsPath = resolveConfigPath(path.join(path.dirname(CONFIG.CLAUDE.PATHS.SETTINGS), `settings_${agentName}.json`), configPath);
|
|
243
|
+
if (fs.existsSync(agentSettingsPath)) {
|
|
244
|
+
let settings = JSON.parse(fs.readFileSync(agentSettingsPath, 'utf8'));
|
|
245
|
+
settings = interpolateEnvVars(settings);
|
|
246
|
+
// Create temporary settings file
|
|
247
|
+
const tempSettings = path.join(path.dirname(agentSettingsPath), `settings_${agentName}_tmp.json`);
|
|
248
|
+
fs.writeFileSync(tempSettings, JSON.stringify(settings, null, 2));
|
|
249
|
+
tmpSettingsPath = tempSettings;
|
|
250
|
+
this.tempFiles.push(tempSettings);
|
|
251
|
+
if (settings.env) {
|
|
252
|
+
Object.assign(agentCustomEnv, settings.env);
|
|
253
|
+
if (!options.model && settings.env.MODEL) {
|
|
254
|
+
agentCustomEnv.ANTHROPIC_MODEL = settings.env.MODEL;
|
|
326
255
|
}
|
|
327
256
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
'ANTHROPIC_AUTH_TOKEN_E': process.env.ANTHROPIC_AUTH_TOKEN_E,
|
|
337
|
-
'ANTHROPIC_BASE_URL_2': process.env.ANTHROPIC_BASE_URL_2,
|
|
338
|
-
'ANTHROPIC_BASE_URL_Y': process.env.ANTHROPIC_BASE_URL_Y,
|
|
339
|
-
'ANTHROPIC_BASE_URL_E': process.env.ANTHROPIC_BASE_URL_E,
|
|
340
|
-
'MINIMAXI_API_KEY_2': process.env.MINIMAXI_API_KEY_2,
|
|
341
|
-
'OPENAI_API_KEY_2': process.env.OPENAI_API_KEY_2,
|
|
342
|
-
'Z_AI_API_KEY_2': process.env.Z_AI_API_KEY_2,
|
|
343
|
-
};
|
|
344
|
-
for (const [key, value] of Object.entries(agentCustomEnv)) {
|
|
345
|
-
if (typeof value === 'string' && value.startsWith('$')) {
|
|
346
|
-
const resolved = placeholders[value.substring(1)];
|
|
347
|
-
if (resolved) {
|
|
348
|
-
agentCustomEnv[key] = resolved;
|
|
349
|
-
if (!silent)
|
|
350
|
-
console.error(`[NousHermesRunner] Resolved ${key}=${value.substring(1)} (resolved)`);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
257
|
+
// MCP configurations
|
|
258
|
+
const agentMcpPath = resolveConfigPath(path.join(path.dirname(CONFIG.CLAUDE.PATHS.SETTINGS), `.mcp.${agentName}.json`), configPath);
|
|
259
|
+
if (fs.existsSync(agentMcpPath)) {
|
|
260
|
+
// Write temporary mcp path
|
|
261
|
+
const tempMcp = path.join(path.dirname(agentSettingsPath), `mcp_${agentName}_tmp.json`);
|
|
262
|
+
fs.writeFileSync(tempMcp, fs.readFileSync(agentMcpPath, 'utf8'));
|
|
263
|
+
tmpMcpPath = tempMcp;
|
|
264
|
+
this.tempFiles.push(tempMcp);
|
|
353
265
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
// --- Load System Prompt (agents/agentName.md) ---
|
|
366
|
-
const agentPromptPath = resolveConfigPath(path.join(path.dirname(settingsDir), 'agents', `${agentName}.md`), options.configPath);
|
|
367
|
-
if (fs.existsSync(agentPromptPath)) {
|
|
368
|
-
systemPrompt = fs.readFileSync(agentPromptPath, 'utf8');
|
|
369
|
-
}
|
|
370
|
-
// --- MCP Config Translation (JSON -> YAML for Hermes) ---
|
|
371
|
-
const agentMcpPath = resolveConfigPath(path.join(path.dirname(settingsDir), `.mcp.${agentName}.json`), options.configPath);
|
|
372
|
-
if (fs.existsSync(agentMcpPath)) {
|
|
373
|
-
try {
|
|
374
|
-
const rawMcpConfig = JSON.parse(fs.readFileSync(agentMcpPath, 'utf8'));
|
|
375
|
-
const mcpConfig = interpolateEnvVars(rawMcpConfig);
|
|
376
|
-
const hermesConfigDir = overmindHermesSubPath;
|
|
377
|
-
if (!fs.existsSync(hermesConfigDir))
|
|
378
|
-
fs.mkdirSync(hermesConfigDir, { recursive: true });
|
|
379
|
-
const mcpJsonPath = path.join(hermesConfigDir, 'mcp.json');
|
|
380
|
-
const configYamlPath = path.join(hermesConfigDir, 'config.yaml');
|
|
381
|
-
// Helper pour convertir le format MCP JSON vers le format mcp.json Hermes (identique à Claude Desktop)
|
|
382
|
-
fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2), 'utf8');
|
|
383
|
-
// Generer aussi config.yaml (format snake_case attendu par Hermes)
|
|
384
|
-
let yamlContent = 'mcp_servers:\n';
|
|
385
|
-
for (const [name, server] of Object.entries(mcpConfig.mcpServers || {})) {
|
|
386
|
-
const s = server;
|
|
387
|
-
yamlContent += ` ${name}:\n`;
|
|
388
|
-
if (s.command)
|
|
389
|
-
yamlContent += ` command: "${s.command}"\n`;
|
|
390
|
-
if (s.args && Array.isArray(s.args)) {
|
|
391
|
-
yamlContent += ` args:\n`;
|
|
392
|
-
for (const arg of s.args) {
|
|
393
|
-
yamlContent += ` - "${String(arg).replace(/"/g, '\\"')}"\n`;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
if (s.env && typeof s.env === 'object') {
|
|
397
|
-
yamlContent += ` env:\n`;
|
|
398
|
-
for (const [k, v] of Object.entries(s.env)) {
|
|
399
|
-
yamlContent += ` ${k}: "${String(v).replace(/"/g, '\\"')}"\n`;
|
|
266
|
+
else if (settings.enableAllProjectMcpServers === false &&
|
|
267
|
+
Array.isArray(settings.enabledMcpjsonServers)) {
|
|
268
|
+
const projectMcpPath = resolveConfigPath(CONFIG.CLAUDE.PATHS.MCP, configPath);
|
|
269
|
+
if (fs.existsSync(projectMcpPath)) {
|
|
270
|
+
const fullMcp = JSON.parse(fs.readFileSync(projectMcpPath, 'utf8'));
|
|
271
|
+
const filteredMcp = { mcpServers: {} };
|
|
272
|
+
for (const serverName of settings.enabledMcpjsonServers) {
|
|
273
|
+
if (fullMcp.mcpServers && fullMcp.mcpServers[serverName]) {
|
|
274
|
+
filteredMcp.mcpServers[serverName] = fullMcp.mcpServers[serverName];
|
|
400
275
|
}
|
|
401
276
|
}
|
|
402
|
-
|
|
403
|
-
|
|
277
|
+
const tempMcp = path.join(path.dirname(agentSettingsPath), `mcp_${agentName}_tmp.json`);
|
|
278
|
+
fs.writeFileSync(tempMcp, JSON.stringify(filteredMcp, null, 2));
|
|
279
|
+
tmpMcpPath = tempMcp;
|
|
280
|
+
this.tempFiles.push(tempMcp);
|
|
404
281
|
}
|
|
405
|
-
fs.writeFileSync(configYamlPath, yamlContent, 'utf8');
|
|
406
|
-
// Remove the model config append - it uses 'provider: custom' which Hermes doesn't accept
|
|
407
|
-
// Instead, rely on MINIMAX_BASE_URL_OVERRIDE + MINIMAXI_API_KEY env vars for MiniMaxi
|
|
408
|
-
// The model config in config.yaml is not the right approach
|
|
409
|
-
if (!silent)
|
|
410
|
-
console.error(`[NousHermesRunner] 🛠️ Hermes configs (mcp.json & config.yaml) generated in ${hermesConfigDir}`);
|
|
411
|
-
}
|
|
412
|
-
catch (err) {
|
|
413
|
-
logger.error({ error: err }, 'Error translating MCP config');
|
|
414
282
|
}
|
|
415
283
|
}
|
|
416
284
|
}
|
|
417
285
|
catch (e) {
|
|
418
|
-
|
|
419
|
-
throw e;
|
|
420
|
-
logger.warn({ agentName, error: e }, 'Error processing agent settings');
|
|
286
|
+
logger.warn({ error: e }, `Failed to process settings/mcp configurations for Hermes agent ${agentName}`);
|
|
421
287
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
// Tronquer si nécessaire pour éviter les limites Windows (8191)
|
|
426
|
-
const MAX_PROMPT_LEN = 7000;
|
|
427
|
-
let cliPrompt = finalPrompt;
|
|
428
|
-
if (cliPrompt.length > MAX_PROMPT_LEN) {
|
|
429
|
-
console.warn(`[NousHermesRunner] ⚠️ Prompt tronqué de ${cliPrompt.length} à ${MAX_PROMPT_LEN} chars`);
|
|
430
|
-
cliPrompt = cliPrompt.substring(0, MAX_PROMPT_LEN);
|
|
431
|
-
}
|
|
432
|
-
// Hermes CLI modes:
|
|
433
|
-
// - `hermes -z <prompt>` : top-level one-shot (no banner, clean stdout, auto-exit)
|
|
434
|
-
// - `hermes chat -q <prompt>` : query mode with banner (interactive)
|
|
435
|
-
// - `hermes chat -z <prompt>` : INVALID (subcommand doesn't accept -z)
|
|
436
|
-
// We use top-level `-z` for runner mode (clean output, auto-exit).
|
|
437
|
-
const cleanArgs = ['-z', cliPrompt];
|
|
438
|
-
// --- Model & Provider selection ---
|
|
439
|
-
const DEFAULT_MODEL = 'tencent/hy3-preview:free'; // Modèle OpenRouter gratuit
|
|
440
|
-
const originalModel = options.model || DEFAULT_MODEL;
|
|
441
|
-
// Guard: ensure model is always a string (not an object from settings.model)
|
|
442
|
-
const modelStr = typeof originalModel === 'string' ? originalModel : DEFAULT_MODEL;
|
|
443
|
-
// Don't use resolveKiloModel here - it adds provider prefix like "minimax/" which
|
|
444
|
-
// causes Hermes to route to OpenRouter instead of MiniMaxi
|
|
445
|
-
const model = modelStr;
|
|
446
|
-
const isNvidiaModel = model.includes('deepseek') || model.includes('nvidia');
|
|
447
|
-
const hasNvidiaKey = !!(agentCustomEnv.NVIDIA_API_KEY || agentCustomEnv.NVAPI_KEY);
|
|
448
|
-
const lowModel = model.toLowerCase();
|
|
449
|
-
const isOpenAIModel = lowModel.includes('gpt') ||
|
|
450
|
-
lowModel.includes('o1') ||
|
|
451
|
-
lowModel.includes('o3');
|
|
452
|
-
const hasOpenAIKey = !!agentCustomEnv.OPENAI_API_KEY;
|
|
453
|
-
const isMiniMaxModel = lowModel.includes('minimax') || lowModel.includes('mini-max');
|
|
454
|
-
const hasMiniMaxKey = !!(agentCustomEnv.MINIMAXI_API_KEY ||
|
|
455
|
-
agentCustomEnv.ANTHROPIC_AUTH_TOKEN ||
|
|
456
|
-
agentCustomEnv.ANTHROPIC_AUTH_TOKEN_1 ||
|
|
457
|
-
agentCustomEnv.ANTHROPIC_AUTH_TOKEN_2 ||
|
|
458
|
-
agentCustomEnv.ANTHROPIC_AUTH_TOKEN_3 ||
|
|
459
|
-
agentCustomEnv.ANTHROPIC_AUTH_TOKEN_4);
|
|
460
|
-
const isGLMModel = lowModel.includes('glm');
|
|
461
|
-
const hasGLMKey = !!agentCustomEnv.Z_AI_API_KEY;
|
|
462
|
-
const isMistralModel = model.includes('mistral') || model.includes('codestral') || model.includes('devstral');
|
|
463
|
-
const hasMistralKey = !!agentCustomEnv.MISTRAL_API_KEY;
|
|
464
|
-
cleanArgs.push('--model', model);
|
|
465
|
-
if (isOpenAIModel && hasOpenAIKey) {
|
|
466
|
-
logger.info({ model, provider: 'openai' }, 'Using OpenAI provider');
|
|
467
|
-
cleanArgs.push('--provider', 'openai');
|
|
468
|
-
// Nettoyage des clés conflictuelles
|
|
469
|
-
delete agentCustomEnv.OPENROUTER_API_KEY;
|
|
470
|
-
delete agentCustomEnv.NVIDIA_API_KEY;
|
|
471
|
-
delete agentCustomEnv.NVAPI_KEY;
|
|
472
|
-
delete agentCustomEnv.MINIMAXI_API_KEY;
|
|
473
|
-
delete agentCustomEnv.Z_AI_API_KEY;
|
|
474
|
-
}
|
|
475
|
-
else if (isMiniMaxModel && hasMiniMaxKey) {
|
|
476
|
-
// Use minimax-cn provider which correctly uses api.minimaxi.com (NOT api.minimax.io)
|
|
477
|
-
// The minimax provider hardcodes api.minimax.io which is wrong for MiniMaxi
|
|
478
|
-
logger.info({ model, provider: 'minimax-cn' }, 'Using MiniMax via minimax-cn provider');
|
|
479
|
-
cleanArgs.push('--provider', 'minimax-cn');
|
|
480
|
-
// Write base_url to config.yaml later (see below, after config generation)
|
|
481
|
-
const resolvedMiniMaxKey = agentCustomEnv.ANTHROPIC_AUTH_TOKEN_4 ||
|
|
482
|
-
agentCustomEnv.ANTHROPIC_AUTH_TOKEN_3 ||
|
|
483
|
-
agentCustomEnv.ANTHROPIC_AUTH_TOKEN_2 ||
|
|
484
|
-
agentCustomEnv.ANTHROPIC_AUTH_TOKEN ||
|
|
485
|
-
agentCustomEnv.MINIMAXI_API_KEY ||
|
|
486
|
-
process.env.MINIMAXI_API_KEY;
|
|
487
|
-
if (resolvedMiniMaxKey) {
|
|
488
|
-
agentCustomEnv.ANTHROPIC_TOKEN = resolvedMiniMaxKey;
|
|
489
|
-
agentCustomEnv.ANTHROPIC_AUTH_TOKEN = resolvedMiniMaxKey;
|
|
490
|
-
agentCustomEnv.ANTHROPIC_AUTH_TOKEN_4 = resolvedMiniMaxKey;
|
|
491
|
-
agentCustomEnv.MINIMAXI_API_KEY = resolvedMiniMaxKey;
|
|
492
|
-
agentCustomEnv.MINIMAX_API_KEY = resolvedMiniMaxKey;
|
|
493
|
-
// minimax-cn provider reads MINIMAX_CN_API_KEY specifically
|
|
494
|
-
agentCustomEnv.MINIMAX_CN_API_KEY = resolvedMiniMaxKey;
|
|
495
|
-
// Force rewrite the auth.json so Hermes picks up the new key
|
|
288
|
+
// Load environment from isolated .env file (to allow overrides)
|
|
289
|
+
const envPath = path.join(overmindHermesSubPath, '.env');
|
|
290
|
+
if (fs.existsSync(envPath)) {
|
|
496
291
|
try {
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
if (
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
292
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
293
|
+
content.split('\n').forEach((line) => {
|
|
294
|
+
const trimmed = line.trim();
|
|
295
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
296
|
+
return;
|
|
297
|
+
const eqIdx = trimmed.indexOf('=');
|
|
298
|
+
if (eqIdx === -1)
|
|
299
|
+
return;
|
|
300
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
301
|
+
let value = trimmed.slice(eqIdx + 1).trim();
|
|
302
|
+
if (value.startsWith('"') && value.endsWith('"'))
|
|
303
|
+
value = value.slice(1, -1);
|
|
304
|
+
else if (value.startsWith("'") && value.endsWith("'"))
|
|
305
|
+
value = value.slice(1, -1);
|
|
306
|
+
if (key) {
|
|
307
|
+
agentCustomEnv[key] = value;
|
|
507
308
|
}
|
|
508
|
-
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
catch (e) {
|
|
312
|
+
logger.warn({ envPath, error: e }, 'Failed to read agent env file');
|
|
509
313
|
}
|
|
510
|
-
catch (e) { /* non-critical */ }
|
|
511
|
-
if (!silent)
|
|
512
|
-
console.error(`[NousHermesRunner] Set ANTHROPIC_AUTH_TOKEN_4 for MiniMax via minimax-cn`);
|
|
513
314
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const resolvedGLMKey = agentCustomEnv.Z_AI_API_KEY;
|
|
527
|
-
if (resolvedGLMKey) {
|
|
528
|
-
agentCustomEnv['GLM_API_KEY'] = resolvedGLMKey;
|
|
315
|
+
resolvedModel = agentCustomEnv.MODEL || agentCustomEnv.ANTHROPIC_MODEL;
|
|
316
|
+
resolvedProvider = agentCustomEnv.PROVIDER || agentCustomEnv.ANTHROPIC_PROVIDER;
|
|
317
|
+
if (resolvedProvider && (resolvedProvider.startsWith('http://') || resolvedProvider.startsWith('https://'))) {
|
|
318
|
+
if (resolvedProvider.includes('minimax')) {
|
|
319
|
+
resolvedProvider = 'minimax-cn';
|
|
320
|
+
}
|
|
321
|
+
else if (resolvedProvider.includes('z.ai') || resolvedProvider.includes('bigmodel')) {
|
|
322
|
+
resolvedProvider = 'zai';
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
resolvedProvider = undefined;
|
|
326
|
+
}
|
|
529
327
|
}
|
|
530
|
-
// Nettoyage des clés conflictuelles
|
|
531
|
-
delete agentCustomEnv.OPENROUTER_API_KEY;
|
|
532
|
-
delete agentCustomEnv.NVIDIA_API_KEY;
|
|
533
|
-
delete agentCustomEnv.NVAPI_KEY;
|
|
534
|
-
delete agentCustomEnv.OPENAI_API_KEY;
|
|
535
|
-
delete agentCustomEnv.MINIMAXI_API_KEY;
|
|
536
|
-
delete agentCustomEnv.ANTHROPIC_AUTH_TOKEN_4;
|
|
537
|
-
}
|
|
538
|
-
else if (isMistralModel && hasMistralKey) {
|
|
539
|
-
logger.info({ model, provider: 'mistral' }, 'Using Mistral provider');
|
|
540
|
-
cleanArgs.push('--provider', 'mistral');
|
|
541
|
-
// Nettoyage des clés conflictuelles
|
|
542
|
-
delete agentCustomEnv.OPENROUTER_API_KEY;
|
|
543
|
-
delete agentCustomEnv.NVIDIA_API_KEY;
|
|
544
|
-
delete agentCustomEnv.NVAPI_KEY;
|
|
545
|
-
delete agentCustomEnv.OPENAI_API_KEY;
|
|
546
328
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
329
|
+
const finalModel = options.model || resolvedModel || CONFIG.HERMES.DEFAULT_MODEL;
|
|
330
|
+
const finalPrompt = systemPrompt ? `${systemPrompt}\n\n[USER QUERY]:\n${prompt}` : prompt;
|
|
331
|
+
const cliPrompt = finalPrompt.length > 7000 ? finalPrompt.substring(0, 7000) : finalPrompt;
|
|
332
|
+
// Build CLI args: chat -q (persistent session, NOT -z oneshot)
|
|
333
|
+
// -z + --resume doesn't work — resume is ignored in oneshot mode
|
|
334
|
+
const cleanArgs = ['chat', '-q', cliPrompt, '-Q'];
|
|
335
|
+
cleanArgs.push('--model', finalModel);
|
|
336
|
+
if (options.provider || resolvedProvider) {
|
|
337
|
+
cleanArgs.push('--provider', options.provider || resolvedProvider);
|
|
555
338
|
}
|
|
556
|
-
|
|
557
|
-
const defaultHermesHome = path.join(process.env.HOME || process.env.USERPROFILE || '', '.hermes');
|
|
558
|
-
writeHermesDotEnv(path.join(overmindHermesSubPath, '.env'));
|
|
559
|
-
writeHermesDotEnv(path.join(defaultHermesHome, '.env'));
|
|
560
|
-
// --- Hermes-native flags: --resume, --mcp-config ---
|
|
561
|
-
// NOTE: --name is NOT supported by Hermes CLI v0.11.0+ (unrecognized argument error).
|
|
562
|
-
// Session naming works via --resume or --continue, not --name.
|
|
563
|
-
// --resume: continue existing session
|
|
564
|
-
if (sessionId) {
|
|
339
|
+
if (sessionId)
|
|
565
340
|
cleanArgs.push('--resume', sessionId);
|
|
341
|
+
// Token fallback setup (same as ClaudeRunner)
|
|
342
|
+
const FALLBACK_KEYS = ['AUTH_FALLBACK_1', 'AUTH_FALLBACK_2', 'AUTH_FALLBACK_3'];
|
|
343
|
+
const TOKEN_KEYS = ['ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_AUTH_TOKEN_E', 'GLM_API_KEY', 'Z_AI_API_KEY', 'MINIMAX_CN_API_KEY'];
|
|
344
|
+
const getAvailableFallbacks = () => {
|
|
345
|
+
const fb = [];
|
|
346
|
+
for (const k of FALLBACK_KEYS) {
|
|
347
|
+
const v = agentCustomEnv[k];
|
|
348
|
+
if (v && typeof v === 'string' && v.length > 0)
|
|
349
|
+
fb.push({ key: k, value: v });
|
|
350
|
+
}
|
|
351
|
+
return fb;
|
|
352
|
+
};
|
|
353
|
+
const getTokenForIndex = (idx) => {
|
|
354
|
+
if (idx === 0) {
|
|
355
|
+
for (const tk of TOKEN_KEYS) {
|
|
356
|
+
const v = agentCustomEnv[tk];
|
|
357
|
+
if (v && typeof v === 'string' && v.length > 0)
|
|
358
|
+
return { tokenEnvKey: tk, tokenValue: v };
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
const fb = getAvailableFallbacks();
|
|
363
|
+
return fb[idx - 1] ? { tokenEnvKey: fb[idx - 1].key, tokenValue: fb[idx - 1].value } : null;
|
|
364
|
+
};
|
|
365
|
+
const isRetryableError = (stderr) => {
|
|
366
|
+
const lower = stderr.toLowerCase();
|
|
367
|
+
return lower.includes('401') || lower.includes('unauthorized') ||
|
|
368
|
+
lower.includes('invalid api key') || lower.includes('authentication failed') ||
|
|
369
|
+
lower.includes('invalid authentication') || lower.includes('429') ||
|
|
370
|
+
lower.includes('rate limit') || lower.includes('quota exhausted') ||
|
|
371
|
+
lower.includes('limit exhausted') || lower.includes('503') ||
|
|
372
|
+
lower.includes('service unavailable') || lower.includes('500') ||
|
|
373
|
+
lower.includes('internal server error');
|
|
374
|
+
};
|
|
375
|
+
// HERMES_HOME setup
|
|
376
|
+
if (!fs.existsSync(overmindHermesSubPath))
|
|
377
|
+
fs.mkdirSync(overmindHermesSubPath, { recursive: true });
|
|
378
|
+
agentCustomEnv.HERMES_HOME = overmindHermesSubPath;
|
|
379
|
+
if (process.platform === 'win32')
|
|
380
|
+
agentCustomEnv.USERPROFILE = overmindHermesPath;
|
|
381
|
+
else
|
|
382
|
+
agentCustomEnv.HOME = overmindHermesPath;
|
|
383
|
+
// Write .env to HERMES_HOME (credential auto-discovery) - Cleaned to prevent duplicates
|
|
384
|
+
// EXCLUDE all OpenRouter keys — OpenRouter is managed internally by Overmind, Hermes must never see it
|
|
385
|
+
const credRegex = /(?:api_key|auth_token|base_url|endpoint|url)$/i;
|
|
386
|
+
const openRouterPrefixes = ['OPENROUTER', 'OVERMIND_EMBEDDING'];
|
|
387
|
+
const envMap = new Map();
|
|
388
|
+
const dotPath = path.join(overmindHermesSubPath, '.env');
|
|
389
|
+
if (fs.existsSync(dotPath)) {
|
|
390
|
+
try {
|
|
391
|
+
const existing = fs.readFileSync(dotPath, 'utf8');
|
|
392
|
+
existing.split('\n').forEach((line) => {
|
|
393
|
+
const trimmed = line.trim();
|
|
394
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
395
|
+
return;
|
|
396
|
+
const eqIdx = trimmed.indexOf('=');
|
|
397
|
+
if (eqIdx === -1)
|
|
398
|
+
return;
|
|
399
|
+
const k = trimmed.slice(0, eqIdx).trim();
|
|
400
|
+
let v = trimmed.slice(eqIdx + 1).trim();
|
|
401
|
+
if (v.startsWith('"') && v.endsWith('"'))
|
|
402
|
+
v = v.slice(1, -1);
|
|
403
|
+
else if (v.startsWith("'") && v.endsWith("'"))
|
|
404
|
+
v = v.slice(1, -1);
|
|
405
|
+
if (k) {
|
|
406
|
+
if (openRouterPrefixes.some(p => k.toUpperCase().startsWith(p)))
|
|
407
|
+
return;
|
|
408
|
+
envMap.set(k, v);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
catch (e) {
|
|
413
|
+
logger.warn({ envPath: dotPath, error: e }, 'Failed to read existing agent env file for deduplication');
|
|
414
|
+
}
|
|
566
415
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
416
|
+
for (const [k, v] of Object.entries(agentCustomEnv)) {
|
|
417
|
+
if (typeof v === 'string' && v.length > 0 && credRegex.test(k)) {
|
|
418
|
+
if (openRouterPrefixes.some(p => k.toUpperCase().startsWith(p)))
|
|
419
|
+
continue;
|
|
420
|
+
envMap.set(k, v);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const finalDotEntries = [];
|
|
424
|
+
for (const [k, v] of envMap.entries()) {
|
|
425
|
+
finalDotEntries.push(`${k}=${v}`);
|
|
573
426
|
}
|
|
574
|
-
|
|
575
|
-
//
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
427
|
+
fs.writeFileSync(dotPath, finalDotEntries.join('\n') + '\n', 'utf8');
|
|
428
|
+
// Generate config.yaml in HERMES_HOME (MCP servers)
|
|
429
|
+
if (tmpMcpPath && fs.existsSync(tmpMcpPath)) {
|
|
430
|
+
try {
|
|
431
|
+
const mc = JSON.parse(fs.readFileSync(tmpMcpPath, 'utf8'));
|
|
432
|
+
const yamlPath = path.join(overmindHermesSubPath, 'config.yaml');
|
|
433
|
+
// Preserve existing config.yaml (tts, llm, etc.) — merge mcp_servers only
|
|
434
|
+
let existingYaml = '';
|
|
435
|
+
if (fs.existsSync(yamlPath)) {
|
|
436
|
+
existingYaml = fs.readFileSync(yamlPath, 'utf8');
|
|
437
|
+
}
|
|
438
|
+
// Build new mcp_servers section
|
|
439
|
+
let newMcpSection = 'mcp_servers:\n';
|
|
440
|
+
for (const [name, srv] of Object.entries(mc.mcpServers || {})) {
|
|
441
|
+
const s = srv;
|
|
442
|
+
newMcpSection += ` ${name}:\n`;
|
|
443
|
+
if (s.url)
|
|
444
|
+
newMcpSection += ` url: "${s.url}"\n`;
|
|
445
|
+
if (s.command)
|
|
446
|
+
newMcpSection += ` command: "${s.command}"\n`;
|
|
447
|
+
}
|
|
448
|
+
// Merge: replace mcp_servers block in existing yaml or append
|
|
449
|
+
let finalYaml;
|
|
450
|
+
if (existingYaml.includes('mcp_servers:')) {
|
|
451
|
+
finalYaml = existingYaml.replace(/mcp_servers:\n([\s\S]*?)(?=\n\w|\n$|$)/, newMcpSection.trimEnd() + '\n');
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
finalYaml = existingYaml.trimEnd() + '\n' + newMcpSection;
|
|
455
|
+
}
|
|
456
|
+
fs.writeFileSync(yamlPath, finalYaml, 'utf8');
|
|
457
|
+
if (!silent)
|
|
458
|
+
console.error(`[NousHermesRunner] MCP config.yaml written to ${yamlPath}`);
|
|
459
|
+
}
|
|
460
|
+
catch (e) {
|
|
461
|
+
console.error(`[NousHermesRunner] config.yaml error: ${e}`);
|
|
462
|
+
}
|
|
581
463
|
}
|
|
464
|
+
// AbortSignal
|
|
465
|
+
if (options.signal?.aborted)
|
|
466
|
+
return Promise.reject(new Error('ABORTED'));
|
|
467
|
+
let currentChildRef = null;
|
|
582
468
|
return new Promise((resolve) => {
|
|
583
469
|
let resolved = false;
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
470
|
+
let retryCount = 0;
|
|
471
|
+
const maxRetries = getAvailableFallbacks().length + 1;
|
|
472
|
+
let currentSessionId = sessionId;
|
|
473
|
+
const safeResolve = (v) => { if (!resolved) {
|
|
474
|
+
resolved = true;
|
|
475
|
+
resolve(v);
|
|
476
|
+
} };
|
|
477
|
+
const cleanupTmpFiles = () => {
|
|
478
|
+
for (const f of [tmpSettingsPath, tmpMcpPath]) {
|
|
479
|
+
if (f && fs.existsSync(f)) {
|
|
480
|
+
try {
|
|
481
|
+
fs.unlinkSync(f);
|
|
482
|
+
}
|
|
483
|
+
catch { /* ignored */ }
|
|
484
|
+
}
|
|
588
485
|
}
|
|
589
486
|
};
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
487
|
+
const writeAuthJson = (tokenInfo) => {
|
|
488
|
+
if (!tokenInfo || !overmindHermesSubPath)
|
|
489
|
+
return;
|
|
490
|
+
try {
|
|
491
|
+
const authPath = path.join(overmindHermesSubPath, 'auth.json');
|
|
492
|
+
const auth = { version: 1, providers: {}, credential_pool: {} };
|
|
493
|
+
if (fs.existsSync(authPath))
|
|
494
|
+
Object.assign(auth, JSON.parse(fs.readFileSync(authPath, 'utf8')));
|
|
495
|
+
if (!auth.credential_pool)
|
|
496
|
+
auth.credential_pool = {};
|
|
497
|
+
const cleanCp = auth.credential_pool;
|
|
498
|
+
// Déterminer le provider effectif (CLI > settings)
|
|
499
|
+
const effectiveProvider = resolvedProvider || 'zai';
|
|
500
|
+
cleanCp[effectiveProvider] = [{
|
|
501
|
+
id: `${effectiveProvider}-default`, label: tokenInfo.tokenEnvKey, auth_type: 'api_key',
|
|
502
|
+
priority: 0, source: `env:${tokenInfo.tokenEnvKey}`, access_token: tokenInfo.tokenValue,
|
|
503
|
+
last_status: null, last_error_code: null,
|
|
504
|
+
base_url: agentCustomEnv['GLM_BASE_URL'] || agentCustomEnv['ANTHROPIC_BASE_URL'] || 'https://api.z.ai/api/coding/paas/v4',
|
|
505
|
+
request_count: 0,
|
|
506
|
+
}];
|
|
507
|
+
fs.writeFileSync(authPath, JSON.stringify(auth, null, 2), 'utf8');
|
|
508
|
+
// Écrire .env pour OVERMIND_AGENT_HOME — le provider effective détermine la clé d'env à écrire
|
|
509
|
+
// minimax-cn attend MINIMAX_CN_API_KEY, zai attend GLM_API_KEY (ou ANTHROPIC_AUTH_TOKEN)
|
|
510
|
+
const dotEnvPath = path.join(overmindHermesSubPath, '.env');
|
|
511
|
+
const baseUrl = agentCustomEnv['GLM_BASE_URL'] || agentCustomEnv['ANTHROPIC_BASE_URL'] || 'https://api.z.ai/api/coding/paas/v4';
|
|
512
|
+
const dotLines = [
|
|
513
|
+
`ANTHROPIC_PROVIDER=${effectiveProvider}`,
|
|
514
|
+
`ANTHROPIC_BASE_URL=${baseUrl}`,
|
|
515
|
+
];
|
|
516
|
+
// Le provider determine quelle variable d'env écrire dans .env pour seed credentials
|
|
517
|
+
if (effectiveProvider === 'minimax-cn') {
|
|
518
|
+
dotLines.unshift(`MINIMAX_CN_API_KEY=${tokenInfo.tokenValue}`);
|
|
519
|
+
}
|
|
520
|
+
else if (effectiveProvider === 'zai' || effectiveProvider === 'z-ai') {
|
|
521
|
+
dotLines.unshift(`GLM_API_KEY=${tokenInfo.tokenValue}`);
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
// Default: ANTHROPIC_AUTH_TOKEN
|
|
525
|
+
dotLines.unshift(`ANTHROPIC_AUTH_TOKEN=${tokenInfo.tokenValue}`);
|
|
526
|
+
}
|
|
527
|
+
fs.writeFileSync(dotEnvPath, dotLines.join('\n') + '\n', 'utf8');
|
|
619
528
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
529
|
+
catch (_e) { /* non-critical */ }
|
|
530
|
+
};
|
|
531
|
+
const spawnHermes = async (tokenInfo) => {
|
|
532
|
+
const spawnEnv = { ...process.env, ...agentCustomEnv };
|
|
533
|
+
if (tokenInfo) {
|
|
534
|
+
for (const tk of TOKEN_KEYS)
|
|
535
|
+
delete spawnEnv[tk];
|
|
536
|
+
let resolvedToken = tokenInfo.tokenValue;
|
|
537
|
+
if (resolvedToken.startsWith('$'))
|
|
538
|
+
resolvedToken = process.env[resolvedToken.slice(1)] || resolvedToken;
|
|
539
|
+
spawnEnv[tokenInfo.tokenEnvKey] = resolvedToken;
|
|
623
540
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
541
|
+
writeAuthJson(tokenInfo);
|
|
542
|
+
// BLOCK: OpenRouter is for embeddings only — never pass to Hermes for LLM inference
|
|
543
|
+
delete spawnEnv['OPENROUTER_API_KEY'];
|
|
544
|
+
delete spawnEnv['OPENROUTER_BASE_URL'];
|
|
545
|
+
delete spawnEnv['OVERMIND_EMBEDDING_KEY'];
|
|
546
|
+
const hermesBin = await findHermesBinary();
|
|
547
|
+
const child = spawn(hermesBin, cleanArgs, {
|
|
548
|
+
cwd, shell: false, windowsHide: true,
|
|
549
|
+
env: {
|
|
550
|
+
...spawnEnv,
|
|
551
|
+
HERMES_HOME: overmindHermesSubPath,
|
|
552
|
+
VIRTUAL_ENV: process.env.HERMES_AGENT_ROOT
|
|
553
|
+
? path.join(process.env.HERMES_AGENT_ROOT, 'venv')
|
|
554
|
+
: path.join(process.env.LOCALAPPDATA || '', 'hermes', 'hermes-agent', 'venv'),
|
|
555
|
+
PATH: `${process.env.HERMES_AGENT_ROOT || path.join(process.env.LOCALAPPDATA || '', 'hermes', 'hermes-agent', 'venv')};${process.env.PATH || ''}`,
|
|
556
|
+
},
|
|
635
557
|
});
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
safeResolve({
|
|
645
|
-
result: stdout.trim(),
|
|
646
|
-
error: 'ABORTED',
|
|
647
|
-
rawOutput: stdout + '\n\n' + stderr,
|
|
648
|
-
model,
|
|
649
|
-
nickname: originalModel !== model ? originalModel : undefined,
|
|
558
|
+
currentChildRef = child;
|
|
559
|
+
if (child.pid) {
|
|
560
|
+
void registerProcess(child.pid, { agentName: agentName || '', runner: 'hermes', configPath });
|
|
561
|
+
void registerLiveAgent({
|
|
562
|
+
pid: child.pid, runner: 'hermes', agentName: agentName || '',
|
|
563
|
+
sessionId: currentSessionId || '',
|
|
564
|
+
cleanupFn: async () => { await killProcessTree(child); },
|
|
565
|
+
childRef: child,
|
|
650
566
|
});
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
}
|
|
655
|
-
else {
|
|
656
|
-
options.signal.addEventListener('abort', onAbort, { once: true });
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
child.on('close', async (code) => {
|
|
660
|
-
clearTimeout(timeout);
|
|
661
|
-
if (child.pid)
|
|
662
|
-
void updateProcessStatus(child.pid, code === 0 ? 'done' : 'failed', code, options.configPath);
|
|
663
|
-
// Parse session ID from Hermes output (e.g. "Session: 20260515_204158_7093cd")
|
|
664
|
-
// This works even when Hermes exits with error, as the banner is still printed
|
|
665
|
-
let parsedSessionId = sessionId;
|
|
666
|
-
const sessionMatch = stdout.match(/Session:\s+(\S+)/);
|
|
667
|
-
if (sessionMatch) {
|
|
668
|
-
parsedSessionId = sessionMatch[1];
|
|
669
|
-
}
|
|
670
|
-
// Hermes exits code 2 on API errors (e.g. max_tokens > 40000).
|
|
671
|
-
// When stdout has content, return it even on non-zero exit — it's useful output.
|
|
672
|
-
if (code !== 0 && !stdout) {
|
|
673
|
-
return safeResolve({
|
|
674
|
-
result: '',
|
|
675
|
-
error: `EXIT_CODE_${code}`,
|
|
676
|
-
rawOutput: stderr || stdout,
|
|
677
|
-
sessionId: parsedSessionId,
|
|
678
|
-
model,
|
|
679
|
-
nickname: originalModel !== model ? originalModel : undefined,
|
|
567
|
+
child.once('exit', (code) => {
|
|
568
|
+
setLiveStatus(child.pid, code === 0 ? 'done' : 'failed', code ?? null);
|
|
569
|
+
void unregisterLiveAgent(child.pid);
|
|
680
570
|
});
|
|
681
571
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
572
|
+
let stdout = '';
|
|
573
|
+
let stderr = '';
|
|
574
|
+
child.stdout?.on('data', (d) => {
|
|
575
|
+
const chunk = d.toString();
|
|
576
|
+
if (child.pid) {
|
|
577
|
+
void appendOutput(child.pid, chunk, configPath);
|
|
578
|
+
void appendLiveOutput(child.pid, chunk);
|
|
579
|
+
}
|
|
580
|
+
if (stdout.length + chunk.length > MAX_BUF)
|
|
581
|
+
stdout = stdout.slice(-MAX_BUF);
|
|
582
|
+
else
|
|
583
|
+
stdout += chunk;
|
|
584
|
+
if (!silent && agentName)
|
|
585
|
+
process.stderr.write(`[Hermes:${agentName}] ${chunk}`);
|
|
688
586
|
});
|
|
587
|
+
child.stderr?.on('data', (d) => {
|
|
588
|
+
const chunk = d.toString();
|
|
589
|
+
if (stderr.length + chunk.length > MAX_BUF)
|
|
590
|
+
stderr = stderr.slice(-MAX_BUF);
|
|
591
|
+
else
|
|
592
|
+
stderr += chunk;
|
|
593
|
+
if (!silent && agentName)
|
|
594
|
+
process.stderr.write(`[Hermes:${agentName}:ERR] ${chunk}`);
|
|
595
|
+
});
|
|
596
|
+
const timer = setTimeout(() => {
|
|
597
|
+
if (child.stdin && !child.stdin.destroyed) {
|
|
598
|
+
try {
|
|
599
|
+
child.stdin.write('\n');
|
|
600
|
+
}
|
|
601
|
+
catch { /* ignore */ }
|
|
602
|
+
}
|
|
603
|
+
setTimeout(async () => {
|
|
604
|
+
await killProcessTree(child);
|
|
605
|
+
cleanupTmpFiles();
|
|
606
|
+
safeResolve({ result: '', error: 'HARD_TIMEOUT', rawOutput: stdout + stderr });
|
|
607
|
+
}, HARD_TIMEOUT_MS);
|
|
608
|
+
}, timeoutMs);
|
|
609
|
+
child.on('close', async (code) => {
|
|
610
|
+
clearTimeout(timer);
|
|
611
|
+
if (child.pid)
|
|
612
|
+
void updateProcessStatus(child.pid, code === 0 ? 'done' : 'failed', code, configPath);
|
|
613
|
+
const sessionMatch = stdout.match(/session_id:\s*(\S+)/i) ||
|
|
614
|
+
stderr.match(/session_id:\s*(\S+)/i) ||
|
|
615
|
+
stdout.match(/Session:\s*(\S+)/) ||
|
|
616
|
+
stderr.match(/Session:\s*(\S+)/);
|
|
617
|
+
if (sessionMatch)
|
|
618
|
+
currentSessionId = sessionMatch[1];
|
|
619
|
+
const retryable = isRetryableError(stderr) || isRetryableError(stdout);
|
|
620
|
+
if (code !== 0 && retryable && retryCount < maxRetries) {
|
|
621
|
+
retryCount++;
|
|
622
|
+
const ti = getTokenForIndex(retryCount);
|
|
623
|
+
if (!silent) {
|
|
624
|
+
process.stderr.write(`\n\x1b[41m\x1b[37m[NousHermesRunner] Retry ${retryCount}/${maxRetries} avec ${ti?.tokenEnvKey || 'UNKNOWN'}...\x1b[0m\n`);
|
|
625
|
+
}
|
|
626
|
+
await killProcessTree(child);
|
|
627
|
+
setImmediate(() => spawnHermes(ti));
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
cleanupTmpFiles();
|
|
631
|
+
if (currentSessionId && agentName) {
|
|
632
|
+
await saveSessionId(agentName, currentSessionId, configPath, 'hermes');
|
|
633
|
+
if (child.pid)
|
|
634
|
+
void linkSessionToPid(currentSessionId, child.pid, configPath);
|
|
635
|
+
}
|
|
636
|
+
if (code !== 0 && !stdout.trim()) {
|
|
637
|
+
safeResolve({ result: '', error: `EXIT_CODE_${code}`, rawOutput: stderr || stdout, sessionId: currentSessionId });
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
safeResolve({ result: stdout.trim(), sessionId: currentSessionId, rawOutput: stdout });
|
|
641
|
+
});
|
|
642
|
+
child.on('error', (err) => {
|
|
643
|
+
clearTimeout(timer);
|
|
644
|
+
killProcessTree(child).then(() => {
|
|
645
|
+
cleanupTmpFiles();
|
|
646
|
+
safeResolve({ result: '', error: `SPAWN_ERROR: ${err.message}`, rawOutput: '' });
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
};
|
|
650
|
+
options.signal?.addEventListener('abort', () => {
|
|
651
|
+
if (currentChildRef)
|
|
652
|
+
killProcessTree(currentChildRef).then(() => {
|
|
653
|
+
cleanupTmpFiles();
|
|
654
|
+
safeResolve({ result: '', error: 'ABORTED', rawOutput: '' });
|
|
655
|
+
});
|
|
689
656
|
});
|
|
690
|
-
|
|
691
|
-
clearTimeout(timeout);
|
|
692
|
-
if (child.pid)
|
|
693
|
-
void updateProcessStatus(child.pid, 'failed', null, options.configPath);
|
|
694
|
-
safeResolve({ result: '', error: `SPAWN_ERROR: ${err.message}`, rawOutput: '' });
|
|
695
|
-
});
|
|
696
|
-
// Do NOT call child.stdin.end() — it sends EOF and Hermes closes.
|
|
697
|
-
// Keep stdin open so Hermes stays alive for resume.
|
|
657
|
+
spawnHermes(getTokenForIndex(0));
|
|
698
658
|
});
|
|
699
659
|
}
|
|
700
660
|
}
|
|
701
|
-
/**
|
|
702
|
-
* Run agent with proper cleanup and telemetry
|
|
703
|
-
*/
|
|
704
|
-
async function runAgentWrapper(options) {
|
|
705
|
-
try {
|
|
706
|
-
const result = await withSpan('hermes.runAgent', async (span) => {
|
|
707
|
-
span.setAttribute('agentName', options.agentName || '');
|
|
708
|
-
span.setAttribute('model', options.model || '');
|
|
709
|
-
span.setAttribute('runner', 'hermes');
|
|
710
|
-
return await this.runAgentInternal(options);
|
|
711
|
-
}, {
|
|
712
|
-
agentName: options.agentName || '',
|
|
713
|
-
model: options.model || '',
|
|
714
|
-
runner: 'hermes',
|
|
715
|
-
});
|
|
716
|
-
// Cleanup on success
|
|
717
|
-
this.cleanupTempFiles();
|
|
718
|
-
// Save session if needed
|
|
719
|
-
if (options.agentName && result.sessionId) {
|
|
720
|
-
await saveSessionId(options.agentName, result.sessionId, options.configPath, 'hermes');
|
|
721
|
-
}
|
|
722
|
-
return result;
|
|
723
|
-
}
|
|
724
|
-
catch (error) {
|
|
725
|
-
// Cleanup on error
|
|
726
|
-
this.cleanupTempFiles();
|
|
727
|
-
logger.error({
|
|
728
|
-
error: error instanceof Error ? error.message : String(error),
|
|
729
|
-
agentName: options.agentName,
|
|
730
|
-
}, 'Hermes runner failed');
|
|
731
|
-
throw error;
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
661
|
//# sourceMappingURL=NousHermesRunner.js.map
|