From 0ff04758b69a1ca27f0ac09e23b25defda5ffbd2 Mon Sep 17 00:00:00 2001 From: ekko Date: Mon, 13 Apr 2026 20:08:32 +0800 Subject: [PATCH] feat: add gateway auto-start on boot and real health detection - Auto-detect gateway connectivity on server startup, start gateway if not running - Fix /health endpoint to actually check gateway reachability instead of just CLI version - Fix MiniMax CN base_url from /anthropic to /v1 - Frontend connection status now reflects real gateway state Co-Authored-By: Claude Opus 4.6 --- server/src/index.ts | 50 ++++++++++++++++++++++++++++--- server/src/services/hermes-cli.ts | 12 +++++++- server/src/shared/providers.ts | 2 +- src/shared/providers.ts | 2 +- src/stores/app.ts | 2 +- 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index a006179b..696f1178 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -15,12 +15,13 @@ import { fsRoutes } from './routes/filesystem' import { configRoutes } from './routes/config' import { weixinRoutes } from './routes/weixin' import * as hermesCli from './services/hermes-cli' -const { restartGateway } = hermesCli +const { restartGateway, startGateway, getVersion } = hermesCli export async function bootstrap() { await mkdir(config.uploadDir, { recursive: true }) await mkdir(config.dataDir, { recursive: true }) await ensureApiServerConfig() + await ensureGatewayRunning() const app = new Koa() @@ -35,12 +36,26 @@ export async function bootstrap() { app.use(configRoutes.routes()) app.use(weixinRoutes.routes()) - // Health endpoint with version + // Health endpoint: check CLI version + gateway connectivity app.use(async (ctx, next) => { if (ctx.path === '/health') { - const raw = await hermesCli.getVersion() + const raw = await getVersion() const version = raw.split('\n')[0].replace('Hermes Agent ', '') || '' - ctx.body = { status: 'ok', platform: 'hermes-agent', version } + + let gatewayOk = false + try { + const res = await fetch(`${config.upstream.replace(/\/$/, '')}/health`, { + signal: AbortSignal.timeout(5000), + }) + gatewayOk = res.ok + } catch { /* not reachable */ } + + ctx.body = { + status: gatewayOk ? 'ok' : 'error', + platform: 'hermes-agent', + version, + gateway: gatewayOk ? 'running' : 'stopped', + } return } await next() @@ -136,4 +151,31 @@ async function ensureApiServerConfig() { } } +async function ensureGatewayRunning() { + const upstream = config.upstream.replace(/\/$/, '') + try { + const res = await fetch(`${upstream}/health`, { signal: AbortSignal.timeout(5000) }) + if (res.ok) { + console.log(' ✓ Gateway is running') + return + } + } catch { + // Gateway not reachable + } + + console.log(' ⚠ Gateway not reachable, starting...') + try { + await startGateway() + await new Promise(r => setTimeout(r, 3000)) + const res = await fetch(`${upstream}/health`, { signal: AbortSignal.timeout(5000) }) + if (res.ok) { + console.log(' ✓ Gateway started successfully') + return + } + console.log(' ✗ Gateway start attempted but still not reachable') + } catch (err: any) { + console.error(' ✗ Failed to start gateway:', err.message) + } +} + bootstrap() diff --git a/server/src/services/hermes-cli.ts b/server/src/services/hermes-cli.ts index 24a7c2c5..3745fe08 100644 --- a/server/src/services/hermes-cli.ts +++ b/server/src/services/hermes-cli.ts @@ -182,7 +182,17 @@ export async function getVersion(): Promise { } /** - * Update Hermes Agent + * Start Hermes gateway + */ +export async function startGateway(): Promise { + const { stdout, stderr } = await execFileAsync('hermes', ['gateway', 'start'], { + timeout: 30000, + }) + return stdout || stderr +} + +/** + * Restart Hermes gateway */ export async function restartGateway(): Promise { const { stdout, stderr } = await execFileAsync('hermes', ['gateway', 'restart'], { diff --git a/server/src/shared/providers.ts b/server/src/shared/providers.ts index 26868fa8..11eb608d 100644 --- a/server/src/shared/providers.ts +++ b/server/src/shared/providers.ts @@ -97,7 +97,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ { label: 'MiniMax (China)', value: 'minimax-cn', - base_url: 'https://api.minimaxi.com/anthropic', + base_url: 'https://api.minimaxi.com/v1', models: ['MiniMax-M2.7', 'MiniMax-M2.5', 'MiniMax-M2.1', 'MiniMax-M2'], }, { diff --git a/src/shared/providers.ts b/src/shared/providers.ts index 26868fa8..11eb608d 100644 --- a/src/shared/providers.ts +++ b/src/shared/providers.ts @@ -97,7 +97,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ { label: 'MiniMax (China)', value: 'minimax-cn', - base_url: 'https://api.minimaxi.com/anthropic', + base_url: 'https://api.minimaxi.com/v1', models: ['MiniMax-M2.7', 'MiniMax-M2.5', 'MiniMax-M2.1', 'MiniMax-M2'], }, { diff --git a/src/stores/app.ts b/src/stores/app.ts index 3a953bb3..6545de9b 100644 --- a/src/stores/app.ts +++ b/src/stores/app.ts @@ -17,7 +17,7 @@ export const useAppStore = defineStore('app', () => { async function checkConnection() { try { const res = await checkHealth() - connected.value = true + connected.value = res.status === 'ok' if (res.version) serverVersion.value = res.version } catch { connected.value = false