Commit Graph

50904 Commits

Author SHA1 Message Date
Oliver Hees
0b319a33d4 Update hermes-video-editor catalog entry to 0.3.0 2026-10-09 00:38:51 -07:00
Oliver Hees
a319bff3aa Add hermes-video-editor to the plugin catalog 2026-10-09 00:38:51 -07:00
teknium1
81134931d6 chore(catalog): merge-side review chores for quota (sweep 1009) 2026-10-09 00:36:28 -07:00
Ronald Reis
62e12200ac catalog(quota): re-pin to v2.10.0 with disclosure 2026-10-09 00:36:28 -07:00
Ronald Reis
2b07e6b714 catalog(quota): re-pin to v2.8.2 2026-10-09 00:36:28 -07:00
teknium1
9dfe5ab153 chore(catalog): merge-side review chores for codex-account-switch (sweep 1009) 2026-10-09 00:35:56 -07:00
Smelter Labs
6c5de1bc33 catalog: codex-account-switch 1.2.2 (sha 1d42d4cb; renewal disclosure in the description) 2026-10-09 00:35:56 -07:00
Smelter Labs
866d62c9f9 chore(plugin-catalog): pin codex-account-switch 1.2.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:35:56 -07:00
Smelter Labs
6d1a7f42fe feat(plugin-catalog): add codex-account-switch 2026-10-09 00:35:56 -07:00
teknium1
14ec243c17 chore(catalog): merge-side review chores for blender (sweep 1009) 2026-10-09 00:22:29 -07:00
Siddharth Balyan
de4acb1ff2 chore(catalog): pin the Blender plugin to 0.2.0, which declares Blender
Re-lands #129296, which merged into onboarding/next and never reached main. 0.2.0 declares the Blender app, so Hermes checks Blender is installed (5.1+ on Windows and macOS) before it installs the plugin, and the onboarding plugins card can preselect Blender when it is present.
2026-10-09 00:22:29 -07:00
Teknium
1744a19e0d ci(desktop): re-smoke an already-built package without rebuilding it
The native smoke only ran inside desktop-bundled-release, and it checked out
its test scripts at the same commit as the artifact it fetched. Changing a
test therefore meant a new commit, and a new commit meant a full signed
build (about an hour per platform) just to rerun the smoke.

