Commit Graph

2499 Commits

Author SHA1 Message Date
ethernet
a5f5a31fe7 fix(install): fall back to the mirror when the progress download fails
Invoke-DownloadWithProgress runs Invoke-WebRequest in a separate runspace,
where an HTTP or DNS failure is non-terminating: EndInvoke returned normally,
the caller skipped the mirror, and Get-FileHash died on a file that was never
written. Rethrow the runspace's first error outside the unwrapping catch, so
the caller sees the same WebException/HttpResponseException it classifies.
2026-09-24 12:16:33 -04:00
ethernet
7e59364c8f Merge remote-tracking branch 'fork/ethie/pm-clean' into ethie/pm-clean 2026-09-24 11:58:45 -04:00
ethernet
e8d1fc76ec fix(install): run the installed hermes under the Restricted policy
`irm | iex` runs install.ps1 as text, which execution policy never
checks, but Invoke-InstalledHermes then dot-sourced runtime.ps1 from
disk. That is a file load, and the default Restricted policy (Windows
Sandbox, fresh machines) refused it right after "hermes command
installed". Load the helper from its text instead.

That failure hid a second one on the same path: the `$command` local
was shadowed inside Invoke-Native by its case-insensitive `$Command`
parameter, so `& $command[0]` invoked the scriptblock itself until the
call depth overflowed. Rename the local.
2026-09-24 11:56:34 -04:00
ethernet
2be53ecd7b fix(encoding): read kernel pseudo-files as plain utf-8
utf-8-sig exists to tolerate BOMs that Windows tooling adds to files
users edit. /proc and /sys files are generated by the Linux kernel, never
BOM'd and absent on Windows, so -sig there only muddies the read/write
policy. Switch every literal /proc/ and /sys/ read to utf-8 and teach
the footgun read rule that string literals starting with /proc/ or
/sys/ are exempt (user-edited files keep utf-8-sig).
2026-09-24 11:50:30 -04:00
ethernet
6f14f6001d ci(lazy-deps): describe tools.lazy_deps as the stub it is
tools/lazy_deps.py was not deleted; it survives as an old-updater stub
that raises or stops for relaunch. The job display name and the
checker/test prose claimed it was deleted, which sends readers looking
for a missing file. The display name is not a required status check
(main requires only "All required checks pass", which keys on job ids),
so rename it to "No production imports of the tools.lazy_deps stub".
2026-09-24 11:50:30 -04:00
ethernet
845b61f2b8 fix(release): rerun failed stable runs on the failure event, drop the cron
Stable Release Publication ran every 15 minutes (96 runs a day, each
checking out full history, setting up node and buildx, logging into
Docker Hub, and taking the release-signing environment) only because the
sequencer held a failed run for a 15-minute backoff that the failure
event could never satisfy, so the cron was what actually retried.

Drop the backoff: the reconcile pass started by a failed Stable Release
reruns its failed jobs right away. MAX_ATTEMPTS burning, oldest-first
retry ordering, the attempt-entry check, and the needs_retarget repair
stay. The schedule trigger goes; workflow_run and workflow_dispatch
remain the recovery paths.

The shared stable-release concurrency group cannot deadlock: the rerun
waits as pending behind this job, and the sequencer only confirms the
new attempt is queued before it exits and frees the group.
2026-09-24 11:50:30 -04:00
ethernet
689d77292a fix(install): say what the ARM64 build-tools step is doing
On a fresh ARM64 Windows host, the venv sync first installs Visual
Studio Build Tools, Rust and a vcpkg OpenSSL to compile dependencies
that have no ARM64 Windows wheel (cryptography among them). The Visual
Studio installer ran with --quiet, so the installer printed nothing
for 20+ minutes after the tool lines and looked hung.

Print a line before each slow step, with what it is for and how long it
can take, and run the Visual Studio installer with --passive so its own
progress window shows.
2026-09-24 11:46:28 -04:00
ethernet
bcabc5b881 fix(install): keep the PowerShell session open when an iex install fails
The documented one-liner, iex (irm .../install.ps1), runs the installer
inside the user's own session. Fail ended with exit 1, so any failed
stage closed the user's PowerShell window.

