52 Commits

Author SHA1 Message Date
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
teknium1
ee0ad5adb2 refactor(pm): expose MUSL_TARGETS publicly; drop the per-URL pin hash cache
packages.py and security_packages.py imported the private _MUSL_TARGETS
across modules; make it a public store constant next to ALL_TARGETS.
The _pin_tool hash memo is an unrelated optimisation, out of scope here.
2026-09-27 03:24:39 -07:00
JoaoMarcos44
8371dd17f6 fix(pm): select native musl artifacts on Linux 2026-09-27 03:24:39 -07:00
ethernet
40d5519836 pm: install the optional defaults after the venv sync
A bare `pm install` fetched agent-browser (~200 MB with Chromium) before
the venv sync, so a Windows ARM64 machine without build tools downloaded
it and then failed preparing the native build. Install defaults only once
the venv (and its build tools) succeeded.
2026-09-24 18:51:00 -04:00
ethernet
3addc73a83 pm: install agent-browser by default, with a recorded --without opt-out
Browser tools find agent-browser only in PM's store or on PATH and their
readiness check never installs it, so a fresh install silently had no
browser_* tools. Package.default marks an optional package that a bare
`pm install` also carries; a failed download of it warns instead of
failing the install. `pm install --without NAME` records the opt-out in
declined-packages.json beside PM's install state, and naming the package
explicitly clears it. agent-browser and chromium gain a Termux gap: Termux
owns its browser stack and there is no bionic Chromium.
2026-09-24 17:27:45 -04:00
ethernet
b4ad4b2c26 fix(pm): hermes pm lock checks a stale uv.lock quietly
The no-argument relock first runs PM's lock check, which reported a stale
lock the way build_env --check-lock does: a red "✗ Resolving Python
dependencies failed" plus uv's raw output, printed right before the relock.
A stale lock is the expected case for this command, so the check now takes
`quiet=True`, which captures uv's output instead of reporting it. The command
prints "→ Checking uv.lock…" then either "already current" or "out of date;
relocking" with the relock's own progress. build_env --check-lock keeps its
failure report unchanged; CI's remediation relies on it.
2026-09-24 15:56:25 -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
3e256363a4 refactor(pm): split cmd_install and cmd_update into their phases
cmd_install: flag validation and the venv/test-environment phase move
to helpers (CC 40 -> 18). cmd_update: resolution, per-row report, pin
application, uv and npm refreshes move to helpers (CC 51 -> 22). The
messages, order and exit codes are unchanged; a dead `target` local in
cmd_update is dropped.
2026-09-24 14:08:21 -04:00
ethernet
bd9e507071 fix(pm): turn on VT processing before drawing progress on a Windows console
PowerShell hands child processes a console with
ENABLE_VIRTUAL_TERMINAL_PROCESSING off, so conhost drew the progress
line's ESC[2K as a glyph: every install.ps1 status line printed as
'<-[2K✓ ffmpeg'. The progress stream now opts its console handle in to VT
before use, and a handle that is not a console (NUL reports isatty()
too) gets plain lines instead.
2026-09-24 10:11:10 -04:00
ethernet
bd392ba922 fix(pm): contain review security and dependency verification regressions 2026-09-24 02:03:59 -04:00
ethernet
babbec1c4c feat(pm): isolate developer test environment from runtime extras 2026-09-23 18:13:38 -04:00
ethernet
b16f953f7c fix(pm): re-activate from the shebang only when an input's mtime differs
The _hermes-python prologue compared uv.lock / pyproject.toml / pm/lock.json
against facts.json with `-nt`. facts.json is only rewritten on a real sync,
so a checkout that rewrites an input without changing it left the input
newer forever: every run from an activated shell re-sourced activate
(~370ms instead of ~13ms).

pm now records, after every successful full-closure install (no-op syncs
included), a stamp per input under installs/<key>/inputs carrying the exact
mtime the install was verified against, snapshotted before installing so
an input edited mid-install still reads as stale. The prologue re-activates
when any input's mtime differs from its stamp in either direction (branch
switches can move it backwards), when an input is missing, or when there
are no stamps. It globs the stamp dir, so pm owns the only input list.

