Commit Graph

2511 Commits

Author SHA1 Message Date
nesquena-hermes 42c677b223 Stage 382: PR #2496 2026-05-18 03:43:59 +00:00
nesquena-hermes ea1261d03b Stage 382: PR #2501 2026-05-18 03:43:26 +00:00
nesquena-hermes f731f1fa43 Stage 382: PR #2499 2026-05-18 03:43:01 +00:00
nesquena-hermes b170980522 Stage 382: PR #2504 2026-05-18 03:43:01 +00:00
nesquena-hermes d9a26d26af Merge pull request #2497 from nesquena/stage-381
Release BL — v0.51.88 (stage-381 — 3-PR security + UX + lineage batch — CSRF + quoted-reply + lineage collapse)
v0.51.88
2026-05-17 18:50:48 -07:00
nesquena-hermes e919da8705 Stamp CHANGELOG for v0.51.88 (Release BL / stage-381 / 3-PR security + UX + lineage batch) 2026-05-18 01:44:41 +00:00
nesquena-hermes 00fc4ccc02 Stage 381: PR #2493 2026-05-18 01:44:05 +00:00
nesquena-hermes db048fade5 Stage 381: PR #2485 2026-05-18 01:32:24 +00:00
nesquena-hermes eef47ea27b Stage 381: PR #2484 2026-05-18 01:32:21 +00:00
nesquena-hermes a27f8c8c24 Merge pull request #2487 from Michaelyklam/docs/issue-1925-approval-clarify-gate
docs(runtime): define approval clarify control gate
2026-05-17 18:22:59 -07:00
nesquena-hermes adcdc261b4 Merge pull request #2495 from nesquena/stage-380
Release BK (stage-380): v0.51.87 — 2-PR Docker hygiene + CI gate — read-only mount tmpfs staging + Docker runtime smoke workflow + agent-source boundary inventory + writable-mount startup warning
v0.51.87
2026-05-17 18:22:12 -07:00
nesquena-hermes 944c634f97 Stamp CHANGELOG for v0.51.87 (Release BK / stage-380, also restore v0.51.86 block clobbered during rebase) 2026-05-18 01:18:53 +00:00
nesquena-hermes 669e815a73 Stage 380: PR #2482
# Conflicts:
#	CHANGELOG.md
#	docker_init.bash
2026-05-18 01:16:19 +00:00
nesquena-hermes c1671d1e5e Stage 380: PR #2490
# Conflicts:
#	CHANGELOG.md
2026-05-18 01:14:28 +00:00
Michaelyklam d9e6dcf3ef docs(runtime): define approval clarify control gate 2026-05-18 01:06:26 +00:00
nesquena-hermes 7ed9117929 Merge pull request #2494 from nesquena/docs/2483-android-avf-rebased
docs: note Android AVF ARM64 field report (refs #2364, supersedes #2483)
2026-05-17 17:58:25 -07:00
Frank Song 144aac28b9 docs: note Android AVF ARM64 field report (refs #2364)
Add a narrow README note for the community ARM64 Android AVF field
report: Hermes Agent + WebUI running inside a Debian 12 VM on a
mid-range Android phone with cloud-hosted inference.

The note frames the report as a compatibility signal rather than an
official support baseline or provider/model benchmark, and records
practical mobile caveats around first-install compile time, Android
tab reloads, and battery optimization.

Refs #2364
Closes nesquena/hermes-webui#2483

Co-authored-by: Frank Song <franksong2702@gmail.com>
2026-05-18 00:51:41 +00:00
Dennis Soong 9b65e2440b fix: collapse WebUI compression continuations in sidebar 2026-05-18 08:35:38 +08:00
Nathan Esquenazi 64590cb6b9 harden(docker-smoke): catch !!ERROR/!!Exiting + tighten egg_info test
Two non-blocking observations from the review, both addressed:

1. The bad-pattern grep listed `error_exit` as a literal token, but the
   `error_exit()` function at docker_init.bash:5-10 only echoes the
   strings `"!! ERROR: "` and `"!! Exiting script (ID: $$)"` — the
   function name itself never appears in container logs. So
   `grep -E -i "error_exit"` would only fire on stray debug prints of
   the name, not on actual failures. The other patterns
   (`Failed to set (UID|GID|...)`, `groupmod: cannot`, etc.) DO catch
   real error_exit output, so this wasn't a coverage gap — just a dead
   token.

   Add `!! ERROR` and `!! Exiting script` to the bad-pattern set so the
   grep actually matches the function's output. Keep the literal
   `error_exit` token as belt-and-suspenders for any debug/echo of the
   name.

2. `test_docker_init_excludes_egg_info_during_staging` was a single
   `assert "egg-info" in src` check. That passes if any occurrence
   appears — including the explanatory comment block above the staging
   logic. A maintainer removing the `--exclude='*.egg-info'` from
   rsync but keeping the comment would slip past the test.

   Tighten to:
   - scope to the staging block (between `_stage_src=` and the
     `uv pip install` line) so comments outside that window can't
     satisfy the assertion;
   - require the literal `--exclude='*.egg-info'` rsync flag;
   - require `*.egg-info` in the block so the cp-fallback cleanup is
     also pinned;
   - additionally require `--exclude='build'`, `--exclude='dist'`,
     `--exclude='__pycache__'` so all four setuptools-touchable
     artifact dirs stay excluded.

Verified:
- tests/test_docker_docs_and_readonly.py — 11/11 pass.
- YAML parses cleanly via `yaml.safe_load`.
- Full suite: 5770 passed, 0 failed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 17:34:46 -07:00
nesquena-hermes 70f371c8b9 fix(docker): stage agent source to writable build dir before pip install
The Docker smoke gate added in this same PR caught a real production
regression on its very first CI run. v0.51.84 (PR #2470) mounted
hermes-agent-src read-only on the WebUI side and widened the chown
prune to keep the read-only walk happy, but missed that the WebUI's
startup also runs:

    uv pip install "$_agent_src[all]"

against the same now-read-only mount. setuptools' egg_info step writes
hermes_agent.egg-info/ inside the source tree even under PEP 517 build
isolation (this is by design -- PEP 517 isolates the BUILD environment,
not the source tree's metadata directory). On a :ro mount this returns
EROFS, the install fails, error_exit fires, and every multi-container
deploy dies at startup. The smoke gate flagged it on both the
two-container and three-container variants.

The fix
-------
Stage the agent source into a writable build dir under /tmp BEFORE
invoking pip install, then point pip at the staged copy.

  _stage_src="/tmp/hermes-agent-build"
  rm -rf "$_stage_src" && mkdir -p "$_stage_src"
  rsync -a --exclude='*.egg-info' --exclude='build' --exclude='dist' \
        --exclude='__pycache__' --exclude='.git' \
        "$_agent_src"/ "$_stage_src"/
  uv pip install "$_stage_src[all]" ...
  rm -rf "$_stage_src"

The exclusion list matters: when setuptools sees a pre-baked *.egg-info,
build, or dist directory, it takes a timestamp-update code path that
also reads/writes inside that directory -- which itself fails on a :ro
source. Excluding them keeps the build on the fresh-build path
unconditionally.

rsync is in the production image (Dockerfile line 41-44). For users
running custom WebUI images without rsync, the script falls back to
cp -a + post-copy rm -rf of the same artifacts.

Tests
-----
Two new source-level invariants in tests/test_docker_docs_and_readonly.py:

  test_docker_init_stages_agent_source_for_writable_install
    -- asserts _stage_src=... is declared
    -- asserts every `uv pip install ...[all]` line uses _stage_src,
       NOT raw $_agent_src

  test_docker_init_excludes_egg_info_during_staging
    -- asserts the staging path excludes *.egg-info (rsync exclude
       form or cp-fallback's explicit rm -rf both pass)

These would have caught the v0.51.84 regression at the source level
(once written; they're new). The Docker runtime smoke gate is the
durable defence for the broader class of :ro x init-script
interactions, since source-level invariants only catch what they're
written to catch.

Verification
------------
- pytest tests/test_docker_docs_and_readonly.py: 11 passed (9 existing
  + 2 new)
- pytest tests/ -q --timeout=60: 5891 passed, 6 skipped (was 5889;
  delta is exactly the 2 new tests)
- bash -n docker_init.bash: clean

Once this lands, the Docker smoke gate's two/three-container variants
should go green, completing the self-validating loop.
2026-05-18 00:21:31 +00:00
nesquena-hermes 5b6f69c884 ci(docker): runtime smoke gate for Docker init logic
Closes the source-only-test gap that let v0.51.84's :ro-mount x chown -h
{} + startup regression reach review with 5800+ green pytests. Adds a
new GitHub Actions workflow .github/workflows/docker-smoke.yml that
actually runs 'docker compose up' against each compose variant.

Triggers
--------
Path-filtered on pull_request + push to master:
  Dockerfile, docker_init.bash, docker-compose*.yml, .dockerignore,
  .env.docker.example, .github/workflows/docker-smoke.yml itself.
Also workflow_dispatch for manual runs.

Jobs
----
1. compose-config -- preflight that 'docker compose config' parses each
   of the three compose files. Cheap, fast, catches schema/interpolation
   drift in parallel before any container starts.

2. smoke (matrix: single / two-container / three-container) -- for each
   variant:
   a. Reap any leftover hermes-smoke-* containers/volumes/networks from
      prior runs (defence-in-depth on self-hosted runners; hosted runners
      are fresh).
   b. docker build -t ghcr.io/nesquena/hermes-webui:latest .
      Critical: the multi-container compose files reference the GHCR
      image. Without this retag, multi-container smoke would test the
      previously-released image, NOT the PR's docker_init.bash / Dockerfile
      changes. With the retag, Compose's default pull_policy=missing keeps
      the local build in place and the PR is genuinely exercised.
   c. mktemp -d for ephemeral HERMES_HOME + HERMES_WORKSPACE so the
      runner's host filesystem is never touched.
   d. docker compose up -d --wait --wait-timeout 120 (Dockerfile carries a
      HEALTHCHECK so --wait blocks on 'healthy', not just 'running').
   e. curl /health probe with a 30-attempt x 2s poll loop as headroom for
      the multi-container variants' Python dep install phase.
   f. grep startup logs for known-bad signatures:
        EROFS | Read-only file system | Traceback | PermissionError |
        error_exit | groupmod: cannot | usermod: cannot |
        Failed to set (UID|GID|owner|permissions|ownership)
      These are the exact patterns that would have flagged #2470 in real
      time. Failed-to-set is anchored to specific objects to avoid false
      positives on benign locale/library bootstrap warnings.
   g. trap on EXIT: docker compose down -v --remove-orphans + rm -rf the
      ephemeral host paths, regardless of how the job exited.

Safety
------
- permissions: contents: read only -- no GITHUB_TOKEN write scope.
- Fork PRs run with no secrets (standard pull_request, not
  pull_request_target).
- No host bind mounts; no ~/.hermes exposure; no network egress beyond
  what compose itself needs to pull the agent image.
- timeout-minutes: 15 on the smoke job as a hard ceiling against a
  hung docker build.
- Per-run COMPOSE_PROJECT name (hermes-smoke-VARIANT-RUNID-ATTEMPT)
  so concurrent runs or reruns can't clobber each other.

Out of scope for v1 (per design review)
---------------------------------------
- HERMES_WEBUI_SMOKE_TEST env flag in docker_init.bash -- production-code
  footgun that would let any leaked env var silently exit before
  serving traffic.
- --user 60000:60000 -- incompatible with the image's root-init phase
  and would skip the very chown branch we are guarding against.
- Local-runnable scripts/docker-smoke-test.sh -- defer until CI gating
  ships and we see what contributors actually trip over.
- Hadolint / yamllint -- separate lint workflow, follow-up PR.
- Podman runtime smoke -- defer until a podman-specific bug ships.

Pre-merge verification
----------------------
- actionlint: clean
- YAML parse: clean (3 triggers, 2 jobs, 3-variant matrix)
- bash -n on all 6 run-blocks: clean
- pytest tests/ -q --timeout=60: 5889 passed, 6 skipped (no test impact;
  workflow-only change)
- Opus design review on the brief (REVISE -> minimum scope adopted)
- Opus implementation review on this workflow (APPROVE)
2026-05-18 00:09:41 +00:00
nesquena-hermes 2927702596 Merge pull request #2486 from nesquena/stage-379
Release BJ (stage-379): v0.51.86 — 4-PR review-bypass batch — WebUI memory-provider session lifecycle + cross-provider /model alias + RuntimeAdapter cancel seam + Fork-from-here messaging coord
v0.51.86
2026-05-17 16:44:05 -07:00
nesquena-hermes 9543addd3d Stage 379 fix: remove stray CHANGELOG entry mis-attributing #2461 to v0.51.83 (Opus catch) 2026-05-17 23:41:15 +00:00
nesquena-hermes 0dc527517c Stamp CHANGELOG for v0.51.86 (Release BJ / stage-379 / 4-PR review-bypass batch) 2026-05-17 23:36:11 +00:00
nesquena-hermes 50d4f4cfb9 Stage 379: PR #2480
# Conflicts:
#	CHANGELOG.md
2026-05-17 23:35:19 +00:00
nesquena-hermes 6f9cead15e Stage 379: PR #2479 2026-05-17 23:35:18 +00:00
nesquena-hermes b861422045 Stage 379: PR #2473 2026-05-17 23:35:18 +00:00
nesquena-hermes 935d9e6402 Stage 379: PR #2461
# Conflicts:
#	CHANGELOG.md
2026-05-17 23:35:18 +00:00
Frank Song 496b34fe4d Fix CSRF test isolation 2026-05-18 07:27:31 +08:00
Frank Song 8daf716307 Repair selected text reply review blockers 2026-05-18 07:26:19 +08:00
Frank Song 996942429c Add session-bound CSRF token checks 2026-05-18 07:14:26 +08:00
Frank Song 9646773487 Add selected text reply composer append 2026-05-18 07:13:14 +08:00
Michael Lam 310d69bed8 docs: inventory agent source boundary 2026-05-17 16:11:29 -07:00
Michael Lam f986507809 fix: align fork-from-here with merged messaging history 2026-05-17 15:01:57 -07:00
Michael Lam a5385e5859 feat(runtime): route cancel through RuntimeAdapter seam 2026-05-17 13:23:22 -07:00
nesquena-hermes f1d399b437 Merge pull request #2478 from nesquena/stage-378
Release BI (stage-378): v0.51.85 — 3-PR review-bypass batch — workspace-prefix display leakage fix + release-tag update banner + Slice 3a cancel-control gate RFC
v0.51.85
2026-05-17 13:05:33 -07:00
nesquena-hermes c728de2a58 Stamp CHANGELOG for v0.51.85 (Release BI / stage-378 / 3-PR batch) 2026-05-17 19:55:48 +00:00
nesquena-hermes 77ccd2a29f Stage 378: PR #2469 2026-05-17 19:55:09 +00:00
nesquena-hermes fa6e939c69 Stage 378: PR #2146 2026-05-17 19:55:09 +00:00
nesquena-hermes dcf9b0f7f5 Stage 378: PR #2145 2026-05-17 19:55:09 +00:00
ts2111 64db8bd794 fix: support /model alias switch for cross-provider custom models
Backend (api/config.py):
- resolve_model_provider(): check custom_providers for prefix match
  BEFORE the config_base_url branch. Previously, providers with a
  base_url set (e.g. deepseek) would catch all slash-delimited model
  ids and return the config provider, preventing custom provider
  routing.
- get_available_models(): include model aliases in response so the
  frontend can resolve them on /model commands.

Frontend (static/commands.js):
- cmdModel(): resolve aliases by fetching /api/models before fuzzy
  matching the dropdown.
- Add bare-model fallback when the alias resolves to a slash-delimited
  provider/model id (e.g. "deepseek/deepseek-v4-flash").
- Add cross-provider fallback: when the model is from a custom provider
  not in the active provider dropdown, call /api/session/update directly
  with the provider/model id and provider override.
2026-05-17 21:22:06 +02:00
Michael Lam f2c5048741 docs(runtime): define cancel control gate 2026-05-17 11:30:50 -07:00
nesquena-hermes 02144aa863 Merge pull request #2471 from nesquena/stage-377
Stage 377: v0.51.84 (Release BH) — Docker hygiene (PR #2470)
v0.51.84
2026-05-17 10:51:12 -07:00
nesquena-hermes 79b690b3d9 Stamp CHANGELOG for v0.51.84 (Release BH / stage-377) 2026-05-17 17:45:23 +00:00
nesquena-hermes 31ae565533 Stage 377: PR #2470 — docs(docker): document agent-image upgrade flow + read-only WebUI source mount (closes #1416 + addresses #2453 read-only half) 2026-05-17 17:44:42 +00:00
Nathan Esquenazi 2d66263a6c fix(docker): widen chown prune to the entire hermes-agent path
PR #2470 introduces a `:ro` mount for the `hermes-agent-src` named volume
on the WebUI side of `docker-compose.{two,three}-container.yml`. The
WebUI's docker_init.bash unconditionally runs `chown_home_hermeswebui`
which walks `/home/hermeswebui` with `find -exec chown -h {} +`,
pruning only `/home/hermeswebui/.hermes/hermes-agent/.git/objects` (the
narrow #2237 fix for macOS bind mounts).

With the new `:ro` mount, every other file inside the hermes-agent
subtree is also on a read-only filesystem.  `chown` returns `EROFS`,
`find -exec ... +` propagates the non-zero exit, and the wrapping
`chown_home_hermeswebui || error_exit "..."` under `set -e` kills the
container before the WebUI server can run.

Verified locally:

    $ /usr/bin/find /tmp/ftest -exec false {} +
    $ echo $?
    1

So `find` does propagate `-exec` command failures, which the existing
`|| error_exit` then catches.

The WebUI never writes to the agent source — `uv pip install
/home/hermeswebui/.hermes/hermes-agent` is a pure read.  So aligning
ownership inside the agent subtree was always a nicety, not a
requirement.  Widen the prune to skip the entire
`/home/hermeswebui/.hermes/hermes-agent` path.  This also subsumes the
original #2237 case (the `.git/objects` packs are inside the now-pruned
subtree) without needing a separate carve-out.

Test updates:

- Renamed `test_home_chown_skips_hermes_agent_git_objects` →
  `test_home_chown_skips_hermes_agent_subtree`, and pinned the broader
  prune target (`-path ".../hermes-agent" -prune`).
- Added `test_home_chown_helper_documents_readonly_mount_compat` so a
  future maintainer narrowing the prune back to `.git/objects` (and
  re-introducing the EROFS failure mode) trips a regression.

Verified:

- `tests/test_issue2237_docker_chown_git_objects.py` 4/4 pass.
- `tests/test_docker_docs_and_readonly.py` 9/9 pass.
- Full suite: 5738 passed, 0 failed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 10:38:40 -07:00
nesquena-hermes 5cc8b6c654 docs(docker): document agent-image upgrade flow + read-only WebUI source mount
The hermes-agent-src named volume in the two- and three-container compose
files is initialised from the agent image's /opt/hermes on first `up` and
Docker reuses it verbatim on every subsequent `up` — even after a fresh
`docker pull` of the agent image. This was the root cause of #1416 (the
'missing entrypoint' symptom was a stale cached volume hiding the new
image's source tree).

Changes:

- Add an 'Upgrading the agent container' section to docs/docker.md with
  the canonical `down → docker volume rm → pull → up -d` recipe, plus the
  same pointer as a comment block in both multi-container compose files
  near the volume declarations.
- Switch the WebUI's hermes-agent-src mount to `:ro` in both multi-container
  compose files. The WebUI only reads this volume to install the agent's
  Python deps at startup; mounting it read-only enforces that at the kernel
  layer and brings the actual mount mode in line with the existing
  docs/docker.md architecture diagram (which already labelled this edge as
  read-only).
- Align the workspace bind default in both multi-container compose files
  with the single-container convention — `${HERMES_WORKSPACE:-${HOME}/workspace}`
  instead of `${HERMES_WORKSPACE:-~/workspace}` — so the default resolves
  the same way across Linux, macOS, WSL2, and Docker Desktop on Windows.
- Add a 'What the multi-container setup isolates (and what it doesn't)'
  section to docs/docker.md to frame the two/three-container setups as
  process/network/resource isolation, not filesystem isolation, so users
  don't reach for multi-container expecting a trust boundary it doesn't
  provide.
- Cross-link #1416 from the Related issues section.

Adds 9 regression tests in tests/test_docker_docs_and_readonly.py covering:
- :ro on the WebUI side of hermes-agent-src in both files
- agent side stays read-write (still needs to populate /opt/hermes on first run)
- ${HOME} (not ~) in workspace bind defaults in both files
- single-container file already uses ${HOME} (pin to prevent drift)
- docs/docker.md has the 'Upgrading the agent container' section + recipe
- compose files reference docs/docker.md + show the upgrade step inline
- docs/docker.md frames the isolation model honestly

Test suite: 42 passed (33 existing Docker tests + 9 new). No behaviour
change for users who set HERMES_WORKSPACE explicitly, and no migration is
required for existing deployments — Docker rebinds the existing volume
read-only on next `up`. Users upgrading the agent image should now follow
the documented `docker volume rm hermes-agent-src` recipe.

Closes #1416 (documented upgrade procedure) and addresses the read-only
half of the multi-container coupling concern raised on #2453.
2026-05-17 17:18:39 +00:00
nesquena-hermes 522efc2921 Merge pull request #2468 from nesquena/stage-376
Release BG (stage-376): v0.51.83 — 12-PR contributor batch — chat-start adapter parity + populated-core journal recovery + thinking card dedup + context metadata refresh + model cache fingerprint + stream fade cap + manual cron delivery + active-session spinner + email gateway label + thinking copy button + /theme i18n + compact activity semantics
v0.51.83
2026-05-17 09:53:15 -07:00
nesquena-hermes 80a09c8f4e Stamp CHANGELOG for v0.51.83 (Release BG / stage-376 / 12-PR contributor batch) 2026-05-17 16:43:45 +00:00
nesquena-hermes a2920c99bc Stage 376: PR #2466
# Conflicts:
#	CHANGELOG.md
2026-05-17 16:42:11 +00:00