overmind-mcp 2.8.40 → 2.8.44
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/bin/..logs/Workflow.err.log +0 -0
- package/bin/..logs/Workflow.log +0 -0
- package/dist/bin/cli.js +2 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/services/AgentManager.d.ts +9 -0
- package/dist/services/AgentManager.d.ts.map +1 -1
- package/dist/services/AgentManager.js +36 -0
- package/dist/services/AgentManager.js.map +1 -1
- package/dist/services/NousHermesRunner.d.ts +0 -16
- package/dist/services/NousHermesRunner.d.ts.map +1 -1
- package/dist/services/NousHermesRunner.js +300 -84
- package/dist/services/NousHermesRunner.js.map +1 -1
- package/dist/tools/manage_agents.d.ts.map +1 -1
- package/dist/tools/manage_agents.js +30 -2
- package/dist/tools/manage_agents.js.map +1 -1
- package/dist/tools/run_agent_cli.js +10 -1
- package/dist/tools/run_agent_cli.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
|
-
import { exec } from 'child_process';
|
|
4
|
+
import { exec, execSync } from 'child_process';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
6
|
import { CONFIG, resolveConfigPath, getWorkspaceDir, getAgentHermesHome, getAgentOvermindHome, getSharedHermesHome } from '../lib/config.js';
|
|
7
7
|
import { getLastSessionId, saveSessionId } from '../lib/sessions.js';
|
|
@@ -18,34 +18,55 @@ const logger = pino({ name: 'NousHermesRunner' });
|
|
|
18
18
|
// orphelin. On utilise taskkill /F /T pour propager le kill au sous-arbre complet.
|
|
19
19
|
const killProcessTree = (child) => {
|
|
20
20
|
return new Promise((resolve) => {
|
|
21
|
-
if (!child
|
|
21
|
+
if (!child) {
|
|
22
|
+
logger.debug('[KILL] No child process reference provided.');
|
|
22
23
|
resolve();
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
26
|
+
if (child.exitCode !== null || child.killed) {
|
|
27
|
+
logger.debug({ pid: child.pid, exitCode: child.exitCode, killed: child.killed }, '[KILL] Process is already dead or marked killed.');
|
|
28
|
+
resolve();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
logger.info({ pid: child.pid }, '[KILL] Initiating process tree termination...');
|
|
25
32
|
let settled = false;
|
|
26
33
|
const finish = () => {
|
|
27
34
|
if (settled)
|
|
28
35
|
return;
|
|
29
36
|
settled = true;
|
|
37
|
+
logger.debug({ pid: child.pid }, '[KILL] Process tree termination sequence completed.');
|
|
30
38
|
resolve();
|
|
31
39
|
};
|
|
32
40
|
child.once('exit', finish);
|
|
33
|
-
if (process.platform === 'win32' && child.pid) {
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
if (process.platform === 'win32' && child.pid && typeof child.pid === 'number' && child.pid > 0) {
|
|
42
|
+
const cmd = `taskkill /F /T /PID ${child.pid}`;
|
|
43
|
+
logger.debug({ cmd }, '[KILL] Executing Windows taskkill...');
|
|
44
|
+
exec(cmd, (err, stdout, stderr) => {
|
|
45
|
+
if (err) {
|
|
46
|
+
logger.debug({ err, stderr }, '[KILL] taskkill failed or process was already dead.');
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
logger.debug({ stdout }, '[KILL] taskkill completed successfully.');
|
|
50
|
+
}
|
|
36
51
|
});
|
|
37
52
|
}
|
|
38
53
|
else {
|
|
39
54
|
try {
|
|
55
|
+
logger.debug({ pid: child.pid }, '[KILL] Dispatched SIGTERM signal.');
|
|
40
56
|
child.kill('SIGTERM');
|
|
41
57
|
}
|
|
42
|
-
catch {
|
|
58
|
+
catch (e) {
|
|
59
|
+
logger.debug({ pid: child.pid, error: e }, '[KILL] SIGTERM dispatch failed.');
|
|
60
|
+
}
|
|
43
61
|
setTimeout(() => {
|
|
44
62
|
if (child.exitCode === null && !child.killed) {
|
|
45
63
|
try {
|
|
64
|
+
logger.warn({ pid: child.pid }, '[KILL] SIGTERM ignored. Escalating to SIGKILL...');
|
|
46
65
|
child.kill('SIGKILL');
|
|
47
66
|
}
|
|
48
|
-
catch {
|
|
67
|
+
catch (e) {
|
|
68
|
+
logger.debug({ pid: child.pid, error: e }, '[KILL] SIGKILL dispatch failed.');
|
|
69
|
+
}
|
|
49
70
|
}
|
|
50
71
|
}, 2000);
|
|
51
72
|
}
|
|
@@ -95,30 +116,33 @@ async function findHermesBinary() {
|
|
|
95
116
|
// Not found in PATH
|
|
96
117
|
}
|
|
97
118
|
// 3. Platform-specific paths
|
|
98
|
-
const platformPaths =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
const platformPaths = [];
|
|
120
|
+
if (isWin) {
|
|
121
|
+
platformPaths.push(
|
|
122
|
+
// Hermes venv (Nous Research install) — PRIORITÉ haute (v0.13.0, supporte -z)
|
|
123
|
+
path.join(process.env.LOCALAPPDATA || '', 'hermes', 'hermes-agent', 'venv', 'Scripts', 'hermes.exe'),
|
|
124
|
+
// Officiel installer Windows (install.ps1) — chemin natif
|
|
125
|
+
path.join(process.env.LOCALAPPDATA || '', 'hermes', 'bin', 'hermes.exe'), path.join(process.env.LOCALAPPDATA || '', 'hermes', 'hermes.exe'));
|
|
126
|
+
// Dynamically scan for Python versions in LOCALAPPDATA
|
|
127
|
+
const programsPython = path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python');
|
|
128
|
+
if (fs.existsSync(programsPython)) {
|
|
129
|
+
try {
|
|
130
|
+
const dirs = fs.readdirSync(programsPython);
|
|
131
|
+
for (const dir of dirs) {
|
|
132
|
+
if (dir.toLowerCase().startsWith('python')) {
|
|
133
|
+
platformPaths.push(path.join(programsPython, dir, 'Scripts', 'hermes.exe'));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch { /* ignored */ }
|
|
138
|
+
}
|
|
139
|
+
platformPaths.push(
|
|
140
|
+
// Fallback installations via pip (legacy)
|
|
141
|
+
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python312', 'Scripts', 'hermes.exe'), path.join(process.env.APPDATA || '', 'Python', 'Python312', 'Scripts', 'hermes.exe'), path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python311', 'Scripts', 'hermes.exe'), path.join(process.env.APPDATA || '', 'Python', 'Python311', 'Scripts', 'hermes.exe'), 'C:\\Python312\\Scripts\\hermes.exe', 'C:\\Python311\\Scripts\\hermes.exe', 'C:\\Program Files\\Hermes\\hermes.exe');
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
platformPaths.push(path.join(process.env.HOME || '', '.local', 'bin', 'hermes'), path.join(process.env.HOME || '', 'miniconda3', 'bin', 'hermes'), path.join(process.env.HOME || '', 'anaconda3', 'bin', 'hermes'), '/usr/local/bin/hermes', '/usr/bin/hermes', '/opt/homebrew/bin/hermes');
|
|
145
|
+
}
|
|
122
146
|
for (const p of platformPaths) {
|
|
123
147
|
if (fs.existsSync(p)) {
|
|
124
148
|
logger.info({ path: p }, 'Found hermes at platform path');
|
|
@@ -148,12 +172,12 @@ async function findHermesBinary() {
|
|
|
148
172
|
return 'hermes';
|
|
149
173
|
}
|
|
150
174
|
/**
|
|
151
|
-
* NousHermesRunner — Runner
|
|
175
|
+
* NousHermesRunner — Runner polyglotte pour Hermes Agent (Overmind 2.8.27+).
|
|
152
176
|
*
|
|
153
177
|
* • Providers : OpenAI, MiniMax GLOBAL/CN, Zhipu/GLM, Mistral, NVIDIA NIM, OpenRouter (embeddings only)
|
|
154
178
|
* • Lit settings_<agent>.json + .mcp.<agent>.json depuis .claude/ comme les autres runners
|
|
155
179
|
* • Interpolation $VAR et ${VAR} sur tout settings + mcp config (via envUtils, regex fix 2.8.25)
|
|
156
|
-
* •
|
|
180
|
+
* • Sélection/Emprunt de jeton 3-pass (Pass 1: settings-explicit, Pass A: prefer provider-specific,
|
|
157
181
|
* Pass B: re-map generic key, Pass C: rare fallback) — see hermesTokenResolver.ts
|
|
158
182
|
* • CN/GLOBAL disambiguation for sk-cp-* via ANTHROPIC_BASE_URL (URL wins)
|
|
159
183
|
* • OVERMIND_MINIMAX_DEFAULT=cn|global|auto for setups where all MiniMax tokens are CN
|
|
@@ -163,6 +187,62 @@ async function findHermesBinary() {
|
|
|
163
187
|
* to prevent Hermes from picking an exhausted bucket from a previous provider config
|
|
164
188
|
* • HOME/USERPROFILE propagated to spawned Hermes so ~/.hermes lookups resolve canonically
|
|
165
189
|
*/
|
|
190
|
+
function filterConfigYaml(sourceYamlPath, allowedServers) {
|
|
191
|
+
try {
|
|
192
|
+
if (!fs.existsSync(sourceYamlPath))
|
|
193
|
+
return 'mcp_servers: {}\n';
|
|
194
|
+
const yamlText = fs.readFileSync(sourceYamlPath, 'utf8');
|
|
195
|
+
const lines = yamlText.split(/\r?\n/);
|
|
196
|
+
let inMcpServers = false;
|
|
197
|
+
let currentServerName = '';
|
|
198
|
+
const serverBlocks = {};
|
|
199
|
+
const headerLines = [];
|
|
200
|
+
for (const line of lines) {
|
|
201
|
+
const trimmed = line.trim();
|
|
202
|
+
if (line.match(/^mcp_servers:\s*$/) || line.match(/^mcp_servers:\s*#.*$/)) {
|
|
203
|
+
inMcpServers = true;
|
|
204
|
+
headerLines.push(line);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (inMcpServers) {
|
|
208
|
+
const indentMatch = line.match(/^(\s+)/);
|
|
209
|
+
if (!indentMatch) {
|
|
210
|
+
inMcpServers = false;
|
|
211
|
+
headerLines.push(line);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const indent = indentMatch[1].length;
|
|
215
|
+
const keyMatch = trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/);
|
|
216
|
+
if (keyMatch && indent === 2) {
|
|
217
|
+
currentServerName = keyMatch[1];
|
|
218
|
+
serverBlocks[currentServerName] = [line];
|
|
219
|
+
}
|
|
220
|
+
else if (currentServerName) {
|
|
221
|
+
serverBlocks[currentServerName].push(line);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
headerLines.push(line);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
let result = '';
|
|
229
|
+
for (const line of headerLines) {
|
|
230
|
+
result += line + '\n';
|
|
231
|
+
if (line.match(/^mcp_servers:\s*$/) || line.match(/^mcp_servers:\s*#.*$/)) {
|
|
232
|
+
for (const srv of allowedServers) {
|
|
233
|
+
if (serverBlocks[srv]) {
|
|
234
|
+
result += serverBlocks[srv].join('\n') + '\n';
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
logger.error({ sourceYamlPath, error: err }, '[YAML_FILTER] Unexpected failure while filtering config.yaml.');
|
|
243
|
+
return 'mcp_servers: {}\n';
|
|
244
|
+
}
|
|
245
|
+
}
|
|
166
246
|
export class NousHermesRunner {
|
|
167
247
|
timeoutMs;
|
|
168
248
|
tempFiles = [];
|
|
@@ -171,20 +251,22 @@ export class NousHermesRunner {
|
|
|
171
251
|
this.timeoutMs = CONFIG.TIMEOUT_MS || 900000; // 15 min default
|
|
172
252
|
}
|
|
173
253
|
cleanupTempFiles() {
|
|
254
|
+
logger.debug({ count: this.tempFiles.length }, '[CLEANUP] Cleaning up temporary run files.');
|
|
174
255
|
for (const tempFile of this.tempFiles) {
|
|
175
256
|
try {
|
|
176
257
|
if (fs.existsSync(tempFile)) {
|
|
177
258
|
fs.unlinkSync(tempFile);
|
|
178
|
-
logger.debug({ tempFile }, 'Cleaned up temp file');
|
|
259
|
+
logger.debug({ tempFile }, '[CLEANUP] Cleaned up temp file');
|
|
179
260
|
}
|
|
180
261
|
}
|
|
181
262
|
catch (err) {
|
|
182
|
-
logger.warn({ tempFile, error: err }, 'Failed to cleanup temp file');
|
|
263
|
+
logger.warn({ tempFile, error: err }, '[CLEANUP] Failed to cleanup temp file');
|
|
183
264
|
}
|
|
184
265
|
}
|
|
185
266
|
this.tempFiles = [];
|
|
186
267
|
}
|
|
187
268
|
async runAgent(options) {
|
|
269
|
+
logger.info({ agentName: options.agentName, model: options.model, sessionId: options.sessionId }, '[RUN_AGENT] Initiating runAgent entrypoint.');
|
|
188
270
|
try {
|
|
189
271
|
const result = await withSpan('hermes.runAgent', async (span) => {
|
|
190
272
|
span.setAttribute('agentName', options.agentName || '');
|
|
@@ -198,13 +280,14 @@ export class NousHermesRunner {
|
|
|
198
280
|
});
|
|
199
281
|
this.cleanupTempFiles();
|
|
200
282
|
if (options.agentName && result.sessionId) {
|
|
283
|
+
logger.info({ agentName: options.agentName, sessionId: result.sessionId }, '[RUN_AGENT] Saving completed session ID.');
|
|
201
284
|
await saveSessionId(options.agentName, result.sessionId, options.configPath, 'hermes');
|
|
202
285
|
}
|
|
203
286
|
return result;
|
|
204
287
|
}
|
|
205
288
|
catch (error) {
|
|
206
289
|
this.cleanupTempFiles();
|
|
207
|
-
logger.error({ error: error instanceof Error ? error.message : String(error), agentName: options.agentName }, 'Hermes runner
|
|
290
|
+
logger.error({ error: error instanceof Error ? error.message : String(error), agentName: options.agentName }, '[RUN_AGENT] Hermes runner execution flow threw an error.');
|
|
208
291
|
throw error;
|
|
209
292
|
}
|
|
210
293
|
}
|
|
@@ -213,16 +296,30 @@ export class NousHermesRunner {
|
|
|
213
296
|
let { sessionId } = options;
|
|
214
297
|
const cwd = options.cwd || process.cwd();
|
|
215
298
|
const configPath = options.configPath || getWorkspaceDir();
|
|
299
|
+
logger.info({ agentName, autoResume, cwd, configPath, silent }, '[RUN_AGENT_INTERNAL] Starting internal agent runner workflow.');
|
|
216
300
|
// Load .env files FIRST
|
|
217
|
-
|
|
218
|
-
|
|
301
|
+
const envPaths = [path.join(cwd, '.env'), path.join(cwd, '../Workflow/.env')];
|
|
302
|
+
for (const envPath of envPaths) {
|
|
303
|
+
if (fs.existsSync(envPath)) {
|
|
304
|
+
logger.debug({ envPath }, '[RUN_AGENT_INTERNAL] Loading quiet environment file.');
|
|
305
|
+
loadEnvQuietly(envPath);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
logger.debug({ envPath }, '[RUN_AGENT_INTERNAL] Environment file not found, skipping.');
|
|
309
|
+
}
|
|
310
|
+
}
|
|
219
311
|
// Auto Resume
|
|
220
312
|
if (autoResume && agentName && !sessionId) {
|
|
313
|
+
logger.info({ agentName }, '[RUN_AGENT_INTERNAL] Auto-resume enabled. Querying last session ID.');
|
|
221
314
|
const lastId = await getLastSessionId(agentName, configPath, 'hermes');
|
|
222
315
|
if (lastId) {
|
|
223
316
|
sessionId = lastId;
|
|
224
317
|
if (!silent)
|
|
225
318
|
console.error(`[NousHermesRunner] Auto-resume session: ${sessionId}`);
|
|
319
|
+
logger.info({ sessionId }, '[RUN_AGENT_INTERNAL] Resolved last session ID for resume.');
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
logger.info('[RUN_AGENT_INTERNAL] No previous session ID found for auto-resume.');
|
|
226
323
|
}
|
|
227
324
|
}
|
|
228
325
|
const MAX_BUF = 10 * 1024 * 1024;
|
|
@@ -233,7 +330,8 @@ export class NousHermesRunner {
|
|
|
233
330
|
// drift between dev/prod installs and between different spawn cwd's.
|
|
234
331
|
const overmindHermesPath = getAgentOvermindHome(agentName);
|
|
235
332
|
const overmindHermesSubPath = getAgentHermesHome(agentName);
|
|
236
|
-
|
|
333
|
+
const agentSettingsPath = agentName ? resolveConfigPath(path.join(path.dirname(CONFIG.CLAUDE.PATHS.SETTINGS), `settings_${agentName}.json`), configPath) : '';
|
|
334
|
+
if (agentName && !fs.existsSync(overmindHermesPath) && !fs.existsSync(agentSettingsPath)) {
|
|
237
335
|
return { result: '', error: `INVALID_AGENT: Agent Hermes "${agentName}" non trouvé (HERMES_HOME=${overmindHermesSubPath}).` };
|
|
238
336
|
}
|
|
239
337
|
// Load agent settings + MCP config (same pattern as ClaudeRunner)
|
|
@@ -242,7 +340,7 @@ export class NousHermesRunner {
|
|
|
242
340
|
let resolvedProvider;
|
|
243
341
|
const agentCustomEnv = {
|
|
244
342
|
...process.env,
|
|
245
|
-
PYTHONIOENCODING: 'utf-8',
|
|
343
|
+
PYTHONIOENCODING: 'utf-8', PYTHONUNBUFFERED: '1',
|
|
246
344
|
PYTHONLEGACYWINDOWSSTDIO: '1', TERM: 'emacs',
|
|
247
345
|
PROMPT_TOOLKIT_NO_INTERACTIVE: '1', ANSICON: '1',
|
|
248
346
|
OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY || '',
|
|
@@ -261,6 +359,7 @@ export class NousHermesRunner {
|
|
|
261
359
|
};
|
|
262
360
|
let tmpSettingsPath = null;
|
|
263
361
|
let tmpMcpPath = null;
|
|
362
|
+
let loadedSettings = null;
|
|
264
363
|
// Capture the RAW (pre-interpolation) settings tokens so getTokenForIndex can
|
|
265
364
|
// fail-loud on unresolved $VAR references and report which one is missing.
|
|
266
365
|
// (Once interpolateEnvVars() runs, $VAR has been replaced with its value, and
|
|
@@ -304,9 +403,29 @@ export class NousHermesRunner {
|
|
|
304
403
|
// agents that haven't been migrated yet.
|
|
305
404
|
const canonicalSoul = path.join(overmindHermesSubPath, 'SOUL.md');
|
|
306
405
|
const legacySoul = path.join(getSharedHermesHome(), `agent_${agentName}`, '.hermes', 'SOUL.md');
|
|
307
|
-
const
|
|
308
|
-
|
|
406
|
+
const claudeSoul = path.join(configPath, '.claude', 'agents', `${agentName}.md`);
|
|
407
|
+
const agentPromptPath = fs.existsSync(claudeSoul)
|
|
408
|
+
? claudeSoul
|
|
409
|
+
: fs.existsSync(canonicalSoul)
|
|
410
|
+
? canonicalSoul
|
|
411
|
+
: fs.existsSync(legacySoul)
|
|
412
|
+
? legacySoul
|
|
413
|
+
: null;
|
|
414
|
+
if (agentPromptPath && fs.existsSync(agentPromptPath)) {
|
|
309
415
|
systemPrompt = fs.readFileSync(agentPromptPath, 'utf8');
|
|
416
|
+
// Sync system prompt to canonical layout if loaded from legacy or Claude paths
|
|
417
|
+
if (agentPromptPath !== canonicalSoul) {
|
|
418
|
+
try {
|
|
419
|
+
if (!fs.existsSync(overmindHermesSubPath)) {
|
|
420
|
+
fs.mkdirSync(overmindHermesSubPath, { recursive: true });
|
|
421
|
+
}
|
|
422
|
+
fs.writeFileSync(canonicalSoul, systemPrompt, 'utf8');
|
|
423
|
+
logger.info({ agentName, source: agentPromptPath, target: canonicalSoul }, 'Synced SOUL.md to canonical Hermes path.');
|
|
424
|
+
}
|
|
425
|
+
catch (e) {
|
|
426
|
+
logger.warn({ error: e }, 'Failed to sync SOUL.md to canonical Hermes path');
|
|
427
|
+
}
|
|
428
|
+
}
|
|
310
429
|
}
|
|
311
430
|
// Load environment variables from .claude/settings_<agentName>.json
|
|
312
431
|
try {
|
|
@@ -343,6 +462,7 @@ export class NousHermesRunner {
|
|
|
343
462
|
}
|
|
344
463
|
let settings = rawSettings;
|
|
345
464
|
settings = interpolateEnvVars(settings);
|
|
465
|
+
loadedSettings = settings;
|
|
346
466
|
// Create temporary settings file
|
|
347
467
|
const tempSettings = path.join(path.dirname(agentSettingsPath), `settings_${agentName}_tmp.json`);
|
|
348
468
|
fs.writeFileSync(tempSettings, JSON.stringify(settings, null, 2));
|
|
@@ -445,7 +565,7 @@ export class NousHermesRunner {
|
|
|
445
565
|
}
|
|
446
566
|
const finalModel = options.model || resolvedModel || CONFIG.HERMES.DEFAULT_MODEL;
|
|
447
567
|
const finalPrompt = systemPrompt ? `${systemPrompt}\n\n[USER QUERY]:\n${prompt}` : prompt;
|
|
448
|
-
const cliPrompt = finalPrompt
|
|
568
|
+
const cliPrompt = finalPrompt;
|
|
449
569
|
// Build CLI args: chat -q (persistent session, NOT -z oneshot)
|
|
450
570
|
// -z + --resume doesn't work — resume is ignored in oneshot mode
|
|
451
571
|
//
|
|
@@ -497,19 +617,29 @@ export class NousHermesRunner {
|
|
|
497
617
|
const effectiveSettings = {};
|
|
498
618
|
if (agentName) {
|
|
499
619
|
try {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if (Array.isArray(raw.enabledMcpjsonServers)) {
|
|
504
|
-
effectiveSettings.enabledMcpjsonServers = raw.enabledMcpjsonServers.filter(Boolean);
|
|
620
|
+
if (loadedSettings) {
|
|
621
|
+
if (Array.isArray(loadedSettings.enabledMcpjsonServers)) {
|
|
622
|
+
effectiveSettings.enabledMcpjsonServers = loadedSettings.enabledMcpjsonServers.filter(Boolean);
|
|
505
623
|
}
|
|
506
|
-
if (
|
|
507
|
-
effectiveSettings.enableAllProjectMcpServers =
|
|
624
|
+
if (loadedSettings.enableAllProjectMcpServers !== undefined) {
|
|
625
|
+
effectiveSettings.enableAllProjectMcpServers = loadedSettings.enableAllProjectMcpServers === true;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
const canonicalPath = path.join(overmindHermesSubPath, 'settings.json');
|
|
630
|
+
if (fs.existsSync(canonicalPath)) {
|
|
631
|
+
const raw = JSON.parse(fs.readFileSync(canonicalPath, 'utf8'));
|
|
632
|
+
if (Array.isArray(raw.enabledMcpjsonServers)) {
|
|
633
|
+
effectiveSettings.enabledMcpjsonServers = raw.enabledMcpjsonServers.filter(Boolean);
|
|
634
|
+
}
|
|
635
|
+
if (raw.enableAllProjectMcpServers !== undefined) {
|
|
636
|
+
effectiveSettings.enableAllProjectMcpServers = raw.enableAllProjectMcpServers === true;
|
|
637
|
+
}
|
|
508
638
|
}
|
|
509
639
|
}
|
|
510
640
|
}
|
|
511
641
|
catch (e) {
|
|
512
|
-
logger.warn({ error: e }, '[HERMES_ARGS] Failed to read
|
|
642
|
+
logger.warn({ error: e }, '[HERMES_ARGS] Failed to read settings for toolset hints.');
|
|
513
643
|
}
|
|
514
644
|
}
|
|
515
645
|
const enabledInSettings = effectiveSettings.enabledMcpjsonServers || [];
|
|
@@ -537,19 +667,33 @@ export class NousHermesRunner {
|
|
|
537
667
|
const hermesConfigPath = path.join(getSharedHermesHome(), 'config.yaml');
|
|
538
668
|
if (fs.existsSync(hermesConfigPath)) {
|
|
539
669
|
const yamlText = fs.readFileSync(hermesConfigPath, 'utf8');
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
670
|
+
// Line-by-line YAML parser for the `mcp_servers` section (handles comments, varying indentation, and exits correctly)
|
|
671
|
+
const lines = yamlText.split(/\r?\n/);
|
|
672
|
+
let inMcpServers = false;
|
|
673
|
+
const serverNames = [];
|
|
674
|
+
for (const line of lines) {
|
|
675
|
+
const trimmed = line.trim();
|
|
676
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
677
|
+
continue;
|
|
678
|
+
if (line.match(/^mcp_servers:\s*$/) || line.match(/^mcp_servers:\s*#.*$/)) {
|
|
679
|
+
inMcpServers = true;
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (inMcpServers) {
|
|
683
|
+
const indentMatch = line.match(/^(\s+)/);
|
|
684
|
+
if (!indentMatch) {
|
|
685
|
+
inMcpServers = false;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
const keyMatch = trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/);
|
|
689
|
+
if (keyMatch) {
|
|
690
|
+
serverNames.push(keyMatch[1]);
|
|
691
|
+
}
|
|
551
692
|
}
|
|
552
693
|
}
|
|
694
|
+
if (serverNames.length > 0) {
|
|
695
|
+
toolsetList.push(...serverNames);
|
|
696
|
+
}
|
|
553
697
|
}
|
|
554
698
|
if (toolsetList.length === 0) {
|
|
555
699
|
// Fallback: read .mcp.json (Overmind format) for any servers not in
|
|
@@ -568,8 +712,9 @@ export class NousHermesRunner {
|
|
|
568
712
|
}
|
|
569
713
|
}
|
|
570
714
|
if (toolsetList.length > 0) {
|
|
571
|
-
|
|
572
|
-
|
|
715
|
+
const formattedToolsets = toolsetList.map((name) => name.startsWith('mcp-') ? name : `mcp-${name}`);
|
|
716
|
+
cleanArgs.push('--toolsets', formattedToolsets.join(','));
|
|
717
|
+
logger.info({ agentName, toolsets: formattedToolsets }, '[HERMES_ARGS] Passing --toolsets (2.8.36: activates MCP servers as callable tools in this session).');
|
|
573
718
|
}
|
|
574
719
|
if (sessionId)
|
|
575
720
|
cleanArgs.push('--resume', sessionId);
|
|
@@ -657,10 +802,10 @@ export class NousHermesRunner {
|
|
|
657
802
|
`but process.env.${varName} is empty. Add it to /home/demon/.overmind/.env or fix the settings reference.`);
|
|
658
803
|
}
|
|
659
804
|
resolvedValue = fromEnv;
|
|
660
|
-
logger.info({ agentName, sourceKey: t.key, referencedVar: varName, resolvedLen: resolvedValue.length }, '[
|
|
805
|
+
logger.info({ agentName, sourceKey: t.key, referencedVar: varName, resolvedLen: resolvedValue.length }, '[TOKEN_RESOLVER] Resolved $VAR reference from settings_<agent>.json against process.env.');
|
|
661
806
|
}
|
|
662
807
|
const detected = detectTokenProvider(resolvedValue);
|
|
663
|
-
logger.info({ agentName, tokenKey: t.key, detectedProvider: detected.provider, mappedTo: detected.envKey }, '[
|
|
808
|
+
logger.info({ agentName, tokenKey: t.key, detectedProvider: detected.provider, mappedTo: detected.envKey }, '[TOKEN_RESOLVER] Using explicit settings_<agent>.json token, re-mapping to detected provider env var.');
|
|
664
809
|
return { tokenEnvKey: t.key, tokenValue: resolvedValue, detectedProvider: detected.provider, source: 'settings-explicit' };
|
|
665
810
|
}
|
|
666
811
|
const candidates = [];
|
|
@@ -689,7 +834,7 @@ export class NousHermesRunner {
|
|
|
689
834
|
// Pass B: re-map the first candidate to the right provider env-var name.
|
|
690
835
|
const first = candidates[0];
|
|
691
836
|
if (first.detected.provider !== 'unknown' && first.detected.envKey !== first.key) {
|
|
692
|
-
logger.info({ agentName, sourceKey: first.key, detectedProvider: first.detected.provider, remappedTo: first.detected.envKey }, '[
|
|
837
|
+
logger.info({ agentName, sourceKey: first.key, detectedProvider: first.detected.provider, remappedTo: first.detected.envKey }, '[TOKEN_RESOLVER] Token prefix detected provider mismatch — re-mapping env var.');
|
|
693
838
|
return {
|
|
694
839
|
tokenEnvKey: first.detected.envKey,
|
|
695
840
|
tokenValue: first.value,
|
|
@@ -930,6 +1075,65 @@ export class NousHermesRunner {
|
|
|
930
1075
|
// edits were being silently deleted after each spawn.
|
|
931
1076
|
logger.info({ agentName, settingsPath: tmpAgentSettings, envKeys: Object.keys(settingsJson.env || {}).length }, '[HERMES] Wrote canonical agents/<name>/settings.json (env block from settings_<name>.json + provider-specific seeds).');
|
|
932
1077
|
}
|
|
1078
|
+
let effectiveHermesHome = sharedHome;
|
|
1079
|
+
if (agentName && enabledInSettings.length > 0) {
|
|
1080
|
+
try {
|
|
1081
|
+
const runsDir = path.join(sharedHome, 'runs');
|
|
1082
|
+
if (!fs.existsSync(runsDir))
|
|
1083
|
+
fs.mkdirSync(runsDir, { recursive: true });
|
|
1084
|
+
const runHome = path.join(runsDir, agentName);
|
|
1085
|
+
if (!fs.existsSync(runHome))
|
|
1086
|
+
fs.mkdirSync(runHome, { recursive: true });
|
|
1087
|
+
// Copy auth.json if exists
|
|
1088
|
+
const sharedAuth = path.join(sharedHome, 'auth.json');
|
|
1089
|
+
const runAuth = path.join(runHome, 'auth.json');
|
|
1090
|
+
if (fs.existsSync(sharedAuth)) {
|
|
1091
|
+
fs.copyFileSync(sharedAuth, runAuth);
|
|
1092
|
+
}
|
|
1093
|
+
// Robust directory junction/symlink helper
|
|
1094
|
+
const linkDirRobust = (target, source) => {
|
|
1095
|
+
let exists = false;
|
|
1096
|
+
let stats = null;
|
|
1097
|
+
try {
|
|
1098
|
+
stats = fs.lstatSync(target);
|
|
1099
|
+
exists = true;
|
|
1100
|
+
}
|
|
1101
|
+
catch {
|
|
1102
|
+
// target does not exist
|
|
1103
|
+
}
|
|
1104
|
+
if (exists) {
|
|
1105
|
+
logger.debug({ target, isJunction: stats ? (stats.isSymbolicLink() || stats.isDirectory()) : false }, '[HERMES_HOME] Target directory/link already exists. Skipping link creation.');
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
logger.info({ target, source }, '[HERMES_HOME] Creating junction/symbolic link.');
|
|
1109
|
+
if (process.platform === 'win32') {
|
|
1110
|
+
execSync(`cmd /c mklink /J "${target}" "${source}"`);
|
|
1111
|
+
}
|
|
1112
|
+
else {
|
|
1113
|
+
fs.symlinkSync(source, target);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
// Link agents directory
|
|
1118
|
+
const sharedAgents = path.join(sharedHome, 'agents');
|
|
1119
|
+
const runAgents = path.join(runHome, 'agents');
|
|
1120
|
+
linkDirRobust(runAgents, sharedAgents);
|
|
1121
|
+
// Link sessions directory
|
|
1122
|
+
const sharedSessions = path.join(sharedHome, 'sessions');
|
|
1123
|
+
const runSessions = path.join(runHome, 'sessions');
|
|
1124
|
+
linkDirRobust(runSessions, sharedSessions);
|
|
1125
|
+
// Generate filtered config.yaml
|
|
1126
|
+
const sharedConfig = path.join(sharedHome, 'config.yaml');
|
|
1127
|
+
const runConfig = path.join(runHome, 'config.yaml');
|
|
1128
|
+
const filteredConfig = filterConfigYaml(sharedConfig, enabledInSettings);
|
|
1129
|
+
fs.writeFileSync(runConfig, filteredConfig, 'utf8');
|
|
1130
|
+
effectiveHermesHome = runHome;
|
|
1131
|
+
logger.info({ agentName, runHome, enabledMcp: enabledInSettings }, '[HERMES_HOME] Isolated agent Hermes Home and filtered config.yaml successfully.');
|
|
1132
|
+
}
|
|
1133
|
+
catch (e) {
|
|
1134
|
+
logger.warn({ error: e }, 'Failed to setup isolated run home; falling back to sharedHome');
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
933
1137
|
// AbortSignal
|
|
934
1138
|
if (options.signal?.aborted)
|
|
935
1139
|
return Promise.reject(new Error('ABORTED'));
|
|
@@ -939,10 +1143,27 @@ export class NousHermesRunner {
|
|
|
939
1143
|
let retryCount = 0;
|
|
940
1144
|
const maxRetries = getAvailableFallbacks().length + 1;
|
|
941
1145
|
let currentSessionId = sessionId;
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1146
|
+
const abortListener = () => {
|
|
1147
|
+
if (currentChildRef) {
|
|
1148
|
+
killProcessTree(currentChildRef).then(() => {
|
|
1149
|
+
cleanupTmpFiles();
|
|
1150
|
+
safeResolve({ result: '', error: 'ABORTED', rawOutput: '' });
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
cleanupTmpFiles();
|
|
1155
|
+
safeResolve({ result: '', error: 'ABORTED', rawOutput: '' });
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
const safeResolve = (v) => {
|
|
1159
|
+
if (!resolved) {
|
|
1160
|
+
resolved = true;
|
|
1161
|
+
if (options.signal) {
|
|
1162
|
+
options.signal.removeEventListener('abort', abortListener);
|
|
1163
|
+
}
|
|
1164
|
+
resolve(v);
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
946
1167
|
const cleanupTmpFiles = () => {
|
|
947
1168
|
for (const f of [tmpSettingsPath, tmpMcpPath]) {
|
|
948
1169
|
if (f && fs.existsSync(f)) {
|
|
@@ -1031,12 +1252,11 @@ export class NousHermesRunner {
|
|
|
1031
1252
|
// etc. relative to HERMES_HOME. Setting it to the per-agent home would
|
|
1032
1253
|
// tell Hermes "this IS the Hermes root" and make it look for config.yaml
|
|
1033
1254
|
// IN the agent dir — wrong layout.
|
|
1034
|
-
const sharedHermesHome = getSharedHermesHome();
|
|
1035
1255
|
const child = spawn(hermesBin, cleanArgs, {
|
|
1036
1256
|
cwd, shell: false, windowsHide: true,
|
|
1037
1257
|
env: {
|
|
1038
1258
|
...spawnEnv,
|
|
1039
|
-
HERMES_HOME:
|
|
1259
|
+
HERMES_HOME: effectiveHermesHome,
|
|
1040
1260
|
...(isVenvInstall
|
|
1041
1261
|
? {
|
|
1042
1262
|
VIRTUAL_ENV: venvRoot,
|
|
@@ -1067,8 +1287,8 @@ export class NousHermesRunner {
|
|
|
1067
1287
|
void appendOutput(child.pid, chunk, configPath);
|
|
1068
1288
|
void appendLiveOutput(child.pid, chunk);
|
|
1069
1289
|
}
|
|
1070
|
-
if (stdout.length + chunk.length > MAX_BUF)
|
|
1071
|
-
stdout = stdout.slice(-MAX_BUF);
|
|
1290
|
+
if (stdout.length + chunk.length > this.MAX_BUF)
|
|
1291
|
+
stdout = stdout.slice(-this.MAX_BUF);
|
|
1072
1292
|
else
|
|
1073
1293
|
stdout += chunk;
|
|
1074
1294
|
if (!silent && agentName)
|
|
@@ -1076,8 +1296,8 @@ export class NousHermesRunner {
|
|
|
1076
1296
|
});
|
|
1077
1297
|
child.stderr?.on('data', (d) => {
|
|
1078
1298
|
const chunk = d.toString();
|
|
1079
|
-
if (stderr.length + chunk.length > MAX_BUF)
|
|
1080
|
-
stderr = stderr.slice(-MAX_BUF);
|
|
1299
|
+
if (stderr.length + chunk.length > this.MAX_BUF)
|
|
1300
|
+
stderr = stderr.slice(-this.MAX_BUF);
|
|
1081
1301
|
else
|
|
1082
1302
|
stderr += chunk;
|
|
1083
1303
|
if (!silent && agentName)
|
|
@@ -1137,13 +1357,9 @@ export class NousHermesRunner {
|
|
|
1137
1357
|
});
|
|
1138
1358
|
});
|
|
1139
1359
|
};
|
|
1140
|
-
options.signal
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
cleanupTmpFiles();
|
|
1144
|
-
safeResolve({ result: '', error: 'ABORTED', rawOutput: '' });
|
|
1145
|
-
});
|
|
1146
|
-
});
|
|
1360
|
+
if (options.signal) {
|
|
1361
|
+
options.signal.addEventListener('abort', abortListener);
|
|
1362
|
+
}
|
|
1147
1363
|
let firstToken;
|
|
1148
1364
|
try {
|
|
1149
1365
|
firstToken = getTokenForIndex(0);
|