Also read pyvenv.cfg as utf-8-sig (footgun lane, same file).
2026-09-23 13:38:54 -04:00
ethernet
dd9efff98b fix(pm): skip the tool re-hash during shell activation
source ./activate and . .\activate.ps1 always run setup, and setup always
runs `python -m pm.cli install`. An explicit install re-hashes every
published tool entry so it can repair a corrupted one. On this machine
that hash reads about 550 MiB and takes 6.0 of the 6.6 seconds, even
when nothing changed.

Shell activation now passes --trust-recorded and trusts the digest the
install recorded, the same check startup already uses. A missing tool
is still installed and a stale venv is still rebuilt. `hermes pm
install` and `hermes update` keep the byte check, and the flag refuses
names, --extra, and --target so it cannot narrow an install someone
asked for by name.

Measured on this machine, already up to date: the activation path drops
from 6.6 s to 0.9 s. A bare `python -m pm.cli install` stays at 7.2 s.
The remaining 0.9 s is process startup and imports, not hashing.
2026-09-23 13:17:55 -04:00
ethernet
9c11bb96bd fix(pm): put tools on PATH before the venv sync
Windows PowerShell 5.1 returns every match from Get-Command. .Source on
that array joins the paths with a space, and the call operator then treats
the joined string as one program name. Git for Windows ships git.exe in
cmd\ and bin\, so setup died with CommandNotFoundException before pm
install ran.

setup-hermes.ps1 now installs the tool closure first (`pm install
--tools-only`), then prepares the ARM64 compiler environment, then syncs
the venv. The sync inherits that compiler environment. A bare `pm install`
and the update takeover path publish tools and put them on PATH before
uv sync. A missing tool stops the sync. A missing venv does not.

Verified: scripts/run_tests.sh on test_install_default_closure.py,
test_install_extra.py, and test_windows_build_deps.py — 13 passed.
2026-09-22 12:33:02 -04:00
ethernet
963566d703 fix(pm): cmd_install tolerates callers that omit --extra
Tests and in-process callers build the args namespace by hand, as the
existing --target lookup already allows for.
2026-09-21 19:11:40 -04:00
ethernet
029cabb466 feat(pm): hermes pm install --extra NAME; one shared hint for missing extras
Twenty-five call sites told users to run
`python -c "from pm import sync_venv; sync_venv(['x'], explicit=True)"`
because `hermes pm install` only took package names. Add `--extra`
(repeatable; syncs the venv with the named extras and nothing else) and
pm.install_hint(extra), the single builder every site now uses, so the
advice stays correct when the command changes.

A cold PM runtime under allow_lazy_installs:false now reports the extra
the caller wanted and the command that provisions both, instead of a
bare "pm-runtime: not installed".
2026-09-21 19:08:19 -04:00
ethernet
15335df680 fix(pm): collect superseded and aborted PM runtime generations
pm-runtime/generations/<uuid> trees were never collected: a kill -9 during
staging orphaned a venv permanently and every input change left the old
runtime behind. `hermes pm gc` now sweeps them under .prepare.lock with the
same lease model as application generations: entry points (worker.py,
launch.py) pin their own runtime, prepare_runtime marks new generations
lease-managed, and the collector removes unpublished stages outright,
superseded generations only once no worker holds a lease, and never a
pre-lease generation.
2026-09-21 19:01:36 -04:00
ethernet
fc140f2598 fix(pm): gc collects abandoned .staging-* scratch dirs
_gc_store skipped every dot-dir, so the mkdtemp scratch a killed installer
left behind (a whole extracted python-build-standalone tree per crash)
was never removed. Scratch dirs live and die under the install lock gc
holds, so any that remain are orphans. .previous-* stays: it is the
restore point the next install of that entry verifies and consumes.
2026-09-21 18:52:33 -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
ee2b165e78 chore: drop merge-resolution notes, dead imports and a dead probe module
- 15 `MERGE-CHECK:` conflict-resolution comments removed from prod code (two were
  TODOs already done: the utf-8-sig sessions.json read lives in session_persistence,
  the pm-aware cron script helpers in scheduler_script).