Fail now throws. The two entry points own reporting and the exit code:
-Stage prints the reason, emits the -Json frame and exits 1, as before;
the full install exits 1 only when it runs from a script file, and under
iex it prints the reason, sets LASTEXITCODE=1 and returns. A scriptblock
literal's File tells the two apart: $MyInvocation.MyCommand.Path names the
caller's script under iex.
2026-09-24 11:31:08 -04:00
ethernet
f30e86c665 fix(install.ps1): more windows log improvements 2026-09-24 11:21:47 -04:00
ethernet
be694719e9 fix(handoff): curl latest sha for repo 2026-09-24 11:09:52 -04:00
ethernet
419a3cbe00 fix(install): request the native bootstrap Python on Windows-on-ARM
A bare version request lets uv pick an emulated x86_64 CPython on
Windows arm64 hosts ("support for the native architecture (aarch64) is
not yet mature"). The bootstrap interpreter then ran as win-amd64.

Request cpython-<minor>-windows-<arch>-none from the machine
architecture the scripts already detect, in install.ps1,
setup-hermes.ps1 and setup-hermes.sh (win32 only; POSIX keeps the bare
version so uv still picks the right libc variant).
2026-09-24 10:58:07 -04:00
ethernet
f39aa8a7a6 feat(install): progress bars on windows 2026-09-24 10:35:19 -04:00
ethernet
f6585964b6 feat(install): windows install progress 2026-09-24 10:32:30 -04:00
ethernet
e8a5e0978c chore(desktop): require prepared build Python and refresh release comment 2026-09-24 09:20:50 -04:00
ethernet
edcb3346a7 fix(desktop): constrain baked environment and validate registered feeds 2026-09-24 09:20:50 -04:00
ethernet
ff14aaecc0 fix: reject incomplete source installer arguments before install work 2026-09-24 09:20:50 -04:00
ethernet
3cfa43f0c5 fix(install): expose published launcher to new login shells 2026-09-24 07:34:16 -04:00
ethernet
dcfe8f2d75 fix(install): refuse occupied Windows checkout before Git provisioning 2026-09-24 06:09:17 -04:00
ethernet
2fb89006cd Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	agent/learning_mutations.py
2026-09-24 06:08:20 -04:00
kshitijk4poor
03584e41b9 test(docker): pin the non-mapping config.yaml boot path
The strict version read in docker_config_migrate is what keeps a list-root
config.yaml on the warn-and-continue path; only a probe covered it. Fold the
list-root case into the existing invalid-YAML test (reverting to the tolerant
read now goes red) and correct the comment about where the warning comes from.
2026-09-24 15:34:19 +05:30
kshitijk4poor
92324790f2 refactor(config): read the version stamp once and drop has_version_stamp
migrate_config() and the docker boot script each parsed config.yaml twice:
check_config_version() coerced a missing `_config_version` to 0 and threw the
"was it present" bit away, so has_version_stamp() re-read the file to recover
it, guarded only by a call-order promise in its docstring. That promise did not
hold for the docker script, which used the tolerant check: a list-rooted
config.yaml read as "unversioned, not below the floor", ran the backup +
migrate_config() dance and exited 1 (base: floor warning, exit 0).

Factor the read into _read_config_version_stamp() -> (Optional[int], latest);
None means the mapping has no stamp. check_config_version() is a thin wrapper
(None -> 0) so its 10 callers see identical output. migrate_config() and the
docker script decide `unversioned` from that single read; the docker script
now does the strict read itself and leaves an unparseable or non-mapping file
alone with a warning and exit 0, matching its invalid-YAML posture.
has_version_stamp() is deleted. Docker tests that mocked the pre-check now
mock the new helper.
2026-09-24 15:34:19 +05:30
John Paul Soliva
a88bef98b2 fix(config): stop the migration ladder rewriting unversioned configs
A config.yaml without _config_version reads as v0 and is exempt from the
support floor, so the first `hermes update`, profile clone,
`hermes doctor --fix` or docker boot ran every one-time migration step on
it. Installers seed config.yaml from cli-config.yaml.example, which had no
version, and targeted writers (`hermes config set`, /personality, the
TUI/Desktop config writers) never stamp one, so this is the normal state
of --skip-setup, non-TTY and Desktop (--non-interactive) installs. The
value- and absence-based steps then reset the personality, raised the
delegation caps, turned verify_on_stop off, shortened the curator windows,
dropped model_catalog.ttl_hours and enabled plugins the user had installed
but never enabled.

- A config with no _config_version now gets only the steps keyed on a
  legacy key or identifier (LEGACY_KEY_STEPS), then the stamp.
- cli-config.yaml.example carries _config_version, so every seeded
  config (install.sh, install.ps1, docker/stage2-hook.sh, doctor --fix)
  starts at the current schema.
- docker_config_migrate.py no longer refuses a version-less volume with
  the "predates version 12" warning; like migrate_config() it migrates
  and stamps it.

(cherry picked from commit 97ba11e07009f633662b0c7fa8701aa5b441bd22)
2026-09-24 15:34:19 +05:30
ethernet
fb7fe862ef fix: restore pinned Git in each Windows bootstrap stage 2026-09-24 04:42:28 -04:00
ethernet
cb7b18431a Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	apps/desktop/src/app/settings/connections-registry.tsx
#	scripts/install.ps1
#	scripts/install.sh
#	tests/hermes_cli/test_update_autostash.py
2026-09-24 03:48:31 -04:00
brooklyn!
c7d2985ae3 fix(install): keep dropped commits behind a rescue ref on the installer reset
Re-running install.sh / install.ps1 over an existing checkout (desktop
bootstrap and its update retry do this) falls back to
`reset --hard origin/<branch>` when a fast-forward fails, with no anchor for
the commits it drops. Park HEAD under refs/hermes-update-backups/, the same
namespace `hermes update` writes and prunes, and print the ref.
2026-09-24 02:40:53 -05:00
brooklyn!
14a8a771de fix(desktop-update): stop launching a Chrome instance for the macOS update shim
Each update started the user's Chrome binary with its own --user-data-dir,
a second instance of the same bundle that the Dock records as a new
recent-app tile. On macOS the outcome now goes through the existing
notification + next-boot result dialog.

Fixes #96374
2026-09-24 02:36:14 -05:00
ethernet
5f78e110e1 test: align Windows launcher and bootstrap fixtures with native behavior 2026-09-24 03:12:29 -04:00
ethernet
d6106975e9 fix(docker): promote desktop variant from its own release digest 2026-09-24 02:03:59 -04:00
ethernet
d288905b11 test: assert rehearsal stops only the target home gateway 2026-09-24 02:03:59 -04:00
ethernet
f91bd82286 fix: restore catalog Hindsight and harden installers and test guards 2026-09-24 02:03:59 -04:00
ethernet
038d7f797f fix(pm): avoid duplicate Daytona install and decode Store output explicitly 2026-09-24 01:36:14 -04:00
ethernet
dcd2bca06a fix(desktop): render icons with core runtime dependencies 2026-09-24 01:30:15 -04:00
ethernet
c3e9ac87a7 fix(installer): clone treeless instead of full history
A --depth 1 clone hides the release tag runtime identity is derived from
and cannot resolve a non-tip --commit pin or the ancestor guard; a full
clone downloads every tree and blob ever committed. --filter=tree:0 keeps
the whole commit graph and its tags and fetches trees on demand: 125M vs
77M for --depth 1 against GitHub, identity exact. The deferred-checkout
fallback uses the same filter.
2026-09-24 00:51:07 -04:00
ethernet
26c6307046 ci(install-e2e): route selects specific legs, not just an OS
route already means "which legs run"; a leg name is the most specific
route. Presets keep their meaning (all, the linux trio, windows-desktop,
macos-desktop, the bundled three); any other value selects legs by name,
so a leg name, a fragment of one, or the job name GitHub shows
("<leg> / e2e") runs just those legs. A route that selects nothing fails
instead of producing a green empty run.

The generator is now the one interpreter of route for source legs: the
linux/windows/macos jobs run when it selected legs for them, replacing
three hand-kept preset lists. Dispatch route becomes a string input (a
choice cannot take a leg name) and reaches the gen step through env, never
interpolated into the script.
2026-09-23 23:42:01 -04:00
ethernet
b1c9d1279a test(install-e2e): every combination also installs HEAD and updates it to a synthetic NEXT
Every leg installed a release tag and updated to HEAD, so nothing ran
HEAD's installer on an empty machine (where all four 2026-09-23
install.ps1 breaks lived) and nothing exercised the updater we ship
today -- tag legs run the OLD build's updater handing off to HEAD.

The generator appends a HEAD -> NEXT start after the sampled tags, so the
column runs wherever the update legs run (dispatch and stable-release).
NEXT is a reserved update ref: the drivers mint a child of the install
commit that adds one marker file, written to the object store only, which
the local bare clone carries into serve.git.

On Windows the HEAD leg takes every git.exe dir off PATH and installs no
remote get-url shim: Get-PinnedGit returns any git on PATH, so either one
skipped pinned-git staging. launch-from-spec's HEAD observer now uses the
driver's real git so it cannot poll '' forever on that leg.
2026-09-23 23:06:28 -04:00
ethernet
74c365a85a fix(release): check the draft body before the claim; release takes --no-changelog
The stable cut built its draft body after pushing the attempt ref, so a
body over GitHub's 125000-character limit failed with HTTP 422 and
burned the attempt. The body is now built first, and an oversized one is
refused before anything is claimed, naming --no-changelog.

--no-changelog was defined only on the top-level parser, so
`release.py release --no-changelog` was an argparse error and the flag
never reached the cut. The release subcommand now takes it, with a
SUPPRESS default so the top-level spelling is not reset.
2026-09-23 22:24:22 -04:00
ethernet
d75d3fe15c Merge origin/main into ethie/pm-clean
Conflict resolutions and semantic fixups:

