feat(pm): unified package manager — pm store foundation

Introduce the pm store: a unified, hash-verified package store that
replaces lazy_deps and the old installer's ad-hoc tool downloads.
Store tools are provisioned on PATH (ffmpeg, node/npm via pinned uv),
with a resumable 8-way downloader, verify() returning failure reasons,
and adopt() made EPERM-safe. chromium ships in the payload for every
target. The 3600-line install.sh is replaced by a staged bootstrapper
(heavy deps are pm's job after this); setup-hermes.sh, Dockerfile and
nix pin tables are rewired onto the store. Old install-script tests,
lazy_deps/managed_uv/build_info, and the ps1/bash installer test
batteries are removed with the machinery they tested.

Rebuilt from ethie/pm onto upstream/main (ac6c8028e0) after the
utf-8-sig sweep. 16 hot files (main also churned them) hand-merged:
platform adapters, main.py, electron/main.ts, tui_gateway/server.py,
cua_backend, installer-tests workflow, install.sh (full rewrite),
setup-hermes.sh, plugins doc.
This commit is contained in:
ethernet
2026-08-29 21:23:27 -04:00
parent 6e294f543d
commit 3d12e86ef1
272 changed files with 18065 additions and 14721 deletions

View File

@@ -247,7 +247,7 @@ if [ "$needs_chown" = true ]; then
# Hermes-owned subdirs: recursive chown is safe here because these are
# created and managed exclusively by hermes (see the s6-setuidgid mkdir
# -p block below for the canonical list).
for sub in cron sessions logs hooks memories skills skins plans workspace home profiles pairing platforms/pairing lazy-packages; do
for sub in cron sessions logs hooks memories skills skins plans workspace home profiles pairing platforms/pairing; do
if [ -e "$HERMES_HOME/$sub" ] && tree_has_non_hermes_owner "$HERMES_HOME/$sub"; then
chown_hermes_tree "$HERMES_HOME/$sub"
fi
@@ -262,16 +262,8 @@ fi
# non-writable prevents an agent session from self-modifying the installed
# source, venv, TUI bundle, or node_modules and bricking the gateway.
#
# Lazy-installable optional backends (Firecrawl, Exa, Feishu, etc.) cannot
# install into the sealed venv, so they are redirected to the writable
# $HERMES_HOME/lazy-packages dir on the data volume (Dockerfile sets
# HERMES_LAZY_INSTALL_TARGET). That dir is appended to the END of sys.path,
# so a package installed there can only ADD modules — it can never shadow or
# break a core module, which is what keeps the sealed-venv guarantee intact
# even though installs are re-enabled. The dir is seeded + chowned to hermes
# in the mkdir/chown blocks above so first-use installs succeed as the
# unprivileged runtime user, and it persists across container recreates /
# image updates (an ABI stamp wipes it if a rebuild bumps the interpreter).
# Lazy installs are fully disabled at runtime (HERMES_DISABLE_LAZY_INSTALLS=1,
# see the Dockerfile), so no writable lazy-install target is provisioned here.
# Always reset ownership of $HERMES_HOME/profiles to hermes on every
# boot. Profile dirs and files can land owned by root when commands
@@ -392,8 +384,7 @@ as_hermes mkdir -p \
"$HERMES_HOME/workspace" \
"$HERMES_HOME/home" \
"$HERMES_HOME/pairing" \
"$HERMES_HOME/platforms/pairing" \
"$HERMES_HOME/lazy-packages"
"$HERMES_HOME/platforms/pairing"
# --- Install-method stamp ---
# The 'docker' stamp is baked into the immutable install tree at
@@ -646,42 +637,31 @@ if [ -d "$INSTALL_DIR/skills" ]; then
|| echo "[stage2] Warning: skills_sync.py failed; continuing"
fi
# --- Discover agent-browser's Chromium binary ---
# The image's Dockerfile runs `npx playwright install chromium`, which
# populates ``$PLAYWRIGHT_BROWSERS_PATH`` (=/opt/hermes/.playwright) with
# a ``chromium_headless_shell-<build>/chrome-headless-shell-linux64/``
# directory. agent-browser (the runtime CLI Hermes spawns for the
# browser tool) doesn't recognise this layout in its own cache scan and
# fails with "Auto-launch failed: Chrome not found" — even though the
# binary is right there (#15697).
# --- Point agent-browser at the pinned Chromium binary ---
# The image's Dockerfile pm-provisions the pinned Chromium pair into
# $HERMES_RUNTIME_DIR (/opt/hermes/tools) at BUILD time and bakes the
# resolved browser binary path into /etc/hermes/agent-browser-executable-path
# (the layout differs per arch — chrome-linux64/chrome on amd64,
# chromium-linux-arm64/chromium on arm64 — so it is resolved at build time,
# not hard-coded). agent-browser (the runtime CLI Hermes spawns for the
# browser tool) doesn't recognise Playwright's directory layout in its own
# cache scan and fails with "Auto-launch failed: Chrome not found" — even
# though the binary is right there (#15697).
#
# Fix: locate the binary at boot and export ``AGENT_BROWSER_EXECUTABLE_PATH``
# Fix: read the baked path and export ``AGENT_BROWSER_EXECUTABLE_PATH``
# via /run/s6/container_environment so the `with-contenv` shebang on
# main-wrapper.sh propagates it into the supervised ``hermes`` process
# and thence to agent-browser subprocesses.
#
# - Skipped when the user has already set ``AGENT_BROWSER_EXECUTABLE_PATH``
# (lets users override with a system Chrome install).
# - Filename-matched (not path-matched): the chromium dir contains many
# shared libraries (libGLESv2.so, libEGL.so, ...) which inherit the
# executable bit from Playwright's tarball but are NOT browser binaries.
# We only accept files whose basename is chrome / chromium /
# chrome-headless-shell / headless_shell / chromium-browser. Compare
# PR #18635's earlier ``find | grep -Ei 'chrome|chromium'`` which would
# match the path ``.../chrome-headless-shell-linux64/libGLESv2.so`` and
# pick a .so.
# - Quietly skipped when $PLAYWRIGHT_BROWSERS_PATH doesn't exist (e.g.
# custom builds that strip Playwright).
# - Quietly skipped when the baked path file is absent (e.g. custom builds
# that strip the pm tool store).
if [ -z "${AGENT_BROWSER_EXECUTABLE_PATH:-}" ] && \
[ -n "${PLAYWRIGHT_BROWSERS_PATH:-}" ] && \
[ -d "$PLAYWRIGHT_BROWSERS_PATH" ]; then
browser_bin=$(find "$PLAYWRIGHT_BROWSERS_PATH" -type f -executable \
\( -name 'chrome' -o -name 'chromium' \
-o -name 'chrome-headless-shell' -o -name 'headless_shell' \
-o -name 'chromium-browser' \) \
2>/dev/null | head -n 1)
if [ -n "$browser_bin" ]; then
echo "[stage2] Found agent-browser Chromium binary: $browser_bin"
[ -f /etc/hermes/agent-browser-executable-path ]; then
browser_bin="$(cat /etc/hermes/agent-browser-executable-path)"
if [ -n "$browser_bin" ] && [ -x "$browser_bin" ]; then
echo "[stage2] Using pinned agent-browser Chromium binary: $browser_bin"
# Write to s6's container_environment so with-contenv picks it
# up for all supervised services (main-hermes, dashboard, etc.).
# Idempotent: each boot overwrites with the current path.
@@ -690,7 +670,7 @@ if [ -z "${AGENT_BROWSER_EXECUTABLE_PATH:-}" ] && \
mkdir -p /run/s6/container_environment
printf '%s' "$browser_bin" > /run/s6/container_environment/AGENT_BROWSER_EXECUTABLE_PATH
else
echo "[stage2] Warning: no Chromium binary under $PLAYWRIGHT_BROWSERS_PATH; browser tool may fail"
echo "[stage2] Warning: baked Chromium binary is missing (${browser_bin:-<empty>}); browser tool may fail"
fi
fi