- 49 imports the branch left unused (ruff F401, none present at the merge base,
  none inside PLUGIN-COMPAT blocks). update_cmd's frozen-surface re-exports are
  trimmed to the names tests/compat/old_updater_surface.json actually lists under
  hermes_cli.update_cmd; the rest resolve through hermes_cli.main.__getattr__.
- tools/environments/local_gitbash_probe.py: nothing imported it once _find_bash
  delegated to pm.shell().
- Three try/except wrappers around calls that cannot raise (install_truststore,
  get_hermes_home, and a duplicated except clause in supermemory).
2026-09-18 19:31:50 -04:00
ethernet
4fbec9c442 feat(pm): repair retired termux pool pins from pm update --termux
The termux-main pool deletes a package's previous archive when it rebuilds, so
the runtime-lib pin table and the bionic lock rows rot without warning. The last
rotation broke a build on eight rows at once, and the stager's concurrent
downloads only surfaced whichever 404 won the race.

pm now owns the pin table it repairs: scripts/termux/runtime_libs.json moves to
pm/termux_runtime_libs.json, so pins live in pm/ and scripts consume them — the
direction scripts/ci/archive_inputs.py already reads pm/lock.json in.

`hermes pm update --termux` repins exactly the rows whose archive the pool has
replaced, hashing each replacement against the index SHA256 before writing
url/version/hash together. `--check` reports without writing and exits 1, so a
retired pin can fail a cheap preflight instead of a payload build.

It is a repair, not an update: an alive pin is never moved, because a repin can
land a rebuilt library under a moved soname and the table is the payload's
recursive DT_NEEDED closure. A pin whose package the pool has dropped outright
is reported and left alone. `--termux` runs alone — names/--target/--uv/--npm
are ignored, since repairing foreign-target pins is not a version resolution.

Verified: `pm update --termux --check` against the live pool reports 89 rows
served; a table deliberately pinned to the retired libiconv 1.18-1 repins to
1.19 with the pool's hash through the real network path; 19 new tests; the
tests/pm, tests/ci and tests/scripts suites have the same failure set as the
base commit (91 pre-existing Windows environment failures, none new).
2026-09-16 11:46:30 -04:00
ethernet
e9c7547c53 feat(pm): redraw install/download progress in place on a terminal
hermes pm install (and activate/activate.ps1 behind it) now updates a
single live progress line instead of printing one line per ~4 MiB tick.
Detected via isatty(); stderr is the fallback because activate.ps1 pipes
stdout through Out-Host while stderr stays on the console. Off a TTY the
output is unchanged (same throttle, one line per tick) so CI logs are
unaffected.
2026-09-16 10:34:04 -04:00
ethernet
59013e8247 fix(pm): exclude internal build tools from source install roots 2026-09-13 20:43:07 -04:00
ethernet
5a89533e1c fix(pm): repair existing launchers before dependency installation 2026-09-13 18:16:30 -04:00
ethernet
308d653baa fix(pm): reuse upstream responses and new artifact hashes per update 2026-09-13 12:52:35 -04:00
ethernet
131ad86ad3 fix(pm): preserve complete environments and bound install work 2026-09-13 12:52:35 -04:00
ethernet
53e6f001c7 refactor(pm): consolidate runtime ownership and updater completion
Run historical updater completion in a fresh interpreter so cached imports
cannot revive retired dependency installers. Share Git and ZIP completion,
carry receipt and recovery state, and preserve child exit status.

Route plugin admission, binary acquisition, desktop launch and build paths
through PM. Replace redundant helpers and tests with real worker, package,
publication and launch checks. Keep the shipped compatibility surface fixed.

Targeted Python and desktop checks pass. Native update journeys and fresh
production image qualification remain pending. This is a checkpoint before
those acceptance runs.
2026-09-12 16:30:35 -04:00
ethernet
5e4a2a3d24 refactor(pm): remove legacy dependency and launch managers
Competing installers and checkout-local venv assumptions bypassed PM
selection, install consent, and generation lifetimes. Route consumers
through PM and installation-bound launchers. Refresh source launchers
before obsolete Python entries can be collected.

Remove Node, browser, and CUA acquisition engines, obsolete venv-holder
handling, detached sync, and unused PM APIs. Keep historical updater
exports inert and preserve external tool ownership and native integration.

