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.
@@ -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 .overmind/hermes/agent_*
46
- const hermesDir = path.join(workspaceDir, '.overmind', 'hermes');
47
- const hermesDirs = await fs.readdir(hermesDir).catch(() => []);
48
- const hermesAgentNames = hermesDirs
49
- .filter((d) => d.startsWith('agent_'))
50
- .map((d) => d.replace('agent_', ''));
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-M2.7';
135
+ let model = 'MiniMax-M3';
113
136
  let provider = 'minimax-cn';
114
- let mcpServers = ['memory-server', 'postgresql-server', 'twilio-mcp'];
137
+ let mcpServers = [];
115
138
  let promptSize = 0;
116
- const agentHermesDir = path.join(hermesDir, `agent_${name}`, '.hermes');
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(agentHermesDir, 'SOUL.md'));
143
+ const soulStat = await fs.stat(path.join(agentDir, 'SOUL.md'));
119
144
  promptSize = soulStat.size;
120
145
  }
121
- catch {
122
- // Ignored
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 content = await fs.readFile(envPath, 'utf-8');
128
- content.split('\n').forEach((line) => {
129
- const trimmed = line.trim();
130
- if (!trimmed || trimmed.startsWith('#'))
131
- return;
132
- const eqIdx = trimmed.indexOf('=');
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
- // Ignored
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: false,
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
- } // Group by runner
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
- * (b) Lecture non-destructive du runner effectif d'un agent.
244
- * - Pour un agent Hermes (.overmind/hermes/agent_<name>) : renvoie 'hermes'
245
- * - Pour un agent Claude (settings_<name>.json) : renvoie settings.runner || 'claude'
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
- * Utilisé par update_agent_config pour produire un warning/actionnable
248
- * quand l'appelant omet le paramètre 'runner'.
237
+ *
238
+ * NE PAS utiliser agent_<name> (ancien layout pré-2.8.30 supprimé)
249
239
  */
250
240
  peekRunner(name) {
251
- const workspaceDir = getWorkspaceDir();
252
- const hermesAgentDir = path.join(workspaceDir, '.overmind', 'hermes', `agent_${name}`);
241
+ const hermesHome = getSharedHermesHome();
242
+ const hermesSettingsPath = path.join(hermesHome, 'agents', name, 'settings.json');
253
243
  try {
254
- // fs synchrone pour rester léger (peek = lecture seule best-effort)
255
- // fallback : on évite de throw si le dossier n'existe pas
256
- // (la résolution async est faite par les appelants si besoin)
257
- // Ici on accepte une lecture bloquante très courte : c'est un warning, pas un hot-path.
258
- if (fsSync.existsSync(hermesAgentDir))
259
- return 'hermes';
260
- const settingsPath = path.join(this.claudeDir, `settings_${name}.json`);
261
- if (fsSync.existsSync(settingsPath)) {
262
- const raw = fsSync.readFileSync(settingsPath, 'utf-8');
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 workspaceDir = getWorkspaceDir();
280
- const hermesAgentDir = path.join(workspaceDir, '.overmind', 'hermes', `agent_${name}`);
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, `mcp_${name}_tmp.json`);
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
- const workspaceDir = getWorkspaceDir();
323
- const hermesAgentDir = path.join(workspaceDir, '.overmind', 'hermes', `agent_${name}`);
324
- const hermesSubDir = path.join(hermesAgentDir, '.hermes');
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(hermesSubDir, 'SOUL.md');
332
+ filePath = path.join(hermesAgentDir, 'SOUL.md');
340
333
  await fs.writeFile(filePath, updates.content, 'utf-8');
341
- changes.push(`✅ Fichier **SOUL.md** réécrit pour l'agent Hermes **${name}**.`);
334
+ changes.push(`✅ **SOUL.md** réécrit ${filePath}`);
342
335
  break;
343
- case 'settings.json': {
344
- let envStr = '';
345
- try {
346
- const s = JSON.parse(updates.content);
347
- if (s.env) {
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
- case '.mcp.json': {
367
- let yamlContent = 'mcp_servers:\n';
368
- try {
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(hermesSubDir, 'skills', 'skill.md');
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(`✅ Fichier **skill.md** réécrit pour l'agent Hermes **${name}**.`);
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
- const envVars = {};
425
- const envPath = path.join(hermesSubDir, '.env');
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 content = await fs.readFile(envPath, 'utf-8');
428
- content.split('\n').forEach((line) => {
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
- const oldModel = envVars.MODEL || envVars.ANTHROPIC_MODEL;
450
- envVars.MODEL = updates.model;
451
- changes.push(`- Modèle : ${oldModel} -> ${updates.model}`);
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 = envVars[key] ? '***' : '(undefined)';
456
- envVars[key] = value;
457
- changes.push(`- Env Var '${key}' : ${oldVal} -> ${value ? '***' : '(vide)'}`);
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
- let newEnvContent = '';
462
- for (const [k, v] of Object.entries(envVars)) {
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
- const workspaceDir = getWorkspaceDir();
647
- const hermesAgentDir = path.join(workspaceDir, '.overmind', 'hermes', `agent_${name}`);
648
- const hermesSubDir = path.join(hermesAgentDir, '.hermes');
649
- const hermesSkillsDir = path.join(hermesSubDir, 'skills');
650
- await fs.mkdir(hermesSubDir, { recursive: true });
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
- const promptPath = path.join(hermesSubDir, 'SOUL.md');
606
+ // SOUL.md system prompt
607
+ const promptPath = path.join(hermesAgentDir, 'SOUL.md');
653
608
  await fs.writeFile(promptPath, finalPrompt, 'utf-8');
654
- // Write .env
655
- const envPath = path.join(hermesSubDir, '.env');
656
- let envContent = '';
657
- for (const [k, v] of Object.entries(envVars)) {
658
- envContent += `${k}=${v}\n`;
659
- }
660
- await fs.writeFile(envPath, envContent, 'utf-8');
661
- // Write config.yaml (MCP config)
662
- const yamlPath = path.join(hermesSubDir, 'config.yaml');
663
- let yamlContent = 'mcp_servers:\n';
664
- const globalMcpPath = resolveConfigPath(CONFIG.CLAUDE.PATHS.MCP);
665
- try {
666
- const globalMcpContent = await fs.readFile(globalMcpPath, 'utf-8');
667
- const globalMcp = JSON.parse(globalMcpContent);
668
- mcpServers.forEach((serverName) => {
669
- if (globalMcp.mcpServers && globalMcp.mcpServers[serverName]) {
670
- const s = globalMcp.mcpServers[serverName];
671
- yamlContent += ` ${serverName}:\n`;
672
- if (s.url)
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 IA créé : '${name}'. Runner : hermes.`,
632
+ text: `Nouvel agent Hermes créé : '${name}'. Credentials : ${settingsPath}.`,
688
633
  source: 'agent',
689
634
  agentName: name,
690
635
  });
691
636
  }
692
- catch {
693
- // Ignored
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
- const workspaceDir = getWorkspaceDir();
748
- const hermesAgentDir = path.join(workspaceDir, '.overmind', 'hermes', `agent_${name}`);
749
- const hermesSubDir = path.join(hermesAgentDir, '.hermes');
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 (_e) {
709
+ catch {
764
710
  return 'MISSING';
765
711
  }
766
712
  };
767
713
  if (isHermes) {
768
- result['prompt.md'] = await readFileSafe(path.join(hermesSubDir, 'SOUL.md'));
769
- const envVars = {};
770
- const envContent = await readFileSafe(path.join(hermesSubDir, '.env'));
771
- if (envContent !== 'MISSING') {
772
- envContent.split('\n').forEach((line) => {
773
- const trimmed = line.trim();
774
- if (!trimmed || trimmed.startsWith('#'))
775
- return;
776
- const eqIdx = trimmed.indexOf('=');
777
- if (eqIdx === -1)
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 using workspace from OVERMIND_WORKSPACE env var
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);