mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-28 18:19:28 +00:00
fix(desktop): hide Windows updater console during handoff (#66040)
* fix(desktop): hide Windows updater console (#56884) * test(desktop): cover hidden updater handoffs behaviorally --------- Co-authored-by: Kyssta <218078013+kyssta-exe@users.noreply.github.com>
This commit is contained in:
parent
1f7d2be22f
commit
531e5763e8
3 changed files with 103 additions and 10 deletions
|
|
@ -126,6 +126,7 @@ import {
|
|||
sandboxPreflight
|
||||
} from './update-relaunch'
|
||||
import { isOfficialSshRemote, OFFICIAL_REPO_HTTPS_URL } from './update-remote'
|
||||
import { spawnUpdaterProcess } from './updater-process'
|
||||
import { fetchMarketplaceThemes, searchMarketplaceThemes } from './vscode-marketplace'
|
||||
import {
|
||||
computeWindowOptions,
|
||||
|
|
@ -2520,7 +2521,7 @@ async function applyUpdates(opts = {}) {
|
|||
|
||||
// Detached so the updater outlives this process — it needs us GONE before
|
||||
// `hermes update` will run (the venv shim is locked while we live).
|
||||
const child = spawn(updater, updaterArgs, {
|
||||
const child = spawnUpdaterProcess(updater, updaterArgs, {
|
||||
cwd: HERMES_HOME,
|
||||
env: {
|
||||
...process.env,
|
||||
|
|
@ -2528,12 +2529,9 @@ async function applyUpdates(opts = {}) {
|
|||
PATH: pathWithHermesManagedNode(venvBin)
|
||||
},
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
windowsHide: false
|
||||
stdio: 'ignore'
|
||||
})
|
||||
|
||||
child.unref()
|
||||
|
||||
// Write the update-in-progress marker IMMEDIATELY — before the 2.5s
|
||||
// quit dwell. The Tauri updater won't write its own marker for several
|
||||
// seconds (window init + manifest), and during that gap our renderer
|
||||
|
|
@ -2598,7 +2596,7 @@ async function handOffWindowsBootstrapRecovery(reason) {
|
|||
|
||||
await releaseBackendLockForUpdate(updateRoot)
|
||||
|
||||
const child = spawn(updater, updaterArgs, {
|
||||
const child = spawnUpdaterProcess(updater, updaterArgs, {
|
||||
cwd: HERMES_HOME,
|
||||
env: {
|
||||
...process.env,
|
||||
|
|
@ -2606,12 +2604,9 @@ async function handOffWindowsBootstrapRecovery(reason) {
|
|||
PATH: pathWithHermesManagedNode(venvBin)
|
||||
},
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
windowsHide: false
|
||||
stdio: 'ignore'
|
||||
})
|
||||
|
||||
child.unref()
|
||||
|
||||
// Same marker pre-write as applyUpdates — see comment there. The recovery
|
||||
// hand-off has the same window where the renderer can respawn a backend
|
||||
// before the updater writes its own marker.
|
||||
|
|
|
|||
62
apps/desktop/electron/updater-process.test.ts
Normal file
62
apps/desktop/electron/updater-process.test.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import assert from 'node:assert/strict'
|
||||
import type { SpawnOptions } from 'node:child_process'
|
||||
|
||||
import { test } from 'vitest'
|
||||
|
||||
import { spawnUpdaterProcess } from './updater-process'
|
||||
|
||||
test('spawnUpdaterProcess hides the updater console and detaches the child on Windows', () => {
|
||||
const calls: Array<{ args: string[]; command: string; options: SpawnOptions }> = []
|
||||
let unrefCalls = 0
|
||||
|
||||
const child = {
|
||||
pid: 4242,
|
||||
unref: () => {
|
||||
unrefCalls += 1
|
||||
}
|
||||
}
|
||||
|
||||
const result = spawnUpdaterProcess(
|
||||
'hermes-setup.exe',
|
||||
['--update', '--branch', 'main'],
|
||||
{ cwd: 'C:\\Hermes', detached: true, stdio: 'ignore' },
|
||||
{
|
||||
isWindows: true,
|
||||
spawnProcess: (command, args, options) => {
|
||||
calls.push({ args, command, options })
|
||||
|
||||
return child
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
assert.equal(result, child)
|
||||
assert.equal(unrefCalls, 1)
|
||||
assert.deepEqual(calls, [
|
||||
{
|
||||
args: ['--update', '--branch', 'main'],
|
||||
command: 'hermes-setup.exe',
|
||||
options: { cwd: 'C:\\Hermes', detached: true, stdio: 'ignore', windowsHide: true }
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
test('spawnUpdaterProcess preserves updater options off Windows', () => {
|
||||
let capturedOptions: SpawnOptions | undefined
|
||||
|
||||
spawnUpdaterProcess(
|
||||
'hermes-setup',
|
||||
['--update'],
|
||||
{ detached: true, stdio: 'ignore' },
|
||||
{
|
||||
isWindows: false,
|
||||
spawnProcess: (_command, _args, options) => {
|
||||
capturedOptions = options
|
||||
|
||||
return { unref: () => {} }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
assert.deepEqual(capturedOptions, { detached: true, stdio: 'ignore' })
|
||||
})
|
||||
36
apps/desktop/electron/updater-process.ts
Normal file
36
apps/desktop/electron/updater-process.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { spawn, type SpawnOptions } from 'node:child_process'
|
||||
|
||||
import { hiddenWindowsChildOptions } from './windows-child-options'
|
||||
|
||||
export interface UpdaterChild {
|
||||
pid?: number
|
||||
unref: () => void
|
||||
}
|
||||
|
||||
export interface SpawnUpdaterProcessDeps {
|
||||
isWindows?: boolean
|
||||
spawnProcess?: (command: string, args: string[], options: SpawnOptions) => UpdaterChild
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn the detached installer used for update and bootstrap-recovery handoffs.
|
||||
* The helper owns both hidden-console selection and unref semantics so every
|
||||
* updater handoff follows the same behavior and can be tested without Electron.
|
||||
*/
|
||||
export function spawnUpdaterProcess(
|
||||
updater: string,
|
||||
updaterArgs: string[],
|
||||
options: SpawnOptions,
|
||||
deps: SpawnUpdaterProcessDeps = {}
|
||||
): UpdaterChild {
|
||||
const isWindows = deps.isWindows ?? process.platform === 'win32'
|
||||
const spawnOptions = hiddenWindowsChildOptions(options, isWindows) as SpawnOptions
|
||||
|
||||
const child = deps.spawnProcess
|
||||
? deps.spawnProcess(updater, updaterArgs, spawnOptions)
|
||||
: spawn(updater, updaterArgs, spawnOptions)
|
||||
|
||||
child.unref()
|
||||
|
||||
return child
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue