Commit Graph

267 Commits

Author SHA1 Message Date
Teknium
6a92a2aa49 feat(update): source installs default to the stable release channel
`hermes update` and the Desktop update button on an official source
checkout now move between published vX.Y.Z releases instead of tracking
main commit by commit, and `hermes update --set-channel stable` works.

- stable resolves through R2 when a record exists; until one is published
  it falls back to the latest published GitHub release (non-draft,
  non-prerelease, strict vX.Y.Z, origin tag verified). It used to fail
  with "Channel object not found: releases/channels/stable.json".
- default_channel(): an official-origin source checkout defaults to
  stable; forks, mirrors and checkout-less trees keep main. Package
  installs (MSIX, Store, bundled, electron-updater) are untouched.
- An install that never chose a channel only moves forward: a checkout
  already newer than the latest release (every install that tracked main)
  stays put until a newer release ships. --set-channel stable is the
  explicit consent to land exactly on the release commit.
- Same rule in --check, the apply path and the passive source_check the
  Desktop and banner use.
2026-10-09 19:46:19 -07:00
Teknium
74943bb889 docs: stop presenting Desktop Light as a shipped download
The installation page said "Light is a remote-only build variant" next to
the download instructions, which read as if a remote-only Desktop client
can be downloaded. Light exists only as a build target with no release
leg, and there are no plans to publish it. Replace the sentence with how
remote use actually works today (install a package, connect from Settings
-> Gateways), drop "Light packages" from the uninstall note, and mark the
variant as unpublished in the desktop README.
2026-10-07 05:14:58 -07:00
Teknium
7664cc21ad fix(update): the POSIX gateway pause never blocks an update
A discovery or durable-record failure refused the whole update at arm_commit_point (exit 1),
so a host where systemctl cannot reach a user bus (SSH without lingering, containers) could no
longer update at all. The pause is protection, not a gate: any failure restarts whatever it
stopped and the update continues the way POSIX always updated (gateways run through the swap,
restarted after the commit). A failed rollback stays owed and restarts after the deps sync; a
successful one clears the restarted set so the fleet restart refreshes those gateways.

