Commit Graph
100 Commits
Author SHA1 Message Date
nesquena-hermes 76d6cc7f74 #5142: office-doc preview + safe docx editing (#540), rebased on master; optional deps 2026-07-02 01:15:57 +00:00
nesquena-hermes 6af9cc3244 chore(changelog): #4682 cross-profile cron visibility 2026-07-02 01:08:12 +00:00
nesquena-hermes 728caf5ec6 #4682: surface read-only other-profile cron jobs in Tasks panel (#3947), rebased on master 2026-07-02 00:44:24 +00:00
nesquena-hermes b709ae6fc2 #5228: require browser provenance on all proxy methods (close GET/POST asymmetry from gate); CHANGELOG 2026-07-02 00:23:10 +00:00
nesquena-hermes 3d99763f3a #5228: opt-in extension loopback proxy (#4747), rebased on master; union urllib imports 2026-07-01 23:49:25 +00:00
nesquena-hermes 01520b8076 chore(changelog): Phase-1 batch — #5153 MoA fail-closed, #5309 ctl.sh .env, #5310 push-to-talk 2026-07-01 23:37:33 +00:00
nesquena-hermes 9fb2731848 #5310: push-to-talk hold gesture (#3700) 2026-07-01 23:26:15 +00:00
nesquena-hermes b1a02bdb72 #5309: ctl.sh load ~/.hermes/.env 2026-07-01 23:24:38 +00:00
nesquena-hermes 36643867f4 #5153: MoA gateway fail-closed routing 2026-07-01 23:24:38 +00:00
nesquena-hermes c49f451402 docs(changelog): manual title regen honors aux timeout (#5374) 2026-07-01 22:58:01 +00:00
nesquena-hermes ffad5e4acd stage #5374 2026-07-01 22:57:37 +00:00
nesquena-hermes cd576cdb25 docs(changelog): MoA picker + Copilot catalog fixes (#5301) 2026-07-01 22:42:36 +00:00
nesquena-hermes d43618beca stage #5301 2026-07-01 22:42:35 +00:00
nesquena-hermes 2e98516b0a stage #5368 2026-07-01 22:18:28 +00:00
nesquena-hermes 8b8d5d990d test(#5364): fully restore sys.modules in the profiles import harness
The regression test re-imports api.profiles in isolation by stubbing
flask/yaml/agent in sys.modules and deleting+reloading the real api /
api.profiles modules. Teardown only popped api/api.* (never restoring the
real modules) and left the flask/yaml stubs behind, so the manipulation
LEAKED: subsequent tests re-imported api.config/api.routes against the stub
yaml (safe_load->None) and a half-populated api package, silently breaking
~120 unrelated tests in the full serial suite (e.g. MCP/provider/config
tests whose get_config patch no longer saw real config). Snapshot every
sys.modules key we touch and restore it exactly (real modules back, injected
stubs removed) in a finally block. Full suite now matches master baseline
(11537 passed, only the 2 known cron-isolation artifacts).
2026-07-01 22:12:30 +00:00
nesquena-hermes c191141615 fix #5368 2026-07-01 21:35:11 +00:00
nesquena-hermes b43d1795ce stage #5326 2026-07-01 21:25:38 +00:00
nesquena-hermes 2574e6bef9 stage #5317 2026-07-01 21:10:21 +00:00
nesquena-hermes 4a3b820e22 stage #5370 2026-07-01 21:06:10 +00:00
nesquena-hermes c8d9e4c1ad stage #5373 2026-07-01 20:49:03 +00:00
nesquena-hermes 3673028a26 stage #5365 2026-07-01 20:35:45 +00:00
nesquena-hermes bc954131de docs(changelog): restore #4765/#5313/#5335 entries dropped in merge conflicts (shipped v0.51.801/803/804) 2026-07-01 19:51:36 +00:00
nesquena-hermes 123270ad37 stage #5335 2026-07-01 19:43:30 +00:00
nesquena-hermesandclaw-io ab38ffe25f Fix profile skills-stats thundering herd at cold startup (#5364)
The two-tier mtime cache from #4783 fixed the per-request SKILL.md rescan
but left two concurrency holes that only bite at container cold start,
when the frontend fires several profile-data requests at once and the
caches are empty:

1. `_get_profile_skills_stats()` had no lock, so concurrent misses on the
   same profile each ran `os.walk(followlinks=True)` + parsed every
   SKILL.md simultaneously.
2. `_build_profile_rows_fast()` ran outside `_LIST_PROFILES_CACHE_LOCK`
   in `list_profiles_api()`, so every concurrent request rebuilt all rows
   (each walking every profile's skill tree) at once.

With ThreadingHTTPServer (one OS thread per request) and Docker overlay2,
this stacked thousands of concurrent stat() calls and stalled workers
57-70s (per the report's thread dumps).

Fix:
- Add a per-profile compute lock (registry guarded by a meta-lock) and
  use double-checked locking in `_get_profile_skills_stats()`: concurrent
  misses on one profile collapse to a single compute, while independent
  profiles still compute in parallel.
- Single-flight the row build in `list_profiles_api()` by holding
  `_LIST_PROFILES_CACHE_LOCK` across the build + cache write. Lock order
  is strictly list-lock -> per-profile skills-lock, so no deadlock.

The report's third suggestion (debounce the mtime probe) is deliberately
NOT taken: the every-call cheap probe is the #4783 out-of-band
change-detection contract (test_issue4783 asserts it MUST run on every
call). Serializing the misses removes the herd without weakening that
contract, since only the expensive compute is guarded, not the probe.

Adds tests/test_issue5364_skills_stats_thundering_herd.py proving the
herd collapses (single compute / single build under a concurrent burst),
independent profiles still parallelize, and the every-call probe contract
is preserved. All existing #4783 contract tests still pass.

Co-authored-by: claw-io <claw-io@users.noreply.github.com>
2026-07-01 19:42:11 +00:00
nesquena-hermes 17b0998c0b stage #5313 2026-07-01 19:35:42 +00:00
nesquena-hermes 91521ea186 docs(changelog): align reconciliation dedup key with workspace-prefix stripping (#5339) 2026-07-01 19:08:50 +00:00
nesquena-hermes 2de28f5e14 stage #5360 2026-07-01 19:08:24 +00:00
nesquena-hermes dd9216c770 stage #5357 2026-07-01 18:46:44 +00:00
nesquena-hermes 92a46aacc4 docs(changelog): crash visibility hardening (#4633) 2026-07-01 18:41:35 +00:00
nesquena-hermes 2693a75d89 stage #5354 2026-07-01 18:41:03 +00:00
nesquena-hermes f967d432fe docs(changelog): drop verification-stop synthetic nudge from transcript (#5334) 2026-07-01 18:36:58 +00:00
nesquena-hermes 30d8edc5ea stage #5353 2026-07-01 18:36:29 +00: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
nesquena-hermes 8bf2cc2461 docs(changelog): subagent sessions no longer vanish from sidebar (#5308) 2026-07-01 18:13:47 +00:00
nesquena-hermes 654f763e7f stage #5308 subagent sidebar msg-count overlay 2026-07-01 18:13:46 +00:00
nesquena-hermes 8b8ee92eca fix(webui): overlay real state.db message count for subagent children so they don't vanish from the sidebar (#5308)
Regression seam behind #5308: a delegated subagent child's sidebar row is built
from a stale sidecar that reports message_count==0, and the state.db count
overlay in _apply_sidebar_state_db_override_metadata was gated on
`state_db_source == 'webui'`. A subagent child (state_db_source=='subagent')
therefore never received its true message count, so the front-end visibility
predicate (_sidebarRowHasVisibleMessages) dropped the row and the subagent
session disappeared entirely (not nested, not orphaned) after #5244+#5306.

Fix: widen the count/last-message overlay to `state_db_source in ('webui','subagent')`,
keeping the same conservative anti-resurrection guard. The source-tag/title
reassignment stays WebUI-only so a subagent child keeps its subagent
classification. Same state.db-blind-metadata root as the #5307 transcript
recovery, fixed server-side rather than by loosening the front-end predicate
(which would fight the #5306 active-parent scoping).

Tests: subagent child gets its count overlaid + classification preserved; a
non-webui/non-subagent foreign source (cron) still gets NO overlay.

Fixes #5308.
2026-07-01 18:06:21 +00:00
nesquena-hermes 86c9ec0a85 fix(webui): drop verification-stop synthetic nudge from transcript (#5334) 2026-07-01 18:00:28 +00:00
nesquena-hermes c1552d70c1 docs(changelog): suppress mobile overflow-anchor double-compensation scroll jump (#5338) 2026-07-01 17:46:03 +00:00
nesquena-hermes 504d9c1f4a docs(changelog): stop live-streaming message flicker on light themes (#5328) 2026-07-01 17:34:57 +00:00
nesquena-hermes 875eb33d97 stage #5328 2026-07-01 17:22:49 +00:00
nesquena-hermes e88564cf4f docs(changelog): fix false clarify-unavailable toast + interrupt provenance (#5345) 2026-07-01 17:17:52 +00:00
nesquena-hermes 5f01033c44 test: drop unused pytest import (ruff F401) — from #5346 be894353 2026-07-01 17:17:11 +00:00
nesquena-hermes 0025466168 test: make cancelStream harnesses tolerate the new reason param + stdout provenance log
Codex gate on #5346 flagged two brittle static extractors that broke on the
cancelStream(reason) signature change + the new '[stream] cancel requested'
stdout log:

- test_cancel_stream_owner_guard.py: the Node harness parsed ALL of stdout as
  JSON; the provenance console.info line (fires during runAll) polluted it.
  Parse the LAST non-empty stdout line (the result JSON is always emitted last,
  after runAll resolves).
- test_sprint36.py: two extractors did src.find('async function cancelStream()')
  (exact, no params). Switched to a signature-tolerant regex and widened the
  catch-block window (the provenance log/comments now precede the try/catch).

Both pre-existing tests, updated to match the intentional #5345 change (not the
code bent to fit the test).
2026-07-01 17:17:11 +00:00
6716f03f4d fix(webui): stop false clarify-unavailable toast; add interrupt provenance (#5345)
/api/clarify/pending always returns HTTP 200 when present (returns
{"pending": null} for an unknown session — it never 404s). The front-end
clarify poller warned "Clarify endpoint unavailable. Please restart
server." on ANY caught error whose message merely contained "404" or
"not found", so an unrelated stale-session 404 ("Session not found", e.g.
an old-profile session polling briefly after a profile switch) or a
transient error produced a misleading missing-endpoint toast that pointed
operators at the wrong layer.

Clarify polling now branches on the structured HTTP status that api()
attaches to the thrown Error (err.status):
- 404 "Session not found" -> handled as a stale-session poll (stop + hide
  card silently), no toast;
- restart-server warning fires only on a genuine route-not-found 404 whose
  body is NOT session-scoped;
- poll failures are logged with path, status, polling session id, and
  current session id for diagnosis.

Interrupt provenance: cancelStream()/cancelSessionStream() now log a
'[stream] cancel requested' line with the trigger reason
(composer-stop / slash-stop / slash-interrupt / busy-interrupt /
sidebar-stop). Passive UI lifecycle events (session switch, tab hide, page
unload) already tear down only the local SSE transport via
closeLiveStream() and never call /api/chat/cancel — only explicit
Stop/interrupt paths interrupt the backend agent/tool run. This is
confirmed by test_clarify_pending_never_404s locking the handler shape.

Supersedes #5343 (which handled only the profile-switch sub-case and kept
the broad message-scrape). Adds tests/test_issue5345_*.py (9 tests).

Co-authored-by: claw-io <claw-io@users.noreply.github.com>
Co-authored-by: ruizanthony <ruizanthony@users.noreply.github.com>
2026-07-01 17:17:11 +00:00
nesquena-hermes 7d58c1e8e1 docs(changelog): prune index-only ghost sessions (#5331) 2026-07-01 17:12:20 +00:00
nesquena-hermes ef9a8b733e stage #5333 2026-07-01 15:53:18 +00:00
nesquena-hermes cf1ab5bbbc fix(#5301): scope models-as-settings-map guard to Copilot only (was breaking providers.<id>.models allowlist for all built-ins, #644 regression) 2026-07-01 07:01:51 -05:00
nesquena-hermes 3569d84a56 test: drop unused api.models import (ruff F401) 2026-07-01 09:19:04 +00:00
nesquena-hermes cda6483153 docs(changelog): view-only delegated subagent session transcript recovery (#5307) 2026-07-01 09:13:34 +00:00
nesquena-hermes 794837d635 Merge #5307 (subagent child transcript view-only recovery) into stage 2026-07-01 09:13:08 +00:00
nesquena-hermes b65ab14538 fix(#5307): gate /api/goal + /api/btw subagent write paths (Codex round 11) 2026-07-01 08:55:58 +00:00
nesquena-hermes ae8496a060 test: widen brittle static-grep windows for delete/duplicate route assertions (#5307 guard lines shifted markers past the fixed windows; functionality unchanged) 2026-07-01 08:49:37 +00:00
nesquena-hermes cdbd84585a fix(#5307): gate final 4 subagent write paths (Codex round 10)
_session_is_subagent_view_only guard added to the remaining paths that could
mutate a stale persisted subagent sidecar:
- _handle_handoff_summary (appends tool msg to state.db)
- _handle_chat_sync (fallback POST /api/chat)
- /api/session/draft POST (composer_draft save)
- /api/personality/set (per-session personality save)
Fixes #5307
2026-07-01 08:47:34 +00:00
nesquena-hermes e6e7454c7a fix(#5307): gate ALL remaining session-mutation routes + stale-webui-row coercion (Codex round 9)
Enumerated every /api/session/<mutation> route and applied _session_is_subagent_view_only:
duplicate, branch, retry, undo, toolsets, compress, archive (existing-sidecar path)
now 400 for subagent children; delete/clear/truncate/pin already gated; rename/move/update
route through the gated _get_or_materialize_session (403). This closes the data-loss
paths (delete_cli_session, retry/undo truncate, branch/duplicate fork).

Also: /api/sessions list coercion now also honors state.db source='subagent' for rows
whose stale index says webui/fork, so they can't surface as writable/CLI sidebar rows.
Fixes #5307
2026-07-01 08:38:47 +00:00
nesquena-hermes 23743c5976 fix(#5307): guard direct mutation routes + coerce list rows (Codex round 8)
The root non-CLI classification surfaced subagent rows in the sidebar without
read_only, exposing delete/truncate/pin — and /api/session/delete calls
delete_cli_session() which erases the child's state.db transcript (data loss).

- /api/sessions list: coerce subagent rows to read_only=True + is_cli_session=False
  so the UI offers no mutation affordances.
- New _session_is_subagent_view_only() shared guard; applied to the direct
  mutation routes that bypass _get_or_materialize_session(): delete / clear /
  truncate / pin now 400 for subagent children. (rename/move already route
  through the gated _get_or_materialize_session and 403.)
- tests: guard helper + static contract that all 4 routes + list coercion present.
Fixes #5307
2026-07-01 08:21:03 +00:00
nesquena-hermes 9eb208ebc3 fix(#5307): root-fix subagent classification + GET serialization (Codex round 7)
- api/agent_sessions.py is_cli_session_row(): add 'subagent' to non_cli_sources
  so EVERY consumer (sidebar rows, /api/sessions, etc.) classifies a delegated
  child as non-CLI (the root the per-site fixes were compensating for).
- api/routes.py GET /api/session happy path: coerce is_cli_session=False +
  read_only=True in the serialized payload for a subagent child before redaction,
  so a stale writable sidecar can't be exposed as writable to the browser.
Fixes #5307
2026-07-01 08:10:24 +00:00
nesquena-hermes 2e33e40272 fix(#5307): guard persisted subagent sidecars against writable use (Codex round 6)
- _get_or_materialize_session happy path: reject an already-persisted subagent
  sidecar (source_tag/raw_source=='subagent' or state.db subagent) even when it
  was stored read_only=False (pre-fix materialization), so chat-start can't write it.
- import_cli existing-session refresh: coerce read_only=True on the persisted
  sidecar (and response) when it's a subagent child.
- test: persisted read_only=False subagent sidecar is still refused by the helper.
Fixes #5307
2026-07-01 08:04:47 +00:00
nesquena-hermes 6e2ead5b1b fix(#5307): close cross-profile + existing-session subagent edges (Codex round 5)
- import_cli _read_only_view now also treats resolved cli_meta source_tag/raw_source
  =='subagent' as view-only (all_profiles=true resolves cli_meta from a non-active
  profile, which the active-profile state.db _sa_child check could miss).
- import_cli existing-session refresh branch no longer hardcodes is_cli_session=True
  for a subagent child (both the persisted update and the response payload).
Fixes #5307
2026-07-01 07:55:00 +00:00
nesquena-hermes 161fdeba7a fix(#5307): gate the 3rd/final import_cli_session write path (archive fallback)
Codex round 4 found POST /api/session/archive's missing-sidecar fallback also
calls import_cli_session(). grep confirms exactly 3 import_cli_session() call
sites in routes.py; all 3 now refuse source='subagent' children:
  - 4792 _get_or_materialize_session (chat-start) -> PermissionError
  - 22382 import_cli endpoint -> read-only view payload
  - 13885 archive fallback -> 400 'Subagent sessions cannot be archived'
So no path can materialize a delegated child as a writable WebUI sidecar.
Fixes #5307
2026-07-01 07:48:34 +00:00
nesquena-hermes c989acc991 fix(#5307): gate subagent children in the shared materialize chokepoint (Codex round 3)
Codex found a 3rd writable path: POST /api/chat/start -> _get_or_materialize_session()
materialized source='subagent' as a writable sidecar before the not_claimable guard.
Fix: refuse subagent children (PermissionError) at that shared chokepoint, checked via
_is_subagent_child_session_id(sid) (state.db source, independent of cli_meta) BEFORE the
materialize path — so all three entry points (GET synth, import_cli, chat-start) now
consistently keep a delegated child view-only. CLI/TUI/Desktop materialization preserved.

Tests: materialize helper refuses subagent child + still allows tui.
Fixes #5307
2026-07-01 07:44:11 +00:00
nesquena-hermes 3ecb2625aa fix(#5307): close 2 more subagent-child writable-session holes (Codex round 2)
- api/routes.py GET /api/session synthesized response: stop hardcoding
  is_cli_session=True; serialize bool(synth.is_cli_session) so a recovered
  subagent child stays not-CLI-classified (was overriding the helper's False).
- api/routes.py POST /api/session/import_cli: gate source='subagent' into the
  read-only view payload (is_cli_session=False, imported=False) BEFORE
  import_cli_session(), so a subagent child can never be materialized as a
  writable WebUI sidecar via this endpoint.
- tests: assert import_cli routes subagent children read-only (no materialize).

Both were paths that bypassed the _is_claimable_cli_source denylist. Fixes #5307
2026-07-01 07:35:48 +00:00
nesquena-hermes a823ab5ff8 fix(#5307): recover subagent child transcript view-only (Codex hardening)
Reworked per the Codex gate finding: the first approach widened the client
import predicate, which (a) conflicted with #3603's intentional _isExternalSession
gate and (b) let import_cli persist the subagent child as a WRITABLE, CLI-classified
session that then passed the poll-skip/active-refresh gates.

Corrected to a server-side, view-only recovery:
- api/routes.py: mark source='subagent' as NON-claimable in _is_claimable_cli_source
  (both cli_meta and state.db source denylists). A subagent child now resolves via
  the not_claimable branch -> read-only Session with its state.db transcript, and
  build_session takes is_cli_flag (False for subagent children) so the recovered
  session is NOT CLI-classified and can't widen the frontend _isExternalSession gates.
- static/sessions.js: REVERTED to master (no client change needed; #3603 contract intact).
- tests: assert reason=not_claimable, read_only=True, is_cli_session!=True, transcript present;
  #2782 deleted-webui 404 preserved; #3603 _isExternalSession contract preserved.

58 tests green (5307 + 3603 + claim-cli + core-data-loss). Fixes #5307
2026-07-01 07:24:16 +00:00
nesquena-hermes bed98c787c fix(sessions): load delegated subagent child transcript from state.db (#5307)
A delegated subagent child (source='subagent' in state.db) usually has no WebUI
sidecar but is registered in the WebUI index sharing the parent's lineage. That
made GET /api/session -> _claim_or_synthesize_cli_session return 'was_webui' ->
404, so the child pane opened empty despite state.db holding messages.

- api/routes.py: add _state_db_session_source() + _is_subagent_child_session_id();
  exclude subagent children from the was_webui 404 gate so they recover their
  state.db transcript (the #2782 self-heal 404 for deleted WebUI sessions is kept).
- static/sessions.js: add _isSubagentChildSession() + _sessionNeedsServerImportForLoad()
  (kept separate from _isExternalSession to avoid widening refresh-gating); the
  main session-tap, lineage-segment, and child-open handlers now trigger the
  import/merge path for subagent children.
- tests: test_5307_subagent_child_transcript.py (7 tests).

Fixes #5307
2026-07-01 07:15:19 +00:00
nesquena-hermes 159ede9276 docs(extensions): link EXTENSIONS.md to the vetted extension library repo
EXTENSIONS.md documented the WebUI-side extension infrastructure (loader, manifest,
capabilities incl. settings_schema / skin scheme / TTS engine, install client) but
never linked to hermes-webui/hermes-webui-extensions — the public repo where the
vetted, one-click-installable gallery entries actually live and where new extensions
are contributed.

Adds two cross-links, no behavior change:
- intro callout: points to the library repo + its docs/extension-entry.md, framing
  this doc as the infrastructure side and the library repo as where entries live.
- a 'Contributing to the extension library' pointer at the end of the authoring
  guidance, describing the entry-PR + CI-safety-gate + registry-publish flow.

Docs-only.
2026-07-01 07:11:16 +00:00
nesquena-hermes 97b5979fd8 docs(changelog): keep active-parent delegate children stably visible (#5306/#5305) 2026-07-01 07:05:37 +00:00
nesquena-hermes 1a429cda3b stage #5320 2026-07-01 07:05:14 +00:00
nesquena-hermes 38ca286acc docs(changelog): honor busy input mode on first send (#5170) 2026-07-01 06:54:46 +00:00
nesquena-hermes 3d780836d3 stage #5170 2026-07-01 06:54:07 +00:00
nesquena-hermes f942b712ad docs(changelog): gate reasoning_content replay for provider-facing history (#5024) 2026-07-01 06:50:23 +00:00
nesquena-hermes d6c01245c5 stage #5024 2026-07-01 06:49:44 +00:00
nesquena-hermes 6095f639ba docs(changelog): native OIDC login for WebUI (#5012) 2026-07-01 06:45:55 +00:00
nesquena-hermes 36436c9397 stage #5012 2026-07-01 06:45:22 +00:00
nesquena-hermes 578e70c2bc docs(changelog): gate embedded-terminal endpoints to local origins (#5268) 2026-07-01 06:41:41 +00:00
nesquena-hermes 9973b36cce stage #5268 2026-07-01 06:41:09 +00:00
nesquena-hermes ebdd48079a fix(sidebar): keep active-parent delegate children stably visible (flicker) (#5306)
#5306 (flicker): while a parent WebUI session is the active/streaming session,
a linked delegate subagent child that transiently reports message_count===0
between /api/sessions polls was dropped by _sidebarRowHasVisibleMessages BEFORE
_attachChildSessionsToSidebarRows could stack it under its parent. It never
entered sessionsRaw, so the row vanished, then reappeared on the next refresh
once its list metadata caught up — the flicker. Extend the visibility predicate
with an active-parent exception (mirroring the existing active-session
exception): a child_session whose parent_session_id is the active sidebar
session stays visible even at message_count 0. Scoped to the active parent so
truly-empty unrelated sessions are still hidden.

#5305 (orphan): a delegated subagent child whose WebUI parent is filtered out of
the current render (project/profile/source scope) was promoted to a contextless
top-level "Subagent Session" orphan. Suppress cross-surface child_session rows
whose parent row is absent from the render instead of orphaning them, mirroring
the existing archived-hidden-parent suppression (#4293). The genuinely-external
parent case (messaging/CLI) still orphans via the parentIsExternal branch.

Tests: tests/test_5306_subagent_sidebar_flicker.py (7 tests) locks both
invariants and the no-regression guards, executing the real sessions.js helper
regions under node like the existing lineage tests.
2026-07-01 06:36:02 +00:00
nesquena-hermes 0533871ef5 docs(changelog): docker rsync .playwright exclude (#5316) 2026-07-01 06:35:55 +00:00
nesquena-hermes b4bfda5504 stage #5316 2026-07-01 06:35:35 +00:00
nesquena-hermes 948e8bfc67 docs(changelog): composer footer control reordering (#5075) 2026-07-01 06:23:51 +00:00
nesquena-hermes 04191939d2 stage #5075 2026-07-01 06:13:17 +00:00
nesquena-hermes adb528cfcb fix(#5170): persist busy-input-mode mirror on Settings autosave + panel-load (Codex: mirror only written on boot-apply, so a Settings change didn't survive the boot race) 2026-07-01 01:45:44 +00:00
nesquena-hermes 2ec7df8a27 rebase #5170 onto current master (union-resolved busy-mode boot conflicts: keep persisted pref on settings-load-fail + preserve placeholder-hint/showBusyPlaceholderHint) 2026-07-01 01:40:53 +00:00
nesquena-hermes d257e5f36c fix(security): gate embedded-terminal endpoints to local origins when auth disabled
The embedded workspace terminal spawns a PTY shell that runs arbitrary
commands as the server-process user. check_auth() returns True
unconditionally when no password/passkey is configured (the default
out-of-the-box state), so without a network-scope gate the terminal
endpoints were reachable by any unauthenticated caller able to hit the
port — which on a passwordless public bind is remote code execution.

Apply the same local-origin gate the onboarding/bootstrap endpoints use
(_onboarding_gate_allows) to /api/terminal/{start,input,resize,close} and
/api/terminal/output: with auth disabled, accept only loopback/private
origins, ignore spoofable X-Forwarded-For/X-Real-IP unless
HERMES_WEBUI_TRUST_FORWARDED_FOR=1, and honor HERMES_WEBUI_ONBOARDING_OPEN=1
as the explicit opt-out for a deliberately-exposed server. Auth-enabled
servers (cookie already verified upstream) and genuine same-host clients
are unaffected.

Also fixes a latent test-isolation leak in
test_extension_route_remains_behind_webui_auth: it set HERMES_WEBUI_PASSWORD
but never invalidated the process-wide password-hash cache, so its result
depended on suite execution order (exposed when the new test file shifted
ordering). Invalidate before+after so it reads the env var deterministically.

12 new gate tests in tests/test_cvd3_terminal_local_origin_gate.py.
2026-07-01 00:40:25 +00:00
nesquena-hermes 7298d91dec docs(changelog): prune orphan zero-message sidebar sessions (#4988) 2026-07-01 00:20:13 +00:00
nesquena-hermes 9bd71e9bae Merge #4988 (enihcam prune orphan zero-msg sessions) into stage
# Conflicts:
#	CHANGELOG.md
2026-07-01 00:19:56 +00:00
nesquena-hermes 1dfdf6a2c1 docs(changelog): extension skin base scheme (#5271) 2026-07-01 00:09:37 +00:00
nesquena-hermes a17df9fd22 Merge #5271 into stage 2026-07-01 00:08:40 +00:00
nesquena-hermes 39a3a6e703 docs(changelog): expand zhCN localization (#5279) 2026-07-01 00:04:24 +00:00
nesquena-hermes 14bb0ae31a Merge #5279 into stage 2026-06-30 23:58:46 +00:00
nesquena-hermes e4b901da53 fix(i18n): restore large_text_paste keys dropped in zh during rebase resolution 2026-06-30 23:55:15 +00:00
nesquena-hermes c80311a5db docs(changelog): SSE-recovery follow-intent sticky guard (#5217) 2026-06-30 23:35:00 +00:00
nesquena-hermes e0f42ca900 Merge #5217 into stage 2026-06-30 23:35:00 +00:00
nesquena-hermes f7ec3c7b39 test(#5217): scope follow-intent ordering assertion to _handleStreamError + strip EOF blank line (Codex gate nits) 2026-06-30 23:18:00 +00:00
nesquena-hermes 740a3a693e docs(changelog): deep-link ?q= composer prefill, converged (#4969) 2026-06-30 23:08:16 +00:00
nesquena-hermes 373c3e4b63 Merge #4969 into stage 2026-06-30 23:07:53 +00:00
nesquena-hermes a616163523 docs(changelog): default-Kanban dispatch fix (#5289) + PWA new-chat hydration defer (#5287) 2026-06-30 22:45:35 +00:00
nesquena-hermes 338060e910 Merge #5287 into stage-b1 2026-06-30 22:45:08 +00:00
nesquena-hermes 7df031592c Merge #5289 into stage-b1 2026-06-30 22:45:06 +00:00
nesquena-hermes ec5f9715a1 docs(changelog): two opt-in neon skins (#4738) 2026-06-30 22:22:14 +00:00
nesquena-hermes 3e84d7bf11 Merge #4738 (savagebread neon skins + skin-persistence fix) into stage 2026-06-30 22:21:28 +00:00
nesquena-hermes 0b2507ac9f fix(#4738): register neon-soft/neon-paint in _SETTINGS_SKIN_VALUES (server-side skin persistence) 2026-06-30 22:13:36 +00:00