- tools/environments/base.py: main's hard-exit kill fence (kill a spawn the
  fence missed, deregister from _live_foreground in a finally) wrapped around
  pm-clean's output collector.
- pyproject.toml: pm-clean's marker list plus main's new `live` marker.
- hermes_cli/main.py: pm-clean runs startup recovery from hermes_bootstrap, so
  the old early-recovery block stays gone; main's interrupted-pull restore
  (auto-merged above it) runs right after bootstrap, as on main.
- hermes_cli/update_cmd.py: main's interrupted-pull marker now guards
  pm-clean's first tree mutation (release-tag detach, ff-only, or reconcile)
  and is cleared once git is done. The marker's target is the ref git actually
  moves to (a release tag, not always origin/<branch>), since the restore
  compares against it.
- hermes_cli/_early_recovery.py: restore `import subprocess`, which pm-clean
  had dropped and main's auto-merged restore needs (NameError on the first
  launch after a killed update; test_update_interrupted_pull red -> green).
- apps/desktop/src/i18n/{de,es,fr}.ts: main's new locales carry the full
  settings.about block; trim it to `updates` as pm-clean's type and the other
  overlays do (tsc: 27 errors -> 0).
- main's new e2e tests: `import yaml` -> hermes_yaml; wake-word import table
  names pyopen_wakeword (pm-clean's wake-openwakeword extra); the anthropic
  key-leak switch leg needs the SDK, and the api_server two-tenant test needs
  aiohttp, both PM runtime extras the test env does not carry.
2026-09-23 21:55:59 -04:00
teknium1
0988a99743 test(runner): forward HERMES_E2E_REQUIRE_TUI, CI and GITHUB_ACTIONS through the hermetic env
run_tests.sh starts every run from env -i with an allowlist, so the e2e
job's HERMES_E2E_REQUIRE_TUI=1 never reached the terminal suite (a missing
ui-tui build skipped instead of failing) and the upgrade suite's CI branch
in sandbox_required_reason() was dead. With ui-tui/dist removed and
HERMES_E2E_REQUIRE_TUI=1: before, 2 skipped; after, 2 failed.
2026-09-23 17:55:23 -07:00
ethernet
e0265925f1 fix(release): --no-changelog no longer raises NameError
generate_changelog() defined all_authors and teknium_aliases inside the
'if not no_changelog' block but read them after it. The canary tests
stub generate_changelog, so nothing ran the real path.
2026-09-23 19:56:46 -04:00
ethernet
c0bd183eef fix(release): build stable draft notes from commits, not generate-notes
GitHub's generate-notes lists merged pull requests since the last
published release. A fork merges none, so the stable draft body was only
a "Full Changelog" link, and it lost the HERMES_BUILDS_TABLE marker that
the stable workflow renders the download tables into.

The cut now builds the body with generate_changelog() over the commits
from the published stable commit (or the seed version's tag before the
first publication) to the cut commit.
2026-09-23 19:56:46 -04:00
ethernet
c58744e59e fix(release): link the draft at cut time, not a page that 404s until green
The stable cut already creates the draft on the claim ref before it
dispatches the gate, but the output pointed at releases/tag/<claim> and
then at releases/tag/v<version> "when it is green". GitHub serves a draft
only at an untagged-* URL, so neither link showed it. Operators read that
as "the draft appears after the build".