Share product freshness and prepared inputs across builders. Align plugin
admission, Docker provisioning, setup instructions, and behavioral tests.

Verified targeted Python and JavaScript tests, desktop and web typechecks,
scoped lint, real product builds, and the Docker frontend smoke test.
The missed post-setup test cleanup is included and verified.

Native Windows/macOS execution, full Rust compilation, and the complete
repository suite remain unverified. Historical compatibility requirements
were preserved and extended, not fully rescanned.
2026-09-12 14:57:38 -04:00
ethernet
2a0b69858d fix(build): reuse the shared PM cache during payload staging
Resolve the default cache before isolating HOME so payload builds use the directory that CI restores and saves. Remove the standalone bundle workflow dependency on an unset cache variable.

Verified offline wheel reuse in isolated children, 20 focused tests, Ruff, and actionlint. Full native release builds and the separate source-build timeout remain unverified.
2026-09-12 13:11:36 -04:00
ethernet
cc48185220 refactor(pm): expose Python operations instead of uv binaries 2026-09-11 18:05:28 -04:00
ethernet
fea2858c99 merge: unify shared product builders, caches, and Windows prerequisites
Merge ethie/shared-product-builders with the CI dependency cache and native Windows setup work. Preserve UTF-8 diagnostics in the shared Python environment runner. Pass a persistent cache through isolated native staging and PM-runtime construction. Reuse one Windows prerequisite installer from source setup, native adapters, and CI, preserving Rust homes across HOME isolation.

Verified 85 targeted Python tests (5 host skips), 18 JavaScript tests, workflow validation, and scoped lint/typecheck. On native Windows ARM64, five prerequisite contracts passed and the actual shared provider reused OpenSSL, compiled its header with MSVC, and retained Rust under isolated HOME. Full signed distribution builds and live Actions cache transfer remain CI verification.
2026-09-11 13:45:05 -04:00
ethernet
284dbaf537 fix(pm): isolate bootstrap dependencies and unify YAML on ruamel
Activation reaches plugin discovery before the application dependencies
exist. Give PM its own locked Python project and runtime so it can install
or repair the application without importing that dependency tree.

Keep PM outside the application workspace. A shared uv workspace resolves
the application graph and cannot provide this isolation. Route mutations
through an isolated worker and preserve transaction callbacks, cancellation,
custom package registrations, and correlated receipts.

Use the same runtime builder for source installs and packaged payloads.
Keep offline wheelhouse support in that builder. Nix builds the independent
PM lock as a separate derivation. Refuse lazy-disabled bootstrap before
installing tools or dependencies.

Move first-party YAML readers and writers to ruamel. Keep the application
lock's transitive PyYAML requirements for third-party packages.

Verification:
- Focused canonical Python suite: 177 passed, 1 host-gated skip.
- Electron backend probes: 12 passed. Electron typecheck passed.
- Both uv locks, scoped lint, Bash syntax, and whitespace checks passed.
- Cold activation, corrupt-app repair, offline staging, and relocation ran.
- Built and exercised the Nix PM runtime and standalone YAML merge script.

Six broader caller test files retain the same 24 failing test IDs as an
archive of HEAD. The existing real-home guard blocks those tests before
they can exercise the affected paths. No full-suite pass is claimed.
Native Windows signing and full Bionic package execution remain unverified.
2026-09-11 12:23:51 -04:00
ethernet
e86d31fade fix(pm): refresh artifacts within the same minor version
BtbN publishes new FFmpeg builds without changing the version number.
The shared-minor comparison therefore reported stale artifacts as current.

Compare advertised artifact URLs during resolution and hash changed URLs
when applying the update. Keep dry-run checks metadata-only and preserve
pins for targets without an update source, including Termux.

Verification: 41 focused tests passed. The regression exercises real
archive downloads, installation, retained target pins, and a second
update that performs no writes. Native ARM64 FFmpeg also passed a real
16 kHz audio encode after installation.
2026-09-11 09:24:18 -04:00
ethernet
b2be572937 fix(pm): refresh dependencies with the installed project tools
The uv step used a nonexistent command. Both dependency steps used the
caller directory instead of the PM repository. Run uv lock --upgrade
and the installed npm executable in the correct project. Require the
installed tool closure without installing a fallback.

