Commit Graph

6 Commits

Author SHA1 Message Date
konsisumer 4b0c1031db fix(desktop-update): wait for rebuilt executable before relaunch 2026-08-14 22:23:00 -07:00
Sascha 5bfb7ee42f fix(desktop-update): drive the Windows hand-off through the venv python, not the hermes.exe shim
`uv pip install -e .` has to replace the console-script shims, so
_quarantine_running_hermes_exe must first rename the running hermes.exe out
of the way. That rename fails whenever any child process spawned from that
hermes.exe is still alive: on Windows a child inherits a handle on the parent
image. It is the inherited handle, not the trampoline, that pins the file --
killing the child makes the identical rename succeed, and the shim flavour
(uv trampoline vs distlib launcher) makes no difference.

The updater spawns such children itself (npx cache warm, memory-provider
refresh -- hindsight-api runs as a daemon with --idle-timeout 300 and outlives
the step that started it), so this presents as a race rather than a hard
failure: the same hand-off succeeds on one run and dies on the next. Step 2's
shim-unlock preflight cannot catch it, because the shim genuinely is unlocked
at that moment; the pinning child appears later, during the update.

When the rename loses that race, _schedule_replace_on_reboot is the last
resort -- and MOVEFILE_DELAY_UNTIL_REBOOT writes to HKLM, so it needs
elevation. A Desktop-driven update is not elevated, so it returns
ERROR_ACCESS_DENIED, `uv pip install -e .` exits 2, and the ZIP fallback
repeats the identical sequence. The desktop build stage is then never reached
while the pre-build clean has already removed apps/desktop/release, leaving an
install whose Start Menu shortcut points at a Hermes.exe that no longer exists.

Running the same code as `python.exe -m hermes_cli.main update` puts the
inherited handles on python.exe, which uv never has to replace.

posix.sh is deliberately untouched: unlinking a running executable is legal
there, so the equivalent call is harmless.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 15:48:31 -05:00
AlexMnrs ca1b3f8705 fix(windows): avoid locale-sensitive update timestamps 2026-08-14 15:41:38 -05:00
Brooklyn Nicholson 968ec6c6f4 fix(update): manual-result protocol so gated outcomes reach the user
Round 4 of helix4u's review — the durable fallback is now real:

- Result protocol gains `manual`: an ok result the user still must act
  on (reopen the app, reinstall the GUI package, fix the sandbox helper).
  Both orchestrators set it on every DONE_NOTE/downgrade path; the Desktop
  consumer surfaces manual results in a real dialog on next boot instead
  of a log line — the browserless-Linux disappearance now ends at a
  visible dialog, worst case one boot later. Older result files without
  the field parse as manual:false (covered).
- notify ladder verifies EXECUTION, not existence: zenity/kdialog must
  survive their first second (an instant death means no display and falls
  through); the no-surface case is an explicit best-effort contract whose
  guaranteed channel is the result dialog.
- mac DONE_NOTE + failed relaunch of the kept/rolled-back bundle is no
  longer swallowed (`|| true` dropped): the durable message carries both
  facts.
- launch/gate matrices assert `manual` in the result JSON; consumer
  round-trip tested in handoff-result.test.ts.
2026-08-11 11:07:17 -05:00
Brooklyn Nicholson 1dd5c9de83 fix(update): launch acceptance before the terminal event, on both orchestrators
gille's round-2 review: the terminal lifecycle claimed outcomes the
launch hadn't delivered yet.

- posix finish() reorders: outcome -> durable result+marker -> LAUNCH
  WITH ACCEPTANCE -> terminal event. mac acceptance is open's exit code
  (launchd rejects broken bundles loudly); linux verifies the setsid
  child is still alive 1.5s after spawn, so an instant exec failure
  downgrades to a held 'manual' state + truthful result instead of a
  vanished 'done'. Gated skew/manual outcomes publish a real 'manual'
  event (new third shim state -- still zero logic in the page).
- Renderer-free linux recovery: when no chromium-family browser exists,
  manual/error outcomes fire notify-send/zenity/kdialog best-effort so a
  gated non-relaunch is never a silent disappearance.
- windows.ps1 mirrors the contract: Start-DesktopRelaunch returns
  verified acceptance (WMI pid alive / fallback process alive; dying
  before the window appears counts as failure), and the finally block
  downgrades to Show-ManualFinale + rewritten result when the launch
  didn't land. Error path still relaunches after showing itself.
- repro.sh launch / npm run update:repro:launch: real-orchestrator
  matrix for instant-exit relaunch downgrade and skew-message surfacing.
- posix.sh cds into the install root before hermes update (found by the
  sandboxed behind-repro: parts of the update resolve the mutated tree
  from cwd, which is the Desktop's cwd -- it updated the DEVELOPER'S
  checkout while reporting success against the sandbox).
2026-08-11 11:07:17 -05:00
Brooklyn Nicholson 503e61b3b1 feat(update): shim UI + event channel for the Windows hand-off
scripts/desktop-update.ps1 moves to scripts/desktop-update/windows.ps1 (a
compat forwarder stays at the old path for one asar/checkout skew cycle)
and gains the shim: scripts/desktop-update/ui.html rendered in a
chromeless Edge app window, fed done|error over a loopback /progress
endpoint. The page is #75895's hand-off screen ported verbatim (Fourier
Flow loader, one title, one line, OS light/dark, charcoal dark seeds);
failure is the terse card pointing at hermes debug share. The WinForms
card stays as the no-Edge fallback, same shape.

Salvaged from the web-shell spike: TcpListener runspace server, Edge
--app spawn with throwaway profile, degradation ladder, -SelfTestUi.

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-08-11 11:07:17 -05:00