Commit Graph

390 Commits

Author SHA1 Message Date
ethernet
3aa215fdc9 build: remove leftover references to the dropped hindsight extra
The extra removal left CI, the Docker image and the nix package still
requesting `hindsight`. Once the extra is gone, `--extra hindsight` and
extraDependencyGroups = [ "hindsight" ] ask for something that no longer
exists. Drop them the same way 73c598e319 originally did: remove it from
the CI extras lists, the Docker sealed-venv build and the nix default
groups, and point the nix examples/check at honcho. Also remove the
stray blank line left in the exclude-newer table.
2026-09-24 22:15:15 -04:00
fangliquan
285768fdfb fix(pm): drop stale Hindsight extra
Merge 27df3b8847 brought back the hermes-agent[hindsight] extra
(hindsight-client==0.6.1) that 73c598e319 removed. The catalog
Hindsight plugin now requires hindsight-client>=0.10.1,<1, so any
workspace containing it fails `uv lock`. Remove the extra, its
exclude-newer entry and its legacy-takeover mapping, and regenerate
uv.lock.

The workspace-member rename from the original PR is dropped here;
#122098 landed that half.

Salvaged from #122092.
2026-09-24 22:12:34 -04:00
ethernet
ccc9d422ed fix(tts): install kittentts on python 3.14, opt-in only
KittenTTS 0.8.1 requires misaki[en]>=0.9.4. PyPI's misaki 0.9.4 caps
Python below 3.13. NousResearch/misaki f03fd2be73 is upstream main with
the cap raised to <3.15 and no code change, so the extra declares
misaki[en] at that commit and uv resolves KittenTTS's requirement to it.

