mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 19:20:16 +00:00
Merge pull request #2817 from nesquena/release/stage-batch6
Release CV: stage-batch6 — 3-PR Windows-only stack (v0.51.124) — start.ps1 paths/docs/hardening
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
@@ -91,23 +97,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
|
||||
}
|
||||
|
||||
@@ -119,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) {
|
||||
@@ -147,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
|
||||
|
||||
Reference in New Issue
Block a user