Take the URL gh release create prints (the release's html_url) and print
it up front, with a note that notes edited during the build survive:
edit_draft_release keeps the body and strips only the warning fence. The
v<version> URL stays, labelled as where the release lives once published.

The canary resume path had the same broken releases/tag/<tag> link; it
now uses the create output or the url field of gh release view.
2026-09-23 19:20:27 -04:00
ethernet
0384a24edc Merge branch 'ethie/release-attempt-refs' into ethie/pm-clean
Conflicts:
- scripts/releases/stamping.py, tests/scripts/test_version_stamping.py:
  took ethie/pm-clean. The release branch's side was only its base's copy
  of "stamping a payload snapshot skips the bootstrap-installer check"
  (8411fdb333, same patch-id as 8d34601f47 here); the install-stamp
  refactor c13ea774e6 supersedes the rest.
- tests/ci/test_stable_release_graph.py: kept pm-clean's release-epoch
  contract (no HERMES_RELEASE_EPOCH on termux-deb, version on docker and
  nix only) and the release branch's per-group receipt wiring.

Semantic conflict: the dispatch log step (6e64e961d8) read
inputs.termux_only, which the jobs input replaced. It reads JOBS now, and a
dispatch that selects only some groups has no release.py replay, as a
termux-only one had none before.
2026-09-23 19:00:44 -04:00
ethernet
babbec1c4c feat(pm): isolate developer test environment from runtime extras 2026-09-23 18:13:38 -04:00
ethernet
4e3684c993 fix(install): create the pm store root before publishing pinned git
Prerequisites is the first stage, so on a fresh Windows host
<HermesHome>\tools does not exist when Get-PinnedGit runs, and Move-Item
throws DirectoryNotFoundException (reported against the source path).
The uv stager already creates its slot with New-Item -Force.
2026-09-23 18:10:09 -04:00
ethernet
9a3fcf9756 fix(install): pass --no-registry to every bootstrap uv python install
install.ps1 already kept uv's bootstrap Python out of the Windows
registry, but setup-hermes.ps1 did not, so every Windows dev checkout
registered that interpreter under HKCU. The flag is Windows-only; the
POSIX installers take it too so every bootstrap issues the same command.
2026-09-23 18:01:43 -04:00
ethernet
e6446eb72e fix(build): pin the ARM64 cargo linker from VCToolsInstallDir, not PATH
PM prepares the ARM64 build environment again inside callers that already
carry a matching developer environment. It is reused as-is, including PATH
order, and a Git Bash caller puts coreutils' link.exe first, so the guard
aborted setup ("MSVC link.exe is shadowed by ...\Git\usr\bin\link.exe").
VsDevCmd's VCToolsInstallDir names the MSVC linker exactly, whichever way
the environment was obtained. PATH order stays the caller's.
2026-09-23 17:51:29 -04:00
ethernet
13ebc163a1 ci: fold the PowerShell installer job into the tests-os Windows lanes
installer-tests.yml predates nothing it still owned. Its pytest step
(test_source_launcher_stages.py) is platforms("windows") and already runs in
both tests-os Windows lanes, so every installer PR ran it twice. The
`installer` lane never gated anything on its own either: every path that set
it also sets `python`, which gates tests-os.

The two standalone scripts/tests/*.ps1 suites become one platforms("windows")
pytest file parametrized over Windows PowerShell 5.1 and pwsh 7, so
list_os_marked_tests picks them up with everything else. The `installer` lane
goes away from the classifier, detect-changes, ci.yaml and the
all-checks-pass gate; the classifier contract now pins that install.ps1 and
its suites turn `python` on.
2026-09-23 17:19:29 -04:00
ethernet
1c2abc0cbf docs(release): the Store submission is held and published by hand
The publication pass checks the held Store submission and prints the
Publish now step; it cannot release it. The developer guide and the draft
warning said the pass released it.
2026-09-23 17:16:19 -04:00
ethernet
eb877f88c5 fix(release): check the held Store submission instead of releasing it
The Partner Center submission API has no call that releases a held
(Manual) submission, and update refuses a committed one, so the release
step's PUT and re-commit could not work. The publication pass now only
reads the submission: a certified one prints a GitHub warning to click
Publish now in Partner Center, one still in certification says what comes
next, a live one is a no-op, and a failed one leaves the run red.

It finds the held submission through pendingApplicationSubmission on the
app, instead of posting a probe submission and scraping an id out of the
409 error. The HTTP runner also sends the request headers it is given:
before, every API call after the token went out without Authorization.
2026-09-23 17:14:46 -04:00
ethernet
20f3b5f38b feat(release): start each install arm from its own receipt
transitions splits into one job per receipt (darwin-arm64, darwin-x64,
win32-bundle), and each packaged install job waits only on its own. The
Mac install arms no longer wait for the other arch or for the smokes.

candidate-manifest moves into stable-release.yml and waits for every
candidate call, so it still runs after every smoke (decision 23). The
smoke results it records are the calls' own results, mapped to the smoke
job names the final manifest requires. publish-bundles and complete read
its digest again, which the per-group split had left unset.

read_manifest resolves its opener per call instead of binding
urllib.request.urlopen as an import-time default, so the process trust
setup applies. The receipt fixtures gain the runner's RUNNER_TEMP and the
baseline's macOS identity.
2026-09-23 17:08:59 -04:00