Reuse npm environment sanitization for unpack and update. The separately
pinned npm keeps its Node dependency on PATH without changing the parent.
Missing tools and failed commands return failure before venv sync.

Real uv and npm commands ran in guarded temporary projects. The test
removes Node's bundled npm before update and preserves unrelated files.
The missing-Python test checks the actual refusal and unchanged facts.
All 87 focused tests pass. Lint passes. No project pins or locks changed.
2026-09-10 03:50:40 -04:00
ethernet
8afc241e6e fix(pm): fail updates when package resolution fails
A failed lookup was reported as no change and returned success.
Track failures where exceptions occur rather than parsing status text.
Report independent successful lookups, then stop before any pin,
install or dependency refresh if one lookup failed.

Verification: the real PM CLI exercises check and apply with failed,
mixed, current and manual-source results. Mutation boundaries stay
unreached and the temporary lock stays unchanged. The focused gate
passed 36 tests with no failures. Lint passed.
No upstream package, real pin table or installed environment changed.
2026-09-09 23:55:53 -04:00
ethernet
1c8fae6180 fix(pm): preserve runtime and user state across failure paths
Keep downloads bound to their remote representation and publish through
atomic destination-local staging. Serialize shared partial ownership.

Keep explicit CA trust scoped to provider probes. Preserve checkpoint
history and edited files, validate all profile inputs before dependency
publication, and separate data removal from installed runtime ownership.

Exclude machine-specific PM state from portable transfers. Keep plugin
files and nested skill tools intact. Preserve native test isolation.

Focused native Windows receipts cover the individual repairs and their
integration. This commit does not claim a full-suite or release build.
2026-09-09 15:17:08 -04:00
ethernet
8b7eae99ef fix(pm): own interpreter selection and dependency recovery
Pin uv and uvx to the PM interpreter instead of ambient Python discovery.
A matching dependency stamp cannot prove that installed files still exist.
Repair now rebuilds the recorded workspace and lock in a fresh generation,
checks startup imports, and publishes the selection only after success.

Run startup recovery before dependency activation. Keep manual PM repair
reachable when the selected environment is damaged. Preserve plugin
selection, retry ownership, and the previous generation on failure.
Remove the separate pip, ensurepip, per-extra, and install-time quarantine
ladders. Keep orphan launcher restoration.

Verification: 717 targeted tests passed on native Windows ARM64, with
56 skipped. Ruff, diff checks, and the source-scoped compat check passed.
A disposable real Hermes install recovered deleted YAML and dotenv files,
then printed CLI help with exit 0. Its lock and stamp stayed unchanged.
The full suite and a release build were not run for this change.
2026-09-08 23:39:55 -04:00
ethernet
712734436e fix(pm): make bootstrap and bundle ownership explicit
Finish bootstrap uv before PM replaces its store entry. Keep failure
receipts stdlib-only and align the cryptography requirement and override
with the locked version.

Let bundle builders declare launch paths and update ownership. Remove
payload discovery, Store probing, and the unused develop command.
Derive Nix Python from the PM lock and share its provenance stamp.

Document setup, activation, optional dependencies, and distribution
ownership. Targeted Windows tests, relocated runtime launches, Electron
bundling, and bilingual docs builds pass. Native Nix and signed-package
acceptance remain CI gates.
2026-09-08 00:24:51 -04:00
ethernet
1a09c42414 refactor(bundle): share Python payload assembly across desktop and Termux 2026-09-06 22:21:06 -04:00
ethernet
0b30c2484d merge: integrate termux cli bundles into pm-clean
Merge ethie/cli-bundles at 0765ad689b.
Keep PM runtime publication, install identity, TLS policy, and module
boundaries from pm-clean.

Resolve the Node version-discovery method in its owning class. Preserve
staged tools if a repin download or publication fails. Carry extra-only
memory-provider setup through PM and retain restart-required reporting.
Keep target-specific TUI path assertions and discard obsolete self-lock
fixtures and the orphaned Windows service handler.

