287c56e95afe5c528beacb7ca8f7ef0ad6216f2a
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
faf5b42a82 |
fix(desktop): fail missing Windows updater handoffs
Salvage the missing-target guard from #104692. Native Windows run 34094671567 returned exit zero for the absent maintained script. Full runtime/artifact completion remains separate. Co-authored-by: Konstantin Khlopkov <konstantin.khlopkov93@gmail.com> |
||
|
|
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> |
||
|
|
854ab7f6df |
fix(desktop/windows): quiet minimal update hand-off window
The hand-off script's WinForms window was a 720x420 dashboard: streaming log box, wide marquee, warning label. Updating is a wait, not a dashboard -- it is now the same shape as the other update surfaces (#75895): a fixed 280x320 panel, marquee loader, one title, one static line, following the OS light/dark theme (charcoal #232323 seeds, never brand blue). Failure gets a terse finale instead of a wall of log: 'Failed to update' + 'Run "hermes debug share" in a terminal to send a report' + Close (held max 5 minutes, then the relaunched Desktop re-surfaces the result banner as before). The result-json message points at debug share too. With nothing streamed to the window, the per-line stdout pump is gone: Invoke-HermesStep drains both pipes async (no deadlock on chatty children, no frozen marquee on quiet ones) and writes full output to the hand-off log afterwards, where hermes debug share picks it up. |
||
|
|
952f44f841 |
fix(desktop): focus the update progress window, then hand focus to the relaunched Desktop
Two focus polish items from the first fully-working hand-off run (ryanc, 2026-08-09): 1. The progress window came up backgrounded: the script is spawned via `cmd start /min`, and Form.Show() + TopMost keeps it above other windows without ACTIVATING it. Claim activation explicitly (Form.Activate + SetForegroundWindow) right after Show. 2. The relaunched Desktop came up behind whatever the user had focused: a WMI-spawned process starts unfocused and cannot take foreground by itself. Since the hand-off owns foreground while its progress window is up, delegate it: AllowSetForegroundWindow(new pid), poll up to 20s for Electron's MainWindowHandle, then ShowWindow(SW_RESTORE) + SetForegroundWindow. Best-effort at every step -- a focus failure never affects the update result. Sequence on success: progress window foreground during the update -> window closes -> freshly relaunched Hermes.exe takes foreground. Verified live on the incident machine: Add-Type shim compiles under PS 5.1; WMI spawn + AllowSetForegroundWindow + MainWindowHandle poll + ShowWindow all execute against a real spawned window. (In the bg test shell SetForegroundWindow returns False by OS design -- only the current foreground owner may delegate; the real flow's TopMost progress window IS that owner.) PS parse clean, check-windows-footguns clean. |
||
|
|
36eda6112b |
fix(desktop): detach relaunched Desktop from the hand-off console + UTF-8 child streams
First real-world run of the #82328/#82366 hand-off (2026-08-09, ryanc) surfaced two defects: 1. The console window never closes after the update finishes -- and closing it manually KILLS the freshly relaunched GUI. Root cause: Start-DesktopRelaunch spawned Hermes.exe as a child of the console PowerShell. Electron/Chromium calls AttachConsole(ATTACH_PARENT_ PROCESS) at boot, so the new Desktop latched onto the hand-off's console: the console can't close while an attached process lives, and closing it takes the attached GUI down with it. Fix: create the process via WMI (Win32_Process.Create) -- parent becomes WmiPrvSE, no console to inherit or attach, same detachment explorer.exe gives a normal launch. Start-Process fallback retained (tethered Desktop beats no Desktop). 2. Both the console and the progress box render hermes update's UTF-8 glyphs (checkmarks, arrows) as mojibake. PS 5.1 defaults redirected child streams to the OEM codepage. Fix: StandardOutput/ErrorEncoding = UTF8 on the child, PYTHONIOENCODING/PYTHONUTF8 so Python emits UTF-8, and [Console]::OutputEncoding = UTF8 for our own echo. Verified live on the incident machine: WMI-created process parents to WmiPrvSE.exe (not the shell); UTF-8 glyph round-trip through the exact ProcessStartInfo shape reads back byte-correct (15/15 chars). PS 5.1 parse clean, check-windows-footguns clean. |
||
|
|
6495ef82f7 |
fix(desktop): hand-off hardening - fail-closed gates, truthful completion, progress UI, result surfacing
Review feedback on the #82328/#82366 hand-off, all four points plus the
missing progress GUI:
1. FAIL CLOSED. Both preflight gates aborted-open: a Desktop still alive
after 30s proceeded anyway, and a shim locked after 20s proceeded
with --force - both mutate a potentially locked install (the exact
Access-denied brick class). Now: desktop-alive -> exit 4, nothing
changed; shim-locked -> exit 5, nothing changed. Both relaunch the
Desktop so the user is never stranded.
2. TRUTHFUL COMPLETION. `hermes update` treats a Desktop GUI build
failure as non-fatal (warns, exits 0) - correct for CLI use, a lie
for a Desktop-driven update that then relaunches the OLD exe as
"success". The script now detects the warning in the update output,
retries the build once (`hermes desktop --force-build --build-only`),
and exits 6 with an honest message when it still fails.
3. MARKER OWNERSHIP. Cleanup now removes the marker only while OUR pid
still owns it - a handoff partner that rewrote the marker keeps its
claim (same rule as UpdateLock.release).
4. RESULT SURFACING. The script writes .hermes-update-result.json on
every exit path (ok, exit_code, message, branch, finished_at). New
electron/handoff-result.ts consumes it exactly once at the boot
update-gate: success logs, failure shows a real dialog pointing at
desktop-update-handoff.log. Stale (>30min) and malformed results are
consumed silently. Previously a failed detached update was
indistinguishable from "nothing happened" - the exact live report
that triggered this work.
5. PROGRESS UI. The old Tauri updater showed a window; the script ran
in a hidden console with zero feedback. It now shows a WinForms
progress window (marquee bar + streaming log) pumped via DoEvents
during the update; -NoUi keeps tests/headless sessions clean, and a
WinForms-unavailable session degrades to log-only.
Also: subprocess execution moved from Start-Process (ExitCode
unreliably $null under PS 5.1 even with the Handle workaround -
observed live: happy path reported "failed (exit )") to
System.Diagnostics.Process with synchronous stdout pumping, which
keeps the UI alive and the exit code real.
E2E on a real Windows box, sandbox HERMES_HOME + compiled fake
hermes.exe, all five paths:
- happy: exit 0, result {ok:true, "Update complete."}
- shim held open via O_RDWR: exit 5, nothing mutated, honest result
- desktop pid alive (60s ping child): exit 4 after the 30s gate
- update exits 0 printing "Desktop build failed" + rebuild fails:
exit 6, result names the stale build and the retry command
- foreign-owned marker: overwritten by step-0 claim, removed as owner;
ownership check verified in the cleanup path
vitest 18/18 (5 new handoff-result tests), typecheck 3 projects clean,
eslint clean, PS 5.1 parse + footguns + ASCII-only clean.
Remaining known gap (deliberate): the full click-to-relaunch lifecycle
through a REAL Desktop build still needs one live Windows verification
after this lands - tracked in the PR body.
|
||
|
|
3b08a0f9b5 |
fix(desktop): give the update hand-off script its own console - a detached hidden powershell dies before -File runs
Live failure on the first real use of #82328 (2026-08-09): clicking Update closed the Desktop with "an updater will happen", then nothing. desktop.log showed `launched repo hand-off script`, but desktop-update-handoff.log was never created - PowerShell exited 0 without executing a single line. Root cause, isolated by spawning the exact production shape against a sandbox HERMES_HOME: `spawn('powershell', [..., '-File', script], { detached: true, stdio: 'ignore', windowsHide: true })` kills powershell.exe during console-subsystem init, before -File processing. Variant matrix: plain pipes -> runs; hide only -> runs; detached only -> runs; detached+hide -> exits 0, script never starts. Unit tests and foreground invocations can't see this class of bug. Fix: wrapHandoffForDetachedConsole() routes the invocation through `cmd /d /s /c start "" /min powershell ...` - `start` allocates the script its own minimized console and fully detaches it; the cmd wrapper exits immediately. Verified the wrapped form survives the full detached+hidden production spawn. Knock-on: child.pid is now the short-lived wrapper, not the script, so the Electron-side marker pre-write can't represent the script. The script now claims the update marker itself as step 0 (its own $PID, byte-exact "<pid>\n<ts>\n" via WriteAllText - Set-Content emits CRLF and would break the three readers' framing). The Electron pre-write is kept as a bridge for the spawn window: the script overwrites it, and if the script never starts the wrapper's dead pid reads as stale and self-deletes (no wedge). `hermes update` adopts the script's claim via update_lock.py's process-ancestry rule, unchanged. E2E in exact production shape (cmd start wrapper, detached, hidden, parent exits 1.5s after spawn) against a sandbox HERMES_HOME with a compiled fake hermes.exe: script ran, claimed marker with its own pid (fake observed "<script-pid>|<ts>|" LF-framed DURING the update), desktop-pid wait worked, update invoked with correct argv, marker removed on completion. vitest 13/13 (new wrapper-shape test), 3-project typecheck clean, eslint clean, PS 5.1 parse + windows-footguns clean. |
||
|
|
92be912d73 |
feat(desktop): repo-owned Windows update hand-off script - stop depending on the frozen hermes-setup binary
The Desktop's Update button hands off to the staged Tauri binary (HERMES_HOME/hermes-setup.exe). That binary has no self-update path (copy_self_to_hermes_home no-ops during --update), so every updater-side fix only reaches users when a new installer is built, signed, and published. In practice the published binary lags main by months and users hit long-fixed bugs on every GUI update: the 2026-08-09 incident chain was four distinct failures (stale install.ps1 cache resolver pre-#67369, marker adoption pre-#74782, straggler teardown) all caused by a June 4 binary running against an August repo. This inverts ownership: scripts/desktop-update.ps1 lives in the repo checkout, so every `hermes update` refreshes the code that drives the NEXT update. Only PowerShell itself - an OS component - stays frozen. Desktop side (apps/desktop/electron): - resolveUpdateScriptHandoff() (updater-process.ts): returns the spawn recipe when scripts/desktop-update.ps1 exists in the checkout; Windows-only (POSIX updates in place via applyUpdatesPosixInApp); null on old checkouts -> caller falls back to the staged binary path completely unchanged. - applyUpdates() prefers the script hand-off. The marker pre-write is ALWAYS safe on this path - no stagedUpdaterSupportsPrewrittenMarker() mtime heuristics - because hermes_cli/update_lock.py's UpdateLock adopts a live marker held by a process ANCESTOR, and the script is the `hermes update` child's parent. This closes the unguarded marker-gap window that pre-#74782 binaries force today (the 23:56 failure in the incident: 'skipping marker pre-write: staged updater predates self-adopt' -> renderer respawned a backend into the gap -> update refused). - CLI-installed users (no staged binary) now get the script hand-off too instead of the manual `hermes update` card, when the script exists. Script (scripts/desktop-update.ps1): waits for the Desktop pid to exit (bounded 30s), waits for the venv shim to unlock (mirrors the Rust is_locked probe, bounded 20s), runs `hermes update --yes --gateway --force --branch <ref>` from the CURRENT checkout with one retry for the update-boundary class (skipped for exit 2), removes the marker on every exit path, relaunches the Desktop. ASCII-only (the #67193 lesson), logs to logs/desktop-update-handoff.log. Verification (real Windows box): - apps/desktop: typecheck (3 projects) clean, eslint clean, vitest updater-process.test.ts 12/12 (3 new resolver tests). - Script E2E against a sandbox HERMES_HOME with a compiled fake hermes.exe: correct argv (update --yes --gateway --force --branch main), stale marker removed, exit code propagated (0 and 1 paths), retry-once fires exactly once on failure, PS 5.1 parse + windows footguns check clean. - Contract E2E with the real UpdateLock: ancestor-owned marker adopted (True), left in place on release, foreign live holder still refused. |