Commit Graph

263 Commits

Author SHA1 Message Date
Teknium
c8ef5a0980 revert(pm): leave pm receipts where pm writes them
pm/ is bundled package-manager code outside this lane. pm.receipt.latest()
(hermes pm status) reads the same dir pm writes, so nothing is lost: the
updater embeds the pm sync section in memory (last_for_update) and its own
receipts stay under the root home.
2026-10-06 03:44:49 -07:00
Teknium
ba03113d96 fix(update): read update.log and receipts from the root home
The writers moved to the root home; the readers did not. `hermes logs update` /
`handoff`, `hermes logs list`, the debug bundle, the dashboard's durable
update-completion marker, the post-swap hand-off file and pm's sync receipts
(same update_receipts dir) now use get_default_hermes_root(), so a sticky
profile no longer hides the update log.
2026-10-06 03:44:49 -07:00
Teknium
3dadeb9246 fix(pm): cold updates on Python < 3.13 trust a stored intermediate CA
An install still on its legacy 3.11 venv runs its first `hermes update`
from that interpreter, which downloads uv and Python 3.14 with plain
urllib before PM's worker (and truststore) exists. Windows can cache Let's
Encrypt "Root YR" in its intermediate store without ISRG Root X1; GitHub's
asset CDN now chains through Root YR, so 3.11 rejected every download with
"unable to get issuer certificate" while PowerShell and 3.14 accepted it.

Python 3.13+ sets VERIFY_X509_PARTIAL_CHAIN by default. Set it on the PM
downloader's opener so the legacy interpreter applies the same rule;
hostname, expiry and pinned sha256 checks are unchanged.

Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com>
2026-10-05 23:37:02 -07:00
Teknium
de06a61c51 fix(pm): record a partial pm update as failed in the pm receipt
Follow-up to the salvaged per-package pin isolation (#125400). Once a
failing pin no longer aborts the run, the surviving pins still run the
venv sync, whose receipt finalizes `ok` as latest.json. `hermes pm
status` and the desktop sync card read latest.json, so a run that left a
package on its old pin and exited 1 was recorded as a clean success.

Write a kind=update `failed` receipt after the sync, with one failed step
per pin, whenever any pin failed (also when every pin failed, which
previously wrote no receipt at all). Trim the tests to the two
invariants and assert the receipt in the mixed case.
2026-10-05 15:34:40 -07:00
Yuan Li
87bfb9426e fix(pm): isolate per-package pin failures instead of aborting the run
_apply_pins let any exception from _pin_artifacts propagate: one broken
pin - a package's resolution bug (gh's 3-part target split) or one
target's upstream pool skew (Termux rolling pool 404 while nodejs.org
is ahead) - aborted the entire run, blocked every unrelated package,
and left lockfile.save() unreached, so any failure meant zero progress.

Catch per package, pin everything that succeeds, install the pinned
set, print a 'pinned X, failed Y' summary, and exit non-zero when
anything failed. A run where every pin fails still leaves the lockfile
untouched.