Verified locally with the canonical Python runner, root JS checks, TUI
checks/build, shell parsing, and workflow YAML parsing. Existing platform
pins and executable modes are unchanged. No full-suite CI, new bionic
bundle, or phone acceptance is claimed for this merge.
2026-09-06 20:13:45 -04:00
ethernet
3c08d16ba7 fix(pm): close runtime publication and updater audit gaps
Dependency publication now recovers interrupted config/facts changes before
activation and leases live generations during collection. Receipts retain
update correlation and failed steps across nested command boundaries.
Doctor and desktop surfaces report those failures through shared owners.

Move checkout updates out of the desktop facade. Stage a detached Windows
relaunch waiter before shutdown, with bounded handshake and process-birth
checks. Keep packaged lifecycle tests isolated from the installed app.

Native verification exposed two production races: cron maintenance imported
the interactive CLI and rewrote TERMINAL_CWD, and install-ID reads collided
with first publication. Use the existing owners and locks. Plugin checks
now run at startup and each due-gated housekeeping tick, not after 60 ticks.

Share updater-test mutation boundaries and remove collection-root fixtures.
Separate cold MCP startup from command latency and give the real HTTP drip
test enough time to reach body handling.

Root npm check passed, including packaging. The fixed-tree Windows Python
run reported 44557 passed, one failed, and 1404 skipped, plus one retry-only
HTTP test. Those final failures now pass in a 35-test bounded batch. A real
isolated gateway wrote startup and periodic plugin-check receipts.

Full final-tree CI, bundled Sandbox deployment, and actual App Installer
relaunch remain unverified. docs/pm-audit-status.md records these limits.
2026-09-06 11:45:41 -04:00
ethernet
92686159d1 fix(pm): integrate audited runtime and lifecycle repairs
Prepare dependency generations before selecting them. Keep shipped tool
bytes separate from writable additions, and store facts beside their entries.
Validate proposed plugin sets before config publication. Restore the previous
config if the facts write fails.

Consolidate duplicate updater, backup, setup, and voice helpers. Repair
launcher selection, dependency consumers, download ownership, update feeds,
and native Windows process and file handling.

Verification: 206 changed/prior-failing Python files reported 4630 passed,
one failed, and 330 skipped. Fix the remaining Hindsight fixture boundary.
The final targeted rerun reported 234 passed and two skipped. The store
review regression batch reported 83 passed and one skipped. Desktop
TypeScript checks, 56 selected Electron tests, 24 release tests, and the
removed-import/compatibility guards passed.

This is an integration checkpoint, not full audit acceptance. The complete
Python suite has not run on this fixed tree. Crash-atomic plugin publication,
generation cleanup, receipt correlation, and packaged lifecycle acceptance
remain open in docs/pm-audit-status.md.
2026-09-05 22:36:48 -04:00
ethernet8023
0ae85925f3 feat(termux): pinned termux toolchain via pm (linux-arm64-bionic)
A seventh pm target (linux-arm64-bionic) stages the TUR python3.11
.deb, the termux nodejs/uv .debs, and their runtime-lib deps into the
payload -- same pm-consumer shape as the desktop legs: pm owns the pin,
the hardened download (redirect-safe, retry-wrapped), the ar+tar
DebPackage extraction, and file-evidence verify for binaries the
staging host cannot execute.
2026-09-05 20:00:00 -04:00
ethernet
5db2124515 fix(pm): drop x64 vcruntime140_1.dll pre-publish so the recorded digest matches shipped bytes
The win32-arm64 bundle stage deleted vcruntime140_1.dll (the x64 VC
runtime python-build-standalone ships beside ARM64 Python; it cannot
load there and would fail the arch guard) AFTER store.publish() — but
facts.record(digest=tree_digest(entry)) had already hashed the entry
WITH the dll. pm doctor's re-hash then flagged python on every
bundle: 'realized bytes do not match recorded digest' — red from the
commit that introduced both the drop and the digest check (3d12e86ef1),
masked until the smoke test's earlier dead-shim failure was fixed.

