overmind-mcp 2.8.43 → 2.8.45
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/dist/bin/launch.js +78 -0
- package/dist/lib/config.js +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/services/AgentManager.d.ts +5 -5
- package/dist/services/AgentManager.d.ts.map +1 -1
- package/dist/services/AgentManager.js +174 -270
- 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 +407 -150
- package/dist/services/NousHermesRunner.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import fsSync from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { CONFIG, resolveConfigPath, getWorkspaceDir } from '../lib/config.js';
|
|
4
|
+
import { CONFIG, resolveConfigPath, getWorkspaceDir, getSharedHermesHome } from '../lib/config.js';
|
|
5
5
|
import { getMemoryProvider } from '../memory/MemoryFactory.js';
|
|
6
6
|
import { interpolateEnvVars } from '../lib/envUtils.js';
|
|
7
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
8
|
+
// ⭐ LAYOUT DES FICHIERS — RÈGLE ABSOLUE
|
|
9
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
10
|
+
//
|
|
11
|
+
// HERMES agents → <HERMES_HOME>/agents/<name>/settings.json
|
|
12
|
+
// <HERMES_HOME>/agents/<name>/SOUL.md
|
|
13
|
+
// <HERMES_HOME>/agents/<name>/.mcp.json (optionnel)
|
|
14
|
+
//
|
|
15
|
+
// CLAUDE/KILO → <WORKSPACE>/.claude/settings_<name>.json
|
|
16
|
+
// <WORKSPACE>/.claude/agents/<name>.md
|
|
17
|
+
//
|
|
18
|
+
// ❌ INTERDIT : agent_<name>/.hermes/.env (ancien layout pré-2.8.30 — SUPPRIMÉ)
|
|
19
|
+
// ❌ INTERDIT : .claude/settings_<name>.json pour les agents Hermes
|
|
20
|
+
//
|
|
21
|
+
// getSharedHermesHome() = OVERMIND_HERMES_HOME || <workspace>/.overmind/hermes/
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
7
23
|
/**
|
|
8
24
|
* Validate agent name to prevent path traversal attacks.
|
|
9
25
|
* Only allows alphanumeric, underscores, hyphens — no path separators or special chars.
|
|
@@ -36,18 +52,25 @@ export class AgentManager {
|
|
|
36
52
|
}
|
|
37
53
|
}
|
|
38
54
|
async listAgents(details = false) {
|
|
39
|
-
const workspaceDir = getWorkspaceDir();
|
|
40
55
|
const claudeAgentsDir = path.join(this.claudeDir, 'agents');
|
|
41
56
|
await fs.mkdir(claudeAgentsDir, { recursive: true });
|
|
42
|
-
// 1. Scan .claude/agents/*.md
|
|
57
|
+
// 1. Scan .claude/agents/*.md (agents Claude/Kilo/Gemini)
|
|
43
58
|
const claudeFiles = await fs.readdir(claudeAgentsDir).catch(() => []);
|
|
44
59
|
const claudeAgentNames = claudeFiles.filter((f) => f.endsWith('.md')).map((f) => f.replace('.md', ''));
|
|
45
|
-
// 2. Scan .
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
// 2. Scan <HERMES_HOME>/agents/*/ (agents Hermes — layout natif 2.8.30+)
|
|
61
|
+
// ❌ NE PAS scanner agent_* (ancien layout pré-2.8.30 supprimé)
|
|
62
|
+
const hermesHome = getSharedHermesHome();
|
|
63
|
+
const hermesAgentsDir = path.join(hermesHome, 'agents');
|
|
64
|
+
const hermesAgentDirs = await fs.readdir(hermesAgentsDir).catch(() => []);
|
|
65
|
+
const hermesAgentNames = (await Promise.all(hermesAgentDirs.map(async (d) => {
|
|
66
|
+
try {
|
|
67
|
+
const stat = await fs.stat(path.join(hermesAgentsDir, d));
|
|
68
|
+
return stat.isDirectory() ? d : null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}))).filter((d) => d !== null);
|
|
51
74
|
const agentsMap = new Map();
|
|
52
75
|
const availableServers = await this.getAvailableMcpServers();
|
|
53
76
|
// Process Claude agents
|
|
@@ -107,66 +130,32 @@ export class AgentManager {
|
|
|
107
130
|
promptSize,
|
|
108
131
|
});
|
|
109
132
|
}
|
|
110
|
-
// Process Hermes agents
|
|
133
|
+
// Process Hermes agents — lit depuis <HERMES_HOME>/agents/<name>/settings.json
|
|
111
134
|
for (const name of hermesAgentNames) {
|
|
112
|
-
let model = 'MiniMax-
|
|
135
|
+
let model = 'MiniMax-M3';
|
|
113
136
|
let provider = 'minimax-cn';
|
|
114
|
-
let mcpServers = [
|
|
137
|
+
let mcpServers = [];
|
|
115
138
|
let promptSize = 0;
|
|
116
|
-
|
|
139
|
+
let missingConfig = false;
|
|
140
|
+
const agentDir = path.join(hermesAgentsDir, name);
|
|
141
|
+
// SOUL.md → system prompt
|
|
117
142
|
try {
|
|
118
|
-
const soulStat = await fs.stat(path.join(
|
|
143
|
+
const soulStat = await fs.stat(path.join(agentDir, 'SOUL.md'));
|
|
119
144
|
promptSize = soulStat.size;
|
|
120
145
|
}
|
|
121
|
-
catch {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
// Try reading .env to get actual model and provider
|
|
125
|
-
const envPath = path.join(agentHermesDir, '.env');
|
|
146
|
+
catch { /* Ignored */ }
|
|
147
|
+
// settings.json → credentials + MCP config (source de vérité unique)
|
|
148
|
+
const settingsPath = path.join(agentDir, 'settings.json');
|
|
126
149
|
try {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (eqIdx === -1)
|
|
134
|
-
return;
|
|
135
|
-
const k = trimmed.slice(0, eqIdx).trim();
|
|
136
|
-
let v = trimmed.slice(eqIdx + 1).trim();
|
|
137
|
-
if (v.startsWith('"') && v.endsWith('"'))
|
|
138
|
-
v = v.slice(1, -1);
|
|
139
|
-
else if (v.startsWith("'") && v.endsWith("'"))
|
|
140
|
-
v = v.slice(1, -1);
|
|
141
|
-
if (k === 'MODEL' || k === 'ANTHROPIC_MODEL') {
|
|
142
|
-
model = v;
|
|
143
|
-
}
|
|
144
|
-
else if (k === 'PROVIDER' || k === 'ANTHROPIC_PROVIDER') {
|
|
145
|
-
provider = v;
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
// Ignored
|
|
151
|
-
}
|
|
152
|
-
// Try reading config.yaml to get actual MCP servers
|
|
153
|
-
const yamlPath = path.join(agentHermesDir, 'config.yaml');
|
|
154
|
-
try {
|
|
155
|
-
const yamlContent = await fs.readFile(yamlPath, 'utf-8');
|
|
156
|
-
const parsedMcp = [];
|
|
157
|
-
const serverBlocks = yamlContent.split('\n ');
|
|
158
|
-
serverBlocks.forEach((block) => {
|
|
159
|
-
const lines = block.split('\n');
|
|
160
|
-
const header = lines[0].trim();
|
|
161
|
-
if (header && header.endsWith(':') && header !== 'mcp_servers:') {
|
|
162
|
-
parsedMcp.push(header.slice(0, -1).trim());
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
if (parsedMcp.length > 0)
|
|
166
|
-
mcpServers = parsedMcp;
|
|
150
|
+
const raw = await fs.readFile(settingsPath, 'utf-8');
|
|
151
|
+
let settings = JSON.parse(raw);
|
|
152
|
+
settings = interpolateEnvVars(settings);
|
|
153
|
+
model = settings.env?.ANTHROPIC_MODEL || settings.env?.MODEL || model;
|
|
154
|
+
provider = settings.env?.ANTHROPIC_PROVIDER || settings.env?.PROVIDER || provider;
|
|
155
|
+
mcpServers = settings.enabledMcpjsonServers || [];
|
|
167
156
|
}
|
|
168
157
|
catch {
|
|
169
|
-
|
|
158
|
+
missingConfig = true;
|
|
170
159
|
}
|
|
171
160
|
if (!agentsMap.has(name)) {
|
|
172
161
|
agentsMap.set(name, {
|
|
@@ -176,7 +165,7 @@ export class AgentManager {
|
|
|
176
165
|
provider,
|
|
177
166
|
mcpServers,
|
|
178
167
|
origin: 'hermes',
|
|
179
|
-
missingConfig
|
|
168
|
+
missingConfig,
|
|
180
169
|
promptSize,
|
|
181
170
|
});
|
|
182
171
|
}
|
|
@@ -187,7 +176,8 @@ export class AgentManager {
|
|
|
187
176
|
existing.missingConfig = false;
|
|
188
177
|
}
|
|
189
178
|
}
|
|
190
|
-
}
|
|
179
|
+
}
|
|
180
|
+
// Group by runner
|
|
191
181
|
const runners = ['hermes', 'claude', 'kilo', 'gemini', 'qwencli', 'openclaw', 'cline', 'opencode'];
|
|
192
182
|
const grouped = new Map();
|
|
193
183
|
for (const r of runners) {
|
|
@@ -240,27 +230,31 @@ export class AgentManager {
|
|
|
240
230
|
return outputLines;
|
|
241
231
|
}
|
|
242
232
|
/**
|
|
243
|
-
*
|
|
244
|
-
* -
|
|
245
|
-
* -
|
|
233
|
+
* Lecture non-destructive du runner effectif d'un agent.
|
|
234
|
+
* - Hermes : <HERMES_HOME>/agents/<name>/settings.json existe → 'hermes'
|
|
235
|
+
* - Claude/Kilo : .claude/settings_<name>.json → settings.runner || 'claude'
|
|
246
236
|
* - Sinon : undefined
|
|
247
|
-
*
|
|
248
|
-
*
|
|
237
|
+
*
|
|
238
|
+
* ❌ NE PAS utiliser agent_<name> (ancien layout pré-2.8.30 supprimé)
|
|
249
239
|
*/
|
|
250
240
|
peekRunner(name) {
|
|
251
|
-
const
|
|
252
|
-
const
|
|
241
|
+
const hermesHome = getSharedHermesHome();
|
|
242
|
+
const hermesSettingsPath = path.join(hermesHome, 'agents', name, 'settings.json');
|
|
253
243
|
try {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
244
|
+
if (fsSync.existsSync(hermesSettingsPath)) {
|
|
245
|
+
try {
|
|
246
|
+
const raw = fsSync.readFileSync(hermesSettingsPath, 'utf-8');
|
|
247
|
+
const parsed = JSON.parse(raw);
|
|
248
|
+
return parsed?.runner || 'hermes';
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return 'hermes';
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const claudeSettingsPath = path.join(this.claudeDir, `settings_${name}.json`);
|
|
255
|
+
if (fsSync.existsSync(claudeSettingsPath)) {
|
|
263
256
|
try {
|
|
257
|
+
const raw = fsSync.readFileSync(claudeSettingsPath, 'utf-8');
|
|
264
258
|
const parsed = JSON.parse(raw);
|
|
265
259
|
return (parsed && typeof parsed.runner === 'string' && parsed.runner) || 'claude';
|
|
266
260
|
}
|
|
@@ -276,16 +270,15 @@ export class AgentManager {
|
|
|
276
270
|
}
|
|
277
271
|
async deleteAgent(name) {
|
|
278
272
|
validateAgentName(name);
|
|
279
|
-
const
|
|
280
|
-
|
|
273
|
+
const hermesHome = getSharedHermesHome();
|
|
274
|
+
// Layout natif Hermes 2.8.30+ : <HERMES_HOME>/agents/<name>/
|
|
275
|
+
const hermesAgentDir = path.join(hermesHome, 'agents', name);
|
|
281
276
|
let isHermes = false;
|
|
282
277
|
try {
|
|
283
278
|
const stat = await fs.stat(hermesAgentDir);
|
|
284
279
|
isHermes = stat.isDirectory();
|
|
285
280
|
}
|
|
286
|
-
catch {
|
|
287
|
-
// Ignored
|
|
288
|
-
}
|
|
281
|
+
catch { /* Ignored */ }
|
|
289
282
|
const deletedFiles = [];
|
|
290
283
|
const errors = [];
|
|
291
284
|
if (isHermes) {
|
|
@@ -298,10 +291,11 @@ export class AgentManager {
|
|
|
298
291
|
}
|
|
299
292
|
return { deletedFiles, errors };
|
|
300
293
|
}
|
|
294
|
+
// Claude/Kilo agent
|
|
301
295
|
const agentsDir = path.join(this.claudeDir, 'agents');
|
|
302
296
|
const promptPath = path.join(agentsDir, `${name}.md`);
|
|
303
297
|
const settingsPath = path.join(this.claudeDir, `settings_${name}.json`);
|
|
304
|
-
const tmpMcpPath = path.join(this.claudeDir, `
|
|
298
|
+
const tmpMcpPath = path.join(this.claudeDir, `mcp_tmp.json`);
|
|
305
299
|
for (const file of [promptPath, settingsPath, tmpMcpPath]) {
|
|
306
300
|
try {
|
|
307
301
|
await fs.unlink(file);
|
|
@@ -319,76 +313,42 @@ export class AgentManager {
|
|
|
319
313
|
validateAgentName(name);
|
|
320
314
|
const changes = [];
|
|
321
315
|
const claudeDir = this.claudeDir;
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
const
|
|
316
|
+
// Layout natif Hermes : <HERMES_HOME>/agents/<name>/
|
|
317
|
+
const hermesHome = getSharedHermesHome();
|
|
318
|
+
const hermesAgentDir = path.join(hermesHome, 'agents', name);
|
|
325
319
|
let isHermes = false;
|
|
326
320
|
try {
|
|
327
321
|
const stat = await fs.stat(hermesAgentDir);
|
|
328
322
|
isHermes = stat.isDirectory();
|
|
329
323
|
}
|
|
330
|
-
catch {
|
|
331
|
-
// Ignored
|
|
332
|
-
}
|
|
324
|
+
catch { /* Ignored */ }
|
|
333
325
|
// --- MODE RÉÉCRITURE DE FICHIER COMPLET ---
|
|
334
326
|
if (updates.file && updates.content) {
|
|
335
327
|
if (isHermes) {
|
|
328
|
+
// Layout natif Hermes : <HERMES_HOME>/agents/<name>/
|
|
336
329
|
let filePath;
|
|
337
330
|
switch (updates.file) {
|
|
338
331
|
case 'prompt.md':
|
|
339
|
-
filePath = path.join(
|
|
332
|
+
filePath = path.join(hermesAgentDir, 'SOUL.md');
|
|
340
333
|
await fs.writeFile(filePath, updates.content, 'utf-8');
|
|
341
|
-
changes.push(`✅
|
|
334
|
+
changes.push(`✅ **SOUL.md** réécrit → ${filePath}`);
|
|
342
335
|
break;
|
|
343
|
-
case 'settings.json':
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
for (const [k, v] of Object.entries(s.env)) {
|
|
349
|
-
if (typeof v === 'string')
|
|
350
|
-
envStr += `${k}=${v}\n`;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (s.model && !envStr.includes('MODEL='))
|
|
354
|
-
envStr += `MODEL=${s.model}\n`;
|
|
355
|
-
if (s.runner && !envStr.includes('RUNNER='))
|
|
356
|
-
envStr += `RUNNER=${s.runner}\n`;
|
|
357
|
-
}
|
|
358
|
-
catch {
|
|
359
|
-
envStr = updates.content;
|
|
360
|
-
}
|
|
361
|
-
filePath = path.join(hermesSubDir, '.env');
|
|
362
|
-
await fs.writeFile(filePath, envStr, 'utf-8');
|
|
363
|
-
changes.push(`✅ Fichier **.env** réécrit pour l'agent Hermes **${name}**.`);
|
|
336
|
+
case 'settings.json':
|
|
337
|
+
// Écrit directement le JSON Hermes natif (pas de conversion .env)
|
|
338
|
+
filePath = path.join(hermesAgentDir, 'settings.json');
|
|
339
|
+
await fs.writeFile(filePath, updates.content, 'utf-8');
|
|
340
|
+
changes.push(`✅ **settings.json** réécrit → ${filePath}`);
|
|
364
341
|
break;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const mc = JSON.parse(updates.content);
|
|
370
|
-
for (const [sName, srv] of Object.entries(mc.mcpServers || {})) {
|
|
371
|
-
const s = srv;
|
|
372
|
-
yamlContent += ` ${sName}:\n`;
|
|
373
|
-
if (s.url)
|
|
374
|
-
yamlContent += ` url: "${s.url}"\n`;
|
|
375
|
-
if (s.command)
|
|
376
|
-
yamlContent += ` command: "${s.command}"\n`;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
catch {
|
|
380
|
-
yamlContent = updates.content;
|
|
381
|
-
}
|
|
382
|
-
filePath = path.join(hermesSubDir, 'config.yaml');
|
|
383
|
-
await fs.writeFile(filePath, yamlContent, 'utf-8');
|
|
384
|
-
changes.push(`✅ Fichier **config.yaml** réécrit pour l'agent Hermes **${name}**.`);
|
|
342
|
+
case '.mcp.json':
|
|
343
|
+
filePath = path.join(hermesAgentDir, '.mcp.json');
|
|
344
|
+
await fs.writeFile(filePath, updates.content, 'utf-8');
|
|
345
|
+
changes.push(`✅ **.mcp.json** réécrit → ${filePath}`);
|
|
385
346
|
break;
|
|
386
|
-
}
|
|
387
347
|
case 'skill.md':
|
|
388
|
-
filePath = path.join(
|
|
348
|
+
filePath = path.join(hermesAgentDir, 'skills', 'skill.md');
|
|
389
349
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
390
350
|
await fs.writeFile(filePath, updates.content, 'utf-8');
|
|
391
|
-
changes.push(`✅
|
|
351
|
+
changes.push(`✅ **skill.md** réécrit → ${filePath}`);
|
|
392
352
|
break;
|
|
393
353
|
default:
|
|
394
354
|
throw new Error(`Fichier non supporté: ${updates.file}`);
|
|
@@ -421,48 +381,41 @@ export class AgentManager {
|
|
|
421
381
|
}
|
|
422
382
|
}
|
|
423
383
|
if (isHermes) {
|
|
424
|
-
|
|
425
|
-
const
|
|
384
|
+
// Lit et écrit dans <HERMES_HOME>/agents/<name>/settings.json (format JSON natif)
|
|
385
|
+
const settingsPath = path.join(hermesAgentDir, 'settings.json');
|
|
386
|
+
let settings = {};
|
|
426
387
|
try {
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
const trimmed = line.trim();
|
|
430
|
-
if (!trimmed || trimmed.startsWith('#'))
|
|
431
|
-
return;
|
|
432
|
-
const eqIdx = trimmed.indexOf('=');
|
|
433
|
-
if (eqIdx === -1)
|
|
434
|
-
return;
|
|
435
|
-
const k = trimmed.slice(0, eqIdx).trim();
|
|
436
|
-
let v = trimmed.slice(eqIdx + 1).trim();
|
|
437
|
-
if (v.startsWith('"') && v.endsWith('"'))
|
|
438
|
-
v = v.slice(1, -1);
|
|
439
|
-
else if (v.startsWith("'") && v.endsWith("'"))
|
|
440
|
-
v = v.slice(1, -1);
|
|
441
|
-
if (k)
|
|
442
|
-
envVars[k] = v;
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
catch {
|
|
446
|
-
// Ignored
|
|
388
|
+
const raw = await fs.readFile(settingsPath, 'utf-8');
|
|
389
|
+
settings = JSON.parse(raw);
|
|
447
390
|
}
|
|
391
|
+
catch { /* fichier absent ou invalide — on repart d'un objet vide */ }
|
|
448
392
|
if (updates.model) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
393
|
+
settings.env = settings.env || {};
|
|
394
|
+
const old = settings.env.ANTHROPIC_MODEL || settings.env.MODEL || '(none)';
|
|
395
|
+
settings.env.ANTHROPIC_MODEL = updates.model;
|
|
396
|
+
changes.push(`- Modèle : ${old} → ${updates.model}`);
|
|
452
397
|
}
|
|
453
398
|
if (updates.env) {
|
|
399
|
+
settings.env = settings.env || {};
|
|
454
400
|
for (const [key, value] of Object.entries(updates.env)) {
|
|
455
|
-
const oldVal =
|
|
456
|
-
|
|
457
|
-
changes.push(`- Env
|
|
401
|
+
const oldVal = settings.env[key] ? '***' : '(undefined)';
|
|
402
|
+
settings.env[key] = value;
|
|
403
|
+
changes.push(`- Env '${key}' : ${oldVal} → ${value ? '***' : '(vide)'}`);
|
|
458
404
|
}
|
|
459
405
|
}
|
|
406
|
+
if (updates.mcpServers) {
|
|
407
|
+
const old = settings.enabledMcpjsonServers || [];
|
|
408
|
+
settings.enabledMcpjsonServers = updates.mcpServers;
|
|
409
|
+
changes.push(`- MCPs : [${old.join(', ')}] → [${updates.mcpServers.join(', ')}]`);
|
|
410
|
+
}
|
|
411
|
+
if (updates.runner) {
|
|
412
|
+
const old = settings.runner || 'hermes';
|
|
413
|
+
settings.runner = updates.runner;
|
|
414
|
+
changes.push(`- Runner : ${old} → ${updates.runner}`);
|
|
415
|
+
}
|
|
460
416
|
if (changes.length > 0) {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
newEnvContent += `${k}=${v}\n`;
|
|
464
|
-
}
|
|
465
|
-
await fs.writeFile(envPath, newEnvContent, 'utf-8');
|
|
417
|
+
await fs.mkdir(hermesAgentDir, { recursive: true });
|
|
418
|
+
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
466
419
|
}
|
|
467
420
|
return changes;
|
|
468
421
|
}
|
|
@@ -643,56 +596,46 @@ Tu es conçu pour être exécuté par différents runners (Claude, Kilo, Gemini,
|
|
|
643
596
|
}
|
|
644
597
|
envVars.ANTHROPIC_MODEL = model; // Ensure model is correctly set
|
|
645
598
|
if (runner === 'hermes') {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
const
|
|
650
|
-
|
|
599
|
+
// Layout natif Hermes 2.8.30+ : <HERMES_HOME>/agents/<name>/
|
|
600
|
+
// ❌ NE PAS créer agent_<name>/.hermes/ (ancien layout supprimé)
|
|
601
|
+
const hermesHome = getSharedHermesHome();
|
|
602
|
+
const hermesAgentDir = path.join(hermesHome, 'agents', name);
|
|
603
|
+
const hermesSkillsDir = path.join(hermesAgentDir, 'skills');
|
|
604
|
+
await fs.mkdir(hermesAgentDir, { recursive: true });
|
|
651
605
|
await fs.mkdir(hermesSkillsDir, { recursive: true });
|
|
652
|
-
|
|
606
|
+
// SOUL.md — system prompt
|
|
607
|
+
const promptPath = path.join(hermesAgentDir, 'SOUL.md');
|
|
653
608
|
await fs.writeFile(promptPath, finalPrompt, 'utf-8');
|
|
654
|
-
//
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
mcpServers
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
yamlContent += ` url: "${s.url}"\n`;
|
|
674
|
-
if (s.command)
|
|
675
|
-
yamlContent += ` command: "${s.command}"\n`;
|
|
676
|
-
}
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
catch {
|
|
680
|
-
// Ignored
|
|
681
|
-
}
|
|
682
|
-
await fs.writeFile(yamlPath, yamlContent, 'utf-8');
|
|
609
|
+
// settings.json — credentials + MCP (format JSON natif Hermes)
|
|
610
|
+
// ❌ NE PAS écrire .env ou config.yaml — Hermes lit settings.json
|
|
611
|
+
const settingsPath = path.join(hermesAgentDir, 'settings.json');
|
|
612
|
+
const hermesSettings = {
|
|
613
|
+
env: {
|
|
614
|
+
ANTHROPIC_AUTH_TOKEN: envVars.ANTHROPIC_AUTH_TOKEN || '',
|
|
615
|
+
ANTHROPIC_BASE_URL: envVars.ANTHROPIC_BASE_URL || 'https://api.minimaxi.com/anthropic',
|
|
616
|
+
ANTHROPIC_MODEL: envVars.ANTHROPIC_MODEL || model,
|
|
617
|
+
ANTHROPIC_PROVIDER: 'minimax-cn',
|
|
618
|
+
// Provider-specific (injecté aussi par NousHermesRunner au spawn)
|
|
619
|
+
MINIMAX_CN_API_KEY: envVars.ANTHROPIC_AUTH_TOKEN || '',
|
|
620
|
+
MINIMAX_CN_BASE_URL: 'https://api.minimaxi.com/anthropic',
|
|
621
|
+
},
|
|
622
|
+
enableAllProjectMcpServers: false,
|
|
623
|
+
enabledMcpjsonServers: mcpServers,
|
|
624
|
+
agent: name,
|
|
625
|
+
runner: 'hermes',
|
|
626
|
+
};
|
|
627
|
+
await fs.writeFile(settingsPath, JSON.stringify(hermesSettings, null, 2), 'utf-8');
|
|
683
628
|
// Mémoriser la création de l'agent
|
|
684
629
|
try {
|
|
685
630
|
const memory = getMemoryProvider();
|
|
686
631
|
await memory.storeKnowledge({
|
|
687
|
-
text: `Nouvel agent
|
|
632
|
+
text: `Nouvel agent Hermes créé : '${name}'. Credentials : ${settingsPath}.`,
|
|
688
633
|
source: 'agent',
|
|
689
634
|
agentName: name,
|
|
690
635
|
});
|
|
691
636
|
}
|
|
692
|
-
catch {
|
|
693
|
-
|
|
694
|
-
}
|
|
695
|
-
return { promptPath, settingsPath: envPath };
|
|
637
|
+
catch { /* Ignored */ }
|
|
638
|
+
return { promptPath, settingsPath };
|
|
696
639
|
}
|
|
697
640
|
const agentsDir = path.join(this.claudeDir, 'agents');
|
|
698
641
|
await fs.mkdir(this.claudeDir, { recursive: true });
|
|
@@ -744,80 +687,40 @@ Tu es conçu pour être exécuté par différents runners (Claude, Kilo, Gemini,
|
|
|
744
687
|
}
|
|
745
688
|
async getDetailedConfigs(name) {
|
|
746
689
|
validateAgentName(name);
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
690
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
691
|
+
// DÉTECTION DU TYPE D'AGENT — LAYOUT NATIF 2.8.45+
|
|
692
|
+
// Hermes → <HERMES_HOME>/agents/<name>/settings.json
|
|
693
|
+
// Claude → <WORKSPACE>/.claude/settings_<name>.json
|
|
694
|
+
// ❌ NE PAS chercher dans agent_<name>/.hermes/ (ancien layout supprimé)
|
|
695
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
696
|
+
const hermesHome = getSharedHermesHome();
|
|
697
|
+
const hermesAgentDir = path.join(hermesHome, 'agents', name);
|
|
750
698
|
let isHermes = false;
|
|
751
699
|
try {
|
|
752
700
|
const stat = await fs.stat(hermesAgentDir);
|
|
753
701
|
isHermes = stat.isDirectory();
|
|
754
702
|
}
|
|
755
|
-
catch {
|
|
756
|
-
// Ignored
|
|
757
|
-
}
|
|
703
|
+
catch { /* Ignored */ }
|
|
758
704
|
const result = {};
|
|
759
705
|
const readFileSafe = async (filePath) => {
|
|
760
706
|
try {
|
|
761
707
|
return await fs.readFile(filePath, 'utf-8');
|
|
762
708
|
}
|
|
763
|
-
catch
|
|
709
|
+
catch {
|
|
764
710
|
return 'MISSING';
|
|
765
711
|
}
|
|
766
712
|
};
|
|
767
713
|
if (isHermes) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
return;
|
|
779
|
-
const k = trimmed.slice(0, eqIdx).trim();
|
|
780
|
-
let v = trimmed.slice(eqIdx + 1).trim();
|
|
781
|
-
if (v.startsWith('"') && v.endsWith('"'))
|
|
782
|
-
v = v.slice(1, -1);
|
|
783
|
-
else if (v.startsWith("'") && v.endsWith("'"))
|
|
784
|
-
v = v.slice(1, -1);
|
|
785
|
-
if (k)
|
|
786
|
-
envVars[k] = v;
|
|
787
|
-
});
|
|
788
|
-
}
|
|
789
|
-
result['settings.json'] = JSON.stringify({
|
|
790
|
-
env: envVars,
|
|
791
|
-
model: envVars.MODEL || envVars.ANTHROPIC_MODEL || 'MiniMax-M2.7',
|
|
792
|
-
runner: 'hermes'
|
|
793
|
-
}, null, 2);
|
|
794
|
-
const mcpServers = {};
|
|
795
|
-
const yamlContent = await readFileSafe(path.join(hermesSubDir, 'config.yaml'));
|
|
796
|
-
if (yamlContent !== 'MISSING') {
|
|
797
|
-
const serverBlocks = yamlContent.split('\n ');
|
|
798
|
-
serverBlocks.forEach((block) => {
|
|
799
|
-
const lines = block.split('\n');
|
|
800
|
-
const header = lines[0].trim();
|
|
801
|
-
if (header && header.endsWith(':') && header !== 'mcp_servers:') {
|
|
802
|
-
const srvName = header.slice(0, -1).trim();
|
|
803
|
-
const srvObj = {};
|
|
804
|
-
lines.slice(1).forEach((l) => {
|
|
805
|
-
const parts = l.trim().split(':');
|
|
806
|
-
if (parts.length >= 2) {
|
|
807
|
-
const key = parts[0].trim();
|
|
808
|
-
let val = parts.slice(1).join(':').trim();
|
|
809
|
-
if (val.startsWith('"') && val.endsWith('"'))
|
|
810
|
-
val = val.slice(1, -1);
|
|
811
|
-
srvObj[key] = val;
|
|
812
|
-
}
|
|
813
|
-
});
|
|
814
|
-
mcpServers[srvName] = srvObj;
|
|
815
|
-
}
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
result['.mcp.json'] = JSON.stringify({ mcpServers }, null, 2);
|
|
819
|
-
const skillPath = path.join(hermesSubDir, 'skills', 'skill.md');
|
|
820
|
-
result['skill.md'] = await readFileSafe(skillPath);
|
|
714
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
715
|
+
// HERMES AGENT — lit directement depuis <HERMES_HOME>/agents/<name>/
|
|
716
|
+
// settings.json = credentials + MCP + provider (source de vérité unique)
|
|
717
|
+
// SOUL.md = system prompt
|
|
718
|
+
// .mcp.json = MCP override (optionnel)
|
|
719
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
720
|
+
result['settings.json'] = await readFileSafe(path.join(hermesAgentDir, 'settings.json'));
|
|
721
|
+
result['prompt.md'] = await readFileSafe(path.join(hermesAgentDir, 'SOUL.md'));
|
|
722
|
+
result['.mcp.json'] = await readFileSafe(path.join(hermesAgentDir, '.mcp.json'));
|
|
723
|
+
result['skill.md'] = await readFileSafe(path.join(hermesAgentDir, 'skills', 'skill.md'));
|
|
821
724
|
return result;
|
|
822
725
|
}
|
|
823
726
|
const agentsDir = path.join(this.claudeDir, 'agents');
|
|
@@ -826,7 +729,8 @@ Tu es conçu pour être exécuté par différents runners (Claude, Kilo, Gemini,
|
|
|
826
729
|
const mcpPath = path.join(this.claudeDir, `.mcp.${name}.json`);
|
|
827
730
|
const skillPath = path.join(this.claudeDir, `agents/${name}_skill.md`);
|
|
828
731
|
const alternativeSkillPath = path.join(this.claudeDir, `skills/${name}.md`);
|
|
829
|
-
// Fallback paths
|
|
732
|
+
// Fallback paths for Claude/Kilo agents
|
|
733
|
+
const workspaceDir = getWorkspaceDir();
|
|
830
734
|
const fallbackAgentsDir = path.join(workspaceDir, '.overmind', 'agents');
|
|
831
735
|
const fallbackPromptPath = path.join(fallbackAgentsDir, `${name}.md`);
|
|
832
736
|
const fallbackSettingsDir = path.join(workspaceDir, '.overmind', 'agents', name);
|