Add workflow_dispatch: `sha` picks the staged artifact to fetch, and the
scripts come from the ref the dispatch runs on. Release callers are
unchanged.
2026-10-08 20:20:55 -07:00
Teknium
f470bf3a32 test(desktop): the MSIX plugin smoke runs with a user's environment, not the CI driver's
The smoke's CLI inherited the driver's HERMES_HOME / HERMES_RUNTIME_DIR /
HERMES_PYTHON, so PM built the plugin's environment on the driver's own
Python under C:\a\_temp\setup-pm and never touched the packaged
interpreter: the bug could not show. Clear HERMES_*/UV_*/PYTHON* and the
driver's tool dirs from PATH in the basic-user batch, and fail if the
install log still names the driver's PM home.
2026-10-08 20:20:55 -07:00
Teknium
eb699a158c fix(pm): MSIX plugin installs build on a writable copy of the bundled Python (#135236)
Enabling a plugin with Python dependencies, and `hermes pm install`, failed
in the packaged Windows app: uv lock/sync/pip check exit 101 with "Unable to
create process using ...\venv\Scripts\python.exe".

uv queries a venv by running its Scripts\python.exe. That redirector lives
outside the package and CreateProcess's the interpreter named by
pyvenv.cfg `home`. 6a031bf1b3 moved generations onto the packaged
interpreter, so `home` names a file under WindowsApps that a process without
package identity may not start (exit 101 is the redirector's
RC_CREATE_PROCESS). Restore the verified writable copy of the pinned Python
in the user store as the build interpreter for sealed Windows payloads.

venv_command (also from 6a031bf1b3) stays: Hermes still enters those venvs
on the packaged interpreter, so its own children keep package access to the
bundled git, rg and PM worker.
2026-10-08 20:20:55 -07:00
Teknium
cd522d80a2 test(desktop): the Windows MSIX smoke installs a plugin with Python deps as a basic user
The installed-package smoke only chatted, so nothing exercised PM building
a dependency generation inside the package. After the chat it now installs
a local plugin that declares a Python dependency through the package's CLI
execution alias with --enable --yes-deps and requires `plugins list` to show
it enabled.

It runs under a Safer NORMALUSER token at medium integrity: GitHub runners
are administrators, and Administrators can start files in a package
directory without package identity, which a normal user cannot. A probe
that the token is refused the packaged python.exe keeps the check honest.
2026-10-08 20:20:55 -07:00
Teknium
3b0dc776b6 fix(tests): the test runner kills detached processes a test leaves behind
`_kill_tree` SIGKILLs the per-file pytest process group, which misses any
child that left the group with `start_new_session=True` (an auto-started
`gateway run`, a setsid server). Those are reparented to init and run
forever: one host accumulated 215 orphan gateways (~52 GB RSS, up to 21 h
old), and every `hermes update` listed each as "another Hermes home — left
alone".

After every attempt the runner now SIGKILLs any process whose environment
still points into that attempt's unique temp root (PYTEST_DEBUG_TEMPROOT /
TMPDIR, or a HOME / HERMES_HOME a fixture created under it). The root is
unique per attempt, so the match names only that attempt's processes.
Linux-only (/proc); the process-group kill stays the fallback elsewhere.
2026-10-08 19:48:33 -07:00
Teknium
ffcd3621a9 ci: E2E suites run only on release builds
Pull requests and pushes to main no longer run any E2E suite: ci.yaml's detect gate
forces every e2e* lane off unless the run is a stable-release run (inputs.release) or a
manual dispatch, whatever the diff, the update-path classifier or the run-e2e label
selects. That covers the Python e2e and e2e-upgrade jobs, the Windows E2E and Windows
install + update journey in tests-os, and the Desktop core/update suites.

install-e2e.yml drops its pull_request (path-filtered `pr` route) and 12-hourly
schedule triggers; it runs from the stable-release gate (workflow_call) or by dispatch.
live-providers drops its nightly schedule and keeps the release-tag push + dispatch.

Why: a one-row change to left_core_migration.py held a PR for 40 minutes on the
Windows install + update E2E; the suites cost most of the larger-runner minutes and
the release gate already runs all of them strictly.
2026-10-08 19:48:31 -07:00
ethernet
908e4a4b44 Merge pull request #135242 from NousResearch/feat/onboarding-connectors-scroll-fade
Onboarding apps list fades at the edges when it scrolls
2026-10-08 22:17:34 -04:00
ethernet
94b1b6d422 refactor(desktop): drive FadeScroll's edge fade with scroll-driven CSS
FadeScroll tracked clipped edges in React state, with a scroll handler and a
resize observer, then rebuilt an inline mask on every change. Move that to a
`scroll-fade-y` utility: the mask's stops are scaled by two registered
<number> properties that the scroller's own scroll position animates
(animation-timeline: scroll(self y)), so an edge only fades while content is
clipped behind it and a list that fits is never dimmed. No listener, no state;
a browser without scroll-driven animations shows no fade.

From each edge inward the mask is `pad` of fully hidden content, then `fade`
of gradient, both optional props. The onboarding apps list sets pad to 1.5rem
so the gradient starts at the chips instead of spending itself on the empty
padding at the box edge. edgeMask/scrollEdges stay for the profile switcher.
2026-10-08 22:10:44 -04:00
ethernet
9809922e04 feat(desktop): fade top/bottom of the onboarding connectors list
The connector/plugin chip picker is height-capped, so the last rows were
clipped with no hint there was more. Wrap the grid in the existing
FadeScroll, which already fades whichever edge has content clipped behind
it and leaves a list that fits untouched.
2026-10-08 22:09:38 -04:00
Teknium
71704ffb49 refactor: move Spotify out of core into the spotify catalog plugin
The spotify toolset (plugins/spotify) and its loopback-PKCE login (hermes_cli/auth_spotify.py)
now live in NousResearch/hermes-spotify, installed from the plugin catalog. Same pattern as the
Home Assistant move (fb9fc8a3dd): a LEFT_CORE row installs the plugin for every home that was
using it (a Spotify login in auth.json, or the spotify toolset in platform_toolsets) on
`hermes update` / first start; the toolset name, tool names, schemas, env vars and stored login
(providers.spotify) are unchanged, so nobody re-authenticates.

