diff --git a/package-lock.json b/package-lock.json index c333e23..b74f2d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hermes-web-ui", - "version": "0.6.7", + "version": "0.6.8", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index 288842e..de4d567 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hermes-web-ui", - "version": "0.6.7", + "version": "0.6.8", "description": "Self-hosted AI chat dashboard for Hermes Agent — multi-model web UI with multi-platform integration", "repository": { "type": "git", diff --git a/packages/desktop/package-lock.json b/packages/desktop/package-lock.json index 85f67e2..d2f3702 100644 --- a/packages/desktop/package-lock.json +++ b/packages/desktop/package-lock.json @@ -1,12 +1,12 @@ { "name": "hermes-studio", - "version": "0.6.7", + "version": "0.6.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hermes-studio", - "version": "0.6.7", + "version": "0.6.8", "license": "BSL-1.1", "dependencies": { "electron-updater": "^6.3.9" diff --git a/packages/desktop/package.json b/packages/desktop/package.json index bf18918..ef339ba 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,6 +1,6 @@ { "name": "hermes-studio", - "version": "0.6.7", + "version": "0.6.8", "description": "Hermes Studio desktop distribution with bundled Python runtime and hermes-agent", "homepage": "https://ekkolearnai.com", "author": { diff --git a/packages/desktop/src/main/runtime-manager.ts b/packages/desktop/src/main/runtime-manager.ts index eb511a4..48f7587 100644 --- a/packages/desktop/src/main/runtime-manager.ts +++ b/packages/desktop/src/main/runtime-manager.ts @@ -13,7 +13,7 @@ import { } from 'node:fs' import { get as httpGet } from 'node:http' import { get as httpsGet } from 'node:https' -import { basename, dirname, join } from 'node:path' +import { basename, dirname, join, relative } from 'node:path' import { promisify } from 'node:util' import { app } from 'electron' import { @@ -58,6 +58,25 @@ export type RuntimeProgress = { type RuntimeProgressHandler = (progress: RuntimeProgress) => void +function requiredRuntimeFiles(root: string): string[] { + const pythonBin = process.platform === 'win32' + ? join(root, 'python', 'python.exe') + : join(root, 'python', 'bin', 'python3') + const hermesBin = process.platform === 'win32' + ? join(root, 'python', 'Scripts', 'hermes.exe') + : join(root, 'python', 'bin', 'hermes') + const nodeBin = process.platform === 'win32' + ? join(root, 'node', 'node.exe') + : join(root, 'node', 'bin', 'node') + const files = [pythonBin, hermesBin, nodeBin, join(root, RUNTIME_MANIFEST_NAME)] + if (process.platform === 'win32') files.push(join(root, 'git', 'cmd', 'git.exe')) + return files +} + +function missingRuntimeFiles(root: string): string[] { + return requiredRuntimeFiles(root).filter(file => !existsSync(file)) +} + function runtimeReady(): boolean { const gitReady = process.platform !== 'win32' || !!bundledGit() return hermesBinExists() && existsSync(bundledNode()) && gitReady @@ -230,10 +249,9 @@ async function extractRuntimeArchive(archive: string, targetRoot: string): Promi await execFileAsync(process.platform === 'win32' ? 'tar.exe' : 'tar', ['-xzf', archive, '-C', tempRoot], { windowsHide: true, }) - for (const required of ['python', 'node']) { - if (!existsSync(join(tempRoot, required))) { - throw new Error(`Runtime archive did not contain ${required}/`) - } + const missing = missingRuntimeFiles(tempRoot) + if (missing.length > 0) { + throw new Error(`Runtime archive is missing required files: ${missing.map(file => relative(tempRoot, file)).join(', ')}`) } rmSync(targetRoot, { recursive: true, force: true }) mkdirSync(parent, { recursive: true }) @@ -265,19 +283,23 @@ export async function ensureDesktopRuntime(onProgress?: RuntimeProgressHandler): const archive = join(dirname(runtimeRoot), `${descriptor.name}.download`) console.log(`[runtime] downloading Hermes runtime ${descriptor.name}`) onProgress?.({ stage: 'download', message: `Downloading ${descriptor.name}...` }) - await downloadFile(descriptor.url, archive, onProgress) - if (descriptor.sha256) { - onProgress?.({ stage: 'verify', message: 'Verifying Hermes runtime...' }) - const actual = await sha256File(archive) - if (actual !== descriptor.sha256) { - throw new Error(`Runtime checksum mismatch for ${descriptor.name}`) + let archiveSize = 0 + try { + await downloadFile(descriptor.url, archive, onProgress) + archiveSize = statSync(archive).size + if (descriptor.sha256) { + onProgress?.({ stage: 'verify', message: 'Verifying Hermes runtime...' }) + const actual = await sha256File(archive) + if (actual !== descriptor.sha256) { + throw new Error(`Runtime checksum mismatch for ${descriptor.name}`) + } } - } - onProgress?.({ stage: 'extract', message: 'Extracting Hermes runtime...' }) - await extractRuntimeArchive(archive, runtimeRoot) - const archiveSize = statSync(archive).size - rmSync(archive, { force: true }) + onProgress?.({ stage: 'extract', message: 'Extracting Hermes runtime...' }) + await extractRuntimeArchive(archive, runtimeRoot) + } finally { + rmSync(archive, { force: true }) + } const manifestPath = join(runtimeRoot, RUNTIME_MANIFEST_NAME) if (!existsSync(manifestPath)) { diff --git a/packages/desktop/src/main/webui-server.ts b/packages/desktop/src/main/webui-server.ts index ae584a7..6d5d3bf 100644 --- a/packages/desktop/src/main/webui-server.ts +++ b/packages/desktop/src/main/webui-server.ts @@ -22,7 +22,7 @@ import { } from './paths' const DEFAULT_PORT = 8748 -const DEFAULT_READY_TIMEOUT_MS = 30_000 +const DEFAULT_READY_TIMEOUT_MS = 120_000 const AGENT_BRIDGE_STARTED_MARKER = '[bootstrap] agent bridge started' const AGENT_BRIDGE_FAILED_MARKER = '[bootstrap] agent bridge failed to start' const execFileAsync = promisify(execFile) @@ -405,10 +405,10 @@ export async function startWebUiServer(port = DEFAULT_PORT): Promise { }) const timeoutMs = readyTimeoutMs() - await Promise.all([ - waitForReady(port, timeoutMs), - bridgeStartup.wait(timeoutMs), - ]) + void bridgeStartup.wait(timeoutMs).catch(err => { + console.warn(`[webui] agent bridge was not ready during startup: ${err instanceof Error ? err.message : String(err)}`) + }) + await waitForReady(port, timeoutMs) return getServerUrl(port) }