The drop belongs in Python.stage(), next to the macOS signing hook —
both are post-extract, pre-publish mutations, so the recorded digest
covers the shipped bytes. The cli-level _drop_unloadable_runtime_files
bundle hook is removed in full (its two tests now pin the stage()
contract). The pm-store CI cache key rotates to v2: failed arm64 runs
saved poisoned facts (dll-less entry + with-dll digest) under the old
key and would restore forever.
2026-09-04 19:01:27 -04:00
ethernet
bf242f3fe7 feat(pm): receipts as the universal machine-readable venv-op surface
Every pm venv sync — startup, plugin install, update rebuild — now
writes a receipt with the SAME schema the updater's receipts use,
into the same <HERMES_HOME>/logs/update_receipts/ dir, separated by a
'kind' field (settled 2026-09-02 plan, task 8):

- pm/receipt.py: begin/record_step/record_venv_rebuild/
  record_bisect/record_feature_list/finalize + rotation (keep 20) +
  latest.json pointer. snapshot() lets the updater EMBED the sync
  sections into its own receipt (one schema, one dir, one reader).
  Exception-swallowing throughout — receipt machinery can never break
  a sync.
- Venv.apply union path: records feature_list, venv_rebuild, and
  bisect decisions; outcome 'ok' | 'bisected' (failures raise before
  the receipt block and surface via the update receipt's error path).
- `hermes pm status` (new verb): prints the latest receipt as JSON —
  the CLI/TUI reader. Desktop IPC reads the same latest.json (the
  hermes:version/syncStatus wiring lands with the desktop branch's
  About/update surface, which already consumes update receipts).

tests/pm/test_receipt.py: 5 tests (roundtrip, latest-pointer,
no-begin no-op, snapshot lifecycle, empty home). tests/pm: 180
passed, 0 failed.
2026-09-02 20:02:15 -04:00
ethernet
ba39525c39 feat(pm): ship the uv cache + mutable-venv bootstrap seed for sealed installs
The blow-the-venv-on-update contract needs rebuilds to be cheap, and
sealed installs need a writable venv at all (settled 2026-09-02 plan,
task 7):

- uv_cache_dir(): hermes-owned machine cache at
  <default hermes root>/cache/uv — content-addressed, shared across
  profiles. uv_env() ALWAYS pins UV_CACHE_DIR there (ambient UV_*
  stripped), so the cache that ships is the cache that gets used.
  First call on a sealed install seeds it from the payload's shipped
  uv-cache/ (read-only payload can't serve uv's working cache); the
  .seeded marker makes it once-only and non-clobbering.
- pm bundle stages the warmed cache into the payload after the venv
  sync (uv-cache/ beside manifest.json) — warm 'uv sync --offline'
  rebuilds probed at 0.4s vs 1.2s cold.
- Venv.venv_dir(): sealed installs resolve the MUTABLE venv to the
  machine hermes root (<root>/venv), not the read-only payload;
  dev/source installs keep the repo-local venv unchanged.
- Venv.seed_mutable_venv(): the bootstrap seed — lazy-off installs
  copy the payload's shipped venv out as the starting point; lazy-on
  installs skip the copy (first sync builds fresh from the shipped
  cache). adopt() triggers it (KeyError-guarded, failure reported
  never fatal — a cold sync still converges).

tests/pm/test_uv_cache.py: 6 tests (env pinning + ambient strip,
payload seed + marker once-only, cold machine, sealed venv_dir, seed
copy idempotence, lazy-on skip). tests/pm: 175 passed, 0 failed.
2026-09-02 20:00:34 -04:00
ethernet
02eb115f60 feat(pm): frozen bundle feature set — enabled-features.json
Lazy installs OFF is now a real contract, not just 'refuse
everything': the bundle's EXACT extras list is the frozen feature set
(settled 2026-09-02 plan, task 5).

- pm/features.py: write_features()/read_features() over
  enabled-features.json at the payload root (bundle-written, beside
  manifest.json; at runtime store_root().parent — the same relative
  location on both install kinds). installed_extras() records what
  `uv sync --all-extras` ACTUALLY installed on the target: every
  declared extra whose pm anchor resolves in the staged venv —
  marker-gated extras show up as missing anchors, the honest
  per-platform record.
- cmd_bundle: after the staged venv sync, write the features file into
  the payload ('✓ enabled-features.json (N extras recorded)').
- sync_venv: when security.allow_lazy_installs is false and the file
  exists, requested extras OUTSIDE the frozen set are refused with a
  message naming the policy.
- Venv.apply: plugin members + lazy-off + frozen file = loud InstallError
  (the bundle IS the install; never union plugin deps into it).

tests/pm/test_features.py: 7 tests (roundtrip, absent/garbage reads,
payload-root path, anchor-truth installed_extras, frozen refusal,
in-set pass). tests/pm: 162 passed 0 failed.
2026-09-02 19:54:34 -04:00
ethernet
c52974ce8a feat(pm): add pm update — resolve latest versions, re-pin the lockfile
Each Package subclass now declares how to find its own latest via a
latest_versions(target) hook (empty = no auto source). `hermes pm update`
intersects those candidate lists across every target the package serves,
compares against the lockfile, and re-pins + reinstalls the changed ones.

Version styles (Package.version_style):
- semver (default): one shared version across targets; update = highest
  version every relevant target serves (node, uv, gh, ripgrep, git,
  cua-driver, llama.cpp, agent-browser, npm).
- minor (ffmpeg): posix martin-riedl and win32 BtbN autobuilds have no
  shared release cadence, so the lockfile version label is major.minor
  and each target's exact patch lives in ITS artifact urls. An update
  moves to the highest major.minor every target serves; within it each
  target pins its own newest patch. A patch-only drift inside the shared
  minor does not move the label.

Resolvers hit the real upstream indexes (GitHub releases with prefix
strip + version-shape filter that drops sandbox/experimental tags,
nodejs.org index.json, npm dist-tags, martin-riedl root page parse for
per-platform epochs, BtbN autobuild assets, python-build-standalone
filtered to the locked 3.11 line — a major bump is never automatic).

CLI:
- `pm update [names...]` — re-pin + install changed packages, sync venv
- `pm update --check` — dry-run report (exit 1 if updates exist), never
  writes the lockfile, store, or venv
- `pm update --target T` — check-only cross-resolution for another target
- `pm update --uv` / `--npm` — also refresh uv.lock + venv / package-lock

chromium + chromium-headless-shell have no resolver (they follow
agent-browser, which pins its own browser pairing); venv is a state
package. Tests are pure (monkeypatched candidate lists) — the index
helpers are network I/O by design.

Verified: 136 pm tests pass; live `pm update --check` resolves node
26.7.0 → 26.8.1, uv 0.12.3 → 0.12.9, ffmpeg up to date, and reports
`no source` for chromium. GitHub unauthenticated rate limits (60/hr)
degrade to an honest per-package `resolve failed`.
2026-09-01 22:59:19 -04:00
ethernet
578a15a190 fix(pm): anchor downloader partials to a writable shared root
store_root() resolves into the read-only sealed payload on MSIX installs
(WindowsApps/agent-payload/tools), so the default partials area there
failed with WinError 5 on local-model downloads. Partials are mutable
machine-scoped state keyed by sha256(url); anchor them to the default
hermes root (cache/partials) instead of the immutable byte store.

- pm/paths.py: add partials_root(); drop the _default_partials indirection
- pm/downloader.py: Download resolves paths.partials_root() directly
- pm/cli.py: pm gc sweeps the new partials area (store root no longer
  contains them); guard bails only when both roots are absent
- tests: point resume + gc tests at the new root
2026-09-01 14:44:19 -04:00
ethernet
3de5990bd0 fix(pm): prune fetch-* cache and stale entries during bundle, test gc
The store's fetch-<sha> download-cache archives are install-time only —
dead weight once a package is published, in a staged payload AND in the
CI cache that restores apps/desktop/build/agent-payload/tools. They were
never in facts.entries_in_use(), so gc already dropped them, but nothing
proved it and nothing ran the sweep during the bundle, so the cached
store accumulated orphaned versions from older locks.

- extract the sweep core into _gc_store() (shared by pm gc and the
  bundle command).
- run it at the end of pm bundle, before venv sync and packaging, so a
  staged payload (and the CI cache) ships only live entries.
- test that gc removes fetch-<sha> dirs while keeping the live package
  entry.
2026-09-01 11:24:45 -04:00