Phase 004: remove the desktop shell, website tooling, and bundled hermes CLI
- Drop all desktop/Electron branches, scripts, and UI (title bar, pets, desktop bridge); LAN endpoint kind is now web/custom only - Replace the bundled hermes CLI with user-side installation: add GET /api/hermes/cli-status plus a HermesMissingView onboarding guide - Remove the version-management UI (snapshots handled by git) and prune dead website/mcu/claude scripts and lockfile entries
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import Koa from 'koa'
|
||||
import type { Context } from 'koa'
|
||||
import cors from '@koa/cors'
|
||||
import bodyParser from '@koa/bodyparser'
|
||||
import serve from 'koa-static'
|
||||
@@ -19,7 +18,7 @@ import { setGroupChatServer } from './routes/hermes/group-chat'
|
||||
import { setChatRunServer } from './routes/hermes/chat-run'
|
||||
import { GroupChatServer } from './services/hermes/group-chat'
|
||||
import { ChatRunSocket } from './services/hermes/run-chat'
|
||||
import { getAgentBridgeManager, startAgentBridgeManager } from './services/hermes/agent-bridge'
|
||||
import { startAgentBridgeManager } from './services/hermes/agent-bridge'
|
||||
import { HermesSkillInjector } from './services/hermes/skill-injector'
|
||||
import { injectBundledMcpServer } from './services/hermes/studio-mcp-autoinject'
|
||||
import { ensureProfileGatewaysRunning } from './services/hermes/gateway-autostart'
|
||||
@@ -33,7 +32,6 @@ import { logger } from './services/logger'
|
||||
import { createStaticCompressionMiddleware } from './middleware/static-compression'
|
||||
import { requireUserJwt, resolveUserProfile } from './middleware/user-auth'
|
||||
import { createCorsOriginResolver, securityHeaders } from './security'
|
||||
import type { ShutdownHandler } from './services/shutdown'
|
||||
|
||||
// Injected by esbuild at build time; fallback to reading package.json in dev mode
|
||||
declare const __APP_VERSION__: string
|
||||
@@ -61,7 +59,6 @@ let chatRunServer: any = null
|
||||
let workflowSocketServer: WorkflowSocketServer | null = null
|
||||
let petStateSocketServer: PetStateSocketServer | null = null
|
||||
let agentBridgeManager: any = null
|
||||
let desktopShutdownHandler: ShutdownHandler | null = null
|
||||
|
||||
interface ListenResult {
|
||||
primary: any
|
||||
@@ -101,65 +98,6 @@ function safeNetworkInterfaces() {
|
||||
}
|
||||
}
|
||||
|
||||
function isDesktopRuntime(): boolean {
|
||||
return String(process.env.HERMES_DESKTOP || '').trim().toLowerCase() === 'true'
|
||||
}
|
||||
|
||||
function isLoopbackAddress(address?: string | null): boolean {
|
||||
if (!address) return false
|
||||
return address === '127.0.0.1'
|
||||
|| address === '::1'
|
||||
|| address === '::ffff:127.0.0.1'
|
||||
|| address.startsWith('::ffff:127.')
|
||||
}
|
||||
|
||||
function bearerToken(ctx: Context): string {
|
||||
const header = ctx.get('authorization')
|
||||
const match = header.match(/^Bearer\s+(.+)$/i)
|
||||
return match?.[1]?.trim() || ''
|
||||
}
|
||||
|
||||
function registerDesktopShutdownRoute(app: Koa): void {
|
||||
app.use(async (ctx, next) => {
|
||||
if (ctx.method !== 'POST' || ctx.path !== '/api/desktop/shutdown') {
|
||||
await next()
|
||||
return
|
||||
}
|
||||
|
||||
if (!isDesktopRuntime()) {
|
||||
ctx.status = 404
|
||||
ctx.body = { error: 'not_found' }
|
||||
return
|
||||
}
|
||||
|
||||
const remoteAddress = ctx.req.socket.remoteAddress
|
||||
if (!isLoopbackAddress(remoteAddress)) {
|
||||
ctx.status = 403
|
||||
ctx.body = { error: 'forbidden' }
|
||||
return
|
||||
}
|
||||
|
||||
const expectedToken = String(process.env.AUTH_TOKEN || '').trim()
|
||||
if (!expectedToken || bearerToken(ctx) !== expectedToken) {
|
||||
ctx.status = 401
|
||||
ctx.body = { error: 'unauthorized' }
|
||||
return
|
||||
}
|
||||
|
||||
if (!desktopShutdownHandler) {
|
||||
ctx.status = 503
|
||||
ctx.body = { error: 'shutdown_not_ready' }
|
||||
return
|
||||
}
|
||||
|
||||
ctx.status = 202
|
||||
ctx.body = { ok: true }
|
||||
setTimeout(() => {
|
||||
void desktopShutdownHandler?.('desktop-api')
|
||||
}, 50).unref?.()
|
||||
})
|
||||
}
|
||||
|
||||
function envFlagEnabled(name: string): boolean {
|
||||
const value = String(process.env[name] || '').trim().toLowerCase()
|
||||
return ['1', 'true', 'yes', 'on'].includes(value)
|
||||
@@ -194,33 +132,6 @@ async function startRuntimeServicesBeforeListen(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function startRuntimeServicesAfterListen(): void {
|
||||
if (gatewayAutostartDisabled()) {
|
||||
console.log('[bootstrap] profile gateway check disabled by HERMES_WEB_UI_DISABLE_GATEWAY_AUTOSTART')
|
||||
} else {
|
||||
void (async () => {
|
||||
try {
|
||||
await ensureProfileGatewaysRunning()
|
||||
console.log('[bootstrap] profile gateways checked')
|
||||
} catch (err) {
|
||||
logger.warn(err, '[bootstrap] failed to ensure profile gateways')
|
||||
console.warn('[bootstrap] failed to ensure profile gateways:', err instanceof Error ? err.message : err)
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
agentBridgeManager = await startAgentBridgeManager()
|
||||
console.log('[bootstrap] agent bridge started')
|
||||
} catch (err) {
|
||||
logger.warn(err, '[bootstrap] agent bridge failed to start')
|
||||
console.warn('[bootstrap] agent bridge failed to start:', err instanceof Error ? err.message : err)
|
||||
return
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
function startLanDiscovery(): void {
|
||||
const discoverySocket = startLanDiscoveryResponder({ httpPort: config.port })
|
||||
let initialScanStarted = false
|
||||
@@ -278,9 +189,7 @@ export async function bootstrap() {
|
||||
console.warn('[bootstrap] failed to inject bundled MCP server:', err instanceof Error ? err.message : err)
|
||||
}
|
||||
|
||||
if (!isDesktopRuntime()) {
|
||||
await startRuntimeServicesBeforeListen()
|
||||
}
|
||||
await startRuntimeServicesBeforeListen()
|
||||
|
||||
const app = new Koa()
|
||||
// Initialize all web-ui SQLite tables
|
||||
@@ -301,8 +210,6 @@ export async function bootstrap() {
|
||||
}))
|
||||
console.log('[bootstrap] cors + bodyParser registered')
|
||||
|
||||
registerDesktopShutdownRoute(app)
|
||||
|
||||
// Register all routes (handles auth internally)
|
||||
registerRoutes(app, [requireUserJwt, resolveUserProfile])
|
||||
console.log('[bootstrap] routes registered')
|
||||
@@ -379,11 +286,6 @@ export async function bootstrap() {
|
||||
startLanDiscovery()
|
||||
refreshConfiguredProviderModelCatalogsInBackground('bootstrap')
|
||||
|
||||
if (isDesktopRuntime()) {
|
||||
agentBridgeManager = getAgentBridgeManager()
|
||||
startRuntimeServicesAfterListen()
|
||||
}
|
||||
|
||||
// Restore group chat agents after server is ready.
|
||||
groupChatServer.restoreWhenReady()
|
||||
|
||||
@@ -394,7 +296,7 @@ export async function bootstrap() {
|
||||
})
|
||||
})
|
||||
|
||||
desktopShutdownHandler = bindShutdown(servers, groupChatServer, chatRunServer, agentBridgeManager)
|
||||
bindShutdown(servers, groupChatServer, chatRunServer, agentBridgeManager)
|
||||
startVersionCheck()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user