Files
Hermes-ui/packages/server/src/services/shutdown.ts
T
ekkoandClaude Opus 4.7 eae7195ba8 Update CLI chat session bridge (#697)
* feat: add CLI chat sessions with Python agent bridge

Introduce a new CLI chat mode that connects Web UI directly to Hermes
Agent's AIAgent via a Python bridge subprocess and Socket.IO, bypassing
the API Server /v1/responses path. Supports streaming, slash commands
(/new, /undo, /retry, /branch, /compress, /save, /title), interrupt,
and steer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat: update CLI chat session bridge

* fix: extend agent bridge startup timeouts

* docs: update bridge chat session design

* feat: align bridge compression and provider registry

* chore: bump version to 0.5.20

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 09:03:57 +08:00

106 lines
3.6 KiB
TypeScript

import { logger } from './logger'
import { closeDb } from '../db'
import { getGatewayManagerInstance } from './gateway-bootstrap'
function shouldStopGatewaysOnShutdown(signal: string): boolean {
// nodemon may use SIGTERM on Windows restarts, so dev mode opts out via env.
// Production keeps stopping owned gateways by default.
const override = process.env.HERMES_WEB_UI_STOP_GATEWAYS_ON_SHUTDOWN?.trim()
console.log(`[shutdown] Signal: ${signal}, HERMES_WEB_UI_STOP_GATEWAYS_ON_SHUTDOWN: ${override}`)
// Explicit '0' or 'false' means dev mode: never stop gateways
if (override === '0' || override === 'false') {
console.log('[shutdown] Dev mode detected: NOT stopping gateways')
return false
}
// Explicit '1' or 'true' means always stop gateways
if (override === '1' || override === 'true') {
console.log('[shutdown] Explicit gateway shutdown enabled: stopping gateways')
return true
}
// Default behavior: only stop gateways on explicit termination, not on reload
const shouldStop = signal !== 'SIGUSR2'
console.log(`[shutdown] Default behavior: ${shouldStop ? 'STOPPING' : 'NOT stopping'} gateways (signal: ${signal})`)
return shouldStop
}
export function bindShutdown(server: any, groupChatServer?: any, chatRunServer?: any, agentBridgeManager?: any): void {
let isShuttingDown = false
const shutdown = async (signal: string) => {
if (isShuttingDown) return
isShuttingDown = true
// Force exit after 3s no matter what
setTimeout(() => process.exit(0), 3000)
logger.info('Shutting down (%s)...', signal)
console.log(`[shutdown] Received signal: ${signal}`)
console.log(`[shutdown] HERMES_WEB_UI_STOP_GATEWAYS_ON_SHUTDOWN = ${process.env.HERMES_WEB_UI_STOP_GATEWAYS_ON_SHUTDOWN}`)
console.log(`[shutdown] shouldStopGatewaysOnShutdown = ${shouldStopGatewaysOnShutdown(signal)}`)
try {
if (shouldStopGatewaysOnShutdown(signal)) {
// Stop gateway processes owned by this Web UI instance first.
try {
const gatewayManager = getGatewayManagerInstance()
if (gatewayManager) {
await gatewayManager.stopAll()
logger.info('All gateways stopped')
}
} catch (err) {
logger.warn(err, 'Failed to stop gateways (non-fatal)')
}
} else {
logger.info('Skipping gateway shutdown for %s', signal)
}
if (agentBridgeManager) {
try {
await agentBridgeManager.stop()
logger.info('Agent bridge stopped')
} catch (err) {
logger.warn(err, 'Failed to stop agent bridge (non-fatal)')
}
}
// Close ChatRunSocket first to abort all active runs and close EventSource connections
if (chatRunServer) {
chatRunServer.close()
logger.info('ChatRunSocket closed')
}
// Disconnect Socket.IO before HTTP server to prevent hanging
if (groupChatServer) {
groupChatServer.agentClients.disconnectAll()
groupChatServer.getIO().close()
logger.info('Socket.IO closed')
}
const servers = Array.isArray(server) ? server : [server].filter(Boolean)
if (servers.length) {
await Promise.all(servers.map((httpServer) => (
new Promise<void>((resolve) => {
httpServer.close(() => {
logger.info('HTTP server closed')
resolve()
})
})
)))
}
} catch (err) {
logger.error(err, 'Shutdown error')
}
closeDb()
process.exit(0)
}
process.once('SIGUSR2', shutdown)
process.on('SIGINT', shutdown)
process.on('SIGTERM', shutdown)
}