From ed9a16373129cfb24faf2d60a2a57cd32117e73e Mon Sep 17 00:00:00 2001 From: Koraji95-coder Date: Sun, 24 May 2026 04:32:43 +0000 Subject: [PATCH 1/4] feat(start.ps1): expand hermes-agent candidate paths for Windows installers (#2805) Squashed from 3 author commits onto current master (the 3 base commits from already-shipped #2783 were filtered out by the squash): - 6822cbbb feat: expand hermes-agent candidate paths - 6f423538 Copilot review: PathType+null-guard+changelog - dbebbedd handle WOW64 ProgramFiles redirection Authorship preserved. CHANGELOG entry merged into batch stamp commit. --- start.ps1 | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/start.ps1 b/start.ps1 index 191db6c1..af61f14b 100644 --- a/start.ps1 +++ b/start.ps1 @@ -91,23 +91,35 @@ if (-not $Python) { # that's about to crash on missing imports. Smoke-test feedback on # PR #2783: nesquena/hermes-webui requested this guard. $AgentDir = $env:HERMES_WEBUI_AGENT_DIR -if ($AgentDir -and -not (Test-Path (Join-Path $AgentDir 'hermes_cli'))) { +if ($AgentDir -and -not (Test-Path (Join-Path $AgentDir 'hermes_cli') -PathType Container)) { Write-Error "HERMES_WEBUI_AGENT_DIR is set to '$AgentDir' but no hermes_cli/ folder exists there. Unset the variable to fall back to auto-discovery, or fix the path." exit 1 } if (-not $AgentDir) { - $candidates = @( - (Join-Path $env:USERPROFILE '.hermes\hermes-agent'), - (Join-Path (Split-Path -Parent $RepoRoot) 'hermes-agent') - ) + # Build candidate list incrementally — ${env:ProgramFiles(x86)} is null on + # 32-bit Windows and in some constrained environments, and Join-Path throws + # on a null Path. Skip any system-wide root that isn't set so the launcher + # stays robust across Windows variants. USERPROFILE is always set so it + # stays unguarded; the dev-checkout sibling is path-derived, not env-based. + $candidates = @() + $candidates += (Join-Path $env:USERPROFILE '.hermes\hermes-agent') + foreach ($root in @($env:LOCALAPPDATA, ${env:ProgramW6432}, ${env:ProgramFiles}, ${env:ProgramFiles(x86)})) { + if ($root) { $candidates += (Join-Path $root 'hermes\hermes-agent') } + } + $candidates += (Join-Path (Split-Path -Parent $RepoRoot) 'hermes-agent') + # De-dup: when running in a WOW64 (32-bit-on-64-bit) PowerShell process, + # $env:ProgramFiles is redirected to C:\Program Files (x86), so without + # $env:ProgramW6432 (the canonical 64-bit override) we'd miss the real + # C:\Program Files\hermes\hermes-agent AND duplicate the x86 entry. + # Select-Object -Unique collapses any collisions regardless of cause. + $candidates = $candidates | Select-Object -Unique foreach ($c in $candidates) { - if (Test-Path (Join-Path $c 'hermes_cli')) { $AgentDir = $c; break } + if (Test-Path (Join-Path $c 'hermes_cli') -PathType Container) { $AgentDir = $c; break } } } if (-not $AgentDir) { - $expectedPrimary = Join-Path $env:USERPROFILE '.hermes\hermes-agent' - $expectedSibling = Join-Path (Split-Path -Parent $RepoRoot) 'hermes-agent' - Write-Error "hermes-agent not found at $expectedPrimary or $expectedSibling. Set HERMES_WEBUI_AGENT_DIR explicitly." + $searched = $candidates -join ', ' + Write-Error "hermes-agent not found. Searched: $searched. Set HERMES_WEBUI_AGENT_DIR explicitly to override." exit 1 } From 9db6be99e17a1a89b0b4e06be8392dbe14f32117 Mon Sep 17 00:00:00 2001 From: Koraji95-coder Date: Sun, 24 May 2026 04:33:31 +0000 Subject: [PATCH 2/4] docs(start.ps1+README): clarify native Windows venv path; remove misleading WSL2-venv-portability claim (#2806) Squashed from 3 author commits onto current master (3 base commits from already-shipped #2783 were filtered out by the squash). #2805's expanded candidate-path discovery + PathType Container check preserved from prior stage commit. Authorship preserved. CHANGELOG entry merged into batch stamp commit. --- README.md | 3 ++- start.ps1 | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d82ecf3c..ccaca241 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,8 @@ A community-maintained native Windows setup is documented at [@markwang2658/herm - **Memory:** community-measured ~330 MB native vs ~1080 MB with WSL2+Docker (varies by configuration). - **What works:** chat, workspace browser, session management, all themes. - **Known limitations:** some POSIX-style file paths surface in the workspace browser; bash-assuming agent tools may not work natively. -- **WSL2 relationship:** WSL2 is recommended *once* for first-time venv creation (since `bootstrap.py` currently refuses on native Windows). After the venv exists, `start.ps1` at the repo root runs the WebUI natively by invoking `server.py` directly — no WSL2 needed for day-to-day use. +- **Native Windows setup:** install Python 3.11+, then from the hermes-agent root in PowerShell: `python -m venv venv` → `pip install -r requirements.txt` → `pwsh .\start.ps1` (it auto-discovers `venv\Scripts\python.exe`). +- **WSL2 relationship:** not a prerequisite — a WSL2-built venv (`venv/bin/python`, ELF) isn't invokable by native Windows Python, so use the native setup above. WSL2 stays useful as a parallel install if you want the full `bootstrap.py` + Linux runtime. If provider setup is still incomplete after install, the onboarding wizard will point you to finish it with `hermes model` instead of trying to replicate the full CLI setup in-browser. For a step-by-step walkthrough of the wizard, provider choices, local model server Base URLs, and safe re-runs, see [`docs/onboarding.md`](docs/onboarding.md). diff --git a/start.ps1 b/start.ps1 index af61f14b..705da3eb 100644 --- a/start.ps1 +++ b/start.ps1 @@ -11,9 +11,15 @@ server.py itself runs cleanly on native Windows. Assumes Python + hermes-agent + the WebUI Python deps are already - installed - same assumption start.sh makes when invoked outside - a fresh bootstrap. For first-time setup, run bootstrap.py inside - WSL2 once to create the venv, then this script can use that venv. + installed natively on Windows - same assumption start.sh makes + when invoked outside a fresh bootstrap. For first-time setup, the + native Windows path is to install Python 3.11+, then create a + Windows venv (`python -m venv venv`) and `pip install -r + requirements.txt` from the hermes-agent root in PowerShell - this + script then finds `venv\Scripts\python.exe` automatically. A venv + created inside WSL2 is a Linux virtual environment (`venv/bin/python`) + and cannot be used by native Windows Python, so the bootstrap.py- + inside-WSL2 path produces a venv `start.ps1` can't invoke. .PARAMETER Port TCP port the WebUI binds to. Overrides HERMES_WEBUI_PORT env. From 055f9b3cd00813e2e99f5d6043507d3d0812a187 Mon Sep 17 00:00:00 2001 From: Koraji95-coder Date: Sun, 24 May 2026 04:34:06 +0000 Subject: [PATCH 3/4] harden(start.ps1): TryParse HERMES_WEBUI_PORT + exit AFTER try/finally cleanup (#2807) Squashed from 2 author commits onto current master (3 base commits from already-shipped #2783 were filtered out by the squash): - f53b9308 fix(start.ps1): TryParse HERMES_WEBUI_PORT + exit AFTER try/finally cleanup - 7b6e0722 fix(start.ps1): drop non-functional @args splat under [CmdletBinding()] Authorship preserved. CHANGELOG entry merged into batch stamp commit. --- start.ps1 | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/start.ps1 b/start.ps1 index 705da3eb..a8aeb2d4 100644 --- a/start.ps1 +++ b/start.ps1 @@ -137,7 +137,26 @@ if (Test-Path $agentVenvPython) { # === Resolve bind + state defaults ===================================== $BindHostFinal = if ($BindHost) { $BindHost } elseif ($env:HERMES_WEBUI_HOST) { $env:HERMES_WEBUI_HOST } else { '127.0.0.1' } -$PortFinal = if ($Port) { $Port } elseif ($env:HERMES_WEBUI_PORT) { [int]$env:HERMES_WEBUI_PORT } else { 8787 } +$PortFinal = if ($Port) { + $Port +} elseif ($env:HERMES_WEBUI_PORT) { + # TryParse + range guard on the env var. A plain [int] cast on the + # env var throws InvalidCastException with no actionable context when + # the env var is set to a non-integer (typo, accidental shell + # expansion, etc.) — surface a targeted error message instead. + $parsedPort = 0 + if (-not [int]::TryParse($env:HERMES_WEBUI_PORT, [ref]$parsedPort)) { + Write-Error "HERMES_WEBUI_PORT='$($env:HERMES_WEBUI_PORT)' is not a valid integer port. Unset the variable to use the default (8787), or set it to a number 1-65535." + exit 1 + } + if ($parsedPort -lt 1 -or $parsedPort -gt 65535) { + Write-Error "HERMES_WEBUI_PORT=$parsedPort is out of TCP-port range. Must be 1-65535." + exit 1 + } + $parsedPort +} else { + 8787 +} $env:HERMES_WEBUI_HOST = $BindHostFinal $env:HERMES_WEBUI_PORT = "$PortFinal" if (-not $env:HERMES_WEBUI_STATE_DIR) { @@ -165,10 +184,23 @@ if (-not (Test-Path $serverPath)) { exit 1 } +# Capture exit code, let finally{} run Pop-Location, exit AFTER the try. +# Plain `exit $LASTEXITCODE` inside the try block can prevent the finally +# from running in some termination paths (especially when dot-sourced or +# in interactive sessions), leaving the caller's working directory stuck +# at $RepoRoot. +$script:serverExitCode = 0 Push-Location $RepoRoot try { - & $Python $serverPath @args - exit $LASTEXITCODE + # @args was non-functional here — PowerShell does NOT populate $args when the + # script declares [CmdletBinding()] with an explicit param() block (Copilot's + # finding on PR #2807). Dropped rather than added a ValueFromRemainingArguments + # parameter, because the existing tracked use case is the launcher running + # server.py with the env-var-driven config — no pass-through args are needed. + # If pass-through becomes a requirement later, add a [Parameter(ValueFromRemainingArguments=$true)] [string[]]$ServerArgs and splat that. + & $Python $serverPath + $script:serverExitCode = $LASTEXITCODE } finally { Pop-Location } +exit $script:serverExitCode From fe0a1cea94f742b23cae9a3e3590cb3fe9d58842 Mon Sep 17 00:00:00 2001 From: nesquena-hermes <[email protected]> Date: Sun, 24 May 2026 04:38:04 +0000 Subject: [PATCH 4/4] Stamp CHANGELOG for v0.51.124 (Release CV / stage-batch6 / 4-PR Windows-only stack) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cherry-picked PRs (all by @Koraji95-coder): - #2805 — expand hermes-agent candidate paths for Windows installers - #2806 — clarify native Windows venv path; remove WSL2-venv-portability claim - #2807 — TryParse HERMES_WEBUI_PORT + exit AFTER try/finally cleanup - #2811 — native-Windows startup E2E CI workflow All 4 PRs were branched off #2783 (now shipped in v0.51.121). Squash-merged each PR's unique changes onto current master with conflict resolution. Authorship preserved on every commit. Zero impact on Linux/macOS runtime — file scope is start.ps1, README.md (Windows section), and a new Windows-CI workflow that only runs on PRs touching start.ps1/requirements.txt/etc. --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecad5762..b8eaaf34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ ## [Unreleased] +## [v0.51.124] — 2026-05-24 — Release CV (stage-batch6 — 3-PR Windows-only stack — agent paths / docs / port hardening) + +### Added + +- **PR #2805** by @Koraji95-coder — `start.ps1`: expand hermes-agent candidate paths for Windows installers. The launcher now searches `$env:USERPROFILE\.hermes\hermes-agent`, the dev-checkout sibling, and the Windows installer roots (`$env:LOCALAPPDATA\hermes\hermes-agent`, `${env:ProgramW6432}\hermes\hermes-agent`, `${env:ProgramFiles}\hermes\hermes-agent`, `${env:ProgramFiles(x86)}\hermes\hermes-agent`) with `Select-Object -Unique` to collapse WOW64 ProgramFiles redirection collisions on 32-bit PowerShell processes. Adds `-PathType Container` to the `HERMES_WEBUI_AGENT_DIR` guard so a file named `hermes_cli` doesn't false-positive. Null-guards `${env:ProgramFiles(x86)}` for constrained environments where it's missing. Zero impact on Linux/macOS — file is `start.ps1`, never loaded by `start.sh` or `bootstrap.py`. + +### Documentation + +- **PR #2806** by @Koraji95-coder — Native Windows venv path corrected in `start.ps1` doc-comment and `README.md`. The previous text suggested "run bootstrap.py inside WSL2 once to create the venv, then this script can use that venv" — but a WSL2-created venv is `venv/bin/python` (ELF) and cannot be invoked by native Windows Python. The corrected guidance is to create a Windows venv natively (`python -m venv venv` from PowerShell), then `start.ps1` auto-discovers `venv\Scripts\python.exe`. WSL2 remains useful as a parallel install for the full `bootstrap.py` + Linux runtime path. + +### Hardened + +- **PR #2807** by @Koraji95-coder — `start.ps1`: `HERMES_WEBUI_PORT` env-var parsing uses `[int]::TryParse` + range guard (1-65535) instead of a bare `[int]` cast that threw `InvalidCastException` with no context on typos or accidental shell expansion. Server-process exit code is captured into `$script:serverExitCode` and emitted via `exit` AFTER the `try/finally` cleanup, so `Pop-Location` always runs (avoids leaving the caller stuck at `$RepoRoot` in interactive or dot-sourced sessions). Also drops a non-functional `@args` splat that PowerShell doesn't populate under `[CmdletBinding()]` — the launcher's existing use case is env-var-driven, no pass-through args needed. + ## [v0.51.123] — 2026-05-24 — Release CU (stage-batch5 — 2-PR low-risk batch — gzip+ETag static caching / Open in VS Code) ### Performance