Commit Graph
726 Commits
Author SHA1 Message Date
c5bb1fcaa0 stage #4688 (franksong2702): running-first session ordering in sidebar (#4699)
Overlays live pending_started_at/updated_at/last_message_at onto cached /api/sessions rows
(when newer), sorts active/running sessions first then by freshest effective timestamp, applies
the same running-first order in the frontend sidebar renderer, and groups date buckets with the
same runtime-aware timestamp (no stale header on resumed running sessions). routes.py +
sessions.js + 81-line test. Composes with #4680 cache-freeze + #4597 (98 session-list tests pass).

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-22 03:52:19 -07:00
a66913ac26 stage #4680 (nesquena-hermes): freeze /api/sessions cache during streaming (fixes #4672) (#4690)
Freezes the session-list cache source-stamp on the SET of active stream ids (not per-write
state) so mid-stream sidebar polls hold the cache instead of rebuilding all_sessions() every
poll and contending for the global LOCK the streaming worker holds (the ~2 tok/s + 15s
/api/sessions cause). Structural mutations still invalidate via publish_session_list_changed;
only a streaming session's own title/message_count lags <=TTL. routes.py + 109-line test.
99 session-list/sidebar/cache tests pass.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-22 02:11:04 -07:00
74d7732778 stage #4597 (santastabber): reduce session-list work for archived histories (perf) (#4665)
Converged after deep bounce cycles: Finding A (production regression — skipped the
state.db source correction before the CLI filter, dropping WebUI sessions with stale-CLI
sidecar flags) FIXED via _apply_sidebar_state_db_overrides(result) before the routes.py
filter on both index + full-scan paths; Finding B (flaky lineage test) FIXED via hermetic
_persisted_session_ids_snapshot fixture. 16 own tests deterministic. Code byte-faithful
from PR head e1a3ecf22eca.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 22:57:28 -07:00
c888dd6a79 stage #4637 (santastabber): allow toggling installed extensions (extensions phase-2) (#4649)
UI enable/disable of installed manifest extensions via a bounded fail-safe
extension-overrides.json state file. Safe boundary: no manifest edits, no asset
fetches, no sidecar proxy, no extension-owned routes. /api/extensions/toggle is
auth+CSRF gated; anchored _EXTENSION_ID_RE (no path traversal); atomic UTF-8 write
under _EXTENSION_STATE_LOCK; 32KiB/512-id DoS bounds; disabled override suppresses
asset/sidecar injection end-to-end. Nathan greenlit the extensions-phase-2 concept.
Code byte-faithful from PR head 8769b4a3902e. 49 own tests pass.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 19:15:54 -07:00
b4c434fb65 Release v0.51.565 — API-server sidecar prune (#4619) + mic insecure-origin message (#4621) (#4639)
* stage batch #4619+#4621 (franksong2702): API-server sidecar orphan-prune + mic insecure-origin message

#4619 (fixes #4591): widen the #3238 orphan-prune to also reconcile API-server
imported sidecars (api/routes.py _is_api_server_sidecar_row). Webui-source guard
intact, fail-closed probe (agent_session_rows_existing degrades to assume-present).
#4621 (fixes #4571): detect insecure non-local http origins for voice dictation +
hands-free, show mic_insecure_origin message, preflight before capture. mic_insecure_origin
added to all 13 locale blocks (zh-Hant real translation + English TODO fallbacks).
Disjoint files (backend routes vs frontend boot/i18n). Code byte-faithful from PR heads.

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

* Release v0.51.565 — Release TX (API-server sidecar prune #4619 + mic insecure-origin #4621)

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-21 16:50:32 -07:00
nesquena-hermes 07e6a839fa stage #4599 onto master (v0.51.555) — add id-ID Edge TTS voice + fix Listen button playing-state (parity)
latipun7: adds id-ID-GadisNeural to the Edge TTS allowlist (Edge TTS is a core builtin -> parity, default-in)
+ picker label + sets speaking-state at start of _playEdgeTtsChunked so the Listen button reflects playback.
Co-authored-by: latipun7 <latipun7@users.noreply.github.com>
2026-06-21 08:35:03 +00:00
nesquena-hermes 64ca1f8686 harden(#2980): profile-scope continuation lookup + safe-sid + no speculative restore write
Gate findings (Codex 2-CORE / Opus SHIP-WITH-FIXES), both fixed:
1. cross-profile leak — _pre_compression_continuation_session_id scanned all profiles' sessions,
   matched only by parent_session_id; now filters children by _profiles_match(child, snapshot)
   + validates returned sid with is_safe_session_id. +cross-profile regression test.
2. restore-state poison — frontend wrote continuationSid to localStorage/URL BEFORE the inner
   loadSession proved it loadable; removed the speculative write (success path at the loaded id
   handles it). Updated the source-grep test to assert the safe form.
Bounded BFS (range(20)+seen) + recursion guard confirmed safe by both gates.
2026-06-21 08:17:32 +00:00
nesquena-hermes dbeb437bcc rebase #2980 onto master (v0.51.548) for ship-gate — recover mobile reloads after compression rotation
george-andra: GET /api/session surfaces continuation_session_id for a hidden pre-compression
snapshot; loadSession follows it so a mobile reload mid-compression recovers to the visible
continuation instead of a hidden snapshot (chat looked 'lost'). Resolved 2 conflicts (1615 behind):
sessions.js merged w/ #2971 re-arm guard; routes.py merged w/ _session_source_is_webui branch.
Co-authored-by: george-andra <george-andra@users.noreply.github.com>
2026-06-21 08:17:32 +00:00
nesquena-hermes 4bf9ff7518 stage #4594 onto master (v0.51.550) — preserve visible transcript after manual /compress (#3133)
Manual /compress wrote the compressed payload into BOTH s.messages (visible) and s.context_messages
(model-facing) + wiped s.tool_calls -> messages vanished from the window. Fix keeps s.messages +
tool history durable, deep-copies compressed only into s.context_messages, anchors off visible msgs.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-21 06:46:47 +00:00
nesquena-hermes 236b165b7a harden(#4587): align scene-builder final-answer filters to the renderer's >=0.9 ratio
Codex gate (SILENT): the scene-builder/hydrator _anchor_scene_row_looks_like_final_answer
(messages.js + routes.py) used a loose '>=80 + prefix' rule while the renderer
_anchorSceneProseMatchesFinalAnswer uses '>=80 + ratio>=0.9'. A short intermediate-prose row that
was a prefix of the final answer could be dropped from the persisted scene by the looser builder,
leaving the stricter renderer no row to show -> silent prose loss. Aligned both filters to the
renderer's rule (drop fewer -> preserve more intermediate prose). Matrix already GREEN; this closes
the edge the fixture didn't exercise.
2026-06-21 06:03:44 +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 8ea7ad582e rebase #3581 onto master (v0.51.546) for review+ship-gate — require current password before disabling auth
starship-s: sudo-mode re-auth on POST /api/settings before any change/clear/passwordless transition
of password auth, + cosmetic auth-disabled-acknowledged nav badge. Operator-hardening (rubric-named).
Security-boundary + visible UI -> Nathan visual sign-off. Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-06-21 02:26:20 +00:00
nesquena-hermes 6b2ac55b8d stage #4566 + stamp v0.51.546 — fix flaky gateway_sync test (#4557)
rodboev: settings-write-version counter widens the session-list cache key + flush CLI-sessions
cache on visibility toggle, killing the mtime_ns-bucket collision flake. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-21 02:04:09 +00:00
nesquena-hermes 81a4945be8 harden(#4411): read-side anchor-scene ref-ambiguity guard (Opus finding)
Mirror the write-side _find_anchor_scene_message ambiguity guard on the read side:
_hydrate_anchor_activity_scenes now counts ref occurrences and skips ref-based
attachment for any ref resolving to >1 assistant message (falls through to the
positional index match), so a duplicate content-digest ref can't double-render the
same worklog scene. Low-severity defense-in-depth on the crown-jewel render surface.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-20 22:05:01 +00:00
nesquena-hermesandfranksong2702 001148d208 fix(#4411): guard /api/session/anchor-scene against cross-profile writes
Codex ship-gate (CORE): _handle_session_anchor_scene loaded the session via
_get_or_materialize_session (by-id, no profile scoping) and persisted
anchor_activity_scenes without the active-profile visibility check GET /api/session
applies — an authenticated request under profile A could write anchor scenes onto a
profile-B session. Add the _session_visible_to_active_profile guard (404, no write),
mirroring routes.py:~8922. Regression test verified red-without/green-with the guard.

Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-20 22:02:56 +00:00
nesquena-hermes 6951d10ae5 rebase #4411 onto master (v0.51.542) for deep-review — Stable Assistant Turn Anchors capstone
franksong2702's activity_scene_v1 anchor-scene model: live-stream/settled/refresh/re-entry
show the same ordered turn. Rebased clean (no conflicts) onto current master.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-20 21:48:55 +00:00
jja881 53affd6936 fix: address review feedback — remove redundant clause, add guard and tests
- Remove the redundant 4th OR clause from _catalog_has_provider() — the
  existing C1–C3 already handle compound provider IDs correctly (C2 IS
  a normalized-vs-raw comparison; the 4th clause was a strict subset).
- Add && _activeProvider guard to _isCrossProviderPick in messages.js
  to prevent spurious True when the profile has no configured default
  provider (reviewer's optional hardening suggestion).
- Add test_catalog_has_provider_compound_ids.py verifying that compound
  provider IDs like 'custom:glm-free-relay' are correctly recognized by
  the existing C1–C3 clauses.
2026-06-20 18:44:25 +00:00
jja881 96265f6ae4 fix: preserve cross-provider model selection across conversation turns
Two-part fix for the model selector jumping back to the profile default
after the first message when a cross-provider model (e.g. a custom relay
like custom:glm-free-relay) is selected.

Backend (api/routes.py):
- _catalog_has_provider() now matches compound provider IDs (e.g.
  custom:zenmux-relay) whose normalized form (custom) is a raw
  catalog provider_id.  Previously the three existing checks missed
  normalized-vs-raw comparison for compound hints, causing the catalog
  lookup to fail and the fallback branch to silently revert the model
  to the profile default on every non-explicit turn.

Frontend (static/messages.js):
- The explicit_model_pick marker was consumed after the first send(),
  so subsequent sends lost the signal and the server repaired the
  model back to the profile default (#3737 regression).
- Now, when the session has a non-default model from a different
  provider than the profile's active provider, every send() sends
  explicit_model_pick=true so the server honors the user's choice
  across the entire conversation.
- The pending onchange marker is still consumed after the first send
  (unchanged behavior); only the explicit_pick inference is broadened.
2026-06-20 18:44:25 +00:00
MinhoJJang 630fd26eec fix configured provider slash model routing 2026-06-20 17:42:18 +00:00
nesquena-hermes 90505c0a8e stage #3285: gateway restart from agent-health alert (POST /api/health/restart) + v0.51.533 CHANGELOG 2026-06-20 04:40:49 +00:00
nesquena-hermes a60ba78298 stage #4517: plugins panel active-provider badge via is_active_provider (#4496) + v0.51.531 CHANGELOG 2026-06-20 03:35:44 +00:00
nesquena-hermes 51e2fce823 stage #4522: fix /api/profiles UnboundLocalError 500 (regression from v0.51.528) + v0.51.530 CHANGELOG 2026-06-20 03:18:33 +00:00
nesquena-hermesandrodboev 0952fcc606 fix(routes): drop redundant local _is_isolated_profile_mode import (#4454 gate)
The /api/profiles handler re-imported _is_isolated_profile_mode locally inside
handle_get, which made the name function-local for the ENTIRE handle_get scope
-> the earlier use at the /api/projects branch (routes.py:8000) hit
UnboundLocalError (F823), failing 6 test shards + lint. It's already a
module-level re-export (routes.py:429), so the local re-import is redundant;
removing it lets every in-function use resolve to the module global. Also drop
an unused 'from pathlib import Path' in the new #4449 state-dir test.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-20 00:48:20 +00:00
Rod Boev 41f2015a32 fix(#2698): close isolated profile escape hatches 2026-06-20 00:48:20 +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
Rod Boev 35eced6f35 fix(profiles): broaden except clause and sanitize error in isolated mode (#2698) 2026-06-20 00:48:19 +00:00
Rod Boev 36dbe1a415 fix(profiles): return 403 for isolated-mode guards, fix fallback identity (#2698) 2026-06-20 00:48:19 +00:00
Rod Boev 963f7c3864 fix(profiles): honor isolated HERMES_HOME profile mode (#2698) 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
nesquena-hermesandrodboev 0fe707b2bf fix(wiki): reject hardlinked page files in allowlist + read revalidation (#4375 Codex gate)
Gate-found (Codex CORE, repro'd): a hardlink at a clean *.md page name can
carry an arbitrary inode (incl. one outside the wiki) through the O_NOFOLLOW +
inode-identity read check — O_NOFOLLOW does not block hardlinks. Reject any
page file with st_nlink > 1 in BOTH the allowlist walk and the read-path
revalidation. Pre-existing on master (the inode recapture predates this PR's
cache); closing it here while on the read path. +1 toothed regression test
(prime cache -> hardlink listed page to outside secret -> assert 404, no leak).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-19 21:11:11 +00:00
Rod Boev 0c4894efe9 fix(wiki): lstat log fallback before reading headings (#4375) 2026-06-19 21:11:11 +00:00
Rod Boev 9242f1facb fix(wiki): bind status metadata to verified file identities (#4375) 2026-06-19 21:11:10 +00:00
Rod Boev 82fd7d0166 fix(wiki): exclude unverified status-file metadata (#4375) 2026-06-19 21:11:10 +00:00
Rod Boev 8e4d390366 fix(wiki): reject post-snapshot metadata races (#4375) 2026-06-19 21:11:10 +00:00
Rod Boev 9a75a85d59 fix(wiki): keep status and browse metadata on verified identities (#4375) 2026-06-19 21:11:10 +00:00
Rod Boev 0542d91824 fix(wiki): recheck status reads against allowlisted identity (#4375) 2026-06-19 21:11:10 +00:00
Rod Boev db0bcaae8f fix(wiki): drop stale cached paths that no longer point to files (#4375) 2026-06-19 21:11:10 +00:00
Rod Boev f93d1332df fix(wiki): align browse and status with page-path rules (#4375) 2026-06-19 21:11:10 +00:00
Rod Boev b93cd10b40 fix(wiki): reject non-listed Windows path aliases (#4375) 2026-06-19 21:11:10 +00:00
Rod Boev 10c8ae4e46 fix(wiki): reuse the checked page allowlist across wiki reads (#4375) 2026-06-19 21:11:10 +00:00
Rod Boev 22bfed43ac fix(wiki): bind cached reads to the requested allowlisted page (#4375) 2026-06-19 21:11:10 +00:00
Rod Boev 1dd0b5bbcf fix(wiki): keep cached reads inside their original section (#4375) 2026-06-19 21:11:10 +00:00
Rod Boev e44fd2a6ac fix(wiki): reassert cached read-path containment (#4375) 2026-06-19 21:11:10 +00:00
Rod Boev 3f6369ae5c Address cross-provider review findings for browse-path resolve and test anchor 2026-06-19 21:11:10 +00:00
Rod Boev 46c3e495fc perf(wiki): cache resolved page allowlist and document hardlink trust boundary (#4375) 2026-06-19 21:11:09 +00:00
nesquena-hermes 0c8e6ebf27 stage #4488: scope live model provider keys (#4488) [CHANGELOG pending post-4506] 2026-06-19 20:43:31 +00:00
nesquena-hermes 87aefad22f stage #4489: scope imported sessions to active profile (#4489) + v0.51.521 CHANGELOG 2026-06-19 20:25:43 +00:00
nesquena-hermesandai-ag2026 54089a0696 fix(streams): only reap stale ACTIVE_RUNS entries when worker is gone from STREAMS (#4492 Codex gate)
Gate-found: reaping on started_at age alone could pop a genuinely-live worker
that's mid-teardown past the 180s ceiling (active_stream_id cleared during final
writeback, STREAMS entry still present) — ACTIVE_RUNS is worker-lifecycle truth
for health/background-wakeup/agent-cache consumers. Add a 'not in STREAMS'
guard (snapshot taken sequentially before ACTIVE_RUNS_LOCK, no nesting) and pop
by the real dict key. +1 regression test (toothed). Keeps the anti-permanent-409
age behavior intact.

Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-06-19 19:53:04 +00:00
nesquena-hermes e74bae1d35 stage #4492: reconcile stale active-run registry (#4492) [CHANGELOG pending post-4501] 2026-06-19 19:53:04 +00:00
nesquena-hermes 4b7a17bf33 stage #4487: read-only memory write 403 (#4480) + v0.51.518 CHANGELOG 2026-06-19 19:22:43 +00:00