Fixes #125386
2026-10-05 15:34:40 -07:00
Brooklyn Nicholson
2d66d6e2f8 fix(pm): wait out Windows scanner holds in the archive hoist and entry swaps
flatten_single_dir renames a tree extracted seconds earlier, while
Defender or another scanner still holds it, and ffmpeg/agent-browser
installs died on the package/bin rename with [WinError 5] (#131884).
The entry displace/restore renames in install.py had the same bare call.

pm.filesystem.retry_held retries EACCES/EPERM/EBUSY for about 3s and
raises anything else immediately; the hoist, merge_tree and the entry
swaps use it.

Co-authored-by: bighamx <10262931+bighamx@users.noreply.github.com>
Co-authored-by: lzmuhioin <1814826+lzmuhioin@users.noreply.github.com>
2026-10-05 17:08:32 -05:00
Ryan Hill
13978ae5db fix(stt): bundle whisper.cpp CPU for Windows ARM64 2026-10-05 12:37:46 -04:00
Teknium
a4140eebb1 docs: root AGENTS.md becomes a hub; area rules move where they load
The root file is project context and was 38.7k chars, past the context-file budget
(20k floor, 30,720 on a 128k window), so every session lost its middle: routing table,
testing rules, code-shape rules. It now keeps the two invariants, a condensed rubric
(every rule kept), environment, structure, one-line rules that apply everywhere and the
routing table (11.8k).

Moved verbatim to where they load: Testing -> new tests/AGENTS.md; dependency pinning and
PM ownership -> new pm/AGENTS.md; machine facts -> new hermes_platform/AGENTS.md;
session-scoped tool availability -> tools/AGENTS.md; the full rubric with its real-close
examples -> developer-guide/contributing.md. Pointers in lint, checker messages, docs
and test comments name the new homes.
2026-10-04 23:05:42 -07:00
emozilla
7db063c59e feat(pm): pin llama.cpp b11370 for every backend
Moves llamacpp-cpu, -cuda, -vulkan, -hip and -metal from b11146 to
b11370 (bed0a8566). Only digests change: every pinned asset keeps its
name, no command-line option or server route Hermes uses went away, and
the Windows cudart zips are byte-identical, so they keep their digests.
Installed b11146 engines read as outdated and offer the update.

The Linux builds keep the same glibc floors (2.34 for x64 CPU/Vulkan,
2.38 elsewhere), so the bundled jammy libgomp still covers them.
2026-10-05 00:47:22 -04:00
emozilla
a7271835fe fix(pm): bundle jammy's libgomp so Linux CPU/Vulkan engines keep glibc 2.34
The libgomp .deb pinned for Linux engines came from Ubuntu 24.04 and
needs glibc 2.38. llama-server's $ORIGIN RUNPATH loads that copy ahead
of the host's, and the dynamic loader stops at the missing symbol
version, so the x64 CPU and Vulkan engines (glibc 2.34, built on 22.04)
failed PM's --version verify on Ubuntu 22.04, Debian 12, RHEL 9 and
Amazon Linux 2023.

Ubuntu 22.04's libgomp1 (gcc-12 12-20220319-1ubuntu1) needs glibc 2.34
and provides GOMP_4.5, the newest version any b11146 engine references.
The arm64, CUDA x64 and ROCm x64 builds already need glibc 2.38 from
upstream.

Verified through pm.install.ensure() in containers: the old pin fails
verification on Ubuntu 22.04; the new pin installs and runs on Ubuntu
22.04/24.04/26.04, Debian 12/13/testing and Fedora (x64, CPU and
Vulkan) and on Ubuntu 26.04 arm64, loading libgomp from the engine
directory.
2026-10-04 22:56:37 -04:00
kshitijk4poor
47e6c2ebb5 fix(update): keep the completion parent stdlib-only around the libatomic pre-install
run_completion runs in the OLD process after the checkout swap, and its
contract is never to import application helpers from the replacement tree.
Importing pm.libatomic there crashed the update when the new tree lacked
the module (downgrade or older pin) and on architectures where
current_target() raises. Run the pre-install as its own process from the
new tree, ignore its outcome, and let the in-lock repair report.

Also: gate on an openable /dev/tty first (cheapest check, and the detached
child exits immediately), treat an unsupported architecture as "skip",
bound the sudo -n probe, and drop the timeout on the interactive install
so a slow prompt or mirror is never killed mid-dpkg (it runs outside PM's
lock). Found in the final-head review of #132664.
2026-10-04 19:19:41 +05:30
kshitijk4poor
c4d4ac569f fix(pm): install libatomic before the detached update child, not a sudo ticket
hermes update's completion runs PM in a start_new_session child with no
controlling terminal, so the in-child sudo -v warm-up could never read a
password and the cold password-sudo update from #124926 still failed. A
cached ticket was also the wrong currency: under sudo's default per-tty
timestamps a ticket minted in one session is not visible to another.

Install the package itself, interactively, at the last point that still
owns the terminal: run_completion() before it detaches, plus the existing
ensure_tools_for_sync() entry for the other update paths. Gate on an
openable /dev/tty instead of isatty() (a detached child inherits a tty
stdin and a forwarded-isatty stdout). Inside PM's lock the repair is
unchanged: sudo -n, stdin closed. Reported by @JoaoMarcos44 on #132664.
2026-10-04 19:19:41 +05:30
kshitijk4poor
4cf2ea805f refactor(pm): prompt for sudo only when the repair would use it
The update warm-up resolved sudo on its own, so on a glibc host with no
supported package manager it still asked for a password the repair then
never used. Derive the prompt from the same _host_install_command and
_command_plan the repair runs, compute the target once, and correct the
module docstring now that there are two entry points.
2026-10-04 19:19:41 +05:30
kshitijk4poor
7d509de7e1 fix(pm): let hermes update authorize sudo before restaging Node
The libatomic repair can only use sudo -n under PM's install lock, and
hermes update enters PM directly without install.sh's prerequisites
warm-up, so a cold password-sudo host still failed the update from
#124926. Before publishing tools, ask once on an interactive glibc host
that still lacks libatomic.so.1 and whose sudo would prompt. Reported by
@JoaoMarcos44 on #132664.
2026-10-04 19:19:41 +05:30
kshitijk4poor
7159021d1e fix(pm): carry the libatomic remedy as InstallError's remedy
The hook glued the remedy into the reason, so the error ended with two
fixes, the second ("retry, or run hermes pm doctor") wrong for a missing
host library. Return (reason, remedy) and pass the remedy through. When the
package installs but Node still cannot load it, say that instead of telling
the user to run the command that just succeeded.
2026-10-04 19:19:41 +05:30
kshitijk4poor
e9886b5919 perf(pm): attempt the libatomic install once per process
Every root that depends on node (npm, browser tools) re-stages it after a
failure, so a host without libatomic ran the package manager under PM's
install lock once per dependent root. Cache the outcome for the process.
Also use platform.freedesktop_os_release() instead of a hand parser, and
run apt-get update once when a minimal image has empty package lists.
2026-10-04 19:19:41 +05:30
JoaoMarcos44
ad6760a416 fix(pm): repair Node libatomic at install boundary
(cherry picked from commit 2ac66739aa5c50fd352155e2c629271227a9697b)
2026-10-04 19:19:41 +05:30
emozilla
5059f20e1b Merge remote-tracking branch 'origin/main' into ethie/llamacpp-0.5.0-linux-cuda-main
# Conflicts:
#	hermes_cli/local_runtime/bootstrap.py
#	hermes_cli/main.py
2026-10-03 22:53:47 -04:00
unsupportedpastels
282a729cd1 fix(pm): name a shared checkout's owner by the interpreter its launcher execs
facts.json under a candidate root only says that root synced dependencies
for the checkout, and a borrowing launch writes that too. Ranking the root
the checkout sits in first fixed a default-root borrower of a custom-root
checkout but not the reverse: a checkout the default root keeps in
`install.sh --dir <dir>`, launched with HERMES_HOME=<dir>, made <dir> the
owner after its first sync, so the next launch rebound the shared launchers
to <dir>'s interpreter.

Only the owner publishes the checkout's launcher, and it execs the owner's
store Python. Among the roots holding state, the one whose tools/ holds that
live interpreter is the owner. Without a live launcher, the parent root
still outranks the platform default.

Co-authored-by: FroRaut <79215537+Froraut@users.noreply.github.com>
2026-10-03 22:46:14 -04:00
unsupportedpastels
78189b2bb6 fix(pm): a borrower's own dependency state never makes it the checkout's owner
owning_home_root() treated any candidate root holding facts.json as the
owner and returned None when the active root was one of them. A checkout
owned by a custom root (`install.sh --hermes-home <custom>`) and launched
from the platform default root borrowed correctly once; that launch's own
sync then wrote <default>/installs/<key>/facts.json, and every later launch
took the default root for the owner: its own tool store, launcher
publication and update tail, the behaviour this layer exists to stop.

The root the checkout sits in now outranks the platform default, and only
that one owner is compared with the active root.

Co-authored-by: FroRaut <79215537+Froraut@users.noreply.github.com>
2026-10-03 22:46:14 -04:00
FroRaut
d2d45b549b fix(pm): a borrowed HERMES_HOME leaves the source checkout to the root that owns it
Dependency state is scoped per data root (<root>/installs/<key>, and the
store under <root>/tools when the install stamp names no runtimeDir, as
source stamps never do). A launch of a shared checkout under another
root -- a test's temporary HERMES_HOME, a per-task home, a CI service
home -- therefore treated the checkout as a fresh install of its own:
it downloaded a full tool store into that root (~0.5 GB), synced a venv,
then ran the checkout's update tail against the shared tree (launchers
repointed at the temporary interpreter, web/TUI/desktop rebuilt,
install stamp and bootstrap receipt rewritten) under an update lock that
lives in the borrowing home. Deleting the home then bricked `hermes`,
and concurrent borrowers raced each other's product builds (#123238).

