Commit Graph
135 Commits
Author SHA1 Message Date
nesquena-hermes a1b3c068f7 docs: document HERMES_WEBUI_SETTINGS_FILE (#6433) incl. restart-to-change note 2026-09-24 03:15:28 +00:00
nesquena-hermes 1c4072228e Merge PR #7493 into stage/0924-a 2026-09-24 03:15:02 +00:00
carlotestor 35921eccbb fix(sidebar): scope project inheritance to subagents, resolve lineages once
Forks and other child sessions keep their own project_id, so a fork moved to
No project stays Unassigned. A per-render memoized, cycle-safe resolver is
shared by partitioning, reference scoping and the Unassigned chip.
2026-09-23 15:28:47 +00:00
carlotestor 35c9b8ae96 fix(sidebar): Unassigned chip resolves child projects through reference rows
The chip check now reuses the ancestor map built by _partitionSidebarSessionRows,
so a child whose project parent is only a hidden reference row is not counted
as unassigned. Document project inheritance for subagent sessions in README.
2026-09-23 12:59:36 +00:00
carlotestor 5c24458dff fix(sidebar): derive the route session cap from the shared limit
The env override never reached /api/sessions because the route kept a
separate hard-coded cap of 20. Resolve the window in api.config before
profile init, clamp it at 200, and refuse a profile .env override.
2026-09-22 17:50:56 +00:00
carlotestor 899b89e1d3 docs+test: document HERMES_WEBUI_VISIBLE_SESSION_LIMIT; probe it in a subprocess
- README env table + .env.example gain the new setting (default 20, fallback
  semantics), pinned by test_setting_is_documented.
- tests/test_visible_session_limit_env.py no longer importlib.reload()s
  api.models in the shared pytest process (which recreated its module-level
  locks/caches/classes behind other modules' backs); each case now imports
  api.models in a fresh interpreter with a scrubbed env, matching
  test_issue3283_profiles_config_import_order.
2026-09-20 11:19:41 +00:00
CharlesMcquade 6f593f80e8 fix(notifications): preserve prompt owners through lifecycle exits 2026-09-14 18:21:54 -05:00
dcb515611d Release — SSE Last-Event-ID resumption fix (#6886) (#7129)
* fix(sse): scope gateway SSE probe and honor Last-Event-ID on /api/chat/stream

Two narrowly-scoped cross-client SSE fixes from the hermes-android#58
follow-up:

1. The /api/sessions/gateway/stream?probe=1 response only describes the
   optional gateway/agent-sessions stream, but its payload gave clients no
   way to tell that. A 404 'agent sessions not enabled' result could be
   misread as SSE being unsupported server-wide, even though the persistent
   per-session stream (/api/session/stream) is always on and independent of
   the show_cli_sessions setting. Add additive scope markers to the probe
   payload (scope, session_stream_available, session_stream_path) and
   clarify the handler docstring.

2. /api/chat/stream emitted id: stream_id:seq on every journaled event
   but ignored the Last-Event-ID request header, so any spec-compliant SSE
   client that auto-reconnects (browser EventSource, Android/CLI clients)
   replayed the journal from seq 0 and double-rendered the transcript.
   _chat_stream_resume_after_seq() now resolves the resume cursor with
   explicit after_event_id/after_seq query params first and the
   Last-Event-ID header as fallback — the same resolution chain
   api/kanban_bridge.py already uses. Both the dead-stream journal replay
   and the live-subscribe gap check consume it; direct callers of the gap
   check that only pass query params keep historical behavior.

Also documents the cross-client SSE endpoint contract in
docs/sse-streams.md.

Related hermes-webui/hermes-android#58

* fix(sse): split cursor presence from validity on /api/chat/stream resume

Address PR #6886 review (gate-fail): the resume logic conflated "no
cursor" with "invalid cursor" into a single None, which broke replay on
three edges. Root cause and fix:

Cursor presence vs validity — _chat_stream_resume_cursor() now returns
(after_seq, resume_requested) instead of a bare seq. A client that
SUPPLIED any cursor (query params, replay=1, or Last-Event-ID) asked to
resume; an invalid / foreign-run / unparseable cursor resolves to
after_seq=None but keeps resume_requested=True, and is normalized to
replay-from-start so no journal events are silently skipped. A genuinely
cursor-less request stays a fresh subscribe.

1. Explicit-query precedence is now decided by parameter PRESENCE, not
   successful parsing: a malformed/foreign after_event_id/after_seq never
   falls through to let the Last-Event-ID header override it and skip
   events.
2. Invalid/foreign cursors no longer bypass replay + gap detection and
   silently drain a truncated buffer; an ahead-of-stream cursor is no
   longer clamped onto the snapshot's terminal fence (which emptied the
   dead-stream replay by filtering the terminal frame).
3. The runner-local observe path now receives the resolved header cursor
   (retaining explicit-query precedence) so header-only runner reconnects
   resume instead of duplicating events from the start.

Regression tests cover invalid-cursor replay-from-start, ahead-of-stream
terminal survival, header-only runner reconnect, and explicit-query
precedence; the stale clamp test is updated to the corrected contract.

Related hermes-webui/hermes-android#58

* fix(sse): consume resolved resume cursor at every consumer on /api/chat/stream

Address PR #6886 r2 review (gate-fail). The r1 presence/validity split
fixed the entry resolution, but the individual consumers still re-derived
ahead/equality/re-parse decisions, so the invariant leaked at the edges.
This makes every consumer honor the resolved (after_seq, resume_requested)
pair and normalizes ahead-of-stream in one place.

1. Equality off-by-one: a valid cursor EQUAL to the snapshot cutoff now
   enters the drain dedup bound (the event at the cursor was already
   delivered), instead of being treated as "ahead" and double-sending the
   retained buffer. Ahead is now strictly after_seq > snapshot_cutoff_seq.
2. Numeric ahead-of-stream cursors are normalized to replay-from-start:
   on the active path, after_seq > snapshot_cutoff_seq resets to 0 before
   gap-coverage/replay so a truncated buffer can no longer falsely declare
   coverage and lose the pre-tail events; on the dead-stream path the
   cursor is normalized against the journal summary's authoritative
   last_seq before _replay_run_journal, so an ahead cursor returns the
   full replay instead of an empty SSE body.
3. Runner-local now consumes the resolved cursor instead of re-parsing
   after_event_id or re-reading Last-Event-ID. The resolver also returns
   the opaque raw cursor so the runner uses the already-precedence-checked
   value; explicit opaque cursor= keeps precedence, invalid/foreign
   cursors pass no cursor (full replay), and the header is honored only
   when no explicit cursor was supplied.

Regression tests cover the equality dedup, active+dead ahead-of-stream
normalization (with after_seq-filtering mocks that assert the real replay
body), and the three runner precedence probes (malformed-explicit blocks
header, foreign passes no cursor, opaque cursor= wins).

Related hermes-webui/hermes-android#58

* fix(sse): carry runner-cursor provenance through /api/chat/stream resume

Address PR #6886 r3 review (gate-fail). The (after_seq, resume_requested,
raw_cursor) tuple carried the cursor VALUE but not its PROVENANCE, so the
runner handoff couldn't distinguish a valid opaque runner cursor from a
journal-shaped one from a malformed one — and gated the runner handoff on
journal-specific validity, which is wrong for the runner adapter (its
cursors are opaque).

The resolver now returns a distinct, centrally-resolved runner cursor:

- a valid after_seq PAIRS with whatever after_event_id shape was supplied
  — including an opaque runner id like event:2 that the journal parser
  reads as a foreign run — so the paired runner cursor resumes at the seq
  (never None / full replay, which would duplicate tokens and tool
  events). after_seq is read directly (via _parse_run_journal_after_seq_value)
  rather than through the after_event_id-first journal parser, which would
  swallow a paired opaque runner id.
- a header-only opaque runner id (Last-Event-ID: event:2) is preserved
  as-is so the runner resumes from it.
- a malformed or foreign explicit cursor WITHOUT a valid paired after_seq
  still yields no runner cursor, blocking Last-Event-ID and replaying from
  start — the r2 malformed-blocks-header guarantee is unchanged.

Regression tests pin the paired opaque id, header-only opaque id, and
malformed-with-valid-seq runner resumes, plus the unchanged
malformed-without-pair replay-from-start behavior.

Related hermes-webui/hermes-android#58

* fix(sse): normalize ahead cursor when the snapshot cutoff is unknown

Address PR #6886 r4 review (gate-fail). The ahead-cursor normalization
was guarded by `snapshot_cutoff_seq is not None`, so when the subscribe
snapshot had no parseable last_event_id (channel has not seen an
id-bearing frame yet), a positive ahead cursor (e.g. Last-Event-ID
run_1:999) was never normalized — it was installed verbatim as the live
dedup bound, filtering EVERY queued frame including the terminal
stream_end fence. The reconnect stalled on heartbeats with an empty SSE
body. origin/master ignored the header on this path and delivered the
events, so this was a new regression.

Fix: treat an unknown, non-truncated snapshot cutoff as fence 0 before
the ahead-cursor normalization, so a positive client cursor can't become
the live dedup bound with no cutoff to bound it — it fails closed to
replay-from-start, delivering the buffered events rather than silently
losing them. Frames dropped while the cutoff is unknown still cannot
prove gap coverage (cutoff None → not covered), so the recovery_control
fail-closed path is preserved.

Regression test: Last-Event-ID run_1:999 + snapshot_cutoff_seq=None
delivers queued events run_1:1 and run_1:2 exactly once, emits the
terminal stream_end, and releases the subscriber. Without the fix the
test stalls on heartbeats (pytest-timeout kills it), proving the stall.

Related hermes-webui/hermes-android#58

* Release — SSE Last-Event-ID resumption fix (#6886)

---------

Co-authored-by: Paladin173 <35980893+Paladin173@users.noreply.github.com>
Co-authored-by: n <a@n>
2026-08-18 15:08:14 +00:00
MHJ e4c3c728d9 fix(commands): wait for bundle metadata before skill suggestions 2026-08-12 00:32:57 +09:00
a725a0f71c Release p1-batch: session cache cap 300→100 (#6362) + virtualization comment (#6318) (#6375)
* perf(transcript): enable DOM virtualization by default (re-enable #4346 fix) (#6151) (#6155)

* fix: revert DOM virtualization default to opt-in, fix gate RED (#6155)

* fix(#6351): lower default session cache cap

* Release p1-batch: session cache cap 300->100 (#6362) + virtualization comment (#6318)

---------

Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>
Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-21 02:53:43 +00:00
98a67ee6cf Release: pip-installable packaging metadata (#6337, @rodboev) (#6344)
* build(#2695): add packaging metadata for the current runtime layout

* docs(changelog): stamp #6337 pip-installable packaging metadata

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-20 02:17:52 +00:00
Rod Boev 9bd96fb75d fix(#2422): support packaged Hermes Agent discovery 2026-07-14 01:06:49 +00:00
Rod Boev b7f523c3a1 fix(#2422): tighten Nix module polish 2026-07-14 01:06:49 +00:00
Rod Boev 72b51a6b78 fix(#2422): avoid hardcoded agent site packages 2026-07-14 01:06:49 +00:00
Rod Boev 70514549c4 fix(#2422): harden NixOS packaged runtime 2026-07-14 01:06:49 +00:00
Rod Boev 5bd940a49b fix(#2422): honor the service-mode host default 2026-07-14 01:06:48 +00:00
Rod Boev 09003b5e92 feat(#2422): add Nix flake package and NixOS module 2026-07-14 01:06:48 +00:00
Jaret Bottoms 2844a5ad31 no-mistakes(document): Document profile-scoped cron unread alerts 🔧🟠 2026-07-12 19:20:53 +00:00
nesquena-hermes e061e8534c Rebase #5337 (public read-only conversation sharing) onto master
Rebased 363 commits forward (branch-point 2026-07-05). Applied the PR's code
diff cleanly across 16 files; resolved api/models.py additively (both master's
process_wakeup_pause/anchor_scene_index fields AND the PR's share_token/
share_created_at fields kept in the Session constructor, metadata list, and
to_dict). CI workflow (.github/workflows/tests.yml) intentionally NOT taken from
the stale branch — master owns it (the branch's apparent removal of Playwright/
Office steps was a PAT-workflow-scope revert; rebase restores master's CI).

Co-authored-by: MicoRobot. Concept approved by maintainer (opt-in public share,
2026-07-11). Note: 4 dynamic share tests fail identically on the original
unrebased head (pre-existing test-env Session.load fragility, not rebase-caused);
to be resolved during the security re-gate in a proper sandbox.
2026-07-11 01:39:42 +00:00
Nathan Esquenazi 264a9eab29 Update the screenshot (#5816) 2026-07-08 23:03:20 -07:00
nesquena-hermes 8a7d3eb6c2 Merge PR #5673 into stage 2026-07-06 17:04:53 +00:00
nesquena-hermes 26a829c952 docs: weekly contributor tally refresh (20260706) (#5689) 2026-07-06 09:29:07 -07:00
Rod Boev cdcb31a948 docs(#2781): document proxy auth PWA update caveat 2026-07-06 04:41:45 -04:00
mo7al876anyandClaude Fable 5 3b56cc9629 docs: refresh stale version/test-count/locale numbers
- Version stamps v0.51.192/v0.50.281 → v0.51.792 (Jul 1) in the factual
  header lines (ROADMAP/ARCHITECTURE/SPRINTS/TESTING); curated narrative
  left untouched
- Test counts: the ~7,150 approximates and leftover absolute 5303/488 and
  3995 figures → ~11,500 tests / ~1,150 files (matches --collect-only)
- Locales: 9 → 14 (add Italian, Turkish, Polish, Vietnamese) in ROADMAP;
  '11 languages' → 14 in TESTING

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 16:04:23 +03:00
nesquena-hermes 6caaced8eb fix(webui): bound in-memory SESSIONS cache with lazy reload (#4765)
Root cause of the silent-crash-after-hours cluster (#4765/#2233/#4633): the
global in-memory SESSIONS LRU evicted with a blind popitem(last=False), which
could drop an actively streaming or not-yet-persisted session (data loss) and
was capped only via an env var. On long-running installs the effective result
was unbounded RAM growth until segfault.

- Add _session_is_evictable(): a session is evictable ONLY when it is not
  streaming (no active_stream_id), has no in-flight turn (no pending_user_message
  / pending_started_at), and its full state is proven on disk (sidecar
  message_count >= in-memory count; metadata-only stubs and zero-message shells
  are trivially safe).
- Add _evict_sessions_over_cap(): replaces all 8 blind popitem loops across
  models.py, routes.py, streaming.py. Walks the LRU oldest-first and removes only
  provably-safe entries; never acquires LOCK/stream locks itself (caller holds
  LOCK) so no lock-ordering deadlock. May briefly exceed the cap rather than ever
  evict an active/unsaved session.
- Make the cap configurable via config.yaml webui.sessions_cache_max
  (get_sessions_cache_max()); precedence config.yaml -> HERMES_WEBUI_SESSIONS_MAX
  (legacy) -> DEFAULT_SESSIONS_CACHE_MAX=300. No new HERMES_* env var. Invalid or
  <1 values fall back so a typo can never disable the bound.
- Evicted sessions lazily reload from their JSON sidecar via the existing
  get_session() accessor; no call sites changed. _index.json sidebar behavior
  unchanged (the sidebar reads the index, not SESSIONS).
- Add tests/test_issue4765_sessions_lru_eviction.py (8 tests): eviction past
  cap, active/streaming never evicted, unsaved/stale-tail never evicted, lazy
  reload with identical content, and no-data-loss under heavy churn.
- README: document the config.yaml key + safety semantics.

Fixes #4765.
2026-07-01 18:14:39 +00:00
Rod Boev f7a9c98810 fix(auth): harden OIDC key selection and startup guidance (#3825) 2026-06-29 22:28:53 -04:00
nesquena-hermesandnesquena-hermes 8cee867f19 docs: weekly contributor tally refresh (20260629) (#5214)
Regenerated by scripts/regen_contributors.py --apply (markers spliced, prose
untouched, --verify clean). 304 contributors / 1771 credits.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-29 09:18:11 -07:00
sr-dotcom ec14a78320 fix(quickstart): clarify HERMES_WEBUI_CHAT_BACKEND=gateway is available today
The previous wording implied external gateway routing was not yet possible.
HERMES_WEBUI_CHAT_BACKEND=gateway already ships; updated wording distinguishes
it from full agent-loop delegation, which is not yet shipped and tracked in #1925.
2026-06-26 21:29:02 +00:00
sr-dotcom 620b794770 docs(quickstart): clarify WebUI runs the agent in-process, not via an external API server
HERMES_API_URL is only read by the Tasks/cron health probe (api/agent_health.py)
and does not route chat. Adds a plain-language note to Quick Start with the two
supported options for users who run an external endpoint. Closes #3873.
2026-06-26 21:29:02 +00:00
nesquena-hermes d61a67b258 docs: weekly contributor tally refresh (20260622) (#4710) 2026-06-22 10:32:31 -07:00
nesquena-hermes 8f3f6ea67a stage #4593 onto master (v0.51.550) — document extension sidecar CSP support + CSP test coverage
santastabber: docs (README + EXTENSIONS.md) clarifying extension sidecar CSP behavior +
test_issue1909_csp_enforcement / test_issue2901_csp_connect_extra assertions. Docs+tests only, no
prod code. Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 07:04:31 +00:00
nesquena-hermes 1b7c1fe619 rebase #4569 onto master (v0.51.547) for ship-gate — extension status diagnostics (#4561 follow-up)
santastabber: authenticated /api/extensions/status endpoint reporting WHY an extension didn't load
(stable codes + coarse sources only, no path/env/URL leak). Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 04:08:44 +00:00
nesquena-hermes 3e229141af stage #4561 + stamp v0.51.545 — extension manifest asset bundles
santastabber. HERMES_WEBUI_EXTENSION_MANIFEST: bundle extension assets via a 64KB JSON manifest
instead of long URL lists. Same same-origin allowlist validator, traversal-guarded manifest path,
default-off. Nathan blessed (richer extension system). Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 00:37:08 +00:00
nesquena-hermes 4d4b11b43c test+docs(#4449): backward-compat state-dir tests + upgrade migration note (Opus gate)
Opus flagged a real upgrade gap on the #4449 state-dir contract folded into
#4454: a deployment that relocated HERMES_HOME to a non-default base WITHOUT
setting HERMES_WEBUI_STATE_DIR sees WebUI state move to $HERMES_HOME/webui on
upgrade (empty app at the new address). Add (1) README note documenting the
move + how to keep the old location, and (2) two backward-compat regression
tests pinning the invariants: HERMES_HOME-unset stays at the platform default
(normal installs don't relocate), and HERMES_WEBUI_STATE_DIR takes precedence.
2026-06-20 00:51:08 +00:00
Rod Boev f086158c10 fix(#2698): keep isolated deployments on their own state and profile roots 2026-06-20 00:48:19 +00:00
nesquena-hermes 580a157ab1 stage #4447: opt-in chunked SSE for buffering proxies (#4447) + v0.51.524 CHANGELOG 2026-06-19 22:09:17 +00:00
26f54b9972 docs: weekly contributor tally refresh + cherry-pick credit accuracy fix (#4473)
* docs: weekly contributor tally refresh (20260619)

Regenerated by scripts/regen_contributors.py --apply. Includes the source (e)/(f)
ship-signal accuracy fix that recovers systematically-undercounted cherry-picked
contributions (~77 contributors). 280 contributors / 1559 credits (was 263/1232).
Markers spliced, prose untouched, --verify clean, 0 double-counts.

* docs: align CONTRIBUTORS header prose with the 5-source credit algorithm

Addresses greptile P2: the header still described the old three-source union
(merged-PR list + CHANGELOG + Co-authored-by) but regen_contributors.py now adds
two more — ship-comment scans and absorb-commit scans — for five total. Updates
the prose to match what the script actually runs.

---------

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-18 22:05:37 -07:00
nesquena-hermes f5f882b1dd docs: fairly count absorbed-but-closed contributor PRs (backfill @Isla-Liu trio) (#4401) 2026-06-17 20:08:08 -07:00
nesquena-hermes 232963791f Merge pull request #4250 from nesquena/contributors-refresh-20260615
docs: weekly contributor tally refresh (20260615)
2026-06-16 09:02:29 -07:00
fadb4fdf32 docs(README): correct server stop instructions — start.sh runs detached, not foreground (#4303) (#4304)
start.sh exec's bootstrap.py without --foreground, which double-forks into a
detached daemon, so it belongs in the 'find PID + kill' row, not the Ctrl-C
foreground row. Docs-only, no version stamp.

Co-authored-by: tomas-forgac

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-16 03:40:26 -07:00
e775fb3355 Release OV (v0.51.435): supported local pytest runner (#3908) (#4252)
* stage-3908: supported local pytest runner + symlink-guard on venv create/clear (Codex CORE fix)

Rebased rodboev/franksong2702 #3908 onto v0.51.434. Adds scripts/test.sh dev
runner + requirements-dev.txt + test_ci_hygiene.py + conftest version guard.
Defensive fix on the authoritative gate: create_or_rebuild_venv refuses a
symlinked .venv so 'python -m venv --clear' can't wipe the symlink target
(same symlink-hardening class as #4217/#4234/#4240). Locked by a hygiene test.

* Release OV: supported local pytest runner (#3908)

scripts/test.sh + requirements-dev.txt + conftest version guard + hygiene tests.
Self-rebased franksong2702's #3908 onto v0.51.434; full Codex+Opus+suite gate.
Codex CORE fix applied: refuse to create/--clear a virtualenv through a symlinked
.venv (symlink-hardening class, #4217/#4234/#4240), locked by a hygiene assertion.

Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-15 10:25:32 -07:00
3ee2f5d693 docs: refresh contributor tally for v0.51.390 (248 contributors) (#4132)
* docs: refresh contributor tally for v0.51.390 (248 contributors)

Regenerated via scripts/regen_contributors.py after fixing three real
over-counting bugs in the generator (release-vehicle PR# leak, reporter
false-positives, prose-token leakage — see workspace commit). Net:

- 248 contributors / 1092 credits (corrected; the prior generator inflated
  this with ~106 spurious release-vehicle co-author credits + reporter mentions)
- CONTRIBUTORS.md now recognizes 3+ PR contributors in ranked tables and lists
  everyone with 1-2 PRs in a combined roll, so nobody already credited is dropped
- README top-10 snapshot + notable-contribution counts updated to match
  (franksong 180, Michaelyklam 118, rodboev 83 [new], ai-ag2026 75, bergeouss 70)

Every name in the tally is authorship-verified (merged PR, CHANGELOG attribution,
or a Co-authored-by trailer); bug reporters / relayers / confirmers are no longer
miscredited as authors.

* docs: add generated-region markers so the contributor tally regenerates cleanly

Wraps the machine-generated tables in CONTRIBUTORS.md + README.md with
<!-- BEGIN/END GENERATED CONTRIBUTORS --> markers. scripts/regen_contributors.py
--apply now splices fresh tables between them on each run while leaving the
hand-written prose (How credit is tracked, Special thanks, README Notable
contributions) untouched. Verified idempotent: a second --apply is a no-op.
This is what the weekly refresh cron runs.

Tally re-synced to current master: 248 contributors / 1093 credits.

---------

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-06-13 12:18:21 -07:00
Rod Boevandnesquena-hermes 299a7a9553 docs(#3794): clarify how to stop each launch method in Quick start (#3942)
* docs(#3794): clarify how to stop each launch method in Quick start

* docs(#3794): use lsof/ss for PID discovery instead of implying bootstrap prints it

---------

Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com>
2026-06-10 12:11:45 -07:00
nesquena-hermesandnesquena 3ca1188f64 Release v0.51.256 — Release HX (stage-r4) (#3596)
## Release v0.51.256 — Release HX (stage-r4)

Performance — bound WebUI memory growth & idle CPU on large installs.

### Fixed
| Issue | Author | Fix |
|-------|--------|-----|
| #3506 | @nesquena-hermes (reported w/ profiling by @djenttleman) | On a large install (~615 sessions / 40k messages / 454 MB state.db) the WebUI process climbed ~100 MB → ~1.5 GB RSS over days and held high idle CPU. Three root causes fixed: (1) `session_lifecycle._sessions` grew unbounded → new `discard_session()` drops the entry at agent-eviction boundaries, only when no in-flight commit / no uncommitted memory work (retry invariant preserved); (2) cache caps now operator-tunable (`HERMES_WEBUI_AGENT_CACHE_MAX` default 50→25, `HERMES_WEBUI_SESSIONS_MAX`); (3) GatewayWatcher computes a cheap fingerprint before the expensive per-session `MAX(messages.timestamp)` projection and only re-projects on change. |

### Rebase + review notes
- Rebased onto current master; the code diff was verified **byte-identical to the nesquena-APPROVED head** at rebase time (only CHANGELOG re-resolved).
- The Codex regression gate then surfaced **two correctness gaps** the approval didn't catch, both fixed here with regression tests:
  1. **Watcher fingerprint missed same-count transcript rewrites.** `/retry`,`/undo`,`/compress` (`SessionDB.replace_messages`) rewrite messages with new timestamps but can leave `message_count` unchanged → stale sidebar `last_activity`. Fixed with a **per-session** grouped message aggregate (`id, count, user_count, MAX(timestamp)`) over the same non-excluded sessions (a global MAX would miss a rewrite of an older, non-newest session); cron/webui stay excluded so idle churn still doesn't re-project.
  2. **LRU agent-cache eviction could close a live worker's agent** (`popitem(last=False)`, liveness-blind — pre-existing, but the lower 50→25 cap made it more likely). Eviction now snapshots `ACTIVE_RUNS` session_ids (before the cache lock — no nested lock) and skips live sessions, deferring (temporarily exceeding cap) rather than closing a live agent.

### Gate
- Full pytest suite: **7612 passed, 0 failed** (one boot-cascade flake re-run; clean on re-run)
- ruff: CLEAN · Codex (regression): 4 rounds → both gaps + a stale test fixed → **SAFE TO SHIP**

Co-authored-by: nesquena <nesquena@users.noreply.github.com>
2026-06-04 12:28:06 -07:00
ba70926e51 Release v0.51.253 — Release HU (stage-r1) (#3591)
## Release v0.51.253 — Release HU (stage-r1)

Phase-1 low-risk batch — 7 PRs (no intervention beyond apply + one inline MUST-FIX).

### Fixed
| Issue/PR | Author | Fix |
|----------|--------|-----|
| #3525 | @TomBanksAU | Streaming DOM-replace "follow" window tightened 1200px→120px — a reader who scrolled up mid-stream no longer gets snapped to the bottom on completion. |
| #3556 | @ai-ag2026 | Topbar count distinguishes a partially-loaded transcript ("loaded of total" via server `message_count`); fully-loaded keeps the tool-row-filtered count. |
| #3502 follow-up | @rodboev | Sidebar messaging source badges (Telegram/Discord/…) render as chips, not just CLI ones. |
| — | @Karlineal | `.pre-header+pre` margin override scoped under `.msg-body` (removes a 10px gap above code blocks). |

### Tests
- `test_ctl_script.py` kills orphan fake-python trees on Windows; conftest `_discover_python` checks the Windows venv layout (`Scripts/python.exe`). (#3537, #3577, @rodboev)

### Docs
- Explicit WebUI–Agent compatibility policy + Docker pinning guidance. (#3232, @franksong2702)

### Dropped from this batch
- **#3538** (self-update stash-pop recovery) — the Codex regression gate found a **BRICK-class data-loss**: the recovery path runs `git reset --merge` then `git stash drop`, permanently discarding the user's local modifications while returning `ok:true` + scheduling a restart. Held with `changes-requested` + a repro and the fix (keep the stash, return `ok:false`, no restart). Concept is good; the destructive `stash drop` must go.

### Gate
- Full pytest suite: **7575 passed, 0 failed**
- ESLint: CLEAN · ruff: CLEAN · browser-smoke: CLEAN
- Codex (regression): SHIP-ONLY-WITH-FIXES (BRICK data-loss #3538 + tool-row count regression #3556) → #3538 dropped, #3556 fixed inline → **SAFE TO SHIP**

Co-authored-by: TomBanksAU <TomBanksAU@users.noreply.github.com>
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
Co-authored-by: Karlineal <Karlineal@users.noreply.github.com>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-04 10:50:34 -07:00
nesquena-hermesandrodboev f64b06ab4f Release v0.51.252 — Release HT (stage-q24) (#3530)
## Release v0.51.252 — Release HT (stage-q24)

Two trivially-safe @rodboev changes (independent).

### Fixed
| Issue | Author | Fix |
|-------|--------|-----|
| #2481 | @rodboev | The floating "selected-text reply" button now has `user-select:none`, so its own label can't get caught in a text selection (no bleed-through). CSS one-liner. |

### Docs
- README **Compatibility** section: upgrade WebUI + hermes-agent together until the stable agent API (#2491) lands. (@rodboev)

### Dropped from this batch
- **#2977 `/use` skill command** was staged here but **dropped** — the Codex regression gate found an async stale-directive race (`cmdUse()` awaits `/api/skills` but `send()` doesn't await the handler → a fast next send can miss it, or a stale directive leaks to a later message) plus an over-eager `finally` clear that silently discards the directive on a local slash-command early-return. Held with `changes-requested` + a detailed rework note (tracked pending promise + clear-on-consume). Concept approved; needs lifecycle hardening.

### Gate
- Full pytest suite: **7570 passed, 0 failed**
- ESLint: CLEAN · ruff: CLEAN · browser-smoke: CLEAN
- Codex (regression): **SAFE TO SHIP** — `user-select:none` scoped to the button only, README docs-only, no `/use` code remains

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-03 21:41:05 -07:00
nesquena-hermes ec168b3c67 docs(readme): re-sequence IA — pull Features up, consolidate access, extract niche docs
Information-architecture pass on the 840-line README so the most important
things come first and secondary/niche content is linked rather than inline:

- Reorder: Why -> Quick start -> FEATURES (was at line 502, now right after
  Quick start) -> Configuration & access -> Docker -> Running tests ->
  Architecture -> Docs -> Contributors. Readers see what it does before the
  deployment minutiae.
- Consolidate the scattered access sections (start.sh discovery, overrides,
  remote/SSH, Tailscale, manual launch) under one '## Configuration & access'
  H2 with H3 subsections.
- Extract two genuinely-niche blocks to new linked docs (nothing deleted):
  - docs/advanced-chat-setup.md — dynamic recall-prefill + Gateway-backed chat
  - docs/remote-access.md — SSH tunnel + Tailscale + ARM64-Android field report
  Quick start keeps a one-line pointer to each.
- Update Contents TOC + Docs index for the new order and new files.

README 840 -> 705 lines; content preserved (verified moved-not-dropped); all
internal links + new docs verified to resolve; docs/*.md gitignore-allowlisted.
2026-06-01 02:26:18 +00:00
nesquena-hermes 9d1b7b1d52 docs: freshen ROADMAP + ARCHITECTURE content + README notable-contrib counts
- ROADMAP.md: 8-themes -> 11-skins in status snapshot; replace stale per-file
  LOC architecture table with role descriptions + CI gate list (ruff/shard/
  browser/docker); note LOC drifts.
- ARCHITECTURE.md: refresh File Inventory — drop chronically-stale per-file LOC,
  add missing modules (state_sync.py, updates.py, i18n/login/icons/sw.js,
  pyproject.toml, ctl.sh, CONTRIBUTORS.md), refresh test count to ~7,150.
- README.md: update the 8 'Notable contributions' blurb headers (PR counts +
  release ranges) to match the regenerated tally.
2026-06-01 02:16:57 +00:00
nesquena-hermes f14efc4368 docs: regenerate contributor tally (137->194, union, nobody dropped)
Regenerate CONTRIBUTORS.md + README contributors section from a verified
3-source union: GitHub merged-PR list, CHANGELOG.md attribution lines, and
Co-authored-by trailers on master commits (the canonical signal for a CLOSED
contributor PR whose commits were cherry-picked/absorbed and attributed).

- New tally: 194 contributors / 843 PR credits (was a stale 137 / 646).
- The increase: ~135 releases since the v0.51.58 pin PLUS newly-detected
  absorbed-CLOSED PRs the prior hand-count missed (e.g. franksong2702 148 =
  129 merged + 19 cherry-picked-and-attributed).
- UNION with the existing hand-curated file as a floor: 27 old contributors
  had no machine-readable signal (very old closed PRs) — preserved, ZERO dropped.
- Refreshed special-thanks PR counts to match.
- Generator committed to the maintainer workspace as scripts/regen_contributors.py
  (--merge-existing keeps it safe for all future refreshes).

Verified: every one of the original 137 logins still present (+57 new).
2026-06-01 02:14:01 +00:00
nesquena-hermes 31fcdfa39a docs: refresh + reorganize README and key markdown files
README:
- Add a Contents table of contents for navigability (800-line doc).
- Freshen stale snapshots: test count 5303/488 files -> ~7,150/~700 files;
  reframe contributor counts to point at CONTRIBUTORS.md as the live source.
- Rebuild the Architecture section: drop per-file exact LOC (drifts every
  release -> chronic staleness) in favor of a stable backend/frontend role map;
  add pyproject.toml + the ruff/browser/docker CI gates.
- Reorganize the Docs index by purpose (Start here / Using / Deploying /
  Contributing & design / Release history); add missing CONTRIBUTING.md,
  DESIGN.md, docs/workspace-git.md; convert bare paths to working links.

ARCHITECTURE.md: header v0.51.54/5303 -> v0.51.192/~7,150 + note that the
  numbers are a periodic snapshot (authoritative source = git tag + collect-only).

TESTING.md: header + footer test counts refreshed; drop the stale
  'through v0.50.21' framing; note the ruff/browser/docker gates.

ROADMAP.md: refresh the 'Last updated' stamp (v0.51.31/5028 -> v0.51.192/~7,150)
  with recent themes.

Markdown-only; all internal links verified to resolve.
2026-06-01 01:42:20 +00:00