Caught by six existing completion/branch-switch tests whose fake subprocess layer makes
discovery raise; they pass unchanged.
2026-10-07 04:39:00 -07:00
Teknium
082ec546aa fix(update): Windows keeps resuming after the fleet restart, never twice
The early before_build resume ran for Windows sets too. The fleet restart skips only what the
POSIX pause restarted (already_restarted), so a Windows gateway resumed before the build was
then drained and restarted a second time by the fleet pass: one extra outage per update that
main never had. The early resume is now POSIX-only and goes through the main facade like every
other resume call; Windows resumes where it always did. Docs say so.
2026-10-07 04:39:00 -07:00
Teknium
d16d2c5c41 test(update): hostile cells + docs for the POSIX pre-swap pause; leave unprivileged system units running 2026-10-07 04:39:00 -07:00
Teknium
9199fc8c30 fix: the update docs state when an update or launch refuses instead of guessing
Documents the deliberate narrowings vs main that kshitijk4poor asked to be
stated, each verified at this head: a launch that waits more than 10 s for
another launch's interrupted-pull repair exits 1 (main warned and
continued); the Windows git->ZIP fallback runs only from an untouched
checkout; a Desktop build refuses when the checkout's .git (where the
shared checkout lock lives) is not writable. Also states the S2 rule: an
unwritable host restart record falls back to the profile's marker only on
an install with no other profile, else the update refuses before moving.
2026-10-06 06:22:29 -07:00
Teknium
2163e536db fix: a completed restart never deletes the unkeyed restart debt another install armed
clear_host_obligation unlinked this install's keyed record AND the older
release's unkeyed host-update-restart.json. An installation still on that
release writes the unkeyed record; install B's completion judged only B's
own keyed record, yet deleted A's debt too (mixed-version schedule
B arm -> A arm -> B complete). Discharge now removes exactly the record
this install's readers judged (_owed_path): its own record whenever it
has one; the unkeyed one only when it was the record judged (this install
armed none), so an upgrade's own interrupted debt stays dischargeable
(review S3 residual).
2026-10-06 06:22:29 -07:00
Teknium
ea355fda17 fix: one install's completed restart never erases another install's host restart debt
Two Hermes installations of one OS user (a second checkout, a ZIP install)
shared the single host-update-restart.json in the host state dir. Install
B's arm for its SHA replaced install A's record, and B's discharge then
unlinked it: A's owed restart was gone. The record is now keyed by the
checkout the module runs from (host-update-restart-<key>.json): every
profile of one install still shares one record, each install owes and
clears its own. The key comes from __file__ and hashlib only, so the N-1
updater that lazily imports this module after its pull still arms it, and
write_host_obligation still never raises. A debt an older release armed in
the unkeyed file is still read and cleared on discharge; all records keep
one mutex sidecar (review S3).
2026-10-06 06:22:28 -07:00
teknium1
781334eea4 chore(memory): remove the bundled mem0 provider (moved to the plugin catalog)
plugin-catalog/mem0.yaml (#131267) points at mem0ai/mem0's own
integrations/hermes-plugin-mem0 at a reviewed pin. The entry stays dormant
while a bundled copy exists (bundled providers win in find_provider_dir), so
deleting the in-tree copy is what lets hermes_cli/memory_provider_migration.py
install it for every home whose memory.provider is "mem0". The vendor copy
keeps the provider name, mem0.json, the MEM0_* keys and the setup wizard.

Also dropped: the hermes-agent[mem0] extra, its win-arm64 platform row and
exclude-newer row, the pm/extras.py anchor (uv lock removes only mem0ai,
posthog, qdrant-client and sqlalchemy), the mem0-specific doctor check (the
generic check now names `hermes plugins install mem0`), and the tests that
imported plugins.memory.mem0. Prose that lists the bundled set now names mem0
as a catalog provider. MEM0_* env hints and redaction stay: the catalog copy
still reads them.
2026-10-06 05:50:08 -07:00
Teknium
f71418d4f8 docs(update): dependency follow-up, Ctrl-C after commit, root-home readers, fleet exit 0 2026-10-06 03:44:49 -07:00
Teknium
045615ac5a fix(update): after the commit point nothing fails hermes update (C3) 2026-10-06 03:44:49 -07:00
Teknium
136c01e1d6 fix(update): drop the duplicate tmp-pack sweep from pack cleanup
#133567 taught git_pack_tidy to delete tmp_* transfer files, but
gitlock.clear_stale_tmp_packs already does that a few lines earlier in the
same update step (since Sep 6), with a stricter guard: it skips while any
git process runs. Remove the copy, keep the shared _unlink helper, and
correct the docs line about when the files go away.
2026-10-05 22:07:03 -07:00
Teknium
1c97b57a93 fix(update): convert a treeless checkout before the dependency work, and on installer re-runs
A Desktop built before the blobless fix keeps walking history during
`hermes update`. On a treeless (tree:0) checkout every walk downloads trees
again, and #129514 filled a 434 GB disk during the minutes of dependency work.
The one-time conversion (#133016) ran at the very end of the update, after that
window. Both update hand-offs (update_completion._prepare and an older
updater's _update_takeover.prepare) now run it first, in the bootstrap
interpreter, and the late call is gone.

Re-running install.sh / install.ps1 over a treeless checkout now converts it
too. The filter is recorded only after the refetch succeeds, so a failure is
retried by the next update instead of leaving a blob:none filter over a
history that has no trees.

The pack cleanup also sweeps tmp_* transfer files that killed fetches left
behind. They are more than an hour old, a storm leaves gigabytes of them,
and git itself only prunes them after two weeks. The docs gain the recovery
steps for a disk that the storm already filled.

Co-authored-by: Brooklyn Nicholson <brooklyn.bb.nicholson@gmail.com>
Co-authored-by: Yuan Li <dskwelmcy@163.com>
2026-10-05 17:32:54 -07:00
Teknium
2fa8090568 fix(update): pack tidy erases only provably redundant packs
An independent live review of the tidy found it could lose data: a merge
under pack.packSizeLimit published only the first of several output packs
(45 of 60 commits gone), erasing "commit-free" on-demand packs dropped trees
and blobs a local branch still needed once upstream had pruned them, a
process killed mid-erase left a multi-pack-index naming a deleted pack, and
a classification timeout was cached as "holds commits" forever.

The erase rule is now proof against local packs alone: a promisor pack goes
only when every object in it is also in another promisor pack (checked by
binary search over the .idx files, no git process per pack). Nothing stored
locally can be lost and nothing depends on the remote still serving it, so
the marker heuristics and commit classification are gone. Merges run with
pack.packSizeLimit=0 and refuse anything but exactly one output; the
multi-pack-index is dropped before any pack is removed, so a kill at any
point leaves git reading; a lock file keeps two tidies from each erasing a
pack because the other still holds its copies.

.keep files written by a fetch-pack/index-pack that died (older than an
hour) no longer pin their packs. git's own repack never touches .keep'd
packs, and on a real dev checkout 53 of them held 4.5 GB of duplicated
history downloads.
2026-10-05 03:00:01 -07:00
teknium1
a0f47657b9 feat(update): clean up a partial clone's packs a little on every update
Each `hermes update` now spends at most 60 s on the on-demand packs a
partial clone piles up (#129712), and every finished unit is kept, so a
large checkout converges over a few updates instead of blocking one
update for 20+ minutes:

1. Erase on-demand packs (empty .promisor marker, older than an hour)
   that hold no commits. Their objects are copies of what the promisor
   remote serves; git fetches what it needs again. This frees disk,
   which the old gc fold never did. A pack holding commits is kept
   because a ref may point into it; a pack git cannot classify in time
   counts as holding one.
2. While more than 50 promisor packs remain, merge the smallest few
   with `git pack-objects --stdin-packs` (cost proportional to those
   packs, no reachability walk), marker first and index last so the
   merged pack is a promisor pack from the moment git sees it.

Verdicts and a merge batch size that halves after a timed-out merge are
remembered in .git/hermes-pack-tidy.json, so no update repeats work an
earlier one could not finish. bounded_probe_run gains `input=` for
stdin-fed git commands.
2026-10-05 03:00:01 -07:00
Teknium
1d9bb51f6b fix(install): clone blobless, convert treeless checkouts once
Installers have cloned with --filter=tree:0 since c3e9ac87a7. A treeless
clone keeps commits only, and git requests a missing tree without telling
the server which trees it already has, so every checkout and every
path-filtered history walk re-downloads complete directory snapshots
(~0.58 MB per commit for this repo). GitHub's own guidance is that
treeless clones are for throwaway CI builds, not long-lived checkouts;
the blobless layout keeps every commit and tree local and fetches only
file contents on demand.

- install.sh / install.ps1 clone --filter=blob:none (same release tags,
  --commit pins and ancestor checks the treeless switch was made for).
- hermes update (post-update step) converts an existing tree:0 checkout once with
  `git fetch --refetch --filter=blob:none origin` (~120 MB, ~8 s
  from GitHub) and records the new filter only after it succeeds, so a
  failed refetch retries next update.
- The shallow-heal conversion unshallows as blob:none instead of tree:0.
- checkout_contains asks git with GIT_NO_LAZY_FETCH: a recorded SHA the
  checkout lacks is "not contained", never a download of that commit's
  whole history.
2026-10-05 01:30:17 -07:00
teknium1
1fd75357e9 fix(update): stop folding lazy-fetch packs during hermes update
The fold added in #131679 runs `git gc --auto`, which on a large partial
clone is a full repack: a 9.6 GB checkout with 463 packs sat in it for
20 minutes per update, got killed at the cap, and started over on the
next update without ever finishing. It also never freed disk (on-demand
packs are kept, only merged). Turn it off until a bounded, progressive
cleanup replaces it.

The partial-clone keys that stop git writing a commit-graph (#127711)
stay: settle_partial_clone_maintenance re-applies them every update and
leaves full clones alone, and git's own automatic gc still folds packs.
2026-10-04 17:18:50 -07:00
kshitijk4poor
e0b843431b docs(install): document the libatomic host package step
Prerequisites now say the installer and hermes update install the distro
package providing libatomic.so.1 (sudo -n, one interactive prompt before
dependencies, never under --non-interactive), and the service-user section
no longer claims the installer provides no package-manager sudo step.
2026-10-04 19:19:41 +05:30
teknium1
0f7a95f16d docs: Home Assistant moves to the plugin catalog 2026-10-04 00:08:20 -07:00
ethernet
5fe12f373e docs: drop source channels and install identity section; main is the only source channel
Remove the "Source channels and install identity" section from Updating &
Uninstalling, and stop documenting stable/canary as selectable for source
installs. `main` is the only valid source channel right now, so the
updating, CLI reference, and stable-releases pages say that instead.
2026-10-02 18:50:11 -04:00
teknium1
35eacd248a fix(update): keep git's own lazy-fetch pack fold on in a partial clone
#131679 set maintenance.auto=false in the treeless install checkout to stop the
commit-graph lazy-fetch loop (#127711). That also switched off the post-fetch
`gc --auto` that git <= 2.53 runs after every lazy fetch, so packs piled up
between updates and only folded on a manual `hermes update` / `--check`
(nothing runs `--check` automatically).

The loop needs a commit-graph write, and gc.writeCommitGraph=false +
fetch.writeCommitGraph=false already stop gc and fetch from writing one.
maintenance.commit-graph.enabled=false closes the last writer (a commit-graph
maintenance task, e.g. from a user's global maintenance.strategy) while
leaving automatic maintenance, and its gc fold, on.

Checkouts that already got maintenance.auto=false from the first cut are
converged: the next update/--check unsets exactly that value.

Measured on a real tree:0 clone with a Bloom commit-graph, 100 lazy fetches
after one updater run:
  git 2.53.0: main 101 packs, this change 1 pack, 0 commit-graph writes
  git 2.55.0: 101 / 101 (stock 2.55 maintenance does not fold), 0 writes
2026-10-02 14:17:41 -07:00
teknium1
4e3fcd5cd7 fix(update): give the lazy-fetch pack fold room to finish and say when it runs out
The fold was killed after 120 s on the assumption it takes "well under a second". That holds
for ~100 small packs, not for the checkouts it exists for (#129712: 2,475 packs / 39 GiB): those
need a full repack, and a killed fold restarted from scratch on every update without a word,
so the worst-hit installs never converged.

- Bound raised to 20 minutes (measured: 750 packs / 530 MB of hermes-agent history fold in
  5.8 s at 7.4 cores; a 39 GiB pack dir on a slow disk is a different order of magnitude).
- consolidate_lazy_fetch_packs returns None on timeout; the shared update_cmd_check reporter
  (used by both `hermes update` and `hermes update --check`) prints the hand-fold command.
- on_fold_start announces a fold that will actually run (pack count past gc.autoPackLimit), so
  a multi-minute first fold is not a silent hang.
- Test fixture disables auto-maintenance: each lazy fetch's detached gc otherwise raced the
  test and folded the packs itself.
2026-10-02 11:41:19 -07:00
ethernet
6880ffc1af fix(update): make the partial-clone pack fold and maintenance keys work together (#129712, #127711)
The two salvaged commits above disagree: #127721 persists gc.auto=0, which
turns the fold's `gc --auto` into a no-op (106 packs stay 106 on git 2.50.1,
2.53.0 and 2.55.0), and the fold itself is exposed to the loop #127721
guards against. Probed on all three gits against a blobless clone whose
commit-graph carries changed-path (Bloom) data:

- gc, `gc --auto`, `maintenance run --task=commit-graph` and
  fetch.writeCommitGraph each lazy-fetch the trees of every commit the graph
  has not seen (30 unseen commits: 30 fetches, 30 packs), and every lazy
  fetch spawns `git maintenance` again. gc.writeCommitGraph=false stops it
  (gc: 31 packs -> 1).
- Without a Bloom graph none of that happens, so the keys are a guard, not a
  fix for every partial clone.

Changes:
- keys: maintenance.auto, gc.writeCommitGraph, fetch.writeCommitGraph; gc.auto
  stays default. Idempotent (no rewrite when already set), `--local`, hidden
  console on Windows. install.sh and install.ps1 set the same three keys.
- consolidate_lazy_fetch_packs applies the keys before the fold, so installs
  that predate the installer change converge on their next update check.
- the fold runs under bounded_probe_run: subprocess.run(timeout=) kills only
  `git gc` and leaves `pack-objects` running (reproduced), and with
  GIT_NO_LAZY_FETCH.
- dropped the post-gc mark_unmarked_packs_promisor: gc keeps the .promisor
  marker on 2.50.1, 2.53.0 and 2.55.0.
- docs: updating.md troubleshooting entry.
2026-10-02 11:41:19 -07:00
teknium1
7e53b3ef82 chore(memory): remove bundled honcho provider; install from the plugin catalog 2026-10-02 04:39:28 -07:00
Superfly73
336b557bec fix(gateway): skip cron runs in their own restart-safe scope in the restart wait
A cron run handed to an external worker in its own transient systemd scope
outlives the gateway: the shutdown drain deliberately leaves it unmarked
(``mark_running_jobs_interrupted``) and its final send rides the durable
delivery queue, drained by whichever gateway is live next. The after-turn
restart wait still counted it as active work, so ``/restart``, SIGUSR1 and
``hermes update`` held the gateway in ``draining`` — refusing every new turn —
for up to ``agent.restart_after_turn_timeout`` (30 min by default) while the
worker kept running regardless. Observed in the field: a 100-minute job turned
a restart into a ~30-minute lockout.

- ``cron.scheduler`` records whether an accepted handoff got its own scope
  (``dispatch.mode == "scoped"``) and exposes
  ``get_restart_safe_external_job_ids()``; runs already reported as wedged are
  excluded there too, because the gateway subtracts both counts and a run in
  both sets would be subtracted twice.
- ``_awaitable_work_count()`` subtracts that count as a second, disjoint
  exclusion; ``_describe_active_work()`` marks the unit (``restart_safe``).
- A degraded worker (external, but no reachable user bus, so it shares the
  gateway cgroup) is NOT listed and is still awaited — the restart would kill
  it mid-run.
- The wait and observation log lines now name both exclusion classes instead of
  claiming everything still listed will be interrupted.

Tests: tests/gateway/test_restart_drain.py (skip + control),
tests/cron/test_restart_safe_worker.py (registry contract).
Docs: updating.md restart section, cli-config.yaml.example.

(cherry picked from commit 6a2f0b77cf07a60510430fc4ef8b75539ee422fb)
2026-10-01 22:25:21 -04:00
kshitijk4poor
596678d665 refactor(update): tighten the partial-clone heal after review
- _deepen_shallow_repo marks packs in its finally without a git config probe
  that could itself raise and turn a finished unshallow into False; markers
  are inert in a clone without a promisor remote.
- Installers warn instead of aborting the stage when a marker cannot be
  written, matching the Python helper.
- Docs: the manual commands follow HERMES_HOME, and the missing-object
  backfill feeds IDs through `fetch --stdin` so a large list cannot exceed
  the Windows command-line limit.
- Test and docstring comments describe the contract, not the incident.
2026-09-29 15:24:15 +05:30
kshitijk4poor
be475657ea docs(update): recover a partial clone stuck on the git pack-objects crash (#124272)
Manual marker one-liners for installs whose own updater cannot fetch the fix,
and the missing-object backfill for checkouts that were already turned into
full clones by hand.
2026-09-29 15:24:15 +05:30
kshitijk4poor
b52c73c3e4 fix(desktop): hermes update puts back an installed Hermes.app that went missing
hermes update only refreshed installed copies that still existed: once
/Applications/Hermes.app was gone (trashed, deleted by a tool, lost in a
migration) every later update skipped it silently, and Finder, the Dock
and Spotlight never found Hermes again. The ownership stamp that marks a
copy as this checkout's lives inside the bundle, so it left with it.

Record the installed copies the update keeps current in
desktop-installed-apps.json under the default Hermes root. When no owned
copy is left, a recorded path that is now missing still counts as
installed: the update rebuilds for it, reinstalls it and says so. A copy
the user moved to the other Applications folder keeps its stamp, so it
is found instead of doubled. The record is machine-wide, so Hermes' GUI
uninstall deletes it from any profile and an uninstalled app stays
uninstalled. Only the checkout that owns installed apps writes it; a
copy that failed to reinstall stays recorded so the next update retries.

The skip dates from 032cf8438f (#113933), which scoped the refresh to
existing bundles.
2026-09-29 02:37:33 +05:30
kshitijk4poor
e59df70a12 refactor(update): name the parked rollback once and reuse the test helpers
Review follow-up: compute the parked-branch case once in the rollback,
fold the baseline comments into one, reuse pull() and a new complete()
helper in the tests, and say in the docs that a rollback restores the
commit the checkout ran before the pull on every update path.
2026-09-28 16:00:01 +05:30
kshitijk4poor
9e8202f802 fix(update): never leave a failed branch rollback on broken code
Review follow-ups for the branch-switch rollback:

- If the parked branch cannot be checked out again (for example, another
  worktree holds it), restore its commit detached. Before this, the install
  stayed on the broken update branch, and the recovery hint repeated the
  failing command.
- Any `merge-base --is-ancestor` result other than "contained" keeps the
  strict baseline. rc 128 used to fall back to the lenient one, which
  contradicted its own comment.
- `_pull_updates` returns the baseline it verified, and
  `_apply_pulled_update` checks against it. Before, that function
  recomputed the baseline with a second rev-parse and merge-base against a
  hardcoded `origin/<branch>`, which a fork push in between could move.
- Drop a `commit_count == -1` check that could never be false.
- Update the rollback description in `updating.md`.
- Tests share the fixture `git` helper, a repo-init helper and a `pull()`
  wrapper instead of repeating those calls.
2026-09-28 16:00:01 +05:30
teknium1
1d287d5375 fix(browser): ship the Browser Use CLI engine in every install, Desktop included
The default browser_exec tool ran the `browser-use` CLI from a PM side
environment (browser-use==0.13.10 in <home>/environments/browser-use),
provisioned by the installers and `hermes update`. Sealed Desktop payloads
skip that step, so the Desktop app never had it and silently fell back to
the built-in tools; the side env was also per-profile and 225 MB.

The CLI's execution path is only `browser_harness.run.main()`; the
browser-use agent framework (anthropic/openai/google-api pins, 93 MB of
googleapiclient) is never imported. browser-harness itself is 2.6 MB of
pure Python whose pins (Pillow 12.3.0, websockets 15.0.1) already match
Hermes's own, so it becomes a core dependency and runs on sys.executable:

- pyproject/uv.lock: browser-harness==0.1.13 (+ cdp-use, fetch-use).
- _find_cli() returns [sys.executable, -m, browser_harness.run]; the child
  env points PYTHONPATH at the harness site dir (the Desktop store
  interpreter boots without a venv and the harness daemon re-runs
  sys.executable), replacing whatever the agent inherited.
- The side-env provisioning (install_cli, the update/installer step) goes.
2026-09-27 23:53:40 -07:00
teknium1
b63c138d78 fix: Hermes never falls back to the user's node/npm/npx/uv
Maintainer ruling: only Hermes and only its packaged package managers
(uv/pip/node/npm) are ever used; no PATH fallback when the managed tool is
missing, no "prefer the user's if new enough".

- hermes_constants.find_node_executable: node/npm/npx resolve to PM's
  installed copy or None. Every caller already pm.ensure()s on None, so a
  missing runtime is now provisioned instead of silently borrowing the
  user's Node (native-addon ABI / npm cache mismatches).
- agent/lsp/install._install_npm: pm.ensure('npm') when PM npm is absent,
  instead of failing over to whatever npm is on PATH.
- gateway._append_node_dir_for_service: stop baking the invoker's PATH node
  dir into generated systemd/launchd units.
- main_install_repair._resolve_node_runtime_npm: drop the PATH re-scan for
  another npm.
- source_build.source_product_current: run the freshness reader only with
  PM's node.
- doctor: Node/npm rows and npm audit use PM's copies (Termux APT distro
  keeps its system Node).
- install.sh ensure_uv / install.ps1 Get-Uv: always stage the pinned uv
  artifact; delete the "uv on PATH if new enough" developer shortcut.
2026-09-27 22:04:26 -07:00
teknium1
27062c3474 fix: install cua-driver and the Browser Use CLI by default again
Computer use and browser use are meant to work out of the box. The PM rewrite
(3d12e86ef1) and the MSIX installer rework (47f4ab3a17) dropped the
install-time cua-driver fetch (7060ac7bed) and the Browser Use CLI install
(baa6b2e34d). On a fresh install the computer_use check_fn therefore stayed
False, so the tool never reached the model and its lazy ensure could not fire,
and browser_exec quietly fell back to the built-in tools.

- cua-driver is a default PM package, so the installers, a bare
  `hermes pm install` and `hermes update` carry it on every target it builds
  for. Adds the missing Android gap (the lock has no bionic artifact).
- The shared default-tool step, which the installers (via source completion)
  and `hermes update` both run, provisions the Browser Use CLI for the default
  and explicit Browser Use backends. `--skip-browser` declines it along with
  agent-browser, and `off`/Camofox never use it.
- Installers regain --skip-computer-use / -SkipComputerUse (recorded as
  `--without cua-driver`).
- The update message stops calling every default "browser tools".
2026-09-27 19:18:58 -07:00
John Paul Soliva
c9ccdb967d fix(desktop): the skip notice names the in-app update, and hermes desktop reopens the app it kept
Review follow-ups on the Windows skip:

- Most people who hit the boot loop launched Desktop from the Start menu and
  never open a terminal. The in-app update also rebuilds the app: the
  Windows shim waits for Desktop to exit, and the already-up-to-date path
  still completes with desktop=True. The notice and updating.md now name
  Update now in Settings -> About next to `hermes desktop`.
- cmd_gui took the skip's None as "no launchable app was found". A build
  that fails raises instead of returning None, so None there only means the
  skip, and `hermes desktop` now reopens the app that was kept.

Tests fold to the two invariants, each red when its half of the fix is
reverted: the stop spares its ancestor Desktop on every platform, and a
Windows packaged build under its own Desktop is skipped (now driven through
the real _desktop_ancestor_in with a fake process tree, instead of a stub).
2026-09-26 23:50:01 -04:00
John Paul Soliva
8f338a845a docs(update): the Windows Desktop rebuild is skipped when the update runs inside the app 2026-09-26 23:50:01 -04:00
Brooklyn Nicholson
959c7649fd fix(updates/win): click-session poll, Intel-Mac installer docs, cua-driver opt-in autostart
click-session flake (#97982): a bare scrollIntoView() smooth scroll could be dropped under load, and the script
slept a fixed 3000ms before reading state. Extract click-session-helpers.mjs:
instant centered scroll, a bounded poll-for-composer loop instead of the
fixed sleep, and correct nested CDP envelope unwrapping (the old read logged
undefined).

Intel-Mac installer docs (#99033): the Hermes-Setup.dmg bootstrap installer
is built for Apple Silicon only, so Intel Macs hit "not supported on this
Mac". The desktop release pipeline already builds a native darwin-x64
bundle, so the docs now scope the arm64 limit to the bootstrap installer and
name the darwin-x64 bundle (or the CLI plus `hermes desktop`) as the Intel
path, in the desktop README and the platform-support build-targets section.

cua-driver autostart opt-in (#97389): Windows installs registered the
cua-driver-serve scheduled task on every install, with no opt-out, and
treated the task as an install-readiness requirement. Gate the
install-ready check and _repair_cua_driver_autostart_windows on the new
computer_use.autostart config key (default false = on-demand, fails
closed), extract the registration PowerShell into a testable helper, and
document the opt-in (EN + zh-Hans). Windows-only code path: unit tests
cover the registration args and the config gate; live Windows
verification pending.
2026-09-26 17:09:16 -05:00
ethernet
2ef41d2b58 Merge pull request #122103 from NousResearch/ethie/pm-evict-incompatible-plugins
fix(pm): hermes update disables plugins that no longer fit instead of failing
2026-09-25 00:08:10 -04:00
ethernet
4c89dbf880 docs(termux): mark the Termux package as currently broken
The APT package does not work at the moment and a fix is in progress.
Warn at the top of the install guide so users do not burn time on steps
that will fail.
2026-09-25 00:04:26 -04:00
ethernet
0978aca962 fix(pm): retry a plugin fetch failure once, then disable it
Sitting a plugin out after a fetch failure left the recorded stamp stale on
purpose, so every launch resynced, and the spawned-process guard in
prepare_launch raised "dependency sync left this install out of date".
One retry covers a blip; after that the plugin is disabled with the reason,
and hermes plugins enable restores it. Only requires_hermes still sits out,
which boot skips the same way.
2026-09-25 00:00:09 -04:00
ethernet
c500ee8771 fix(pm): disable a plugin only on evidence about the plugin
An update disabled any plugin that failed a trial build or its
requires_hermes check. Both can be about us, not the plugin: an untagged
source checkout reads as an older release (#122054), so requires_hermes
misjudges a fine plugin, and a download failure says nothing about the
plugin's code. Those now sit the plugin out of the build: config stays
untouched and it rejoins once the cause clears.

Disabling still happens on evidence about the plugin: requires-python vs
the pinned interpreter, manifest_version, an invalid declaration, a uv
resolution conflict, or its own build backend failing (new BuildFailure,
keyed on uv's 'The build backend returned an error').

enabled_member_dirs now skips a requires_hermes misfit instead of
raising. Boot's currency check raised on it before any sync could run, so
the launch path never reached the update sync. The loader skips such a
plugin anyway; admission still refuses enabling one.
2026-09-25 00:00:09 -04:00
ethernet
ccf631701a fix(pm): disable plugins that no longer fit instead of failing the update
An update resolves the enabled plugin union against the NEW core. A plugin
admitted against the old core can stop fitting when core moves (managed
Python 3.13 -> 3.14 vs a member's requires-python <3.14, a requires_hermes
upper bound, a bumped pin), and the whole update then died after the
source swap with a non-resolver InstallError whose 'retry' hint failed the
same way every time.

Update syncs now pass evict_incompatible_plugins=True (update completion,
historical takeover, launch-time completion, venv_sync, post-update
drift). PM screens statically first (requires-python vs the target
interpreter, manifest/requires_hermes), then, if the rest still fails,
builds core alone to prove the plugins are the cause and re-adds members
in config order, disabling each one that breaks the build. Misfits land in
plugins.disabled (memory.provider cleared) in every home that enables
them, published through the existing journaled change hook (the journal
now carries several configs), and are reported on stderr + receipt
warnings. Admission and ordinary syncs still refuse; only a core that
cannot build on its own fails an update.
2026-09-25 00:00:09 -04:00
ethernet
473fd1663c fix(desktop): update pre-probe source checkouts from main over git
A source checkout without hermes_cli/source_check.py predates release
channels, so the only line it can be on is git. The desktop treated the
missing probe as unsupported and parked the user on a manual
`hermes update --help` card ("This checkout predates desktop
source-channel checks"), so an older non-bundled install could never
update itself from the app.

Report such a checkout as tracking main with an update available and let
apply take the normal git handoff. That update brings in the probe, so
later checks resolve normally. A probe that exists but fails still throws.
2026-09-24 19:23:27 -04:00
ethernet
90963a6f74 install: --skip-browser / -SkipBrowser become PM's persisted opt-out
The flag used to exit 1 as retired. Now that PM installs the browser tools
by default, it maps to `pm.cli install --without agent-browser`, which later
installs and `hermes update` honour.
2026-09-24 17:27:45 -04:00
ethernet
15aced7a24 docs(install): installer status lines, install.log and verbose switches 2026-09-24 14:23:48 -04:00
ethernet
5c062d0cc3 docs: clarify Python updater range and test sandbox marker 2026-09-24 09:20:50 -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!
511b879c40 docs(update): document rescue refs for local commits on the target branch 2026-09-24 02:40:53 -05:00
ethernet
f91bd82286 fix: restore catalog Hindsight and harden installers and test guards 2026-09-24 02:03:59 -04:00
ethernet
9a8cdf96bf Merge origin/main into ethie/pm-clean
- tools/browser_tool_install.py: keep pm-clean's frozen old-updater stub; main's
  UTF-8 decode fix touched only the npx prefetch body it replaces.
- tests/hermes_cli/test_update_scoped_reconciliation.py: keep pm-clean's test
  subset (catch-up rides the PM completion owner) and take main's gateway-less
  host evidence (#120740): the updated seed that holds the host at a running
  gateway, and the two gateway-less matrices for the source change that merged
  cleanly into update_cmd_fleet.py.
2026-09-23 20:04:27 -04:00
Austin Pickett
d94769da64 fix(update): a Desktop-only host settles an inventory-less restart obligation (#120740)
* refactor(update): one predicate for serve rows outside the gateway matrix

The inventory branch of `_marker_only_restart_obsolete` inlined the rule for which
serve/dashboard rows the gateway matrix neither covers nor needs to (supervisor-owned,
or a manual serve handed to its own reminder). The inventory-less branch needs the same
rule for #118742, so it moves to `update_cmd_fleet_gatewayless.runtime_outside_gateway_evidence`
and both branches will read one definition. No behaviour change.

* fix(update): a Desktop-only host settles an inventory-less restart obligation

A host that runs no gateway (the Desktop app alone) can be left with an inventory-less
fleet-restart obligation: an updater that died before recording its inventory, or the
pre-inventory writer. With no owed set, the live gateway matrix is its only evidence, and
on that host the matrix is empty forever, so `_marker_only_restart_obsolete` never settled
and every later `hermes update` exited 1 with "gateways are still off the checkout code"
(#118742).

An empty fleet alone cannot tell that host from one whose gateway the dying update stopped,
so the inventory-less branch now asks the live host, never a historical receipt:
`host_owes_no_gateway_restart` settles only when no profile's `gateway_state.json` claims a
state other than stopped/startup_failed (a gateway that went away without a clean stop keeps
the obligation) and every live runtime sits outside the gateway matrix
(`runtime_outside_gateway_evidence`, shared with the inventory branch). HEAD must still
contain the pulled SHA (`checkout_contains`, same rule as #119367). Probe failures keep it.

Tests: the scoped-reconciliation matrix now holds its host at "the update stopped a gateway"
so it keeps pinning receipt independence; a new host-evidence matrix covers Desktop-only,
clean stop, carried commit, stopped gateway in a named profile, unclassified and
unidentified serves, a gateway row without fleet identity, and a diverged checkout. Two
manual-serve tests that assumed an empty fleet always stays pending now stub the live host
and assert the manual reminder survives the gateway obligation settling.

Co-authored-by: KoNit-K <124019182+KoNit-K@users.noreply.github.com>

---------

Co-authored-by: KoNit-K <124019182+KoNit-K@users.noreply.github.com>
2026-09-23 18:48:33 -05:00