mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(desktop): harden remote lifecycle — skip-build spawn + adoption liveness
- Remote dashboard spawn now passes --skip-build so a headless SSH bootstrap never triggers an npm web-UI build; if no built dist exists the backend fails loudly (scraped from the readiness log) instead of hanging on a build. - Served-token adoption no longer asserts childAlive: () => true. Fresh spawn confirms the spawned remote pid is still alive (remotePidAlive) at adoption time; the reuse path reuses the pid-alive gate it already computed. This restores the foreign-backend guard: a served token from a DIFFERENT backend that grabbed the same forwarded port after the dashboard exited is rejected. - Tests: --skip-build asserted in buildSpawnCommand. (Note: the awaited before-quit SSH teardown landed in cfc0082b2 with the scoping fixes — preventDefault + bounded await + one-shot guard so local forwards do not linger after quit.)
This commit is contained in:
parent
cc96ac617a
commit
bb898b80f9
2 changed files with 14 additions and 4 deletions
|
|
@ -252,10 +252,12 @@ function buildSpawnCommand(hermesPath, profile, token) {
|
|||
const logPath = `"$(eval echo ${shq(REMOTE_LOG)})"`
|
||||
// --isolated => dedicated loopback dashboard, NOT routed into the host's
|
||||
// unified machine dashboard. --port 0 => server picks a free port and prints
|
||||
// HERMES_DASHBOARD_READY port=<n>.
|
||||
// HERMES_DASHBOARD_READY port=<n>. --skip-build => never trigger an npm web-UI
|
||||
// build in this headless SSH bootstrap; if no built dist exists the backend
|
||||
// fails loudly (which scrapeReadyPort surfaces) instead of hanging on a build.
|
||||
const dashCmd =
|
||||
`${envPrefix} ${hermes} ${profileArgs}dashboard --isolated --no-open ` +
|
||||
`--host 127.0.0.1 --port 0`
|
||||
`--host 127.0.0.1 --port 0 --skip-build`
|
||||
return (
|
||||
`mkdir -p "$(dirname ${logPath})" && ` +
|
||||
`setsid sh -c ${shq(`${dashCmd} </dev/null >> ${logPath} 2>&1 & echo $!`)}`
|
||||
|
|
@ -395,7 +397,9 @@ async function connect(deps) {
|
|||
// the lockfile was written is picked up; the remote pid is alive so
|
||||
// a served-token mismatch is benign (our backend regenerated it).
|
||||
const token = await adoptServedToken(baseUrl, reuseToken, {
|
||||
childAlive: () => true,
|
||||
// pidAlive was checked above as the reuse gate; reuse it for the
|
||||
// foreign-backend guard rather than asserting () => true.
|
||||
childAlive: () => pidAlive,
|
||||
label: 'reused remote dashboard'
|
||||
})
|
||||
log(`reusing remote dashboard pid=${lock.pid} port=${lock.port}`)
|
||||
|
|
@ -446,8 +450,13 @@ async function connect(deps) {
|
|||
|
||||
// Served-token adoption against the TUNNELED baseUrl — the served token is
|
||||
// what /api/ws will accept; the minted token is only the spawn credential.
|
||||
// Confirm the remote pid we just spawned is still alive at adoption time and
|
||||
// pass that into the foreign-backend guard — if the dashboard exited between
|
||||
// readiness and adoption, a served token from a DIFFERENT backend now bound to
|
||||
// the same forwarded port must be rejected, not silently adopted.
|
||||
const spawnedAlive = await remotePidAlive(ssh, pid)
|
||||
const token = await adoptServedToken(baseUrl, spawnToken, {
|
||||
childAlive: () => true, // liveness is the remote pid; the tunnel is the client side
|
||||
childAlive: () => spawnedAlive,
|
||||
label: 'remote dashboard'
|
||||
})
|
||||
const tokenFingerprint = fingerprintToken(token)
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@ test('buildSpawnCommand uses --isolated --port 0 --no-open and a detached setsid
|
|||
assert.match(cmd, /--isolated/)
|
||||
assert.match(cmd, /--no-open/)
|
||||
assert.match(cmd, /--host 127\.0\.0\.1 --port 0/)
|
||||
assert.match(cmd, /--skip-build/)
|
||||
assert.match(cmd, /--profile/)
|
||||
assert.match(cmd, /work/)
|
||||
assert.match(cmd, /setsid/)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue