55 Commits

Author SHA1 Message Date
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
24487f3db6 fix(pm): keep musl installs on compatible runtime artifacts
Skip glibc-linked FFmpeg on musl in both default install and update roots.
Select musl uv during the standalone shell bootstrap, and let the native
userland resolve libc before bootstrap Python build metadata.

Add regression checks for the closure, target precedence, and installer pins.
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
emozilla
9c1ef17550 fix(local-runtime): move a pre-PM llama.cpp engine into the PM store and pin b10964
The package-manager switch (#102765) made engine detection read only PM's
store and pinned every llama.cpp backend to b10362. A machine with an
engine under runtimes/llamacpp/b<tag>/<backend>/ reported no engine, so
the local models pane showed one-click setup with models already on disk.

installed_engine() now moves the newest pre-PM install into the store
once. The old manifest's archive digests become the PM identity, the
package verifier runs llama-server --version, and os.rename puts the
directory in place before facts.json records it. A store that already
holds an engine is left alone, a store lock held by another PM operation
defers the move, and an install that fails verification stays where it
is for the rest of the process.

The lock returns to b10964 on all five backends, the default before the
PM switch. With b10362 pinned, the pane offered a downgrade from the
moved engine as an update. Upstream renamed the ROCm archives to 10.0 at
b10767, and the hip assets follow. Only the CUDA build is measured at
b10964.
2026-09-25 22:07:48 -04:00
ethernet
067934a106 Merge pull request #122093 from benbarclay/fix/pm-agent-browser-exec-bit
fix(pm): make the staged agent-browser binary executable
2026-09-25 00:35:56 -04:00
ethernet
cae75f0ead fix(pm): an unreadable secondary profile config cannot fail an update
Venv.apply refused the whole graph when any secondary profile's
config.yaml was unreadable, so one broken sibling config failed every
update. Update syncs now leave that profile's plugins out of the union,
report it (stderr + receipt warning), and build the rest; the profile's
plugins rejoin on the next sync once its config is fixed. Boot currency
already skips broken secondaries, so the result reads as current.
Ordinary syncs keep refusing to shrink the recorded graph.
2026-09-25 00:00:09 -04:00
Ben Barclay
28edf7c75a fix(pm): make the staged agent-browser binary executable
The npm tarball ships every bin/agent-browser-* as 0644; agent-browser's
own postinstall sets the exec bit, and pm runs no postinstall. The first
browser_navigate auto-installs agent-browser and then fails with
PermissionError.
2026-09-25 11:19:18 +10:00
ethernet
f67b3fcece fix(update): install required PM tools before the venv sync on every update
A normal `hermes update` only re-synced the venv. The sync pulls uv/python
in through its own dependency, but a ripgrep/ffmpeg/node/npm pin bump in
pm/lock.json was never installed, so PATH activation warned and skipped the
managed tool dirs on every CLI start and gateway boot. Only the takeover
route for historical releases ensured the tool roots.

Move the takeover's loop into pm.client.ensure_tools_for_sync() and call it
from both routes before the sync: update_completion._prepare (the CLI and
Desktop route, running from the new tree so the new lockfile applies) and
_update_takeover.prepare. It uses explicit=True like the takeover (an update
is an explicit user action) and a failed download fails the update.

post_update.step_provision_runtimes / MACHINE_STEPS stay: `python -m
hermes_cli.post_update --scope machine` and tests still reference them.
The ffmpeg docstring no longer claims that step re-ensures it.
2026-09-24 17:30:44 -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
fdea3e92e0 fix(pm): ship the VC++ runtime beside ARM64 Windows ripgrep
The aarch64-pc-windows-msvc rg.exe imports VCRUNTIME140.dll (the x64
build embeds the CRT). A fresh Windows, such as Windows Sandbox, has no
copy, so staging failed verification:

  rg.exe --version exited 3221225781   (0xC0000135, STATUS_DLL_NOT_FOUND)

Our pinned Python already ships the ARM64 vcruntime140.dll. Make ripgrep
depend on python and, on win32-arm64, copy that DLL into ripgrep's entry
before publication. The loader searches the exe's folder first, and the
entry digest covers the copy.
2026-09-24 11:21:47 -04:00
ethernet
0372a8b2da fix(pm): keep npm's self-install cache out of the download entry
npm caches the tarball it installs. The cache was written next to the
archive, inside the store's fetch-<sha> entry. Removing that entry after
publication then had to delete a tree, and on Windows it failed while
Defender still held the fresh tarball copy:

  [WinError 145] The directory is not empty: ...\fetch-<sha>\.npm-cache\...

Give npm a throwaway temp cache whose cleanup cannot fail the install,
so the download entry holds only the archive.
2026-09-24 10:53:27 -04:00
ethernet
fec22d3ecd fix: refuse venv replacement when a secondary profile is unreadable 2026-09-24 03:11:50 -04:00
ethernet
bd392ba922 fix(pm): contain review security and dependency verification regressions 2026-09-24 02:03:59 -04:00
ethernet
c98bdb77f5 fix(pm): prepare the Windows ARM64 compiler environment for every source dependency build
cryptography ships no win_arm64 wheel, so every Windows ARM64 venv sync
compiles it from the sdist and needs MSVC, Clang, Rust and static OpenSSL.
Only setup-hermes.ps1 (and so activate.ps1) prepared that environment,
between a `pm install --tools-only` and the real sync. install.ps1,
`hermes update` and repair ran the same sync without it and failed in
openssl-sys.

PM owns the sync, so PM prepares it. pm/native_build.py holds the adapter
(moved from scripts/build/windows_deps.py) plus source_build_environment(),
which prepares only on win32-arm64 when the synced project carries the
provider script. A payload has prebuilt dependencies and needs no compiler.
VenvPackage.apply and build_environment pass the result to uv children
only. It carries the bridged pip index settings, which managed_environment
applies only to the ambient environment. The state root stays the store
parent, so existing vcpkg/OpenSSL builds are reused.

setup-hermes.ps1 collapses to one `pm install`: the tools-only split existed
only for this preparation, and pm install already puts its tools on PATH
before the venv sync (pm/cli.py activate check).

Not yet verified live on Windows ARM64.
2026-09-23 16:07:47 -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
bbec973514 refactor(pm): pm owns the dependency-environment layout and interpreter paths
hermes_cli.runtime_paths (venv generations, selection, activation) moves to
pm.environments, and gains venv_bin_dir / venv_python / project_python. Every
in-tree caller asks pm for an interpreter now; pm no longer reaches back into
hermes_cli for its own environment layout (pm.packages, pm.extras, pm.ensure,
pm.paths imported hermes_cli.runtime_paths). The three open-coded
"Scripts/python.exe or bin/python" ladders in pm collapse onto venv_python.

hermes_constants.venv_python_path / venv_bin_dir and hermes_cli.runtime_paths
stay as frozen-updater-surface shims only (tests/compat/old_updater_surface.json).

To keep the boot path light, pm/__init__ resolves its facade lazily (PEP 562)
and pm.registry loads the built-in package definitions on first read instead of
at import: `import hermes_bootstrap` now loads pm + pm.environments only (25ms,
was 37ms with the eager facade dragging in the downloader). The stripped-payload
fixtures that ship only pre-import files keep working for the same reason.

Also restores two frozen-surface re-exports the F401 sweep dropped
(banner._github_compare_behind, cua_backend.resolve_cua_driver_cmd).
2026-09-18 20:02:36 -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
5ed85fc620 fix(pm): take Linux ffmpeg from BtbN, not martin-riedl
martin-riedl's Linux build is compiled without x11grab, so screen capture
on X11 cannot work with the ffmpeg we ship. Verified on the pinned
artifact: the 9.0.1 amd64 binary contains no 'x11grab'/'xcbgrab' symbol at
all, while BtbN's linux64-gpl build carries it.

Linux now resolves through the same BtbN/FFmpeg-Builds stream as Windows
(btbn_index() learned the linux64/linuxarm64 assets; Linux ships .tar.xz
where Windows ships .zip, hence the archive-extension split in the
binary_rel table). macOS keeps martin-riedl and the bionic target keeps
its termux .deb.

Lockfile: both Linux rows repinned to the autobuild tag the Windows rows
already use, so one upstream snapshot covers win+linux. Hashes are from
the real downloads.

Tests: the three fixtures that encoded 'linux -> martin-riedl' now encode
the source split instead.
2026-09-16 18:41:17 -04: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
ethernet
2efa4ff94f refactor(desktop): prepare dependencies before saving build caches
Dependency acquisition during packaging left native wheels and packager
inputs outside the pre-build cache save. Compose PM and existing providers
into a preparation phase, then require builds to consume admitted inputs.

Share native preparation with PM Bundle. Keep path-bound environments and
signing outputs separate from reusable caches. Use read-only cache tokens
for commit builds and preserve the one-command local build path.

Verify pinned tools through PM, probe PTYs under the prepared Electron,
and supply dmgbuild through a build-only PM package. Resolve bundled tool
stores from their payload manifest so relocation preserves discovery.

Validation: focused Python and JS tests, checkJs, Ruff, Windows checks,
anti-slop, cache relocation, and network-denied Linux AppImage builds.
Relocated runtime smoke passed with NixOS host libraries supplied.
Native Windows/macOS signing and live GitHub cache behavior remain untested.
2026-09-13 14:28:31 -04:00
ethernet
131ad86ad3 fix(pm): preserve complete environments and bound install work 2026-09-13 12:52:35 -04:00
ethernet
7d73a180ae Merge branch 'ethie/uv-build-logs' into ethie/pm-clean 2026-09-13 11:18:46 -04:00
ethernet
d190198c77 Make PM workers own plugin publication and recovery
Replace member and publication callbacks with concrete selection/staged-tree requests. Discover under the install lock, validate staged identities, preserve YAML 1.1 semantics, reject stale config and concurrent inputs, and journal code/config/metadata before selecting facts. Keep boot recovery stdlib-only and preserve inactive/code-only publication.

Fix missing-but-enabled target discovery and propagate explicit install intent into Venv tool acquisition. Migrate memory-provider candidates to concrete additional directory inputs. Preserve progress/cancellation and existing updater compatibility surfaces.

Verification: focused baseline 64 passing tests; final 32-file run 282 passed, 0 failed. Real PM-only workers, local wheels/projects, competing edits, exact rollback, and 16 process-death publication boundary cases exercised.
2026-09-12 19:06:05 -04:00
ethernet
b1cad3aa24 fix(pm): preserve cross-target stages and recorded build inputs
Cross-target Node verification attempts to execute foreign bytes before
and after publication. Check the native target before smoke probes, while
retaining file and architecture checks for every target.

Repair must retain a plugin's build directory when it contains the declared
PEP 517 backend. Use the same copy exclusions as the initial snapshot.

The foreign-ELF execution trap and offline real-uv replay test fail before
the fixes and pass after them. Native smoke probes and bionic no-execution
checks also pass. The focused PM run reports nine unrelated failures,
all reproduced at the starting commit. No full suite or native Windows
validation was run.
2026-09-12 19:04:37 -04:00
ethernet
7f82a86978 Make dependency workspace preparation fresh-generation only
Require the caller-selected source, output, seed and prepared environment. Delete reusable-root defaults, changed detection, seed precedence, replacement cleanup and fallback engine creation. Preserve frozen recorded-workspace replay; refuse existing outputs and missing explicit seeds.

Migrate lifetime tests onto fresh snapshots and real uv builds. Remove obsolete managed-uv default-lifetime tests, retaining explicit-engine routing coverage. Combined focused acceptance: 221 passed, 2 skipped; known stale node-sidecar and bionic PATH assertions excluded, along with unavailable Python 3.11 bootstrap.
2026-09-12 19:00:34 -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
cc48185220 refactor(pm): expose Python operations instead of uv binaries 2026-09-11 18:05:28 -04:00
ethernet
01821b8e14 refactor(pm): consolidate private Python environment engine 2026-09-11 18:00:04 -04:00
ethernet
1bf588234c refactor(build): share product recipes across distributions
Build TUI, web, desktop UI and runnable agent products from explicit
prepared inputs. Keep dependency preparation separate from distribution
packaging, with PM and native builds sharing uv environment construction.

Docker copies compiled frontend products instead of build dependencies.
Nix retains uv2nix environments and consumes shared assembly through store
references. Native desktop and Termux use the same launcher and frontend
contracts. Preserve the independent PM runtime and source imports from
arbitrary working directories.

Keep failed frontend builds from replacing the previous product, reject
source/output overlap, and bound dependency-process output draining.
Include hermes_wisdom in the Nix wheel: real CLI smoke tests exposed its
missing package declaration on the base revision too.

Verified focused Python and JavaScript suites, Docker build/runtime checks,
Nix desktop and CLI/ACP checks, standalone TUI and packaged Electron PTY,
and real full-Chromium interaction. Native signed installers, Android device
installation and the full repository suite remain CI verification.
2026-09-11 13:16:55 -04:00
ethernet
6756d11b5f fix(pm): ship full chromium without headless shell
Full Chromium serves both headed and headless sessions. The separate
shell duplicates the browser payload and is not needed for either mode.

Remove the shell from PM and Docker. Select the managed Chromium
executable for agent-browser and the full Chromium channel for direct
Playwright callers. Route setup through PM and remove retired packages
from cached bundle stores without changing the user's tool store.

Update signing, architecture checks, launch probes and install guidance.
Leave llama packages and Docker archive cleanup unchanged.

Verification:
- Real agent-browser navigation, clicks, DOM reads and screenshots pass
  in headed and headless modes with the same Chromium executable.
- The direct Playwright doctor probe passes.
- Focused Python and desktop packaging tests pass, as do six Docker
  checks and both real-browser task-scroll tests.
- The built linux/amd64 image is 1.393 GB compressed, 223.6 MB smaller.
- The broader PM suite and two unrelated setup tests still fail.
  Those failures reproduce on unchanged HEAD.
- Five updated eval scripts parse; their full scenarios were not run.
2026-09-10 16:08:03 -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
e9bf6d97c2 fix(pm): select the Python identity advertised by its asset
The resolver combined the locked patch with a newer release tag.
That combination did not name the archive advertised by the release.
Return the complete identity from an exact matching filename instead.

Keep the locked minor and reject free-threaded or mismatched-tag assets.
The URL builder supplies its existing target mapping to the resolver.
Bionic remains a manual source. No version pins change in this commit.

Verified: 47 tests passed. The real package caller reconstructs each
accepted asset name, with negative controls for lookalikes and tag skew.
A fresh upstream metadata snapshot agrees with all six PBS target URLs.
No interpreter artifacts were downloaded or installed.
2026-09-10 02:40:54 -04:00
ethernet
bb824c381f merge: combine network retries with downloader safety
Keep the retry branch's bounded network policy under installation.
Preserve destination-local atomic publication, partial ownership,
representation checks, aggregate progress and operation-owned pause.
Keep the serial CDN fallback active across subsequent retry attempts.

Retain exact-origin authorization for index reads and safe redirects.
The retry policy does not retry hash, disk or certificate failures.

Verified with canonical native Windows ARM64 tests for both branches,
local-model download consumers, PM core and update resolution.
The commit-build draft remains uncommitted and unchanged.
2026-09-09 17:40:19 -04:00
ethernet
bdb52ce711 fix(local-models): use pm pins and resumable component downloads
Use PM for engine binaries, dependent runtime archives, and model files.
Keep one operation-owned pause event through installation and download.
Report whole-plan bytes and retain paused jobs across desktop remounts.

Preserve the completed lm-pm worktree as its own integration parent.
The owning session verified focused Python and desktop tests, actual
Windows ARM64 CUDA downloads, and rendered pause/resume controls.
Combined verification with the safety repairs follows in the merge.
2026-09-09 15:17:09 -04:00
ethernet
8e4a804f53 fix(pm): retry transient network failures
A transient HTTP 500 during the range probe aborted bundle staging.
Retry PM-owned requests and body transfers with one bounded policy,
instead of retrying complete installs or stacking caller retry loops.

Keep partial ranges across attempts and interrupt backoff on pause.
Keep the CDN serial fallback active until its source finishes. Do not
retry permanent HTTP errors, certificate failures, bad hashes or disk
errors. Use the same policy for metadata reads and artifact hashing.

Verified: 217 focused tests passed, 1 skipped, and Ruff passed on the
changed Python files. The pinned Windows ARM64 uv archive also survived
an injected HTTP 500 through the PM CLI and its installed binary ran.
The full suite and desktop release build were not run.
2026-09-09 12:54: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
cd0f97f833 feat(python): pin bundled runtime to 3.14 everywhere (pm, termux lane, CI, installers)
pm python node: 3.14.7+20260901 (freshest python-build-standalone 3.14
build) for the 6 desktop targets; the bionic row moves from the third-party
TUR python3.11 deb to the official termux-main python_3.14.6-1 deb (which
lags PBS by one patch — pinned manually, documented). All 7 digests fetched
from the live sources (PBS release API + termux-main Packages index).
pm/packages.py: main_bin_rel python3.14, deb_package python, bionic fetch
constant, latest_versions guards bionic (no PBS build exists).

termux lane: PYTHON_ABI cp311->cp314, python3.11->python3.14 paths,
libpython3.11.so->3.14, TARGET_ENV 3.11.15->3.14.6 AND sys_platform
linux->android (CPython 3.13+ reports 'android', docs-verified) — linux-
gated markers no longer admit the termux target. runtime_libs.json needs no
change: every python 3.14.6-1 Depends is already staged.

CI: python-version/--python 3.11->3.14 across all 11 workflows incl. the
uv lockfile-check lane. Installers derive the minor from the lock already;
fallbacks bumped. Sandbox images nikolaik/python-nodejs:python3.11-nodejs20
-> python3.14-nodejs22 (tag exists). runtime_repair fall-forward cap now
tracks the <3.15 requires-python window. Docs/README python version claims
updated.
2026-09-07 14:19:18 -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
7606b014f5 fix(termux): complete the sealed CLI payload and verify installed startup
Stage npm, ffmpeg and its bionic runtime libraries, and static ARM ripgrep. Bind caches to actual build inputs. Generate entrypoints from the project manifest and verify real CLI/TUI startup and media conversion offline. Keep versions unchanged. Windows service work remains out of scope.
2026-09-06 14:00:42 -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
ad433f8029 fix(pm): review fixes — enabled-state union, recency tiebreak, gated bridge, sync authority
Spec + standards review (2-subagent /code-review) found five real
gaps against the settled design; all fixed:

- enabled_member_dirs() now FILTERS by enabled state: only plugins in
  some profile's plugins.enabled join the union (a disabled plugin
  never syncs). Result is ENABLE-RECENCY-ORDERED (newest last) via
  new pm/plugins_state.py — order-preserving reads of every profile's
  enabled list — so the bisect's incumbent-wins tiebreak (pop last)
  now disables the most-recently-enabled, not the alphabetical last.
