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 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-04-13 20:08:32 +08:00
parent 5143a264c5
commit 0ff04758b6
5 changed files with 60 additions and 8 deletions
+46 -4
View File
@@ -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()
+11 -1
View File
@@ -182,7 +182,17 @@ export async function getVersion(): Promise<string> {
}
/**
* Update Hermes Agent
* Start Hermes gateway
*/
export async function startGateway(): Promise<string> {
const { stdout, stderr } = await execFileAsync('hermes', ['gateway', 'start'], {
timeout: 30000,
})
return stdout || stderr
}
/**
* Restart Hermes gateway
*/
export async function restartGateway(): Promise<string> {
const { stdout, stderr } = await execFileAsync('hermes', ['gateway', 'restart'], {
+1 -1
View File
@@ -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'],
},
{
+1 -1
View File
@@ -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'],
},
{
+1 -1
View File
@@ -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