The one user-visible change is the login command: plugins cannot extend `hermes auth`, so the
plugin registers `hermes spotify login|status|logout`. LeftCoreFeature gains a `cli` field and
moved_command_hint() turns `hermes auth [status|logout] spotify`, and `hermes spotify` while the
plugin is not loaded, into a line naming the new command and the install/enable step for this
home. The dashboard's "needs login" hint names the new command too.

Core drops the Spotify auth constants/dispatch, the CONFIGURABLE_TOOLSETS row and post-setup
step, the core `spotify` entries in the shared-metrics toolset enums, its tests, and the docs
(en + zh-Hans) now point at `hermes plugins install spotify` / `hermes spotify login`.
2026-10-08 19:00:45 -07:00
Teknium
358ec68b90 Plugin catalog: spotify-desktop notes the Spotify plugin move
Its description named core's `hermes auth spotify`; the pinned sha imports core's
plugins.spotify.client, which the Spotify core move removes. A known_issues line points
at the upstream fix (cruzlxyz/hermes-spotify-desktop#2) until the pin can move to it.
2026-10-08 19:00:21 -07:00
Teknium
aad3fc74e2 Plugin catalog: spotify (official, NousResearch) 2026-10-08 19:00:21 -07:00
Teknium
1e0c7730d7 refactor(gateway): service-definition guards live in gateway_service_owner
Moves the temp-home guard (`temp_home_in_service_definition`,
`refuse_temp_home_service_write`) and the systemd install's ownership
resolution (`unit_belongs_to_caller`) out of the gateway.py facade into
the owner sibling, so the facade shrinks instead of growing. Narrows the
plist read's catch-all to the errors plistlib raises. Temp-home tests
move with their code into test_gateway_service_owner.py.
2026-10-08 17:06:02 -07:00
Teknium
7e17bab684 docs(gateway): a service definition belongs to the home it pins 2026-10-08 17:06:02 -07:00
Teknium
245ce8e0ff refactor(gateway): keep the service-owner fix to the writers
Trims the salvaged ownership guard to the part that closes the bug: a
refresh or install never rewrites a unit/plist that pins another
HERMES_HOME (--force-unit-path is the explicit repoint).

Dropped the new `gateway install` admission gate for homes outside the
platform-default tree. It refused every custom HERMES_HOME (Docker's
/opt/data, any non-default root) on first install unless the user knew
the new flag, which is a behaviour flip for legitimate installs; it also
needed a bypass monkeypatch in five unrelated test files. Tests cut to
the two invariants (foreign home refused, own stale unit refreshed).
2026-10-08 17:06:02 -07:00
ang-fleet-workers[bot]
b6ab666b6d fix(gateway): a service definition is written only by the home it pins
Two homes can resolve to one gateway service name: `<root>/profiles/<name>`
and `~/.hermes/profiles/<name>` both take `hermes-gateway-<name>`
(`ai.hermes.gateway-<name>` on launchd). Every gateway boot, start and
restart calls `refresh_systemd_unit_if_needed` / `refresh_launchd_plist_if_needed`,
which rewrote whatever definition sat at that name with no check of the
HERMES_HOME it pins. A scratch/E2E gateway started from a custom root
therefore repointed the real install's unit at the scratch directory; the
real gateway failed on its next restart (200/CHDIR once the scratch dir was
cleaned up). `systemd_install --force` and `launchd_install --force` did
the same. Uninstall already refused a unit pinning another home
(`_systemd_unit_belongs_to_current_home`); the writers did not.

- hermes_cli/gateway_service_owner.py (new sibling): `pinned_home()` reads
  the HERMES_HOME a unit or plist pins; `definition_belongs_to_home()`
  refuses a write from any other home; `refuse_foreign_home_install()`
  gates `gateway install` for a home outside the platform-default tree.
- Writers refuse a definition pinned to another home: the systemd and
  launchd refreshes (boot/start/restart) and both installers, `--force`
  included. The system-scope check runs on the caller's own home, before
  `systemd_unit_is_current` adopts the unit's home into os.environ (else
  the check compares the unit with itself); a sudo-stripped caller with no
  HERMES_HOME still acts for the unit's pinned home, as before.
