Fix bridge history, profile models, and Windows gateway handling (#845)
* feat: support profile-aware group chat bridge flows * feat: route cron jobs through hermes cli * Fix group chat routing and isolate bridge tests * Add Grok image-to-video media skill * Default Grok videos to media directory * Fix bridge profile fallback and cron repeat clearing * Refine bridge chat and gateway platform handling * Filter bridge tool-call text deltas * Preserve structured bridge chat history * Prepare beta release build artifacts * Fix Windows run profile resolution * Fix Windows path compatibility checks * Fix profile-scoped model page display * Hide Windows subprocess windows for jobs and updates * Hide Windows file backend subprocess windows * Avoid Windows gateway restart lock conflicts * Treat Windows gateway lock as running on startup * Force release Windows gateway lock on restart * Tighten Windows gateway lock cleanup * Update chat e2e source expectation * Bump package version to 0.5.30 --------- Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import { execFile } from 'child_process'
|
||||
import { existsSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { promisify } from 'util'
|
||||
import { stripLegacyApiServerGatewayConfig } from '../config-helpers'
|
||||
import { logger } from '../logger'
|
||||
import { safeFileStore } from '../safe-file-store'
|
||||
import { getProfileDir, listProfileNamesFromDisk } from './hermes-profile'
|
||||
import { startGatewayRunManaged } from './gateway-runner'
|
||||
|
||||
const execFileAsync = promisify(execFile)
|
||||
|
||||
function resolveHermesBin(): string {
|
||||
return process.env.HERMES_BIN?.trim() || 'hermes'
|
||||
}
|
||||
|
||||
function isDockerRuntime(): boolean {
|
||||
return existsSync('/.dockerenv')
|
||||
}
|
||||
|
||||
function isTermuxRuntime(): boolean {
|
||||
const prefix = process.env.PREFIX || ''
|
||||
return !!process.env.TERMUX_VERSION ||
|
||||
prefix.includes('/com.termux/') ||
|
||||
existsSync('/data/data/com.termux/files/usr')
|
||||
}
|
||||
|
||||
export function gatewayStatusLooksRunning(output: string): boolean {
|
||||
const text = output.toLowerCase()
|
||||
if (text.includes('gateway is not running') || text.includes('not running')) return false
|
||||
return text.includes('gateway is running') || text.includes('running')
|
||||
}
|
||||
|
||||
export function gatewayStatusLooksRuntimeLocked(output: string): boolean {
|
||||
const text = output.toLowerCase()
|
||||
return text.includes('runtime lock is already held')
|
||||
|| text.includes('gateway runtime lock is already held')
|
||||
|| text.includes('already held by another instance')
|
||||
}
|
||||
|
||||
export async function isGatewayRunningForProfile(hermesBin: string, profileDir: string): Promise<boolean> {
|
||||
try {
|
||||
const { stdout, stderr } = await execFileAsync(hermesBin, ['gateway', 'status'], {
|
||||
timeout: 10000,
|
||||
windowsHide: true,
|
||||
env: {
|
||||
...process.env,
|
||||
HERMES_HOME: profileDir,
|
||||
},
|
||||
})
|
||||
return gatewayStatusLooksRunning(`${stdout}\n${stderr}`)
|
||||
} catch (err: any) {
|
||||
const output = `${err?.stdout || ''}\n${err?.stderr || ''}\n${err?.message || ''}`
|
||||
if (gatewayStatusLooksRuntimeLocked(output)) {
|
||||
logger.info({ profileDir }, 'Hermes gateway status reported runtime lock held; treating gateway as already running')
|
||||
return true
|
||||
}
|
||||
if (output.trim()) {
|
||||
logger.warn({ err, profileDir }, 'Hermes gateway status failed; treating as not running')
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function startGatewayForProfile(hermesBin: string, profile: string, profileDir: string): Promise<void> {
|
||||
if (isDockerRuntime() || isTermuxRuntime()) {
|
||||
const result = startGatewayRunManaged(hermesBin, { profileDir })
|
||||
logger.info(
|
||||
'[gateway-autostart] gateway started via background run profile=%s home=%s pid=%s',
|
||||
profile,
|
||||
profileDir,
|
||||
result.pid || 'unknown',
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await execFileAsync(hermesBin, ['gateway', 'start'], {
|
||||
timeout: 30000,
|
||||
windowsHide: true,
|
||||
env: {
|
||||
...process.env,
|
||||
HERMES_HOME: profileDir,
|
||||
},
|
||||
})
|
||||
logger.info('[gateway-autostart] gateway started via Hermes CLI service profile=%s home=%s', profile, profileDir)
|
||||
} catch (err) {
|
||||
logger.warn(err, '[gateway-autostart] Hermes CLI gateway start failed; falling back to background run profile=%s home=%s', profile, profileDir)
|
||||
const result = startGatewayRunManaged(hermesBin, { profileDir })
|
||||
logger.info(
|
||||
'[gateway-autostart] gateway started via fallback background run profile=%s home=%s pid=%s',
|
||||
profile,
|
||||
profileDir,
|
||||
result.pid || 'unknown',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearApiServerForProfile(profileDir: string): Promise<void> {
|
||||
const configPath = join(profileDir, 'config.yaml')
|
||||
try {
|
||||
await safeFileStore.updateYaml(configPath, (config) => {
|
||||
const result = stripLegacyApiServerGatewayConfig(config)
|
||||
return { data: result.config, result: undefined, write: result.changed }
|
||||
}, { backup: true })
|
||||
} catch (err) {
|
||||
logger.warn(err, 'Failed to clear legacy api_server gateway config before gateway startup: %s', profileDir)
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureProfileGatewaysRunning(): Promise<void> {
|
||||
const hermesBin = resolveHermesBin()
|
||||
const profiles = listProfileNamesFromDisk()
|
||||
for (const profile of profiles) {
|
||||
const profileDir = getProfileDir(profile)
|
||||
const running = await isGatewayRunningForProfile(hermesBin, profileDir)
|
||||
if (running) {
|
||||
logger.info('[gateway-autostart] gateway already running profile=%s home=%s', profile, profileDir)
|
||||
continue
|
||||
}
|
||||
|
||||
await clearApiServerForProfile(profileDir)
|
||||
await startGatewayForProfile(hermesBin, profile, profileDir)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user