overmind-mcp 2.8.27 → 2.8.30
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/lib/config.d.ts +41 -20
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +85 -59
- package/dist/lib/config.js.map +1 -1
- package/dist/services/NousHermesRunner.d.ts +14 -5
- package/dist/services/NousHermesRunner.d.ts.map +1 -1
- package/dist/services/NousHermesRunner.js +115 -260
- package/dist/services/NousHermesRunner.js.map +1 -1
- package/docs/SUBTILISATION_EXPLAINED.md +306 -284
- package/docs/provider-config-map.md +27 -0
- package/package.json +1 -1
- package/scripts/postinstall.mjs +6 -0
|
@@ -3,7 +3,7 @@ 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, getWorkspaceDir, getAgentHermesHome, getAgentOvermindHome } from '../lib/config.js';
|
|
6
|
+
import { CONFIG, resolveConfigPath, getWorkspaceDir, getAgentHermesHome, getAgentOvermindHome, getSharedHermesHome } from '../lib/config.js';
|
|
7
7
|
import { getLastSessionId, saveSessionId } from '../lib/sessions.js';
|
|
8
8
|
import { linkSessionToPid } from '../lib/processRegistry.js';
|
|
9
9
|
import { interpolateEnvVars } from '../lib/envUtils.js';
|
|
@@ -148,11 +148,20 @@ async function findHermesBinary() {
|
|
|
148
148
|
return 'hermes';
|
|
149
149
|
}
|
|
150
150
|
/**
|
|
151
|
-
* NousHermesRunner — Runner polyglote pour Hermes Agent.
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
151
|
+
* NousHermesRunner — Runner polyglote pour Hermes Agent (Overmind 2.8.27+).
|
|
152
|
+
*
|
|
153
|
+
* • Providers : OpenAI, MiniMax GLOBAL/CN, Zhipu/GLM, Mistral, NVIDIA NIM, OpenRouter (embeddings only)
|
|
154
|
+
* • Lit settings_<agent>.json + .mcp.<agent>.json depuis .claude/ comme les autres runners
|
|
155
|
+
* • Interpolation $VAR et ${VAR} sur tout settings + mcp config (via envUtils, regex fix 2.8.25)
|
|
156
|
+
* • Subtilisation 3-pass (Pass 1: settings-explicit, Pass A: prefer provider-specific,
|
|
157
|
+
* Pass B: re-map generic key, Pass C: rare fallback) — see hermesTokenResolver.ts
|
|
158
|
+
* • CN/GLOBAL disambiguation for sk-cp-* via ANTHROPIC_BASE_URL (URL wins)
|
|
159
|
+
* • OVERMIND_MINIMAX_DEFAULT=cn|global|auto for setups where all MiniMax tokens are CN
|
|
160
|
+
* • HERMES_HOME resolved via getAgentHermesHome() — deterministic across cwd (2.8.27+)
|
|
161
|
+
* Priority: OVERMIND_AGENT_HOME > legacy workspace > $HOME/.overmind/hermes/agent_<name>/
|
|
162
|
+
* • auth.json credential_pool is PRUNED every run (keep version+oauth, drop stale creds)
|
|
163
|
+
* to prevent Hermes from picking an exhausted bucket from a previous provider config
|
|
164
|
+
* • HOME/USERPROFILE propagated to spawned Hermes so ~/.hermes lookups resolve canonically
|
|
156
165
|
*/
|
|
157
166
|
export class NousHermesRunner {
|
|
158
167
|
timeoutMs;
|
|
@@ -288,7 +297,14 @@ export class NousHermesRunner {
|
|
|
288
297
|
'MISTRAL_API_KEY_6', 'MISTRAL_API_KEY_7', 'MISTRAL_API_KEY_E', 'MISTRAL_API_KEY_Y',
|
|
289
298
|
];
|
|
290
299
|
if (agentName) {
|
|
291
|
-
|
|
300
|
+
// Locate the per-agent SOUL.md (system prompt). We support the canonical
|
|
301
|
+
// Hermes layout (HERMES_HOME/agents/<name>/SOUL.md) and a one-shot legacy
|
|
302
|
+
// path (HERMES_HOME/agent_<name>/.hermes/SOUL.md) for existing installs.
|
|
303
|
+
// The canonical path wins; the legacy is fallback so we don't break
|
|
304
|
+
// agents that haven't been migrated yet.
|
|
305
|
+
const canonicalSoul = path.join(overmindHermesSubPath, 'SOUL.md');
|
|
306
|
+
const legacySoul = path.join(getSharedHermesHome(), `agent_${agentName}`, '.hermes', 'SOUL.md');
|
|
307
|
+
const agentPromptPath = fs.existsSync(canonicalSoul) ? canonicalSoul : legacySoul;
|
|
292
308
|
if (fs.existsSync(agentPromptPath)) {
|
|
293
309
|
systemPrompt = fs.readFileSync(agentPromptPath, 'utf8');
|
|
294
310
|
}
|
|
@@ -369,7 +385,21 @@ export class NousHermesRunner {
|
|
|
369
385
|
catch (e) {
|
|
370
386
|
logger.warn({ error: e }, `Failed to process settings/mcp configurations for Hermes agent ${agentName}`);
|
|
371
387
|
}
|
|
372
|
-
// Load environment from isolated .env file
|
|
388
|
+
// Load environment from the agent's isolated .hermes/.env file, BUT only as a
|
|
389
|
+
// fallback for keys that the explicit settings_<agent>.json did NOT set.
|
|
390
|
+
//
|
|
391
|
+
// CRITICAL (2.8.29): The .hermes/.env file is a STALE WRITE of the previous
|
|
392
|
+
// spawn — it gets re-written by the runner itself at line ~1013, but if a
|
|
393
|
+
// user's first run was for Z.AI and they later switch the agent to MiniMax
|
|
394
|
+
// CN, the stale .hermes/.env from the previous run gets re-loaded into
|
|
395
|
+
// agentCustomEnv HERE, OVERWRITING the MiniMax settings that were just merged
|
|
396
|
+
// from settings_<agent>.json in the block above (line ~412). Symptom: the
|
|
397
|
+
// agent silently reverts to the old provider (e.g. Z.AI glm-5.1) on every
|
|
398
|
+
// spawn, causing persistent 401s.
|
|
399
|
+
//
|
|
400
|
+
// Fix: only load keys from .hermes/.env that are NOT already in agentCustomEnv
|
|
401
|
+
// (i.e. settings_<agent>.json wins; .hermes/.env is a fallback for unrelated
|
|
402
|
+
// custom vars the user might have set manually).
|
|
373
403
|
const envPath = path.join(overmindHermesSubPath, '.env');
|
|
374
404
|
if (fs.existsSync(envPath)) {
|
|
375
405
|
try {
|
|
@@ -387,7 +417,10 @@ export class NousHermesRunner {
|
|
|
387
417
|
value = value.slice(1, -1);
|
|
388
418
|
else if (value.startsWith("'") && value.endsWith("'"))
|
|
389
419
|
value = value.slice(1, -1);
|
|
390
|
-
if (key) {
|
|
420
|
+
if (key && agentCustomEnv[key] === undefined) {
|
|
421
|
+
// Only fill in keys that settings_<agent>.json did NOT set.
|
|
422
|
+
// settings_<agent>.json is the user's source of truth; .hermes/.env
|
|
423
|
+
// is a stale write from a previous spawn and must not override it.
|
|
391
424
|
agentCustomEnv[key] = value;
|
|
392
425
|
}
|
|
393
426
|
});
|
|
@@ -415,10 +448,19 @@ export class NousHermesRunner {
|
|
|
415
448
|
const cliPrompt = finalPrompt.length > 7000 ? finalPrompt.substring(0, 7000) : finalPrompt;
|
|
416
449
|
// Build CLI args: chat -q (persistent session, NOT -z oneshot)
|
|
417
450
|
// -z + --resume doesn't work — resume is ignored in oneshot mode
|
|
451
|
+
//
|
|
452
|
+
// DO NOT pass --provider explicitly. We learned empirically (Hermes-MiniMax-2.bat
|
|
453
|
+
// works while `hermes chat -q --provider minimax-cn` 401s) that letting Hermes
|
|
454
|
+
// auto-detect the provider from MINIMAX_CN_API_KEY / ZAI_ANTHROPIC_FALLBACK_KEY
|
|
455
|
+
// / etc. in the env gives correct results, while the explicit --provider flag
|
|
456
|
+
// activates a buggy code path that sends an auth header the upstream rejects.
|
|
457
|
+
// The ANTHROPIC_MODEL + ANTHROPIC_BASE_URL + provider-specific env var are
|
|
458
|
+
// enough for Hermes to pick the right plugin on its own.
|
|
418
459
|
const cleanArgs = ['chat', '-q', cliPrompt, '-Q'];
|
|
419
460
|
cleanArgs.push('--model', finalModel);
|
|
461
|
+
// resolvedProvider is logged for debugging but NOT passed as --provider.
|
|
420
462
|
if (options.provider || resolvedProvider) {
|
|
421
|
-
|
|
463
|
+
logger.info({ agentName, resolvedProvider: options.provider || resolvedProvider, hint: 'omitted --provider; letting Hermes auto-detect from env' }, '[HERMES_ARGS] Not passing --provider (auto-detect from MINIMAX_CN_API_KEY et al. is more reliable).');
|
|
422
464
|
}
|
|
423
465
|
if (sessionId)
|
|
424
466
|
cleanArgs.push('--resume', sessionId);
|
|
@@ -600,10 +642,12 @@ export class NousHermesRunner {
|
|
|
600
642
|
lower.includes('service unavailable') || lower.includes('500') ||
|
|
601
643
|
lower.includes('internal server error');
|
|
602
644
|
};
|
|
603
|
-
// HERMES_HOME setup
|
|
645
|
+
// HERMES_HOME setup — the SHARED root, not the per-agent home.
|
|
646
|
+
// Hermes upstream resolves `agents/<name>/`, `config.yaml`, `auth.json`, etc.
|
|
647
|
+
// relative to this single root. We do NOT seed `agentCustomEnv.HERMES_HOME`
|
|
648
|
+
// here anymore because spawnHermes() sets it explicitly from getSharedHermesHome().
|
|
604
649
|
if (!fs.existsSync(overmindHermesSubPath))
|
|
605
650
|
fs.mkdirSync(overmindHermesSubPath, { recursive: true });
|
|
606
|
-
agentCustomEnv.HERMES_HOME = overmindHermesSubPath;
|
|
607
651
|
// HOME / USERPROFILE override: point Hermes at the parent .overmind dir,
|
|
608
652
|
// NOT the cwd. This makes relative .hermes lookups inside Hermes
|
|
609
653
|
// (e.g. `~/.hermes/.env` resolution) resolve to the same canonical
|
|
@@ -612,86 +656,62 @@ export class NousHermesRunner {
|
|
|
612
656
|
agentCustomEnv.USERPROFILE = overmindHermesPath;
|
|
613
657
|
else
|
|
614
658
|
agentCustomEnv.HOME = overmindHermesPath;
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
659
|
+
// AbortSignal
|
|
660
|
+
if (options.signal?.aborted)
|
|
661
|
+
return Promise.reject(new Error('ABORTED'));
|
|
662
|
+
// =============================================================
|
|
663
|
+
// 2.8.30 — WRITE CANONICAL Hermes SETTINGS
|
|
664
|
+
// =============================================================
|
|
665
|
+
// Hermes upstream uses the standard appdirs-style layout:
|
|
666
|
+
// <HERMES_HOME>/agents/<name>/settings.json ← per-agent env, mcp, persona
|
|
667
|
+
// <HERMES_HOME>/agents/<name>/SOUL.md ← per-agent system prompt
|
|
668
|
+
// <HERMES_HOME>/config.yaml ← global, Hermes manages
|
|
669
|
+
// <HERMES_HOME>/auth.json ← global, Hermes manages
|
|
670
|
+
//
|
|
671
|
+
// Overmind's only job here is to:
|
|
672
|
+
// 1. Convert Workflow/.claude/settings_<name>.json (Overmind runner format)
|
|
673
|
+
// into the canonical Hermes format and write it to <HERMES_HOME>/agents/<name>/settings.json
|
|
674
|
+
// 2. Make sure <HERMES_HOME>/agents/<name>/SOUL.md exists (canonical path)
|
|
675
|
+
// 3. Let Hermes upstream manage config.yaml, auth.json, sessions/, etc.
|
|
676
|
+
//
|
|
677
|
+
// This replaces the previous "polylgot" hack where Overmind wrote
|
|
678
|
+
// `<HERMES_HOME>/agent_<name>/.hermes/.env`, `.hermes/config.yaml`, and
|
|
679
|
+
// `.hermes/auth.json` — files that don't match Hermes's expected layout
|
|
680
|
+
// and caused credential drift + silent 401s.
|
|
681
|
+
if (agentName) {
|
|
682
|
+
const agentHome = overmindHermesSubPath; // = <HERMES_HOME>/agents/<name>/
|
|
683
|
+
if (!fs.existsSync(agentHome))
|
|
684
|
+
fs.mkdirSync(agentHome, { recursive: true });
|
|
685
|
+
// Build the canonical Hermes settings.json from the agent's settings_<name>.json.
|
|
686
|
+
// We preserve: env, enableAllProjectMcpServers, enabledMcpjsonServers, agent, runner.
|
|
687
|
+
// We do NOT touch: config.yaml, auth.json, .env — Hermes upstream owns those.
|
|
688
|
+
const tmpAgentSettings = path.join(agentHome, 'settings.json');
|
|
689
|
+
const settingsJson = {};
|
|
690
|
+
// Read the interpolated settings the runner just merged (line ~412 above)
|
|
691
|
+
// and copy the Hermes-relevant fields. We don't re-interpolate here because
|
|
692
|
+
// the caller already did it on the raw `settings` object.
|
|
693
|
+
if (tmpSettingsPath && fs.existsSync(tmpSettingsPath)) {
|
|
694
|
+
try {
|
|
695
|
+
const raw = JSON.parse(fs.readFileSync(tmpSettingsPath, 'utf8'));
|
|
696
|
+
if (raw.env)
|
|
697
|
+
settingsJson.env = raw.env;
|
|
698
|
+
if (raw.enableAllProjectMcpServers !== undefined) {
|
|
699
|
+
settingsJson.enableAllProjectMcpServers = raw.enableAllProjectMcpServers;
|
|
700
|
+
}
|
|
701
|
+
if (Array.isArray(raw.enabledMcpjsonServers)) {
|
|
702
|
+
settingsJson.enabledMcpjsonServers = raw.enabledMcpjsonServers;
|
|
641
703
|
}
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
catch (e) {
|
|
645
|
-
logger.warn({ envPath: dotPath, error: e }, 'Failed to read existing agent env file for deduplication');
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
for (const [k, v] of Object.entries(agentCustomEnv)) {
|
|
649
|
-
if (typeof v === 'string' && v.length > 0 && credRegex.test(k)) {
|
|
650
|
-
if (openRouterPrefixes.some(p => k.toUpperCase().startsWith(p)))
|
|
651
|
-
continue;
|
|
652
|
-
envMap.set(k, v);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
const finalDotEntries = [];
|
|
656
|
-
for (const [k, v] of envMap.entries()) {
|
|
657
|
-
finalDotEntries.push(`${k}=${v}`);
|
|
658
|
-
}
|
|
659
|
-
fs.writeFileSync(dotPath, finalDotEntries.join('\n') + '\n', 'utf8');
|
|
660
|
-
// Generate config.yaml in HERMES_HOME (MCP servers)
|
|
661
|
-
if (tmpMcpPath && fs.existsSync(tmpMcpPath)) {
|
|
662
|
-
try {
|
|
663
|
-
const mc = JSON.parse(fs.readFileSync(tmpMcpPath, 'utf8'));
|
|
664
|
-
const yamlPath = path.join(overmindHermesSubPath, 'config.yaml');
|
|
665
|
-
// Preserve existing config.yaml (tts, llm, etc.) — merge mcp_servers only
|
|
666
|
-
let existingYaml = '';
|
|
667
|
-
if (fs.existsSync(yamlPath)) {
|
|
668
|
-
existingYaml = fs.readFileSync(yamlPath, 'utf8');
|
|
669
|
-
}
|
|
670
|
-
// Build new mcp_servers section
|
|
671
|
-
let newMcpSection = 'mcp_servers:\n';
|
|
672
|
-
for (const [name, srv] of Object.entries(mc.mcpServers || {})) {
|
|
673
|
-
const s = srv;
|
|
674
|
-
newMcpSection += ` ${name}:\n`;
|
|
675
|
-
if (s.url)
|
|
676
|
-
newMcpSection += ` url: "${s.url}"\n`;
|
|
677
|
-
if (s.command)
|
|
678
|
-
newMcpSection += ` command: "${s.command}"\n`;
|
|
679
|
-
}
|
|
680
|
-
// Merge: replace mcp_servers block in existing yaml or append
|
|
681
|
-
let finalYaml;
|
|
682
|
-
if (existingYaml.includes('mcp_servers:')) {
|
|
683
|
-
finalYaml = existingYaml.replace(/mcp_servers:\n([\s\S]*?)(?=\n\w|\n$|$)/, newMcpSection.trimEnd() + '\n');
|
|
684
704
|
}
|
|
685
|
-
|
|
686
|
-
|
|
705
|
+
catch (e) {
|
|
706
|
+
logger.warn({ tmpSettingsPath, error: e }, 'Failed to read tmp settings for canonical write');
|
|
687
707
|
}
|
|
688
|
-
fs.writeFileSync(yamlPath, finalYaml, 'utf8');
|
|
689
|
-
if (!silent)
|
|
690
|
-
console.error(`[NousHermesRunner] MCP config.yaml written to ${yamlPath}`);
|
|
691
|
-
}
|
|
692
|
-
catch (e) {
|
|
693
|
-
console.error(`[NousHermesRunner] config.yaml error: ${e}`);
|
|
694
708
|
}
|
|
709
|
+
// Always declare the agent name + runner so Hermes can route to the right MCP servers.
|
|
710
|
+
settingsJson.agent = agentName;
|
|
711
|
+
settingsJson.runner = 'hermes';
|
|
712
|
+
fs.writeFileSync(tmpAgentSettings, JSON.stringify(settingsJson, null, 2) + '\n', 'utf8');
|
|
713
|
+
this.tempFiles.push(tmpAgentSettings);
|
|
714
|
+
logger.info({ agentName, settingsPath: tmpAgentSettings, envKeys: Object.keys(settingsJson.env || {}).length }, '[HERMES] Wrote canonical agents/<name>/settings.json (env block from settings_<name>.json).');
|
|
695
715
|
}
|
|
696
716
|
// AbortSignal
|
|
697
717
|
if (options.signal?.aborted)
|
|
@@ -716,176 +736,6 @@ export class NousHermesRunner {
|
|
|
716
736
|
}
|
|
717
737
|
}
|
|
718
738
|
};
|
|
719
|
-
const writeAuthJson = (tokenInfo) => {
|
|
720
|
-
if (!tokenInfo || !overmindHermesSubPath)
|
|
721
|
-
return;
|
|
722
|
-
try {
|
|
723
|
-
const authPath = path.join(overmindHermesSubPath, 'auth.json');
|
|
724
|
-
// Read existing auth.json to preserve non-credential_pool state
|
|
725
|
-
// (e.g. oauth tokens, settings, version). But we PRUNE credential_pool
|
|
726
|
-
// entries for OTHER providers — those are stale from previous provider
|
|
727
|
-
// configs and Hermes may pick them up by mistake, causing silent 401s
|
|
728
|
-
// on the wrong endpoint. This is the source of the "auth.json drift"
|
|
729
|
-
// bug where the runner would seed `minimax-cn` credentials while a stale
|
|
730
|
-
// `zai` entry with last_status="exhausted" still existed in the pool.
|
|
731
|
-
let preservedAuth = { version: 1, providers: {} };
|
|
732
|
-
if (fs.existsSync(authPath)) {
|
|
733
|
-
try {
|
|
734
|
-
const parsed = JSON.parse(fs.readFileSync(authPath, 'utf8'));
|
|
735
|
-
// Keep the version + any oauth providers; drop credential_pool entirely
|
|
736
|
-
// (it will be re-seeded below with only the effectiveProvider's entries).
|
|
737
|
-
preservedAuth = {
|
|
738
|
-
version: parsed.version ?? 1,
|
|
739
|
-
providers: parsed.providers ?? {},
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
catch (e) {
|
|
743
|
-
logger.warn({ authPath, error: e }, 'auth.json was malformed; re-creating from scratch');
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
const auth = {
|
|
747
|
-
...preservedAuth,
|
|
748
|
-
credential_pool: {},
|
|
749
|
-
};
|
|
750
|
-
const cleanCp = auth.credential_pool;
|
|
751
|
-
// Determine effective provider from MULTIPLE signals
|
|
752
|
-
// Priority: TOKEN PREFIX (most reliable) > BASE_URL (very reliable) > settings.ANTHROPIC_PROVIDER (hint only)
|
|
753
|
-
// The user can put anything in settings.ANTHROPIC_PROVIDER — we don't blindly trust it.
|
|
754
|
-
const baseUrlHint = agentCustomEnv['ANTHROPIC_BASE_URL'] || agentCustomEnv['GLM_BASE_URL'] || '';
|
|
755
|
-
// First, detect from token prefix
|
|
756
|
-
const detectedFromToken = detectTokenProvider(tokenInfo.tokenValue);
|
|
757
|
-
// Then, detect from base URL
|
|
758
|
-
let detectedFromUrl = null;
|
|
759
|
-
if (baseUrlHint) {
|
|
760
|
-
const url = baseUrlHint.toLowerCase();
|
|
761
|
-
if (url.includes('minimaxi')) {
|
|
762
|
-
// The "i" suffix in api.minimaxi.com is the CN-specific endpoint
|
|
763
|
-
detectedFromUrl = 'minimax-cn';
|
|
764
|
-
}
|
|
765
|
-
else if (url.includes('minimax')) {
|
|
766
|
-
// api.minimax.com (no i) is the GLOBAL endpoint
|
|
767
|
-
detectedFromUrl = 'minimax';
|
|
768
|
-
}
|
|
769
|
-
else if (url.includes('z.ai') || url.includes('bigmodel') || url.includes('zhipu')) {
|
|
770
|
-
detectedFromUrl = 'zai';
|
|
771
|
-
}
|
|
772
|
-
else if (url.includes('anthropic.com')) {
|
|
773
|
-
detectedFromUrl = 'anthropic';
|
|
774
|
-
}
|
|
775
|
-
else if (url.includes('openai.com')) {
|
|
776
|
-
detectedFromUrl = 'openai';
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
// Then, the hint from settings
|
|
780
|
-
const settingsHint = resolvedProvider || '';
|
|
781
|
-
// Voting: token > URL > settings
|
|
782
|
-
// SPECIAL CASE: if token says "minimax" and URL says "minimax-cn" (or vice versa),
|
|
783
|
-
// the URL wins because the token prefix sk-cp- is shared between both endpoints.
|
|
784
|
-
// The URL is the only signal that can disambiguate CN vs GLOBAL.
|
|
785
|
-
//
|
|
786
|
-
// DEFAULT FOR MiniMax WHEN AMBIGUOUS:
|
|
787
|
-
// The sk-cp- prefix is shared between MiniMax GLOBAL and MiniMax CN. The
|
|
788
|
-
// URL is the only signal that disambiguates. For users whose setup
|
|
789
|
-
// exclusively uses CN tokens (the most common case for non-China
|
|
790
|
-
// operators), an absent/ambiguous URL should default to CN rather than
|
|
791
|
-
// silently picking GLOBAL and getting a 401. Override via env var:
|
|
792
|
-
// OVERMIND_MINIMAX_DEFAULT=cn (default: CN when ambiguous)
|
|
793
|
-
// OVERMIND_MINIMAX_DEFAULT=global (treat sk-cp-* as GLOBAL)
|
|
794
|
-
// OVERMIND_MINIMAX_DEFAULT=auto (never infer, require URL to disambiguate)
|
|
795
|
-
const minimaxDefault = (process.env.OVERMIND_MINIMAX_DEFAULT || 'cn').toLowerCase();
|
|
796
|
-
const minimaxDefaults = { cn: 'minimax-cn', global: 'minimax', auto: 'minimax' };
|
|
797
|
-
const minimaxFallback = minimaxDefaults[minimaxDefault] || minimaxDefaults.cn;
|
|
798
|
-
let effectiveProvider;
|
|
799
|
-
if (detectedFromToken.provider === 'minimax' && detectedFromUrl === 'minimax-cn') {
|
|
800
|
-
// URL has more specific info than the token prefix
|
|
801
|
-
effectiveProvider = 'minimax-cn';
|
|
802
|
-
logger.info({ agentName, tokenSays: 'minimax', urlSays: 'minimax-cn', settingsHint }, '[SUBTILISATION] URL is more specific than token prefix (minimax vs minimax-cn) — using URL.');
|
|
803
|
-
}
|
|
804
|
-
else if (detectedFromToken.provider === 'minimax-cn' && detectedFromUrl === 'minimax') {
|
|
805
|
-
effectiveProvider = 'minimax';
|
|
806
|
-
logger.info({ agentName, tokenSays: 'minimax-cn', urlSays: 'minimax', settingsHint }, '[SUBTILISATION] URL is more specific than token prefix (minimax vs minimax-cn) — using URL.');
|
|
807
|
-
}
|
|
808
|
-
else if (detectedFromToken.provider === 'minimax' && !detectedFromUrl) {
|
|
809
|
-
// Token says MiniMax, no URL hint — use OVERMIND_MINIMAX_DEFAULT
|
|
810
|
-
effectiveProvider = minimaxFallback;
|
|
811
|
-
logger.info({ agentName, tokenSays: 'minimax', urlSays: '(none)', minimaxDefault, effectiveProvider }, '[SUBTILISATION] MiniMax token without explicit URL — applying OVERMIND_MINIMAX_DEFAULT.');
|
|
812
|
-
}
|
|
813
|
-
else if (detectedFromToken.provider !== 'unknown') {
|
|
814
|
-
effectiveProvider = detectedFromToken.provider;
|
|
815
|
-
if (settingsHint && settingsHint !== effectiveProvider) {
|
|
816
|
-
logger.warn({ agentName, settingsHint, tokenSays: effectiveProvider, urlSays: detectedFromUrl }, '[SUBTILISATION] settings.ANTHROPIC_PROVIDER contradicts token prefix — using token.');
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
else if (detectedFromUrl) {
|
|
820
|
-
effectiveProvider = detectedFromUrl;
|
|
821
|
-
if (settingsHint && settingsHint !== effectiveProvider) {
|
|
822
|
-
logger.warn({ agentName, settingsHint, urlSays: effectiveProvider, tokenSays: detectedFromToken.provider }, '[SUBTILISATION] settings.ANTHROPIC_PROVIDER contradicts BASE_URL — using URL.');
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
else if (settingsHint) {
|
|
826
|
-
effectiveProvider = settingsHint;
|
|
827
|
-
}
|
|
828
|
-
else {
|
|
829
|
-
effectiveProvider = 'zai';
|
|
830
|
-
}
|
|
831
|
-
cleanCp[effectiveProvider] = [{
|
|
832
|
-
id: `${effectiveProvider}-default`, label: tokenInfo.tokenEnvKey, auth_type: 'api_key',
|
|
833
|
-
priority: 0, source: `env:${tokenInfo.tokenEnvKey}`, access_token: tokenInfo.tokenValue,
|
|
834
|
-
last_status: null, last_error_code: null,
|
|
835
|
-
base_url: baseUrlHint || defaultBaseUrlFor(effectiveProvider),
|
|
836
|
-
request_count: 0,
|
|
837
|
-
}];
|
|
838
|
-
fs.writeFileSync(authPath, JSON.stringify(auth, null, 2), 'utf8');
|
|
839
|
-
// ============================================================
|
|
840
|
-
// Write .env for HERMES_HOME — emit the 4 canonical fields
|
|
841
|
-
// Hermes needs: ANTHROPIC_MODEL, ANTHROPIC_AUTH_TOKEN,
|
|
842
|
-
// ANTHROPIC_PROVIDER, ANTHROPIC_BASE_URL
|
|
843
|
-
// ============================================================
|
|
844
|
-
const dotEnvPath = path.join(overmindHermesSubPath, '.env');
|
|
845
|
-
const dotLines = [];
|
|
846
|
-
// 1. ANTHROPIC_MODEL (always — Hermes needs it)
|
|
847
|
-
if (finalModel) {
|
|
848
|
-
dotLines.push(`ANTHROPIC_MODEL=${finalModel}`);
|
|
849
|
-
}
|
|
850
|
-
// 2. ANTHROPIC_PROVIDER (the kebab-case provider name)
|
|
851
|
-
dotLines.push(`ANTHROPIC_PROVIDER=${effectiveProvider}`);
|
|
852
|
-
// 3. ANTHROPIC_BASE_URL (from settings, or fallback)
|
|
853
|
-
const resolvedBaseUrl = baseUrlHint || defaultBaseUrlFor(effectiveProvider);
|
|
854
|
-
dotLines.push(`ANTHROPIC_BASE_URL=${resolvedBaseUrl}`);
|
|
855
|
-
// 4. ANTHROPIC_AUTH_TOKEN = literal token value (for backward compat with older Hermes versions)
|
|
856
|
-
// (Hermes reads this env var directly — no more provider-specific mapping)
|
|
857
|
-
dotLines.push(`ANTHROPIC_AUTH_TOKEN=${tokenInfo.tokenValue}`);
|
|
858
|
-
// 5. ALSO seed the provider-specific env var for plugins that need it
|
|
859
|
-
// For MiniMax/Z.AI plugins, the provider-specific var is the PRIMARY key
|
|
860
|
-
// the plugin reads. The .bat launchers in C:\Users\Deamon\Desktop\launcher\
|
|
861
|
-
// set MINIMAX_CN_API_KEY directly (not ANTHROPIC_AUTH_TOKEN), confirming
|
|
862
|
-
// that this is what the upstream plugin actually consumes.
|
|
863
|
-
if (effectiveProvider === 'minimax' || effectiveProvider === 'minimax-cn') {
|
|
864
|
-
// Both: the plugin reads whichever is set
|
|
865
|
-
if (effectiveProvider === 'minimax-cn') {
|
|
866
|
-
dotLines.push(`MINIMAX_CN_API_KEY=${tokenInfo.tokenValue}`);
|
|
867
|
-
}
|
|
868
|
-
else {
|
|
869
|
-
dotLines.push(`MINIMAX_API_KEY=${tokenInfo.tokenValue}`);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
else if (effectiveProvider === 'zai' || effectiveProvider === 'z-ai') {
|
|
873
|
-
dotLines.push(`GLM_API_KEY=${tokenInfo.tokenValue}`);
|
|
874
|
-
dotLines.push(`ZAI_ANTHROPIC_FALLBACK_KEY=${tokenInfo.tokenValue}`);
|
|
875
|
-
}
|
|
876
|
-
else if (effectiveProvider === 'openai') {
|
|
877
|
-
dotLines.push(`OPENAI_API_KEY=${tokenInfo.tokenValue}`);
|
|
878
|
-
}
|
|
879
|
-
else if (effectiveProvider === 'anthropic') {
|
|
880
|
-
// ANTHROPIC_AUTH_TOKEN already set above
|
|
881
|
-
}
|
|
882
|
-
fs.writeFileSync(dotEnvPath, dotLines.join('\n') + '\n', 'utf8');
|
|
883
|
-
logger.info({ agentName, effectiveProvider, baseUrl: resolvedBaseUrl, model: finalModel, sourceKey: tokenInfo.tokenEnvKey, detectedProvider: detectedFromToken.provider, envKeysWritten: dotLines.length }, '[AUTH] Wrote agent .env with 4 canonical Hermes fields + provider-specific seeds.');
|
|
884
|
-
}
|
|
885
|
-
catch (e) {
|
|
886
|
-
logger.warn({ error: e, agentName }, '[AUTH] Failed to write auth.json or agent .env');
|
|
887
|
-
}
|
|
888
|
-
};
|
|
889
739
|
const spawnHermes = async (tokenInfo) => {
|
|
890
740
|
const spawnEnv = { ...process.env, ...agentCustomEnv };
|
|
891
741
|
if (tokenInfo) {
|
|
@@ -896,7 +746,6 @@ export class NousHermesRunner {
|
|
|
896
746
|
resolvedToken = process.env[resolvedToken.slice(1)] || resolvedToken;
|
|
897
747
|
spawnEnv[tokenInfo.tokenEnvKey] = resolvedToken;
|
|
898
748
|
}
|
|
899
|
-
writeAuthJson(tokenInfo);
|
|
900
749
|
// BLOCK: OpenRouter is for embeddings only — never pass to Hermes for LLM inference
|
|
901
750
|
delete spawnEnv['OPENROUTER_API_KEY'];
|
|
902
751
|
delete spawnEnv['OPENROUTER_BASE_URL'];
|
|
@@ -913,11 +762,17 @@ export class NousHermesRunner {
|
|
|
913
762
|
const venvBin = isWin ? path.join(venvRoot, 'Scripts') : path.join(venvRoot, 'bin');
|
|
914
763
|
const isVenvInstall = hermesBin.startsWith(venvBin + path.sep) || hermesBin === venvBin;
|
|
915
764
|
const pathSep = isWin ? ';' : ':';
|
|
765
|
+
// 2.8.30: HERMES_HOME is the SHARED root, not the per-agent home.
|
|
766
|
+
// Hermes upstream resolves `agents/<name>/`, `config.yaml`, `auth.json`,
|
|
767
|
+
// etc. relative to HERMES_HOME. Setting it to the per-agent home would
|
|
768
|
+
// tell Hermes "this IS the Hermes root" and make it look for config.yaml
|
|
769
|
+
// IN the agent dir — wrong layout.
|
|
770
|
+
const sharedHermesHome = getSharedHermesHome();
|
|
916
771
|
const child = spawn(hermesBin, cleanArgs, {
|
|
917
772
|
cwd, shell: false, windowsHide: true,
|
|
918
773
|
env: {
|
|
919
774
|
...spawnEnv,
|
|
920
|
-
HERMES_HOME:
|
|
775
|
+
HERMES_HOME: sharedHermesHome,
|
|
921
776
|
...(isVenvInstall
|
|
922
777
|
? {
|
|
923
778
|
VIRTUAL_ENV: venvRoot,
|