misaki[en] also lists spacy-curated-transformers. The newest release
that fits spacy 3.8, 0.3.1, caps Python below 3.14, so PM's `uv pip
check` rejected every 3.14 venv that carried it. Nothing imports it:
only transformer spaCy models use it, and KittenTTS never builds a
misaki G2P. A never-true override marker removes it from the lock.
torch, transformers, spaCy and num2words stay: misaki.en imports them
at module level and KittenTTS imports misaki.en.

Native bundles sync with --all-extras, so un-gating the extra would put
KittenTTS, torch and spaCy in every payload. [tool.hermes] opt-in-extras
names the extras that only an explicit selection installs. PM adds
--no-extra for each of them to an all-extras sync, so bundles and their
recorded feature list leave kittentts out, and setup still installs it
through sync_venv when the user picks it.

The extra is gated on platform instead of Python: onnxruntime has no
Intel macOS wheel and torch has no Windows ARM64 wheel. Nix gets the
hatchling build backend the misaki git source needs.

Verified on macOS arm64 with CPython 3.14.7 and uv 0.12.3:
uv sync --frozen --extra kittentts from this lock, then uv pip check
reports all 126 packages compatible. _generate_kittentts writes a
3.87 s 24 kHz WAV (RMS 3661), and no spaCy model is downloaded. A dry-run
all-extras sync with --no-extra kittentts selects none of kittentts,
misaki, torch, transformers or spaCy. The new build test fails without
the environment change and passes with it.
2026-09-24 16:17:35 -04:00
ethernet
99768fd127 feat(pm): hermes pm lock relocks uv.lock after a pyproject edit
Contributors had to run `python -m pm.build_env --source . --lock-only`, then
re-source activate, then call sync_venv for opt-in extras, while `hermes pm
lock` only pinned tool artifacts in pm/lock.json. Now `hermes pm lock` with no
arguments is the one step: it runs the same PM check_project_lock /
lock_project operations as build_env's --check-lock / --lock-only (same
exclude-newer quarantine), writes nothing when the lock is current, changes
no environment, and prints the activate command to run next. Activation
syncs [all] plus recorded extras only, and --test-extras replaces the
default, so a newly added extra outside [all] gets
`source ./activate --test-extras all,NAME` (`-TestExtras` on PowerShell).

`hermes pm lock --bump NAME VERSION` keeps writing pm/lock.json; NAME and
VERSION are now only accepted together. build_env --lock-only is unchanged
for scripts. Contributor docs, AGENTS.md, CONTRIBUTING.es.md, the pyproject
comment, and the uv.lock CI remediation text point at `hermes pm lock`.
2026-09-24 15:55:08 -04:00
ethernet
eedbddbeb1 Revert "fix(tts): install kittentts on python 3.14 via the misaki fork"
This reverts commit 694fe220a4.

misaki[en] also requires spacy-curated-transformers. With spacy 3.8
(thinc 8.3) only its 0.3.x line resolves, and 0.3.1 declares
requires-python <3.14. uv's lock ignores that upper bound, but PM's
dependency validation rejects it, so the win32-x64 bundle failed with
"spacy-curated-transformers requires Python >=3.9, <3.14". Restore the
<3.13 gate until misaki's English stack installs cleanly on 3.14.
2026-09-24 15:19:06 -04:00
ethernet
694fe220a4 fix(tts): install kittentts on python 3.14 via the misaki fork
KittenTTS 0.8.1 requires misaki[en]>=0.9.4, and PyPI's misaki 0.9.4
declares requires-python <3.13, so the extra was gated off the managed
3.14 interpreter. NousResearch/misaki f03fd2be73 is upstream main
(0.9.4 + "Enable Python 3.13") with the cap raised to <3.15 and no code
change. The extra now declares misaki[en] at that commit directly, so
uv resolves KittenTTS's transitive requirement to the fork. A
[tool.uv.sources] entry would not reach a transitive dependency, and
an override would bypass pip installs of the extra.

The extra is gated on platform instead of Python: onnxruntime has no
Intel macOS wheel and torch (pulled by misaki[en]) has no Windows ARM64
wheel, the same gate piper already carries.

Nix: uv.lock records no build backend for the git source, so
buildSystemOverrides supplies hatchling for misaki. Without it the
opt-in build fails with "No module named 'hatchling'". The default
package derivation is unchanged; kittentts is in no Nix bundle.

Verified on macOS arm64 with CPython 3.14.7: uv sync --frozen --extra
kittentts from this lock installs misaki from the fork commit, and
tools.tts_tool_local._generate_kittentts writes a 3.87 s 24 kHz WAV.
2026-09-24 14:47:06 -04:00
ethernet
e0cca41772 fix(tls): pin truststore until explicit-CA path has a public API 2026-09-24 09:36:18 -04:00
ethernet
7f05c3e020 fix: prepare Anthropic SDK in provider catalog test environment 2026-09-24 09:05:04 -04:00
ethernet
29c56a27d4 fix: align CI tests and workflows with PM build contracts 2026-09-24 02:03:59 -04:00
ethernet
dcd2bca06a fix(desktop): render icons with core runtime dependencies 2026-09-24 01:30:15 -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
bac905227e test: secrets-gated LIVE provider canary suite behind a new live marker
Classes: C9 provider wire-format drift, C17 prompt-cache hits, C11 real
auth / credential routing / `/models` parse.

Mocks encode our own belief about each vendor's wire format; vendor-side
schema changes, reasoning-replay rules, streaming shape changes and cache
behaviour are only observable against the real APIs. This drives the REAL
AIAgent + real adapters (chat_completions, anthropic_messages via OpenRouter,
codex_responses for xAI/OpenAI, GeminiNativeClient) in a temp HERMES_HOME
through a scripted 3-turn conversation with a deterministic registered tool:

  turn 1  one forced tool call; valid JSON args; result round-trips
  turn 2  two tool calls in ONE assistant message; both results round-trip
  turn 3  no tools; answer from replayed history (tool results + reasoning
          replay) under a cache breakpoint

Invariants per turn: no 4xx on any agent-loop request (429 excepted), the
turn completes, no <think>/DSML/control-token/raw tool JSON in user text,
the credential resolved from env is the one sent and ONLY to the provider's
host, tool results are persisted to state.db. Anthropic-family routes also
assert 1..4 well-formed breakpoints (none on role:tool / inside
tool_result.content[]) and cache-read tokens > 0 on turn 3 (warm once).
A `/models` listing check per provider uses the live fetchers without the
curated fallback (Gemini is strict-xfail on open #62259).

Spend: cheap models, <= 3 turns, max_tokens 2048, max_iterations 6, a hard
per-test token and $ guard; usage + estimated $ printed per test and
appended to $HERMES_LIVE_USAGE_FILE. Every case skips cleanly without its
key. `live` is excluded by default addopts; select with `-m live`.

(cherry picked from commit 3bd7de6991bcb03bfe5d11945de6f2317c534b7d)
2026-09-23 17:55:23 -07:00
ethernet
babbec1c4c feat(pm): isolate developer test environment from runtime extras 2026-09-23 18:13:38 -04:00
ethernet
16652eea18 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	gateway/config.py
#	gateway/config_loader.py
#	gateway/readiness.py
#	hermes_cli/managed_scope.py
#	hermes_cli/plugin_python_deps.py
#	hermes_cli/plugins_cmd.py
#	hermes_cli/update_cmd_maint.py
#	plugin-catalog/hindsight.yaml
#	plugins/plugin_loader.py
#	providers/__init__.py
#	scripts/run_tests.sh
#	tests/gateway/test_control_socket_windows_live.py
#	tests/gateway/test_gateway_streaming_nested_config.py
#	tests/hermes_cli/test_doctor.py
#	tests/hermes_cli/test_plan_reconciliation_windows_live.py
#	tests/hermes_cli/test_update_apply_shallow_count.py
#	tests/hermes_cli/test_update_concurrent_quarantine.py
#	tests/hermes_cli/test_update_shim_self_lock.py
#	tests/hermes_cli/test_verify_console_scripts.py
#	tests/tools/test_lazy_deps.py
#	tests/tui_gateway/test_subprocess_encoding.py
#	tools/lazy_deps.py
2026-09-23 15:26:34 -04:00
teknium1
2fb564d2df fix(cli): move the hermes-agent argv layer into agent/legacy_cli
Follow-up to the salvaged wrapper: the argument layer now lives in a topical
sibling (agent/legacy_cli.py) instead of growing the run_agent facade, and
`python run_agent.py` (the installer's PATH launcher for hermes-agent) routes
through the same parser instead of fire, so `--version` and a bare invocation
no longer run the demo turn there either.

- --help/-h/--version use argparse's own exit path; options carry help text
- the runner is injected (`run=`) so `python run_agent.py` does not import
  run_agent a second time
- tests trimmed to two invariants that resolve the console-script target
  from pyproject exactly like pip's wrapper (red on main, green here)
- docs: hermes-agent section in the CLI reference
2026-09-23 10:40:39 -07:00
luyifan
f80977c0bd fix(cli): guard legacy hermes-agent entrypoint 2026-09-23 10:40:39 -07:00
ethernet
3f4b8840f1 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	pyproject.toml
#	tests/fixtures/resolution_allowlist.json
2026-09-23 07:36:31 -04:00
teknium1
7ed6534c7e Merge origin/main: browser fence composed with the dispatch/retry split (#115184); server registration, i18n, contracts 2026-09-23 03:25:06 -07:00
teknium1
73c598e319 build: drop the hermes-agent[hindsight] extra
The catalog plugin declares hindsight-client in its own plugin.yaml and the
plugin installer resolves it (into HERMES_LAZY_INSTALL_TARGET on the sealed
Docker image), so the pyproject extra, its exclude-newer entry and every
consumer that pre-installed it — the Dockerfile, the nix full package /
module examples / check override, and the CI `uv sync` matrices — stop naming
it. uv.lock regenerated with `uv lock` (only the hindsight-client package and
the extra leave the lock; the exclude-newer block is re-sorted by uv).
2026-09-23 01:16:41 -07:00
ethernet
8da6c5f386 Merge branch 'ethie/release-machinery' into ethie/pm-clean
# Conflicts:
#	tests/ci/test_desktop_store_eligibility.py
2026-09-22 11:05:14 -04:00
ethernet
207f8fedfd Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	hermes_cli/local_runtime/binaries.py
#	hermes_cli/plugins_cmd.py
#	hermes_constants.py
#	tests/test_hermes_constants.py
#	tests/tools/test_clipboard.py
#	tests/tools/test_voice_wsl_pipewire.py
#	tools/computer_use/cua_backend.py
#	tools/voice_mode.py
2026-09-22 09:55:47 -04:00
Siddharth Balyan
4094ab610d hermes_platform.resolver: locate/inspect/probe tiers, AppResolver, gh lookup migrated (NS-921) (#118065)
* feat(platform): resolver core with locate/inspect/probe tiers and ordered candidates

Every resource lookup needs one result shape and one cost contract. `locate` reads
metadata only, `inspect` may open files and call OS APIs in-process, `probe` is fresh
and the only tier that may spawn or connect. `Resolution.candidates` keeps probe order
so fan-out consumers can try every present binary.
Linear NS-921.

* feat(platform): AppResolver over AppDef with plist, PE, registry, and server.json sources

Desktop apps need presence, version, and liveness as separate observations. The runtime
file's bearer token is parsed, used for one request, and discarded inside the probe;
no public type carries it. Endpoints are accepted only when loopback with a numeric port.

* refactor(copilot): gh candidates through locate_command and the Homebrew table

First consumer of the resolver. The gh token probe still tries every present binary in
order; the allowlist loses its two copilot_auth rows.

* feat(platform): availability() over an application declaration

locate() + inspect() only, never probes; the fail-closed _version in
app.py treats a vendor's plist/PE/registry entry as untrusted input.
Salvaged from PR #118122; reads any object with requires_app,
min_version, app_for(os) — nothing here imports the MCP catalog.

* feat(platform): application declarations parsed into AppDef per OS

The parser slice of PR #118122's catalog manifest, re-homed as a
catalog-free module: whoever owns an MCP server declares the app it
fronts per OS and what it needs, and registers it here. Stdlib +
hermes_platform.resolver only. register/lookup/clear are the one seam
the MCP check_fn and the skill gate both read.

* feat(mcp): check_fn honours a registered application declaration

_make_check_fn ANDs the declared app's availability into the
connection-alive check; with nothing registered for the server the
behaviour is the pre-PR3 connection check. Provenance is explicit
registration, not endpoint matching. Returns a plain bool: the registry
caches bool(fn()).

* feat(skills): requires_apps gate through registered declarations

Offer-time filter beside environments:; names resolve through
hermes_platform.declaration, an unknown name hides the skill (fail
closed). The disk snapshot carries requires_apps and the fast path
re-evaluates it (snapshot version bumped to 3): app presence is a host
fact that changes without SKILL.md changing.

* docs: application declarations page

The plugin-facing schema reference: app: and requires: blocks,
availability() states, and the two gates that read the registry.
Registered under Extending > Plugins in the docs sidebar.

* test(platform): declaration parser, availability, gates

The PR3 app-block tests re-homed off the catalog: fixtures are dicts
passed to parse_declaration, the check_fn gate keys on explicit
registration (not endpoint matching), and the import-hygiene probe now
covers hermes_platform.declaration and resolver.availability.
2026-09-22 17:08:30 +05:30
alt-glitch
48ed4e5364 feat(platform): hermes_platform.host with machine facts, runtime predicates, SoC recognizer
Host facts and runtime predicates need one stdlib-only source that stays correct under architecture emulation.
Hardware recognition reads host facts, not environment variables or GPU proxies.
The package boundary gives later resolvers and catalog checks a shared foundation.
Linear NS-920.
2026-09-22 01:13:23 -07:00
teknium1
95481bb54d fix(plugins): LAZY_DEPS mirror plugin.yaml ranges; manifest parser names list-typed files and reads hooks:
- hindsight-client==0.6.1 / mem0ai==2.0.10 exact pins made _is_satisfied() reject every newer
  compatible release, so hermes update kept downgrading a working client and broke embedded
  daemons whose DB a newer client had migrated (#86992, #39424, #98407, #99317). The lazy entries
  now mirror the plugin manifests (>=0.6.1,<1 and >=2.0.10,<3); pyproject extras stay the floor
  install. Slim redo of #99557 (nateEc) / #98416 / #98527 (ttomiczek) / #39754.
- A list-typed plugin.yaml is refused with "top level must be a mapping" instead of an
  AttributeError swallowed as "Failed to parse" (#14066, discovery side).
- ``hooks:`` (the spelling bundled manifests carried) still populates provides_hooks (#108371).
2026-09-21 23:26:19 -07:00
ethernet
cc5e9504c2 fix: keep the PM runtime pure Python on Windows ARM64
Pin ruamel.yaml before the release that requires ruamel.yaml.clib on
Python 3.14. Windows ARM64 has no clib wheel, and source builds fail in
CI's long temporary paths. Refresh both application and PM locks and
keep wheelhouse tests aligned with the locked pure-Python runtime.
2026-09-22 00:18:07 -04:00
ethernet
9cc319f9a1 feat(version): main carries 0.0.0 and dev distance counts from release tags
The committed version is a placeholder now; a build stamps the real one.
Dev installs report their distance from the highest reachable release tag,
so an -rc claim sitting on the same commit never becomes the version.
2026-09-21 20:41:59 -04:00
ethernet
034c3fdb02 ci: update ty to avoid full-tree allocation failure 2026-09-21 14:43:35 -04:00
ethernet
e22809bb9e merge: retain PM branch version across upstream release 2026-09-21 14:13:41 -04:00
teknium1
d337b736aa chore(release): v0.21.4 (v2026.9.21)
Some checks are pending
Install & Update E2E / Pick release tags (push) Waiting to run
Install & Update E2E / Expand combinations (push) Blocked by required conditions
Install & Update E2E / ${{ matrix.name }} (push) Blocked by required conditions
Install & Update E2E / Upload leg player (push) Waiting to run
Install & Update E2E / Result chart (push) Blocked by required conditions
Patch rollup of the ~1,800 PRs merged since v0.21.3 so downstream consumers
(Docker images, Hermes Cloud) get a stable tag. Full curated notes ship with v0.22.0.
2026-09-21 11:10:32 -07:00
ethernet
d87d134564 chore: bump version to v1.0.0 (2026.9.21) 2026-09-21 13:15:16 -04:00
ethernet
7bc32b575f chore: bump version to v0.22.2 (2026.9.21) 2026-09-21 13:09:52 -04:00
ethernet
dcd40f61a4 chore: bump version to v0.22.1 (2026.9.21) 2026-09-21 13:09:36 -04:00
ethernet
483b46f7e3 chore: bump version to v0.22.0 (2026.9.21) 2026-09-21 13:08:31 -04:00
ethernet
9f2ba1b74d merge origin/main (779 commits) into ethie/pm-clean
Branch semantics kept where main and PM disagree: update_cmd_deps.py,
constraints-termux.txt, the Electron update-api-check module and the
post-swap hand-off test stay deleted; the pending-fleet-restart catch-up
and the local_runtime tag/download ladder stay retired (PM owns engines).

Ported from main onto the branch's shape: profile_scoped_chore for the
auto-archive and plugin-update housekeeping chores, the local-runtime
cross-process boot lock and residency cap, the checkpoint tmp_pack sweep,
the cua daemon-liveness status probe, the remote-served Desktop update
flag (posix.sh / windows.ps1), sign-in for env-pinned remote gateways
(urlDisabled on RemoteSetupFields), the uvloop extra split (uvicorn
without [standard]), and the umask-scoping spawn test.

uv.lock regenerated with pm.build_env --lock-only; new utf-8 reads from
main switched to utf-8-sig (check-windows-footguns).
2026-09-21 00:58:39 -04:00
teknium1
536e88673c fix(termux): policy pins, full uvloop marker, lazy dashboard mirror, lock
Follow-up to the cherry-picked #116014:

- Pin per the dependency policy (pre-1.0: `<0.(minor+2)`): httptools
  `>=0.6.3,<0.9` (floor = uvicorn[standard]'s own floor), uvloop
  `>=0.15.1,<0.24`. `watchfiles>=0.20,<2` already complied.
- Copy uvicorn's own uvloop marker (win32, cygwin, PyPy) plus
  `sys_platform != 'android'` so `pip install '.[all]'` on those
  hosts does not fail on the extra either.
- `tools/lazy_deps.py` mirrors the `web` extra for the lazy dashboard
  install: it also requested `uvicorn[standard]`, so a Termux user
  opening the dashboard would have hit the same uvloop build at first
  use. The web_server install hint follows.
- `uv lock` regenerated; the lock delta is exactly the pyproject delta.
- Two invariant tests: no Termux-reachable extra (or core, or the lazy
  dashboard feature) requests uvloop; `[all]` still does, off Android.
- Docs: troubleshooting entry in the Termux guide.
2026-09-20 12:00:44 -07:00
Guillaume D.Isabelle (Ilex)
78bfd8ffb0 fix(termux): add upper bounds to httptools, watchfiles, uvloop specifiers
Address review feedback: the repository dependency policy requires
>=floor,<next_major bounds.  Tighten the unbounded ranges introduced
in the prior commit.

- httptools>=0.6.0,<1  (lockfile resolves 0.7.1)
- watchfiles>=0.20,<2  (lockfile resolves 1.1.1)
- uvloop>=0.15.1,<1    (lockfile resolves 0.22.1)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-20 12:00:44 -07:00
Guillaume D.Isabelle (Ilex)
99049aa1f5 fix(termux): gate uvloop behind optional extra to unblock Android installs
uvloop (pulled in by uvicorn[standard]) cannot build on Android/Termux
because libuv's ./configure script fails on the non-FHS Bionic layout.
This bricks `pip install -e '.[termux]'` and `hermes update` on every
Android device.

Decompose `uvicorn[standard]` in core dependencies:
- Keep `uvicorn` (without [standard]) as a core dep
- Add `httptools` and `watchfiles` directly (both build cleanly on
  Android with ANDROID_API_LEVEL set)
- Gate `uvloop` behind a new `[uvloop]` optional extra
- Include `[uvloop]` in `[all]` so desktop/server installs retain the
  performance benefit automatically
- Omit `[uvloop]` from `[termux]` and `[termux-all]`

Also update the `[web]` extra to use plain `uvicorn` (without
[standard]) since httptools/watchfiles are now core deps — this
prevents `[termux-all]` (which includes `[web]`) from re-introducing
the uvloop dependency.

Update constraints-termux.txt with documentation explaining the
uvloop exclusion rationale.

uvicorn falls back to the stdlib asyncio event loop when uvloop is
absent — no hermes codepath requires uvloop specifically.

Tested on: Termux 0.119 / Android 10 / aarch64 / Python 3.13.12

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-20 12:00:44 -07:00
ethernet
9214174e07 fix(ci): lint legs after the main merge
- check_no_tmp_literals: resolve scratch via tempfile/os.tmpdir; the termux
  container mount point is one marked variable per script
- ruff TID251: desktop E2E fixtures may reach PM internals like tests do;
  the pm.runtime_stage ban message no longer names a module that never existed
- auth_codex: build the capped httpx stream subclass on first use so importing
  hermes_cli.auth_codex no longer forces httpx (the lazy proxy in auth_constants
  was defeated by a module-scope base class; broke lanes without httpx)
- desktop-smoke: launchApp is a parameter; the bundle-env test substitutes a
  refusing launcher instead of letting Playwright spawn a dying binary
  (3 unhandled rejections failed the tests-js lane)
2026-09-19 23:25:18 -04:00
ethernet
6a5a6a05d2 refactor(pm): rename the pm.ensure submodule to pm.install; lazy_deps back to the shim
`import pm.ensure` bound the submodule onto the package, shadowing the facade's
`pm.ensure()` function for every later caller in the process (photon's sidecar
start hit `'module' object is not callable`). The module is pm.install now; the
function keeps its name. The facade resolves through `__import__` rather than
`importlib.import_module` so a test that patches import_module globally does not
break attribute access on pm.

tools/lazy_deps.py returns to the 16-line stop_for_relaunch shim the branch wrote
(an origin/main merge had replaced it with main's 775-line implementation); the
project-metadata tests follow. update_cmd re-exports the four old_updater_deps
names the shim tests resolve through hermes_cli.update_cmd.
2026-09-18 23:27:05 -04:00
ethernet
9645434dce fix(deps): pin sherpa-onnx-core beside sherpa-onnx
Every native leg of the bundled release died at "Prepare the complete desktop
build" with the same validation failure:

    The package `sherpa-onnx` requires `sherpa-onnx-core==1.13.8`, but it's not installed

`pm/environment.py::check` runs `uv pip check` after the payload venv is built,
and `pm/operations.py::build_environment` builds that venv with
`uv sync --frozen`. The lock is therefore authoritative, and the lock carried no
`sherpa-onnx-core` edge and no core package block.

uv resolves one distribution per version and reads the dependency graph from
that distribution's metadata. For sherpa-onnx 1.13.8 it selects the
cp311-linux_armv7l wheel (`uv lock -v` prints `Selecting: sherpa-onnx==1.13.8
[compatible] (sherpa_onnx-1.13.8-cp311-cp311-linux_armv7l.whl)`), and that wheel
is the one artifact of the release that declares no dependencies. Every wheel
the bundles install declares `Requires-Dist: sherpa-onnx-core==1.13.8`. The
sdist declares nothing either.

A lock regen cannot repair this, because the same metadata source is selected
again: `0dab3f5e3f` regenerated the lock for the widened floor, dropped the core
edge, and a fresh `uv lock` drops it again. Pin the core beside every
sherpa-onnx pin instead. Its wheels are platform-tagged and `py3-none`, so one
pin covers every target.

Verified with uv 0.12.3 on CPython 3.14.7 against the real project lock:

    before (dbe4bfbc06): uv sync --frozen --extra wake --extra wake-sherpa
                         uv pip check -> Found 1 incompatibility (message above)
    after:               uv sync --frozen --all-extras
                         uv pip check -> All installed packages are compatible
                                         (257 packages, sherpa_onnx_core present)
2026-09-18 18:21:55 -04:00
teknium1
b1be493b41 Merge origin/main: SDK export list (WORKSPACE_PAGE_HEADER_AREA beside the profile-group exports) 2026-09-18 14:09:20 -07:00
ethernet
e1020f32d4 fix(packaging): let a pre-PM updater finish its dependency step on old python
A released updater runs its own `uv pip install -e .` on the interpreter it
already had -- 3.11 in the wild -- and only after that step succeeds does it
reach the handoff shims this tree ships for it (`managed_uv`,
`_old_updater.stop_for_relaunch`). With requires-python at >=3.14 that step
cannot resolve at all:

    error: No solution found when resolving dependencies
      cause: the current Python version (3.11.16) does not satisfy Python>=3.14,<3.15

so the compat surface built for exactly those updaters (tests/compat/README.md
covers updaters shipped before the PM migration) is unreachable for them.

There is no flag-only way around it, measured on a real 3.11 against uv 0.12.3:
--no-deps and UV_NO_DEPS both still fail on the project's own requires-python,
and uv has no --ignore-requires-python at all. The metadata is the only lever,
so the floor now admits the oldest updater we support and the core dependencies
are gated on the runtime we actually support:

  * requires-python = ">=3.11,<3.15" -- 3.14 is a runtime requirement, not an
    install requirement;
  * every core dependency carries `python_version >= '3.14'`, which is true on
    every runtime this package supports and false for a bridge interpreter, so
    an old resolver sees them as absent instead of installing ~180 packages
    into a venv the handoff is about to rebuild.

Verified on Windows against a real 3.11.15 and 3.14.7 with the real dependency
list: the exact command now exits 0 on 3.11, installing only the editable
project plus the three packages the two `override-dependencies` pull (uv rejects
markers in override-dependencies, so those cannot be gated); 3.14 still installs
the full tree. All 41 requirements parse as PEP 508 -- which caught two real
bugs: `A or B and C` binds the appended marker to the last clause only, so
nemo-relay's marker is parenthesised, and a direct-URL requirement needs
whitespace before its `;`.
2026-09-18 13:35:43 -04:00
ethernet
b4a294fff9 Merge origin/main; keep PM as plugin dependency owner
Reconcile plugin declarations and validation through PM's atomic generation publication; preserve external runtimes, target markers, and conflict refusal. Keep one source-update completion owner and port upstream lifecycle changes to the PM desktop/runtime paths.
2026-09-17 13:52:05 -04:00
teknium1
94ced1a2b2 fix(update): finish hermes update in an interpreter born on the pulled code
`hermes update` started in an interpreter that had imported the PRE-pull tree, then
kept running every post-swap phase (dependency sync, Node/web/Desktop builds,
maintenance, config migration, fleet restart, verification, receipt) in that same
process, lazily importing NEW source into an OLD `sys.modules` graph. Any rename
between the two commits surfaced as an ImportError/AttributeError inside the updater
after the code swap had already succeeded (#87134, #111271, #112465, #112558, #112604).
Each incident added another purge, reload list or per-step isolation, and each moved
the crash to the next module nobody had listed.

The pre-pull process now stops at the swap: it writes the open receipt, the pre-update
fleet plan, the pre-update version/active features and the Windows pause token to a
hand-off file and re-executes `hermes update <same flags> --post-swap <file>` under the
venv interpreter. The child imports exclusively from the pulled tree, resumes the
receipt and owns the rest of the run; the parent relays its exit code. Git and ZIP paths
both hand off. On Windows, when the updater runs from `hermes.exe`, the child is spawned
detached exactly as the shim hand-off already did (the shim cannot be awaited while the
sync must replace it).

With no pulled code ever executing in a pre-pull interpreter, the stale-module layer is
dead and removed: `_purge_stale_hermes_modules`, `_stale_purge_prefixes`,
`_evict_module`, `_STALE_PURGE_*`, `_reload_updated_runtime_modules`,
`_reload_process_scan_modules`, `_reload_config_modules`, `_UPDATE_RUNTIME_RELOAD_MODULES`
and their tests. `_run_config_check_fresh` / `_run_migrate_config_fresh` keep their names
and simply call the config API.

Tests: the hand-off boundary (child argv/env, detached receipt + plan in the payload,
exit-code relay) and the child side (receipt resumed with its history, plan rebuilt,
pre-update snapshots taken from the payload). Mocked updater flows run the tail
in-process through the same payload round-trip (autouse fixture; opt out with
`@pytest.mark.real_post_swap_handoff`).
2026-09-17 00:02:09 -07:00
ethernet
045f4d9c8b fix(pm): bound the install lock, gate an unsatisfiable extra, retry a partial cache seed
Three ways a fresh install punished a second backend:

- `runtime_lock` waited forever, and its holder can be rebuilding the whole dependency
  environment. `activate_dependencies` runs at every boot, so the second backend — the
  onboarding profile — never bound. It now yields whether it holds the lock, and boot
  proceeds without it: recovery is the holder's job, and the generation being leased is the
  selected one, which the collector never removes. Explicit installs still pass
  `timeout=None`; an opportunistic lazy install refuses instead of queueing behind a rebuild
  it did not request. Same rule `boot_bootstrap._RecordLock` already states for home
  maintenance.
- `stt-whisper` had no platform gate although its anchor `faster_whisper` cannot install on
  win32/ARM64 (ctranslate2 ships no wheel) or darwin-x64, so the lazy install rebuilt the
  whole environment and still failed the anchor on every retry. The extra is gated to match
  its dependency markers; `voice` stays ungated because its sounddevice/numpy do install on
  those targets.
- the first sync copies the payload's uv cache out to the machine cache. A copy that failed
  still recorded `.seeded`, so the partial seed was permanent and every later offline sync
  failed closed on the missing entries.

Validated: tests/pm/ and tests/hermes_cli/ for the touched modules. A/B on HEAD: the gate
test, the cache-seed test and both lock tests fail there, pass here.
2026-09-15 10:48:13 -04:00
teknium1
e4f591d1b0 Merge origin/main: wire-contract registry + server→client requests + screenshot dedup
Main replaced every *.request/*.respond event pair with real JSON-RPC server→client requests and
made Python the single source of the wire (tui_gateway/contracts → generated TS). Bot Screen now
speaks that dialect:

- tui_gateway/contracts/display.py declares the eight display.* methods, the display.install.sudo
  server request and the four display.* events; the generated TS/OpenRPC is regenerated.
- The install sudo card is a `display.install.sudo` server request (app-level, empty session);
  the desktop answers it through respondToServerRequest, so the respondMethod/origin plumbing
  that pinned a reply to its socket is gone: a JSON-RPC response cannot land elsewhere.
- screen-connection.ts consumes the generated DisplayStatus/DisplayLease instead of hand-typed
  copies (holder named by viewer_hash only; epoch always present).
- computer_use: the lease fence and main's screenshot-dedup session key ride the same read
  handlers; the fence fires before a frame reaches the dedup cache.
- server.py keeps the display module registration a naive --theirs would have dropped.
2026-09-14 18:35:43 -07:00
ethernet
612d542281 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	.gitignore
#	Dockerfile
#	agent/onboarding.py
#	apps/desktop/electron/main.ts
#	apps/desktop/electron/pool-stop.ts
#	apps/desktop/src/components/model-picker.test.tsx
#	apps/desktop/src/store/updates.ts
#	apps/desktop/vite.config.ts
#	datagen-config-examples/run_browser_tasks.sh
#	docs/rca-ssl-cacert-post-git-pull.md
#	gateway/run.py
#	hermes_cli/backup.py
#	hermes_cli/credential_lifecycle.py
#	hermes_cli/dashboard_procs.py
#	hermes_cli/doctor_state.py
#	hermes_cli/env_loader.py
#	hermes_cli/gateway_windows.py
#	hermes_cli/local_runtime/endpoint.py
#	hermes_cli/psutil_android.py
#	hermes_cli/update_cmd.py
#	hermes_cli/update_cmd_windows.py
#	hermes_cli/web_routers/local_models.py
#	hermes_cli/web_server_config.py
#	hermes_cli/web_server_cron.py
#	plugins/memory/hindsight/__init__.py
#	plugins/memory/holographic/__init__.py
#	plugins/memory/honcho/cli.py
#	plugins/memory/mem0/__init__.py
#	plugins/platforms/google_chat/oauth.py
#	plugins/platforms/photon/adapter.py
#	scripts/ci/list_os_marked_tests.py
#	scripts/run_tests.sh
#	tests/agent/test_compression_stall_fallback.py
#	tests/agent/test_create_openai_client_ssl_verify.py
#	tests/gateway/test_google_chat_oauth_dependencies.py
#	tests/hermes_cli/conftest.py
#	tests/hermes_cli/test_cli_init.py
#	tests/hermes_cli/test_gateway_migrate_multiplex.py
#	tests/hermes_cli/test_psutil_android_extract.py
#	tests/hermes_cli/test_relaunch.py
#	tests/hermes_cli/test_update_check.py
#	tests/hermes_cli/test_update_handoff_desktop_rebuild.py
#	tests/hermes_cli/test_worktree_gc.py
#	tests/scripts/desktop_update/test_desktop_update_windows_python_handoff.py
#	tests/scripts/desktop_update/test_desktop_update_windows_retry_policy.py
#	tests/scripts/desktop_update/test_desktop_update_windows_timestamp.py
#	tests/scripts/install/test_install_autostash_conflict_recovery.py
#	tests/scripts/install/test_install_clone_throttle_fallback.py
#	tests/scripts/install/test_install_commit_pin_rollback.py
#	tests/scripts/install/test_install_diverged_update.py
#	tests/scripts/install/test_install_lockfile_churn.py
#	tests/scripts/install/test_install_macos_launcher.py
#	tests/scripts/install/test_install_no_initial_commit.py
#	tests/scripts/install/test_install_ps1_ascii_only.py
#	tests/scripts/install/test_install_ps1_browser_install.py
#	tests/scripts/install/test_install_ps1_managed_node_swap.py
#	tests/scripts/install/test_install_ps1_native_stderr_eap.py
#	tests/scripts/install/test_install_ps1_node_path_for_npm.py
#	tests/scripts/install/test_install_ps1_python_fallback_venv.py
#	tests/scripts/install/test_install_ps1_resolver_strictmode.py
#	tests/scripts/install/test_install_ps1_uv_install_fallback.py
#	tests/scripts/install/test_install_ps1_uv_powershell_host.py
#	tests/scripts/install/test_install_ps1_venv_process_tree.py
#	tests/scripts/install/test_install_ps1_venv_recreate_safety.py
#	tests/scripts/install/test_install_ps1_venv_rename_abort.py
#	tests/scripts/install/test_install_ps1_venv_transaction_boundary.py
#	tests/scripts/install/test_install_ps1_web_server_syntax_probe.py
#	tests/scripts/install/test_install_scripts_computer_use.py
#	tests/scripts/install/test_install_sh_acp_launcher.py
#	tests/scripts/install/test_install_sh_bootstrap_marker.py
#	tests/scripts/install/test_install_sh_browser_install.py
#	tests/scripts/install/test_install_sh_install_method_stamp.py
#	tests/scripts/install/test_install_sh_node_deps_failure.py
#	tests/scripts/install/test_install_sh_node_deps_workspaces.py
#	tests/scripts/install/test_install_sh_node_global_prefix.py
#	tests/scripts/install/test_install_sh_node_npm_check.py
#	tests/scripts/install/test_install_sh_node_prerelease.py
#	tests/scripts/install/test_install_sh_node_probe.py
#	tests/scripts/install/test_install_sh_node_tarball_without_xz.py
#	tests/scripts/install/test_install_sh_pythonpath_sanitization.py
#	tests/scripts/install/test_install_sh_reuse_supported_python.py
#	tests/scripts/install/test_install_sh_root_fhs_uv_python_path.py
#	tests/scripts/install/test_install_sh_setup_wizard_tty_probe.py
#	tests/scripts/install/test_install_sh_symlink_stomp.py
#	tests/scripts/install/test_install_sh_termux_network_prereqs.py
#	tests/scripts/install/test_install_sh_termux_python_bounds.py
#	tests/scripts/install/test_install_sh_uv_lock_config.py
#	tests/scripts/install/test_install_unmerged_index.py
#	tests/scripts/test_run_tests_parallel.py
#	tests/test_managed_runtime_resolution.py
#	tests/test_project_metadata.py
#	tests/tools/test_browser_use_cli.py
#	tests/tools/test_tts_pythonpath_fallback.py
#	tests/tui_gateway/test_hosted_room_driver_runtime.py
#	tests/tui_gateway/test_tui_gateway_server.py
#	tools/lazy_deps.py
#	tools/voice_mode.py
#	uv.lock
#	website/docs/developer-guide/macos-bundle-updates.md
#	website/docs/developer-guide/pm-audit-status.md
#	website/docs/developer-guide/shared-bundle-builds.md
#	website/docs/developer-guide/source-update-completion.md
#	website/docs/developer-guide/stable-releases.md
2026-09-14 15:38:34 -04:00
teknium1
345cd2b057 chore(release): v0.21.3 (v2026.9.14)
Some checks are pending
Install & Update E2E / Pick release tags (push) Waiting to run
Install & Update E2E / Expand combinations (push) Blocked by required conditions
Install & Update E2E / ${{ matrix.name }} (push) Blocked by required conditions
Install & Update E2E / Upload leg player (push) Waiting to run
Install & Update E2E / Result chart (push) Blocked by required conditions
Rollup patch tag so Hermes Cloud agents (which deploy the newest v* release tag)
pick up #110061: remote dashboard sessions no longer expire on refresh bursts.
Full curated notes for the window ship with v0.22.0.
2026-09-14 09:02:28 -07:00
xxxigm
f3dbb19707 fix(google-chat): install optional deps into the sealed-image lazy target
Hosted/Docker images lock /opt/hermes/.venv, so --install-deps writing
site-packages fails with Permission denied and the adapter never starts.
Route Google Chat through lazy_deps (HERMES_LAZY_INSTALL_TARGET) and bake
the extra into the published image so a configured gateway can connect.
2026-09-13 19:19:36 -07:00
Sam Painter
352d8b0929 chore(deps): use bounded pillow-heif constraint and regenerate uv.lock
AGENTS.md:559-576 requires every PyPI dependency to carry an upper bound
rather than an exact runtime pin. Replace "pillow-heif==1.4.0" with
"pillow-heif>=1.4.0,<2" and regenerate uv.lock (resolves 1.5.0, with
hashes), confirming the range admits later 1.x releases.
2026-09-13 09:18:54 -07:00