fix Windows desktop startup readiness (#1167)
* fix desktop startup readiness on windows * add manual desktop build workflow * hide Windows desktop server process window * hide Windows Python bridge worker windows * use no-window Python for Windows desktop CLI calls --------- Co-authored-by: xingzhi <chuzihao.czh@alibaba-inc.com>
This commit is contained in:
@@ -18,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 { startAgentBridgeManager } from './services/hermes/agent-bridge'
|
||||
import { getAgentBridgeManager, startAgentBridgeManager } from './services/hermes/agent-bridge'
|
||||
import { HermesSkillInjector } from './services/hermes/skill-injector'
|
||||
import { ensureProfileGatewaysRunning } from './services/hermes/gateway-autostart'
|
||||
import { logger } from './services/logger'
|
||||
@@ -81,6 +81,28 @@ function safeNetworkInterfaces() {
|
||||
}
|
||||
}
|
||||
|
||||
function startRuntimeServicesAfterListen(): void {
|
||||
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)
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
export async function bootstrap() {
|
||||
console.log(`hermes-web-ui v${APP_VERSION} starting...`)
|
||||
await mkdir(config.uploadDir, { recursive: true })
|
||||
@@ -109,23 +131,7 @@ export async function bootstrap() {
|
||||
console.warn('[bootstrap] failed to inject bundled skills:', err instanceof Error ? err.message : err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
const app = new Koa()
|
||||
|
||||
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)
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
// Initialize all web-ui SQLite tables
|
||||
const { initAllStores } = await import('./db/hermes/init')
|
||||
@@ -201,6 +207,9 @@ export async function bootstrap() {
|
||||
console.log(`Log: ${config.appHome}/logs/server.log`)
|
||||
logger.info('Server: http://localhost:%d (LAN: http://%s:%d)', config.port, localIp, config.port)
|
||||
|
||||
agentBridgeManager = getAgentBridgeManager()
|
||||
startRuntimeServicesAfterListen()
|
||||
|
||||
// Restore group chat agents after server is ready.
|
||||
groupChatServer.restoreWhenReady()
|
||||
|
||||
|
||||
@@ -65,6 +65,12 @@ def _positive_int(value: str | None) -> int | None:
|
||||
return parsed if parsed > 0 else None
|
||||
|
||||
|
||||
def _hidden_subprocess_kwargs() -> dict[str, int]:
|
||||
if os.name != "nt":
|
||||
return {}
|
||||
return {"creationflags": getattr(subprocess, "CREATE_NO_WINDOW", 0)}
|
||||
|
||||
|
||||
def _process_exists(pid: int) -> bool:
|
||||
if pid <= 0:
|
||||
return False
|
||||
@@ -76,6 +82,7 @@ def _process_exists(pid: int) -> bool:
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
**_hidden_subprocess_kwargs(),
|
||||
)
|
||||
return str(pid) in (result.stdout or "")
|
||||
except Exception:
|
||||
@@ -2785,6 +2792,7 @@ class WorkerProcess:
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
**_hidden_subprocess_kwargs(),
|
||||
)
|
||||
self._pipe_stderr()
|
||||
self._wait_ready()
|
||||
@@ -2957,6 +2965,7 @@ def _windows_listening_pids_on_port(port: int) -> list[int]:
|
||||
encoding=_platform_text_encoding(),
|
||||
errors="ignore",
|
||||
timeout=5,
|
||||
**_hidden_subprocess_kwargs(),
|
||||
)
|
||||
except Exception:
|
||||
return []
|
||||
@@ -2999,6 +3008,7 @@ def _kill_windows_endpoint_occupants(endpoint: str) -> None:
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
**_hidden_subprocess_kwargs(),
|
||||
)
|
||||
except Exception as exc:
|
||||
print(
|
||||
|
||||
@@ -52,10 +52,11 @@ export function shouldUseManagedGatewayRun(): boolean {
|
||||
process.platform === 'win32'
|
||||
}
|
||||
|
||||
export function shouldUseManagedGatewayRunForAutostart(): boolean {
|
||||
export function shouldUseManagedGatewayRunForAutostart(platform: NodeJS.Platform = process.platform): boolean {
|
||||
return envFlagEnabled('HERMES_WEB_UI_MANAGED_GATEWAY') ||
|
||||
isDockerRuntime() ||
|
||||
isTermuxRuntime()
|
||||
isTermuxRuntime() ||
|
||||
platform === 'win32'
|
||||
}
|
||||
|
||||
export function gatewayStatusLooksRunning(output: string): boolean {
|
||||
|
||||
@@ -17,18 +17,20 @@ export function resolveHermesBin(customBin?: string): string {
|
||||
return customBin?.trim() || process.env.HERMES_BIN?.trim() || 'hermes'
|
||||
}
|
||||
|
||||
function bundledPythonForWindows(hermesBin: string): string | null {
|
||||
const envPython = process.env.HERMES_AGENT_BRIDGE_PYTHON?.trim()
|
||||
function bundledCliPythonForWindows(hermesBin: string): string | null {
|
||||
const envPython = process.env.HERMES_AGENT_CLI_PYTHON?.trim()
|
||||
if (envPython) return envPython
|
||||
|
||||
if (basename(hermesBin).toLowerCase() !== 'hermes.exe') return null
|
||||
const pythonw = resolve(dirname(hermesBin), '..', 'pythonw.exe')
|
||||
if (existsSync(pythonw)) return pythonw
|
||||
const python = resolve(dirname(hermesBin), '..', 'python.exe')
|
||||
return existsSync(python) ? python : null
|
||||
}
|
||||
|
||||
export function resolveHermesInvocation(hermesBin = resolveHermesBin()): HermesInvocation {
|
||||
if (process.platform === 'win32') {
|
||||
const python = bundledPythonForWindows(hermesBin)
|
||||
const python = bundledCliPythonForWindows(hermesBin)
|
||||
if (python) return { command: python, argsPrefix: ['-m', 'hermes_cli.main'] }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user