- materialize_legacy_pyproject() is gated on lazy_installs_allowed():
  lazy-off installs keep the plugin dir untouched (materializing
  would create a member candidate and then hard-fail every sealed
  sync). Settled: 'never runs when lazy installs are disabled'.
- plugins_cmd dep install routes through resolve_union + the lazy
  gate (it previously drove lock_and_sync directly, bypassing both):
  the would-be union resolves as a check before enable, with the
  plugin's own bisect decision surfaced as the refusal reason. The
  real sync after enable still runs through sync_venv (the one
  authority, with receipt + write-back).
- resolve_union disable decisions are written back to the plugins
  enabled config (record_disabled_plugins → pm.plugins_state.
  disable_plugins) so hermes plugins list reflects reality and
  re-enable retries; best-effort, never breaks the sync.

tests: member discovery now asserts enabled-filter + recency order +
orphan exclusion; plugins_state suite (5: cross-home reads, order
preservation, disable write-back across homes, noop, garbage-config);
materialize lazy-off test; deps_flow updated to the resolve_union
shape. tests/pm + deps_flow: 193 passed, 0 failed.
2026-09-02 20:21:00 -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
1e97613206 feat(pm): venv-union auto-bisect — fail-alone disabled, incumbent wins
When the plugin union fails to resolve (update rebuild, plugin
install, any sync), resolve_union() bisects instead of leaving the
venv broken (settled 2026-09-02 plan, task 6):

- try the full union first — clean resolve passes through untouched;
- phase 1: each member alone against core — a plugin that conflicts
  with core pins is disabled with the resolver's message;
- phase 2: reduced union retry; mutual conflicts resolve incumbent-
  wins — the newest-enabled member (last in the discovery list) is
  disabled, the longer-standing setup survives; retries until the
  union resolves or every member is dropped.

Venv.apply routes the union through resolve_union and logs each
disable decision with its resolver reason (receipt surfacing lands
with the universal-receipts task). Pure and unit-tested with stubbed
lock_and_sync: pass-through, fail-alone, incumbent-wins tiebreak.

tests/pm: 165 passed, 0 failed.
2026-09-02 19:55:44 -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
878d33c3ad feat(pm): generated uv-workspace root unions plugin deps into the venv
pm/workspace.py owns the generated workspace root (settled design:
.hermes/plans/2026-09-02_164500-plugin-deps-workspace-union.md):

- build_root() writes <store_root>/.pm-workspace/pyproject.toml =
  core's pyproject verbatim + [tool.uv.workspace] members pointing at
  each enabled plugin dir via relative ../-escaping paths. The
  committed pyproject is never touched (sealed installs are read-only;
  member lists are machine-specific).
- enabled_member_dirs() scans all profiles' plugin dirs for python-dep
  declarations: pyproject.toml (modern) or legacy pip_dependencies/
  python_dependencies in plugin.yaml (bridge candidates). Per-install
  union (settled): profiles share the venv, so their plugins share the
  resolution graph. OSError-safe walks (dangling junctions).
- lock_and_sync(): build root -> uv lock -> uv sync --frozen --extras
  into the venv (UV_PROJECT_ENVIRONMENT pinned). One lock, one graph:
  core pins preserved (proven live: union of the real repo pyproject +
  a plugin kept rich==14.3.3 / httpx==0.28.1 while adding the plugin's
  own dep), conflict = loud resolver refusal naming both sides
  (proven live: a plugin pinning rich==13.9.4 against core's 14.3.3).
- Venv.expected_stamp folds in members_stamp() so a changed plugin set
  re-syncs; Venv.apply routes through the workspace root when members
  exist, plain uv sync otherwise (no behavior change for
  zero-plugin installs).

tests/pm/test_workspace.py: 8 tests (root location, verbatim core
carry-over, relative escaping members, idempotent build, zero-member
build, stamp hashing, member discovery incl. legacy + broken-root
resilience). tests/pm suite: 152 passed, 0 failed.
2026-09-02 19:43:35 -04:00