pm.environments.owning_home_root() identifies the root that installed
the checkout: the platform default root or the root the checkout sits
in, whichever holds committed state (facts.json) for it, when that state
is not the active root's own (the owner, one of its profiles, or a home
whose installs link back to it). A fresh install and a custom root that
owns its tree resolve to None and keep today's behaviour.

- store_root(): an unstamped store resolves to the owner's tools/, so a
  borrowing launch uses the interpreter the checkout's launchers already
  exec and downloads no tool store of its own. HERMES_RUNTIME_DIR and a
  stamped runtimeDir still win.
- prepare_launch(): a borrowing root syncs its own dependency
  generation, as a process spawned under a live update does, and returns
  its relaunch interpreter. It never arms or runs the tail, never
  publishes launchers and never removes the owner's update markers.

Tests: tests/hermes_cli/test_borrowed_home_launch.py (10). Each half
fails without its change: store_root's two borrowing cases, and the
prepare_launch tail/marker cases.

(cherry picked from commit 8c427c8389634f6cebc3a8ced9a626c6354e8a18)
2026-10-03 22:46:14 -04:00
emozilla
361963828d feat(pm): llama.cpp CUDA on Linux arm64; bundle libgomp for Linux engines
b11146 ships ubuntu-cuda-13.4-arm64 (+ cudart), so llamacpp-cuda gains
linux-arm64, auto resolves NVIDIA arm64 Linux to CUDA, and the desktop's
localModels flag ships on Linux arm64 too.