- `gateway install` refuses a HERMES_HOME outside the account's Hermes
  tree that owns no bare service name and has no service installed yet: a
  service registered for a scratch home outlives the run.
- New `gateway install --force-unit-path` overrides both refusals (an
  explicit repoint of the definition at this home).
- Refused installs exit 1, so `gateway install` no longer goes on to start
  the other home's service; the setup wizard reports the refusal and
  continues, as ensure_gateway_service already did.
- Existing tests whose sandbox HERMES_HOME is a tmp dir opt in to the
  admission check (5 files, one monkeypatch each).

Tests: tests/hermes_cli/test_gateway_service_owner.py, 18 tests. On
e36a818033 (sources reverted, tests kept): 11 failed, 7 passed (foreign
unit/plist rewritten on refresh and --force, refused install starts the
service, scratch install proceeds, no --force-unit-path). With this change:
18 passed. test_colliding_profile_names_share_a_unit pins the premise on
the real name resolution.
2026-10-08 17:06:02 -07:00
ethernet
8ac5c74432 Harder linter fixes (#135281)
* ci: fix B004 linter rule

* ci: prefix unused variables with underscore (RUF059)

* ci: don't cast int to int

* ci: remove unnecessary if/else where both blocks yield same result

* ci: RUF024

* ci: fix RUF017, RUF018, RUF022

* ci: RUF013, RUF015

* ci: RUF007

* ci: RUF009

* ci: address code review
2026-10-08 18:56:50 -04:00
teknium1
28af0872b8 chore(plugin-catalog): delist antigravity-oauth-plus (rule 16, one listing per lineage)
The entry declares itself derived from antigravity-subscription-directsdk and
antigravity-oauth, both listed. Its additions (live model discovery, quota
caching, a reset dock) run on the same agy transport, so under rule 16 they go
upstream rather than into a competing listing. Plain delist: no removed.yaml
entry, existing installs keep working. Author notified:
taylorwtf/hermes-antigravity-oauth-plus#1
2026-10-08 15:47:05 -07:00
Teknium
9d05e7ff92 test(desktop): model settings tests clear their toast timers before jsdom tears down
The main-model apply test raises a success toast, which arms a 5 s
auto-dismiss timer. Nothing cleared it, so when it fired after the file's
jsdom window was gone, vitest reported "window is not defined" from
dismissNotification as an unhandled error and failed JS & TS checks with
every test green. afterEach now calls clearNotifications().
2026-10-08 15:20:58 -07:00
kshitijk4poor
d2cbfcf7db test(desktop): the React Compiler cache is its own module with focused tests
The cache lived inline in vite.config.ts, and the renderer builder tests use a bare fixture
config, so nothing exercised it: a broken warm path would still pass every test.

Move it to scripts/compiler-cache.mjs (behaviour unchanged; toolchain files and cache root are
now parameters, and an existing closeBundle hook is chained instead of replaced) and test hit
vs fresh output, toolchain/NODE_ENV generations, corrupt entries, pruning, incremental (watch)
rebuilds and the dev-server bypass. A cold and warm production build of the real app stay
byte-identical. Reported by Greptile on #135092.
2026-10-08 15:20:58 -07:00
kshitijk4poor
1f6d8b6ba8 fix(desktop): a node-pty layout without its macOS spawn-helper is an incomplete native tree
nativeTreeComplete decides whether a later build may skip restaging, but never looked at
node-pty. A partially extracted package with pty.node and no spawn-helper was recorded
complete; repairing the installed package without a lockfile change then left the staged
copy broken, and every later build shipped a terminal that fails on its first spawn.

Check the layout node-pty itself loads (build/Release, build/Debug, then prebuilds, as in
lib/utils.js) for a binding, and on macOS for the spawn-helper beside it (lib/unixTerminal.js).
Reported by Greptile on #135092.
2026-10-08 15:20:58 -07:00
kshitijk4poor
bc0758c393 fix(desktop): a renderer built under other VITE_*/NODE_ENV settings is never reused
Vite inlines every VITE_* variable into the renderer and NODE_ENV selects its mode, but the
desktop receipt recorded only sources and prepared inputs. After `VITE_PERF_PROBE=1 npm run
build`, the next plain build reused (and productCurrent certified) the renderer that still
carries the perf probe; a NODE_ENV change had the same gap.

The desktop build inputs now record the renderer environment (VITE_* plus NODE_ENV, unset
read as vite's production default), so both rendererCurrent and productCurrent require it to
match. Reported by Greptile on #135092.
2026-10-08 15:20:58 -07:00
kshitijk4poor
27e0f189ac fix(desktop): the React Compiler cache serves production builds only, keyed by NODE_ENV
The compiler emits HMR cache-reset code when NODE_ENV is development, and the cache key ignored it, so a dev-server session could seed entries a later production build reused. A dev server also wrote one entry per save with no build to prune them. The cache now wraps only 'vite build', NODE_ENV joins the toolchain key, and entries drop the map's sourcesContent (the module text the key already hashes; restored on a hit): 22 MB -> 15 MB. Verified: warm == cold byte-identical without sourcemaps; with --sourcemap two cold builds already differ in one map's __VITE_ASSET__ placeholder order, so that diff is vite's, not the cache's.
2026-10-08 15:20:58 -07:00
kshitijk4poor
3fc37481eb refactor(desktop): one receipt reader, one output list, no native double copy on renderer reuse
rendererCurrent and productCurrent share hostReceipt() for the schema/product/host checks. The stamped outputs come from bundleElectronMain's own electronOutputs list, so a renamed or added bundle can never be copied stale from the previous dist. The reuse copy skips node_modules/ and native/, which copyNativeTree recopies from nativeDeps right after, and a --typecheck build never reuses a renderer that skipped tsc.
2026-10-08 15:20:58 -07:00
kshitijk4poor
c3b06d1443 refactor(update): trust the npm closure through pm.ensure(verify=False)
The previous commit pre-checked the closure with a hand-rolled walk + is_installed before ensure, which repeated ensure's own check and left a window between them. pm.ensure already has the knob: verify=False trusts the recorded digest (as startup does) and an explicit ensure still installs a missing tool. Thread it through pm.client and source_build_env; the old comment claimed a verification that the interrupted-update finish path never ran.
2026-10-08 15:20:58 -07:00
kshitijk4poor
dcabf76310 fix(install): a pinned fresh clone pins, with retries, before it publishes
With --no-checkout the file download moved out of the retried clone into the single unretried pin checkout, which ran after the empty clone was already published: a throttled pin left INSTALL_DIR with no index, and the next rerun stashed a 'delete every file' autostash. The pin now runs in the staged tree inside the deferred-checkout retry loop (which also stops materializing the tip first), and nothing is published until it succeeds.
2026-10-08 15:20:58 -07:00
kshitijk4poor
136a4282bb fix(desktop): an incomplete native tree restages on every build
Skipping stage-native-deps on an admitted receipt kept a tree staged without a piece forever:
get-windows (or its win32 x64 binding, which only the native installer inside staging can fetch),
the macOS get-windows helper, or the host HUD helper (no X11 toolchain on Linux). The receipt
identity cannot see the host fix that would complete it.

The receipt now records `degraded` from the staged tree itself (nativeTreeComplete: every piece
this host can produce for the target is present), and build.mjs reuses only a complete one
(nativeInputsComplete). Gaps no host fix closes — win32-arm64 has no get-windows prebuild, a
cross-target pack never builds the helpers — do not count. Judging the tree rather than each
builder's return value covers soft-fail paths that still return a path (the win32 binding).
2026-10-08 15:20:58 -07:00
kshitijk4poor
7ec7857b0b perf(update): the product tail trusts the npm closure the PM step just verified 2026-10-08 15:20:58 -07:00
kshitijk4poor
2f6716dd7d perf(install): a pinned install checks out only the pin, not the branch tip first 2026-10-08 15:20:58 -07:00
kshitijk4poor
97260490e1 perf(desktop): skip restaging native inputs whose receipt still matches 2026-10-08 15:20:58 -07:00
kshitijk4poor
6232135eca perf(desktop): cache the React Compiler pass by content, so an update recompiles only the files it changed 2026-10-08 15:20:58 -07:00
kshitijk4poor
1d41d9f610 fix(desktop): native helpers build byte-identical, so the prepared native input stays comparable 2026-10-08 15:20:58 -07:00
kshitijk4poor
eeb8526569 test(desktop): a stamp-only rebuild reuses the renderer; tampered output or a source change recompiles 2026-10-08 15:20:58 -07:00
kshitijk4poor
1a4d7768c5 perf(desktop): reuse the compiled renderer when only the install stamp changed 2026-10-08 15:20:58 -07:00
teknium1
05df0382a5 fix(plugins): re-enabling a plugin restores its toolset in a saved platform_toolsets list
The Desktop/TUI/dashboard toggle edited the saved list before the plugin
loaded, so a backend that scanned while the plugin was off found no toolset
key and left the toolset out: the plugin showed enabled with its tools off.
Enable now edits the list after activation; disable resolves the key before
the disable is written. hermes plugins enable/disable make the same edit.
Only saved lists are touched; a fresh config still gets no list.
2026-10-08 15:15:47 -07:00
Teknium
dbbf98371d perf(install): a pinned install.ps1 clone checks out only the pin, before it publishes
Mirrors the install.sh change from #135092 on Windows. With -Commit (what
the desktop bootstrap passes), the fresh clone runs with --no-checkout and
the pin is checked out with retries inside the staging dir, so the branch
tip is never materialized first and a refused or failed pin never publishes
a checkout. A rerun over an existing checkout keeps pinning after the fetch.
2026-10-08 15:15:33 -07:00
ethernet
51609d827b fix(solstice): load the transport on first use, not at discovery (#134233)
Provider discovery imports every bundled plugin's __init__, and it runs in
interpreters without the app's HTTP stack: PM's runtime calls
lazy_installs_allowed() -> hermes_cli.config, whose import-time
_inject_profile_env_vars() lists providers. solstice/__init__ imported
transport.py at the top, which pulls agent.gemini_native_adapter -> httpx,
so every install logged "Failed to load bundled provider plugin solstice:
No module named 'httpx'" and solstice went unregistered in that process.

INFERENCE_BASE_URL now lives in __init__ and SolsticeClient is imported
inside create_client, matching the other provider plugins.
2026-10-08 18:06:07 -04:00
ethernet
3d7f1090d4 fix(desktop): build-bundle.ps1 parses the claim timestamp and rejects a missing -Python
The tagger-line lookup indexed into a single string, so the epoch was the
character 't'. A -Python path that does not exist left LASTEXITCODE at 0, so the
script printed done and listed a previous build's package.
2026-10-08 18:02:26 -04:00
ethernet
aac3440bac fix(desktop): build-bundle cleans under the lock, restores the shell, never signs
Review findings on the local build scripts:

- Cleanup ran before desktop.py took the checkout lock, so a second run deleted
  the outputs of a build that was still going. desktop.py gains --clean, which
  removes this checkout's outputs after build_lock is held. Both scripts pass it
  instead of deleting files themselves.
- build-bundle.ps1 left RELEASE_CLAIM_* and HERMES_PAYLOAD_* in the caller's
  session. It now saves those variables and restores them in the finally block.
- The scripts said "unsigned" but inherited signing credentials. The ps1 removes
  AZURE_* for the build. The sh removes CSC_* and APPLE_* and sets
  CSC_IDENTITY_AUTO_DISCOVERY=false, the switch the desktop rebuild command uses.
  The wording now says what is ignored.
- The Store bundle step needs the claim timestamp, and the script deletes the
  claim. The ps1 prints it as HERMES_RELEASE_EPOCH, and BUILDING.md documents it.
2026-10-08 18:02:26 -04:00