Every upstream Linux llama.cpp build (CPU included) links the system
libgomp, which minimal hosts (WSL, containers) lack, and a normal install
never touches the system package manager. Each Linux engine now pins
Ubuntu 24.04's libgomp1 .deb (glibc 2.38, the floor upstream already sets)
and stages libgomp.so.1 beside llama-server, where the $ORIGIN RUNPATH
finds it; the artifact mirror covers the pool URL after 24.04's EOL.
DebPackage's ar+tar unpack becomes pm.package.unpack_deb and reads zstd
data.tar (Ubuntu's default) with the stdlib codec PM's 3.14 runtime has.

Pre-PM engines on Linux carry only llama.cpp's own archives, so adoption
maps their manifests to those and records them as outdated (usable now,
update offered) instead of failing to match.

Verified on an RTX Spark N1X (WSL, Ubuntu 26.04, no system libgomp):
ensure_engine() for cuda, cpu and vulkan through PM's real download/stage/
verify path; ldd resolves libgomp from the entry; llama-server serves
completions (CUDA0 at 445 tok/s). linux-x64 cpu/vulkan staged through the
same path with no NEEDED library missing beyond glibc/libstdc++/OpenSSL 3.
2026-10-03 22:32:13 -04:00
686f6c61
2c79189699 fix(launchers): never bind a published launcher to an inherited runtime
stage_launcher resolved the store python through
pm.environments.store_root, which honors HERMES_RUNTIME_DIR. Any process
spawned with that variable set (PM's runtime_environment, the desktop
toolchain, an e2e fixture home) that reached launcher publication minted
.hermes/bin wrappers bound to the foreign store. When that store lived
under a scratch tree, its later cleanup deleted the interpreter, and the
gateway sat in an exit-127 crash loop after the next reboot (#131745).

Publication now resolves the tree's own store: resolve_store_python
gains honor_runtime_override (default True, so execution-side callers
keep today's behavior), and the persisting paths — stage_launcher, the
gateway service publication gate, expose_cli and the CLI publish entry —
pass False.

(cherry picked from commit 77370d46611efe1338c39c64f4951ab08dd32f1f)
2026-10-03 21:44:47 -04:00
teknium1
eb7e862032 fix(pm): confine the read-only retry to Windows unlink/rmdir failures
Review of #131847: the retry ran for every rmtree failure on every OS.
On POSIX an unreadable directory hands the callback os.open, and
func(path) raised TypeError (missing flags), which escaped the OSError
set-aside path and failed an install that had already published. And
os.chmod followed a symlink inside an unwritable directory, rewriting
the mode of a file outside the tree to 0200.

The retry now only fires for a PermissionError from os.unlink/os.rmdir
on Windows, and clears the link's own attribute where chmod can follow
links; everything else raises as plain rmtree does.
2026-10-03 01:11:10 -07:00
teknium1
3a744c7d66 fix(pm): clear read-only bits with a pm-local helper, not hermes_cli
The salvaged commit imported hermes_cli.fs_utils.rmtree_force inside
_remove_entry. PM installs run before the rest of the tree exists: the
Docker toolchain stage copies only pm/, so every image build died with
"No module named 'hermes_cli.fs_utils'" on its first fetch cleanup.

pm/filesystem.py is the stdlib-only home for exactly this kind of
primitive. hermes_cli/fs_utils.py stays as is; it is part of the frozen
old-updater surface.
2026-10-03 01:11:10 -07:00
teknium1
d00d57b70f test(pm): pin read-only file removal on a real Windows runner
A read-only file (PortableGit's etc/hosts) inside a replaced entry made
_remove_entry fail every attempt. Since the held-tree fix the install no
longer aborts, but the tree is set aside and gc can never reclaim it, so
every replaced git entry leaked. The live Windows case requires the
replaced tree to be gone outright after a reinstall. Also trims the
salvaged comment to its why.
2026-10-03 01:11:10 -07:00
blennerhassett_btye
be431788dd fix(pm): clear read-only bits when removing a replaced tool entry
`_remove_entry` used a bare `shutil.rmtree`. Git for Windows' PortableGit
archive ships `etc/hosts` read-only, and Windows refuses to unlink a
read-only file, so removing the rotated `.previous-git-*` entry raised
WinError 5 on every attempt. The retry loop cannot help here: a read-only
attribute is not a transient handle, so all 5 retries failed and the
whole update aborted with:

    Source update preparation failed: git: install failed:
    [WinError 5] Access is denied:
    '...\.previous-git-2.53.0+3-win32-x64\etc\hosts'

The abort happened before the checkout was reset, so `hermes update` made
no progress at all and the install stayed pinned indefinitely.

Use the existing `hermes_cli.fs_utils.rmtree_force`, which clears the
read-only bit before deleting and whose docstring already describes this
exact scenario. It is imported lazily to match how the rest of `pm`
reaches into `hermes_cli`.
2026-10-03 01:11:10 -07:00
teknium1
bd75d30917 fix(pm): set a held replaced tree aside instead of failing the install
Every cleanup that runs after a publish or restore has finished deletes
garbage: the displaced .previous-* entry, a restored .displaced-* tree,
the fetch-* archives. A running Hermes process keeps the replaced
interpreter's DLLs mapped, so on Windows that delete can fail far past
the retry window and turned a completed update into "install failed:
[WinError 5]" (#124807).

One helper now owns that cleanup for all four sites. When the delete
fails it renames the tree to .reclaim-<uuid> (Windows permits renaming a
tree with mapped images): the old slot is free for the next publish of
the same entry, no restore can ever pick the half-deleted tree up, and
the next install or `hermes pm gc` deletes it once the hold is gone.
This replaces the sidecar marker for restored displacements (#128955)
and the warn-and-keep for previous entries from the salvaged commit:
a kept .previous-<entry> blocked the next publish of that entry, could
be restored half-deleted, and was unreachable by gc after a version bump.
2026-10-02 16:45:39 -07:00
janviernine
62761ec991 pm: keep locked stale previous entries instead of failing install
Assisted-by: Hermes Agent
2026-10-02 16:45:39 -07:00
ethernet
d6bb42e8ba feat(activate): add activate.fish for the fish shell
`activate` is bash syntax, so fish users had no way to take on the dev
environment. `activate.fish` does what `activate` does: snapshots the keys it
will change, applies the composed env, defines a `hermes` function that
refuses outside the worktree, adds the prompt prefix, and defines
`deactivate`. It shells out to the same bash library, so composition is not
reimplemented.

`pm.environments --format fish` emits the env as `set -gx` statements.
fish's read-only names (`PWD`, `SHLVL`, `_`, ...) are skipped because
`set -gx PWD` aborts the script, and values named `*PATH` stay lists.
`hermes_compose_env` takes the dialect (`sh` or `fish`) as its second argument.
2026-10-02 18:43:30 -04:00
ethernet
b4d5092d3f refactor(activate): split interactive activation from running a command in the env
`activate` doubled as the thing scripts sourced non-interactively, so it
carried `--` argument guards and every caller had to tolerate its prompt and
`hermes` function. Composition (setup sync, bootstrap Python, pm env) now
lives in scripts/_activation.sh, and there are two consumers:

- `activate`: interactive, reversible (`deactivate`).
- new `scripts/run-in-hermes-env CMD...`: applies the env to one process and
  execs into it. Replaces `scripts/_hermes-python`; every Python script's
  shebang and `scripts/run_tests.sh` (which re-executes itself under it,
  however it was launched) use it.

`pm.environments --format sh` emits the env as shell exports, so the inline
JSON-to-export Python is gone from `activate`. An install that did not build
the test environment now reads as stale for every consumer.

Tests are split along the same seams: activation support fixtures, bash/ps1
activation, env-script quoting, and the runner's staleness.
2026-10-02 18:15:08 -04:00
Gille
98cceaef2b fix(pm): keep restored runtimes usable after locked displaced cleanup (#128955)
* fix(pm): tolerate locked restored displacement cleanup
2026-10-02 18:08:16 -04:00
ethernet
54e2a59bbc feat(pm): ship llama.cpp v0.5.0 (b11146) with Linux x64 CUDA
Upstream first published ubuntu-cuda archives after b10964, which is why
llamacpp-cuda was Windows-only and NVIDIA Linux users fell back to Vulkan.
b11146 (the v0.5.0 release) ships ubuntu-cuda-13.4-x64, so pin every llama.cpp
backend to it and add linux-x64 to llamacpp-cuda.

- The engine archive carries no CUDA libraries, so the Linux target pins the
  versioned cudart tarball too (Windows' cudart zip has no build tag in its
  name). The engine's RUNPATH is $ORIGIN, so stage() hoists the cudart
  tarball's top-level dir beside llama-server.
- Upstream dropped win-cuda-13.3 in the same window, so win32-x64 moves to
  13.4, matching win32-arm64. 13.x drivers run any 13.x runtime.
- linux-arm64 is left out: not verified on hardware.

Verified: ensure_engine("cuda") through PM's real download/stage/verify path
on an RTX 4060 Ti; llama-server --list-devices reports CUDA0.
2026-10-02 12:47:34 -04:00
teknium1
7b510caed0 build: drop the hermes-agent[supermemory] extra
The catalog plugin declares the Supermemory SDK in its own pyproject, so
the plugin manager installs it with the plugin. Removed the extra, its
exclude-newer row and the pm/extras.py import anchor; `uv lock` removed
only the supermemory package. The mem0 extra and its win-arm64 gate stay.
2026-10-02 05:10:06 -07:00
teknium1
7e53b3ef82 chore(memory): remove bundled honcho provider; install from the plugin catalog 2026-10-02 04:39:28 -07:00
teknium1
92ce6f4760 fix(pm): heal agent-browser entries staged without the exec bit
28edf7c75a made stage() chmod the native agent-browser binary, but
entries staged before it (lazy installs since the pm store landed) stay
0644 forever: every browser_* call fails with PermissionError and the
store never restages because the entry verifies. Restore the bit in
place when the entry is looked up on its own target (modes are not part
of the pinned digest); an entry that cannot be chmodded counts as not
installed so ensure() restages it.
2026-09-28 10:11:33 -07:00
JoaoMarcos44
0b105e0714 fix(pm): report unwritable install state once 2026-09-28 06:42:00 -07:00
Yuan Li
80e834b129 fix(pm): preserve transport knobs under no_config isolation, strip only index redirects
The isolation filter now drops only settings that redirect WHERE packages
resolve from (index URLs, find-links, strategy, index credentials); transport
knobs (UV_NATIVE_TLS / UV_INSECURE_HOST / UV_HTTP_TIMEOUT) survive — corporate
networks need them to reach the pinned wheel URLs at all.

Addresses the review note on the no_config scope in the #125071 triage.
2026-09-28 06:42:00 -07:00
Yuan Li
32aeb9ef7b fix(pm): honor no_config isolation for bridged index settings in uv runs
stage_runtime (the pinned pm/ self-repair builder) passes no_config=True,
but that only appended uv's --no-config, which removes config FILES. The
pip-mirror bridge (pm.index_config) reaches uv as env vars instead, so a
user's pip.conf index-url still rode into the runtime sync via
runtime_environment() -> _base_environment(env=None). Re-resolving the
official-index pm/uv.lock against a mirror changes the resolution, and
uv sync --locked refuses — breaking the top-level update / doctor /
pm-repair recovery paths on any machine with a pip mirror (#124418).

When no_config is set, strip forwarded index/transport knobs from the
child env: a fully-pinned graph never needs a mirror to resolve, and the
default path keeps the bridge so mirrored/air-gapped application installs
still work.
2026-09-28 06:42:00 -07:00
chelsealong
42941c0a37 fix(pm): put the checkout launcher ahead of the venv's own hermes script (#124627, salvage #124828)
The runtime venv installs the core project editable against the PM
build snapshot (workspace/), so venv/bin/hermes always imports that
frozen copy. PM only rebuilds the venv on lock/extras/python-pin/plugin
changes, so a source-only update leaves the copy stale while the
gateway keeps running the checkout via activate_dependencies's
sys.path override. Any child that resolves `hermes` off PATH (terminal
tool, cron, kanban workers) still finds venv/bin first and runs the
stale snapshot instead — including `gateway install`/`restart`, which
then pins launchd/systemd to the snapshot's launcher path.

Prepend the checkout's own .hermes/bin ahead of venv/bin in
activate_dependencies so `hermes`/`hermes-acp` always resolve to the
launcher that puts the checkout first, regardless of what else lives
in the venv. This also fixes tools/environments/local.py's
_resolve_hermes_bin_dir(), whose shutil.which("hermes") now finds the
launcher dir instead of the venv's console script, so no separate guard
on PROJECT_ROOT in generate_launchd_plist / systemd unit generation is
needed: the snapshot's hermes_cli is never the running one.

Fixes #124627
2026-09-28 04:44:29 -07:00
teknium1
a31e618e70 fix(pm): download npm-hosted tools through the user's npm registry
pm/lock.json and the Npm/AgentBrowser templates record registry.npmjs.org, and
pinned_source handed that URL straight to the downloader, so ~/.npmrc /
npm_config_registry were ignored and closed networks could not provision npm or
agent-browser. pinned_source now routes public-npm URLs through the configured
registry (npm's own precedence: npm_config_registry, then the user npmrc); the
lock's SHA256 still verifies the bytes and progress keeps the lockfile URL.
npm_dist_tags reads from the same registry.

The E2E cell test_npm_registry_mirror_serves_pm_npm_download drops its gate.

Co-authored-by: funky-xamarin <30426178+Wenfengcheng@users.noreply.github.com>
2026-09-28 02:00:41 -07:00
teknium1
e0fae5566a fix: Hermes's own lint, activate, tui and MCP launchers use PM's node/npx/uv, never the user's
Post-write .js/.ts lint on the local backend runs PM's node/npx instead of
the terminal shell's PATH (user dirs first). pm.activate always puts the
store dirs in front, also on drift, activating only packages whose whole
chain is installed. tui_gateway puts the store dirs ahead of ~/.local/bin.
Bare npx/npm/node/uv/uvx MCP commands resolve to PM's copies; the system
fallback tables are gone and an absolute command: stays the user's.
2026-09-27 22:30:55 -07:00
teknium1
27062c3474 fix: install cua-driver and the Browser Use CLI by default again
Computer use and browser use are meant to work out of the box. The PM rewrite
(3d12e86ef1) and the MSIX installer rework (47f4ab3a17) dropped the
install-time cua-driver fetch (7060ac7bed) and the Browser Use CLI install
(baa6b2e34d). On a fresh install the computer_use check_fn therefore stayed
False, so the tool never reached the model and its lazy ensure could not fire,
and browser_exec quietly fell back to the built-in tools.

- cua-driver is a default PM package, so the installers, a bare
  `hermes pm install` and `hermes update` carry it on every target it builds
  for. Adds the missing Android gap (the lock has no bionic artifact).
- The shared default-tool step, which the installers (via source completion)
  and `hermes update` both run, provisions the Browser Use CLI for the default
  and explicit Browser Use backends. `--skip-browser` declines it along with
  agent-browser, and `off`/Camofox never use it.
- Installers regain --skip-computer-use / -SkipComputerUse (recorded as
  `--without cua-driver`).
- The update message stops calling every default "browser tools".
2026-09-27 19:18:58 -07:00
kshitijk4poor
084645fba4 fix(pm): PortableGit unpack raises InstallError and never pipes the extractor
- Refusal, non-zero exit and timeout now raise InstallError (pm/client.py
  catches only InstallError; TimeoutExpired escaped entirely). The off-Windows
  refusal names a real remedy instead of the generic 'retry'.
- stdin/stdout/stderr -> DEVNULL: with capture_output the stub's RunProgram
  children inherit the pipes and hold run() open past the stub's exit (and
  CPython re-communicates without a timeout after a kill). The stub prints
  nothing under -y, so the output tail was dead code.
- TemporaryDirectory(ignore_cleanup_errors) as in Npm.unpack; unpack empties
  its destination like extract(); drop redundant local imports.
- Tests: CompletedProcess over hand-rolled stubs, no raising=False, timeout
  case added, change-detector pin-suffix test removed.
2026-09-28 02:04:08 +05:30
finn763
7a40c28750 fix(pm): execute the pinned git SFX from a scratch copy, not the cache
CI run 36189416163 failed after "git: unpacking": executing the cached
fetch-<sha> PortableGit PE in place left it handle-held (Defender
on-execute scan / the stub's RunProgram child chain) past pm's ~2 s
_remove_entry retry, so download cleanup raised WinError 32. Review
(teknium1, 5 threads) adds the rest:

- Git.unpack now copies the artifact into a .sfx-* dir beside the
  staging tree and executes the copy; pm's .staging-* teardown
  (ignore_errors) owns that path, so any hold lands on a disposable
  path and the cache dir only ever holds read handles
- refuse off-Windows with the cross-host trade-off stated instead of a
  raw PermissionError; documented in package-management.md and the PR
- the extractor is a GUI-subsystem stub that is silent under -y: error
  messages now carry the exit code and the usual causes (disk full,
  path length, antivirus) instead of promising captured output; the
  docstring no longer claims "no GUI" and records that the stub shows
  an Extracting window and runs the vendor post-install
- install.ps1: WaitForExit(600000) + Kill() mirrors pm's timeout=600,
  Fail reports the exit code and the silence
- tests: the OS-refused-exec assertion and the not-in-text change
  detectors are replaced by subprocess-argv invariants (scratch copy
  location, exit-code message, off-Windows guard before any execution)

Related to #122512

(cherry picked from commit adaf76a286bebe30c89aee4174df27f1945b60c9)
2026-09-28 02:04:08 +05:30
finn763
6d9ae2da6f fix(install): stage pinned git without tar/bzip2 (PortableGit SFX)
Windows 10 boxes whose System32 tar.exe cannot run the bzip2 filter die
at stage=prerequisites with "unable to run program bzip2 -d" while
extracting the pinned Git-2.53.0.3 tar.bz2 (#122512). Repin git for
both win32 targets to git-for-windows' PortableGit self-extracting 7z,
which carries its own extractor and the bundled usr/bin/bash.exe:

- pm/lock.json: new artifact urls + sha256 (the pin authority)
- scripts/install.ps1: generated fragment regenerated; Get-PinnedGit
  downloads the SFX and waits on it explicitly (the stub is a
  GUI-subsystem exe, so PowerShell's & does not wait); the System32
  tar invocation and its msys symlink excludes go away
- pm/packages.py: Git.fetch_url/Git.unpack run the self-extractor
  after the sha256-verified download
- pm/store.py: drop the now-callerless git_msys branch of extract_tar
- tests: RED->GREEN test runs the real Get-PinnedGit against a
  bzip2-less System32 tar.exe stub with no bzip2 on PATH; the three
  obsolete tar-contract tests and the install.ps1/PM msys-links parity
  test are replaced by a no-external-decompressor contract test

Closes #122512

(cherry picked from commit 4915304213495d3207ec6cd659e57cd16ef08d60)
2026-09-28 02:04:08 +05:30
kshitijk4poor
3955eac079 fix(pm): re-pin BtbN ffmpeg to the August month-end build (#125350)
The Windows and Linux rows pointed at autobuild-2026-09-10-15-31, a daily
BtbN has deleted, so a fresh install 404s wherever the artifact mirror is
also refused. autobuild-2026-08-31-13-27 is the month-end build BtbN keeps
for two years; its n9.0.1-11 snapshot matches the locked 9.0.1 label and
is what the updated `pm update ffmpeg` resolves. Digests from the GitHub
release API; install-verified on win32-x64 and linux-arm64.

Co-authored-by: Yuan Li <dskwelmcy@163.com>
2026-09-28 00:51:32 +05:30
kshitijk4poor
7546680ff8 fix(pm): pm update pins only BtbN month-end ffmpeg builds
BtbN deletes daily autobuild tags after 14 days and keeps the last build
of each month for two years (util/prunetags.sh). btbn_index took the
newest daily, so every `pm update ffmpeg` wrote a pin that 404s two weeks
later (#125350, #122240). It now skips the current month's tags (still
dailies the next build displaces) and every tag but the last in each
finished month, so the tool only ever pins a retained build.
2026-09-28 00:51:32 +05:30
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