Commit Graph
1252 Commits
Author SHA1 Message Date
nesquena-hermes 396d0d0abd Release v0.51.326 — Release KP (#3618 + #3802 + #3762 + #3810) (#3816)
Batch: mic STT capability probe+fallback (#3618, live-drive verified), journal cleanup on delete (#3802), minimal-schema SQL guard (#3762), Help hover readability (#3810). Full suite 8265, Codex SAFE, Opus SHIP, CI 11/11. Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com> Co-authored-by: dso2ng <dso2ng@users.noreply.github.com>
2026-06-07 23:18:51 -07:00
nesquena-hermes cadbe1e5d3 Release v0.51.325 — Release KO (in-app Help tab, #3518) (#3806)
#3518 (@rodboev): in-app Help tab in Settings (docs + GitHub issues links), redesigned as polished icon-led cards. UX-approved by Nathan. Full suite 8242, CI 11/11. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-07 18:59:01 -07:00
nesquena-hermes 0f4931d81b Release v0.51.324 — Release KN (i18n: settings + gateway-status translations, #3801) (#3805)
#3801 (@leszek3737): translate settings/gateway-status labels across 13 locales. +placeholder-parity fix for gateway_session_count pluralization. Full suite 8235, 469 locale tests pass, CI 11/11. Co-authored-by: leszek3737 <leszek3737@users.noreply.github.com>
2026-06-07 17:58:04 -07:00
nesquena-hermes 1d5c054815 Release v0.51.323 — Release KM (7-day provider spend chart, #3600) (#3804)
#3600 (@rodboev): 7-day spend chart + monthly pace in the provider quota card. UX-approved by Nathan. Full suite 8235, Codex SAFE (backend contract verified), Opus SHIP + refresh-keeps-chart fix (live-verified). CI 11/11. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-07 17:41:59 -07:00
nesquena-hermes ee982a7581 Release v0.51.322 — Release KL (sortable + filterable markdown tables, #3728) (#3803)
#3728 (@rodboev): markdown tables sortable+filterable on desktop, suppressed <=640px so mobile is unchanged. UX-approved by Nathan via Telegram. Full suite 8234, CI 11/11, live-driven. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-07 16:35:28 -07:00
nesquena-hermes 9d94298278 Release v0.51.321 — Release KK (Phase 3 light: load renderable transcript tails, #3790) (#3798)
Phase-3-light. #3790 (@ai-ag2026): expand cold-load transcript window to ~msg_limit renderable rows so tool-heavy sessions don't open showing 1-2 messages. Codex CORE fix: explicit expand_renderable flag (cold-load only; Load-earlier keeps raw cap). Also fixed a recurring CI timing flake (git-parallel test → deterministic Barrier). Full suite 8228, Codex SAFE, Opus SHIP, CI 11/11. Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-06-07 15:22:44 -07:00
nesquena-hermes 59de540b3d Release v0.51.320 — Release KJ (Phase 2: Polish (pl) language support, #3781) (#3796)
Complete Polish locale. #3781 (@leszek3737). Full suite green, CI 11/11, 180 locale/parity tests pass. Co-authored-by: leszek3737 <leszek3737@users.noreply.github.com>
2026-06-07 13:48:52 -07:00
nesquena-hermes 2e1aa3c99c Release v0.51.319 — Release KI (Phase 3 light: refresh stale continuation metadata, #3789) (#3795)
Phase-3-LOW. #3789 (@ai-ag2026, refs #3740): refresh stale-indexed compression continuation rows from sidecar; gate excludes session_source='fork' (release-gate MUST-FIX). Full suite 8213, Opus SHIP, Codex MUST-FIX applied, CI 11/11. Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-06-07 13:12:23 -07:00
nesquena-hermes ce9adc5e2c Release v0.51.318 — Release KH (Phase 3 light: warm account-usage probe worker pool, #3722) (#3792)
Phase-3-LOW backend refactor. #3722 (@rodboev, #1912): per-probe subprocess.run -> warm worker pool for codex/anthropic quota probes; all hardening retained + idle reaper + credential invalidation + fallback. Codex SAFE, Opus SHIP, real-thread self-verify clean, CI 11/11. Follow-up #3787. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-07 12:07:13 -07:00
nesquena-hermes bc6949219d Release v0.51.317 — Release KG (Phase 3 light: align CSP enforcement with report policy, #3727) (#3786)
Phase-3-LOW backend security fix. #3727 (@rodboev, #1909): enforced CSP now honors the same connect-src as report-only (built from one shared template). Full suite 8205 passed, Codex SAFE (live-verified headers), Opus SHIP. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-07 10:52:07 -07:00
nesquena-hermes 07235674ad Release v0.51.316 — Release KF (Phase 2: agent-source dependency audit, #3723) (#3785)
Phase-2 release. #3723 (@rodboev): read-only agent-source dependency audit script + contract docs + test, no runtime change. (#3781 Polish locale dropped from batch — incomplete locale, kicked back to contributor.) Full suite 8199 passed, Opus SHIP, Codex clean. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-07 10:38:49 -07:00
nesquena-hermes ee229f32ad Release v0.51.315 — Release KE (Phase 1: cross-platform workspace-fallback tests, #3780) (#3783)
Phase-1 test-portability fix (#3780/#3771, @rodboev): replace hard-coded POSIX paths + chmod simulation with monkeypatch-based simulation. Release gate caught + fixed a test-isolation regression (media-fixtures dir polluting workspace-suggest) — dot-prefixed it. Full suite 8188 passed. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-07 10:11:52 -07:00
nesquena-hermes 7b60151cc1 Release v0.51.314 — Release KD (test infra: reliable test-server boot + diagnostics) (#3782)
Test-harness only. conftest test-server fixture now captures server output, fails fast on early subprocess death, retries once, raises timeout to 45s, and surfaces the log tail on failure — fixing the recurring ConnectionRefused boot-cascade flake. Full suite 8187 passed, Codex SAFE, Opus SHIP-IT. No app code changed.
2026-06-07 09:38:29 -07:00
nesquena-hermes 1bc190fb61 Release v0.51.313 — Release KC (instant profile switcher — skip per-profile alias scan) (#3779)
list_profiles_api() builds rows from cheap upstream helpers and skips find_alias_for_profile (which reads ~2.4GB of binaries from ~/.local/bin per dropdown open), with a 4s TTL cache + graceful fallback. 4700ms->250ms cold, 0ms cached. Full suite 8183 passed, Codex SAFE, Opus SHIP-IT. Supersedes #3772.
2026-06-07 09:27:32 -07:00
nesquena-hermes f1f56a901b Release v0.51.312 — Release KB (brick-wave: purge stale __pycache__ after self-update, fixes #3774) (#3778)
Fix-ourselves pickup of #3774 (@bambalados). _purge_agent_pycache() before os.execv() in _schedule_restart() so the re-exec'd process recompiles freshly-pulled source — fixes AttributeError on first chat after self-update. Full suite 8180 passed, Codex SAFE, Opus SHIP-IT. Co-authored-by: bambalados <bambalados@users.noreply.github.com>
2026-06-07 08:26:36 -07:00
nesquena-hermes 938ac9f55b Release v0.51.311 — Release KA (brick-wave: workspace Git RCE hardening #3769 + stale-snapshot sidebar visibility #3770) (#3776)
Brick-wave batch. #3769 (@Hinotoi-agent) hardens workspace Git config execution against repo-local RCE; #3770 (@ai-ag2026) keeps fuller pre-compression snapshots visible when _index.json is stale. Full suite 8176 passed, Codex SAFE, Opus SHIP IT. Co-authored-by: Hinotoi-agent; Co-authored-by: ai-ag2026
2026-06-07 08:05:30 -07:00
nesquena-hermesandreinocheong 4b390e115c Release v0.51.310 — Release JZ (#3760 — long-press project chips to manage on touch) (#3767)
* feat(sidebar): long-press project chips to open the context menu on touch (#3760)

Project filter chips could only be deleted/renamed via the right-click context
menu (oncontextmenu), which has no touch equivalent — so mobile/tablet users had
no way to delete a project from the sidebar; the list grew forever.

Adds a 500ms long-press gesture mirroring the existing session-item long-press
pattern: touchstart schedules the menu, touchmove cancels on >10px drift,
touchend suppresses the synthetic click when the long-press fired, touchcancel
cleans up. `.project-chip.long-pressing` gives accent + slight-scale feedback;
`touch-action:manipulation` + `user-select:none` + `-webkit-touch-callout:none`
prevent the native callout/selection from competing.

Maintainer fix on top of the contributor PR (multi-touch correctness, flagged in
review): touchstart now clears any in-flight `_lpTimer` before scheduling a new
one (a second finger / stray touchstart previously orphaned the prior timer,
which then fired unsuppressed ~500ms later and popped the menu after the gesture
was cancelled), and the timer body bails if `_lpHandled` is already set so a
stale fire is a no-op — matching the session-item belt-and-suspenders. Also
dropped a stale, unrelated issue reference from the original comment.

Co-authored-by: reinocheong <[email protected]>

* docs(changelog): stamp v0.51.310 — Release JZ (stage-3760 long-press project chips)

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 21:36:24 -07:00
nesquena-hermesandfranksong2702 a20ef5e0c3 Release v0.51.309 — Release JY (#3763 — replay restored live tool cards on reconnect, fixes #3707) (#3766)
* fix(streaming): replay restored live tool cards on reconnect (#3763, fixes #3707)

Post-#3401 (#3400 live-to-final epic) recovery residual. When a running session
is restored from its in-memory live-turn snapshot and then reattached to the SSE
stream, the restore-success path skipped replaying persisted live tool calls,
leaving restored live text/thinking but an EMPTY Worklog until a later SSE event
or the final render rebuilt the turn.

- Extract the persisted-tool-card replay into replayPersistedLiveToolCards()
  (reads S.toolCalls or INFLIGHT[sid].toolCalls); run it on restoredLiveTurn &&
  didReconnect, not only the !restoredLiveTurn fallback.
- Dedup safety: restore-success replay passes {skipUnkeyedRestoredDuplicates:true}
  — when the restored snapshot already has .tool-card-row rows, an UNKEYED
  persisted tool is skipped to avoid a duplicate; keyed cards still replay and
  appendLiveToolCard's tid-dedup replaces the correct restored row.
- appendLiveToolCard() and the new liveToolReplayId() both key on
  tid||id||tool_call_id||tool_use_id||call_id (consistent 5-alias set), so the
  dedup covers all known id shapes.
- Both replay sites pass {sessionId, streamId} so the ownership guard applies.
- Regression coverage: restore-success+reconnect replays tools; unkeyed-restored
  duplicates skipped; all-id-alias dedup; prior ordering invariants preserved.

Correct post-#3401 fix for #3707 (supersedes the closed #3724).

Co-authored-by: franksong2702 <[email protected]>

* docs(changelog): stamp v0.51.309 — Release JY (stage-a5b #3763)

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 21:11:13 -07:00
nesquena-hermesandnesquena-hermes b6431eb677 Release v0.51.308 — Release JX (#3765 — gate onboarding-complete like its siblings) (#3765)
* fix(security): gate /api/onboarding/complete on the local-network check (#3765)

Sibling-path gap surfaced by the #3758 release gate. /api/onboarding/oauth/start,
/setup, and /probe are gated by _onboarding_gate_allows(), but
/api/onboarding/complete was not — it called complete_onboarding() unconditionally
(persists onboarding_completed=True, which hides the first-run wizard). On a
passwordless public bind, an unauthenticated no-Origin POST passes generic CSRF
and could flip the wizard off.

Pre-existing (the endpoint was ungated before #3758 too; #3758 only refactored the
three already-gated siblings). Low severity — it toggles a UI flag, not credentials
or access — but the inconsistency is a real hole, so close it the same way as its
siblings.

- Gate /api/onboarding/complete with _onboarding_gate_allows() → 403 when denied.
- Regression tests: public client (no forwarded headers) → 403 + complete_onboarding
  NOT called; loopback client → 200; auth-enabled → 200.
- Mark the legacy _is_local_from_handler mirror in test_onboarding_network.py as a
  STALE pre-#3758 contract (it trusts unauthenticated XFF); the authoritative
  trust-matrix tests live in test_security_review_fixes.py. Migrating the mirror to
  delegate to the real helper is tracked as follow-up test debt, out of scope here.

* docs(changelog): stamp v0.51.308 — Release JX (#3765 onboarding-complete sibling-consistency gate)

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 20:42:23 -07:00
nesquena-hermesandfantasticsquirrel 70596e6993 Release v0.51.307 — Release JW (stage-a3 — onboarding spoof fix + update-check CSRF, #3758 partial) (#3764)
* fix(security): ignore spoofable forwarded IPs in onboarding gate + make update-check CSRF-safe (#3758, partial)

Ships the two unambiguous slices of #3758's security review. The two slices with
breakage risk for existing installs — the Docker-default public-bind-requires-auth
gate and removing /tmp from the /api/media allowed roots — are held for separate
review/decision.

Onboarding forwarded-IP spoof hardening (+ release-gate CORE fix):
- The unauthenticated first-run onboarding local-network gate now IGNORES
  X-Forwarded-For / X-Real-IP by default (a direct client can spoof them to a
  private/loopback address to bypass the gate), trusting them only when
  HERMES_WEBUI_TRUST_FORWARDED_FOR=1 is set behind a trusted proxy (rightmost
  proxy-appended hop).
- Release-gate (Codex) CORE catch + refinement: when forwarded headers are
  present but untrusted, the header is ignored and locality is judged by the raw
  socket — but a PRIVATE/LAN raw socket (a separate proxy box that could forward
  an arbitrary public client) is no longer treated as local; only a LOOPBACK raw
  socket is (genuine same-host; a remote attacker can't forge a 127.0.0.1 TCP
  source). This closes the new fail-open the initial refactor introduced (public
  client behind a LAN proxy read as local) while preserving genuine same-host
  onboarding. LAN-proxy operators must set HERMES_WEBUI_TRUST_FORWARDED_FOR=1.
  Regression tests lock the full matrix (spoof-block, LAN-proxy-deny,
  loopback-allow, trusted-proxy-rightmost-hop, direct-public-deny).
- Three duplicated inline gate blocks unified into _onboarding_gate_allows /
  _onboarding_request_is_local; ONBOARDING_OPEN normalized to canonical truthy
  values via _truthy_env.

Update-check CSRF hardening:
- GET /api/updates/check is cache-only (cached_update_status(): no network/git
  mutation); forced refresh moves to POST /api/updates/check {force:true}; both
  frontend call sites updated and the test_api_timeout contract assertion updated.
- cached_update_status() preserves cached agent info when include_agent re-enabled.

Docker log masking: ENV_OBFUSCATE_PART also masks PASSWORD/SECRET/CREDENTIAL/COOKIE/SESSION.

Held for separate review (NOT in this PR): public-bind-requires-auth startup gate
(server.py + Dockerfile default) and the /api/media /tmp-root removal.

Co-authored-by: fantasticsquirrel <[email protected]>

* docs(changelog): stamp v0.51.307 — Release JW (stage-a3 #3758 partial)

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 19:50:56 -07:00
nesquena-hermesandai-ag2026 6bf455fa40 Release v0.51.306 — Release JV (stage-a2 — branchy compression lineage freshest-tip) (#3761)
* fix(sessions): resolve branchy compression lineage to the freshest tip (#3751)

Compression parents can have multiple continuation-looking children when a stale
segment is resumed after a newer compressed branch already exists. The previous
projection followed the newest DIRECT child only, so it could hide the deeper
branch with the latest real activity and make a conversation look missing/stale
after compaction or session rotation.

- compression_tip() now walks ALL reachable continuation descendants (DFS) and
  selects the freshest importable (messageful) tip by (last_activity, depth).
- read_session_lineage_metadata() expands descendants from the materialized
  ancestors via the parent index (scoped, 20-hop cap), pulls per-session message
  stats from the messages table, and exposes a canonical _lineage_tip_id so the
  WebUI sidebar collapse picks the same tip as the projection.

Backward-compat hardening (two release-gate Codex findings, both fixed + tested):
The new message-stats / tip-scoring code must not raise on older/minimal or
non-standard state.db schemas, which previously collapsed the whole projection:
  - messages table with NO `timestamp` column → MAX(timestamp) raised in SQL →
    read_session_lineage_metadata returned {} (lost all lineage metadata).
  - ISO-8601 TEXT messages.timestamp → float()/raw comparison raised TypeError;
    in read_importable_agent_session_rows that propagated through compression_tip
    and get_cli_sessions() swallowed it, hiding ALL imported agent rows.
Fixes:
  - PRAGMA table_info(messages) detection: require session_id, only SELECT
    MAX(timestamp) when present (else NULL + COUNT only); fall back to message_count.
  - new _as_score() helper (first numerically-coercible value, else next
    candidate e.g. started_at) used at EVERY tip-scoring / sort site in both
    compression_tip() and freshest_continuation_tip(), plus the projection sort.
  - regression tests: lineage metadata survives REAL/absent/TEXT messages.timestamp,
    and read_importable_agent_session_rows survives a TEXT timestamp (no empty hide).

Co-authored-by: ai-ag2026 <[email protected]>

* docs(changelog): stamp v0.51.306 — Release JV (stage-a2 #3751)

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 19:10:04 -07:00
nesquena-hermesandrodboev c7a389e0a7 Release v0.51.305 — Release JU (stage-p2b — dormant unified-SessionDB adapter) (#3759)
* refactor(sessions): add dormant JSON-backed SessionDB adapter (#3720, #3383)

First, lowest-risk slice of the unified-session-db migration: a SessionDB-shaped
adapter over the existing WebUI JSON store, behind a dormant experimental flag
(experimental.unified_session_db, default false). No runtime call site is rewired
— is_unified_session_db_enabled() has no live callers, so persistence behavior is
unchanged until a later migration PR opts in. Includes the adapter, the dormant
config flag + _apply_config_defaults wiring, an architecture doc, and adapter tests.

Also adds docs/architecture/ to the .gitignore docs allowlist (the docs/* rule
excludes subdirectories; the new architecture doc and its presence test would
otherwise be silently dropped on a clean checkout).

Co-authored-by: rodboev <[email protected]>

* docs(changelog): stamp v0.51.305 — Release JU (stage-p2b #3720)

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 18:16:07 -07:00
3a8a51e507 Release v0.51.304 — Release JT (stage-p2a — un-held terminal reaper + opt-in Docker GPU) (#3757)
* fix(terminal): reap reparented terminal descendants by process group (#3725, #2577)

Embedded-terminal descendants reparented to the WebUI process could linger as
zombies. The reaper now calls os.waitpid(-terminal_pgid, WNOHANG) scoped to the
terminal's own process group (terminals spawn with start_new_session=True, so
proc.pid == pgid) rather than process-wide waitpid(-1), which would otherwise
reap unrelated WebUI subprocess children and silently coerce their exit codes to
0. Bounded by a 64-iteration limit and lock-guarded. Runs on reader cleanup and
terminal close.

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

* docs(docker): add opt-in GPU runtime image path (#3721, #3243)

The default image stays CPU-only. A new INSTALL_GPU_LIBS=1 build arg installs
VA-API user-space libraries for users passing through host GPU devices, and
docker_init.bash preserves Docker --group-add supplemental groups (e.g. render/
video for /dev/dri) when dropping privileges to the runtime user. Default
(INSTALL_GPU_LIBS=0) is a no-op. Docs + regression test included.

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

* docs(changelog): stamp v0.51.304 — Release JT (stage-p2a #3725 #3721)

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-06 18:04:16 -07:00
4580f58496 Release v0.51.303 — Release JS (stage-p1a — cron toggle + config var expansion + git-discard hardening) (#3756)
* fix(cron): toggle run output rows instead of re-fetching when already open (#3732)

_loadRunContent() only ever expanded, so clicking an already-open cron run row
re-fetched its content pointlessly. It now toggles: an open row collapses (clears
the expansion state + resets the toggle button) and returns early, avoiding the
redundant API call.

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

* feat(config): expand ${VAR} references in config.yaml at load time (#3736)

hermes-agent already expands ${ENV_VAR} in config.yaml, but the WebUI's own
loader stored the raw dict, leaving literal ${...} strings. Recursively expand
${VAR} against os.environ on both config load paths (reload_config and
_load_yaml_config_file); unset vars are left untouched (${VAR} preserved).

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

* fix(security): anchor untracked-file deletes in git_discard (#3702)

git_discard(delete_untracked=True) used raw shutil.rmtree / Path.unlink after a
separate safe_resolve_ws validation, leaving a validation-to-use symlink-swap
window. Route untracked deletes through the anchored helpers (rmtree_anchored /
unlink_anchored) so a swapped path component is rejected at delete time; preserve
the prior missing_ok tolerance for benign concurrent-removal races. Adds
regression coverage for both the symlink-swap block and the concurrent-missing case.

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

* docs(changelog): stamp v0.51.303 — Release JS (stage-p1a #3732 #3736 #3702)

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: mysoul12138 <mysoul12138@users.noreply.github.com>
Co-authored-by: Carry00 <Carry00@users.noreply.github.com>
Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>
2026-06-06 17:24:18 -07:00
bf088cbbc4 Release v0.51.302 — Release JR (stage-brick — mobile/iOS brick + large-session perf hotfixes) (#3754)
* fix(ui): stop hidden toast from intercepting clicks on mobile (#3735)

The .toast container kept pointer-events:auto while hidden (opacity:0), so its
fixed padding sat over mobile profile action buttons and ate their clicks. Set
pointer-events:none when hidden; restore auto on .toast.show.

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

* fix(sessions): rename saves on blur so iOS Safari rename works (#3729)

iOS Safari has no Enter key; the keyboard 'Done' button fires blur, and the old
onblur=cancel discarded the rename. Flip blur to save (Escape still cancels) for
session rename and project create/rename, with a _finishDone guard to prevent a
double-fire between blur and the API callback.

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

* perf(session): skip fuzzy dedup matching for giant merge payloads (#3730)

Large tool/log payloads made _matching_visible_duplicate() casefold+regex-tokenize
multi-megabyte contents on every visible key, so /api/session took 10s+ and blocked
/api/sessions for ~19s. Keep loose normalization lazy+cached and skip substring/fuzzy
matching for non-exact payloads >200KB; exact visible-key matches still short-circuit.

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

* docs(changelog): stamp v0.51.302 — Release JR (stage-brick brick/perf hotfixes #3735 #3729 #3730)

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: timlawrenz <timlawrenz@users.noreply.github.com>
Co-authored-by: reinocheong <reinocheong@users.noreply.github.com>
Co-authored-by: alvistar <alvistar@users.noreply.github.com>
2026-06-06 16:58:13 -07:00
1649a22f5a Release v0.51.301 — stage-3710 (hide test-helper console windows on Windows #3710) (#3753)
* test(windows): hide test helper console windows (#3706)

* test(windows): use CREATE_NO_WINDOW constant (#3706)

* test(windows): hide test-helper console windows on Windows (#3710 fixes #3706)

@rodboev. Long-lived test helper subprocesses (test server, browser-smoke, TLS helper,
ctl helpers) now spawn with CREATE_NO_WINDOW on Windows so a local pytest run doesn't
pop focus-stealing console windows. sys.platform=='win32' guarded → no-op on macOS/Linux
(expands to **{}). Test-only, no production code, no user impact. + CHANGELOG v0.51.301.

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 16:16:39 -07:00
nesquena-hermesandnesquena-hermes 1cb9d3b550 fix(#3717): context-length indicator honors provider per-model overrides (#3726) (#3752)
@rodboev. providers.<name>.models.<model>.context_length overrides (standard provider,
no base_url) were invisible to the session context resolver → wrong window shown/persisted,
could trip auto-compression at the wrong threshold. New _context_length_lookup_inputs_for_model
helper resolves provider config / base_url / custom_providers across route-load, session-save,
and SSE-usage paths; provider-scoped overrides match by provider name and forward as
config_context_length (returned before any base-url-gated probe).

Maintainer pre-merge items both already satisfied in PR head: no-base_url regression test
(test_route_resolver_uses_provider_model_context_length_without_base_url) present; session-save
_cfg_base_url assigned before the helper call (safe-bound, no NameError on TypeError fallback).
Verified api code byte-identical to PR head; 14 context-length tests pass. + CHANGELOG v0.51.300.

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 15:57:09 -07:00
nesquena-hermesandnesquena-hermes 1fed11c085 Release v0.51.299 — stage-3713 (update flow waits for new server instance #3713) (#3749)
* fix(#3619): update flow waits for a genuinely new server instance before reload (#3713)

@rodboev. _waitForServerThenReload() reloaded on the first healthy /health response
without confirming the server instance changed, so a slow restart could reload onto
the old process or show a premature error toast. Now captures a baseline server identity
from /health's existing server_started_at and only reloads on a changed identity.
Frontend-only (server_started_at already in /health on master). ui.js verified
byte-identical to PR head; test_update_banner_fixes 82 passed. + CHANGELOG v0.51.299.

* fix(#3713): reload on observed outage when uptime-only identity not lower (Codex catch)

Codex found a CORE edge case: when a deployment strips server_started_at and BOTH the
baseline and replacement /health expose only uptime_seconds, the reload trigger relied
solely on 'next.uptime < baseline.uptime'. If the old server's baseline uptime was very
low (~0) and the new instance is first seen at equal/higher uptime, that never fires and
the user is stranded on the restart banner.

Fix: track _observedOutage (set in the /health probe catch arm) and, for the
uptime-only-on-both-sides case, treat an outage-then-healthy sequence as the new instance
and reload. A real restart almost always produces an outage given the pre-probe interval,
so this closes the gap without weakening the identity check. + regression test.

* fix(#3713): harden outage fallback — require >=2 consecutive outages, count non-OK (Codex round 2)

Codex round 2 found two refinements to the outage fallback: (1) a single transient
fetch blip could set the flag and reload onto the still-running old server; (2) a
reverse-proxy 502/503 returns r.ok===false (not a thrown error) so the proxy-outage
case didn't register. Replaced the boolean with a _consecutiveOutages counter: require
>=2 consecutive outages before the uptime-only reload; count BOTH thrown errors and
non-OK/non-status-ok responses; reset to 0 when the old server answers healthy (so
unrelated blips can't accumulate). Updated the regression test to the counter contract.

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 15:38:51 -07:00
nesquena-hermesandnesquena-hermes ce4c2c1de1 Release v0.51.298 — stage-3719 (live model probe for custom providers with model config #3719) (#3747)
* fix(#3718): /api/models/live probes upstream for custom providers with model config (#3719)

@DanielMaly. Config model IDs were added to the ids list before the 'if not ids:' guard,
so a custom provider with a model: field skipped the live /v1/models probe and Settings'
refresh returned only the config entry. Now collects config IDs separately, always probes
for custom providers, merges live (priority) + config (fallback). Includes the maintainer
review follow-ups (CUSTOM_MODELS_ENDPOINT_TIMEOUT_SECONDS constant + behavioral tests).

Captured all 3 logical PR commits' net effect; routes.py + test verified byte-identical
to the PR head. + CHANGELOG v0.51.298.

* test(#3718): remove unused BytesIO import (ruff F401)

* test(#3719): update timeout assertion to CUSTOM_MODELS_ENDPOINT_TIMEOUT_SECONDS

The #3719 maintainer-review commit replaced the hardcoded urlopen timeout=8 with the
CUSTOM_MODELS_ENDPOINT_TIMEOUT_SECONDS constant (5.0). test_named_custom_live_fetch_uses_matching_entry_endpoint
asserted the old literal 8. Reference the constant directly now so the assertion can't
drift again. Not a behavior change — only the live-probe timeout value (8s -> 5s) moved,
URL + auth unchanged.

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 14:48:39 -07:00
4f842d5095 Release v0.51.297 — stage-3711 (terminal remote-backend guard #3711) (#3745)
* fix(terminal): guard embedded terminal on remote backends (#3673)

* fix(terminal): add missing remote-backend locale key

* fix(terminal): add missing remote-backend locale coverage (#3673)

* docs(changelog): v0.51.297 — terminal remote-backend guard (#3711) only

Dropped #3725 (descendant reaper) from this stage: Codex caught a SILENT exit-code
clobber — its process-wide os.waitpid(-1, WNOHANG) can reap a sibling WebUI child that
another subsystem is waiting on, coercing that child's returncode to 0 (failures become
successes). Held for the contributor to scope the reaper to terminal PGIDs
(os.waitpid(-term.proc.pid, WNOHANG)) or a terminal-PGID registry.

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 14:17:17 -07:00
91a89fb5d5 Release v0.51.296 — stage-3731 (remote-workspace blocked-root security fix #3731) (#3744)
* fix: reject blocked roots for remote workspaces

* test: cover remote blocked root subpaths

* docs(changelog): v0.51.296 security fix + backfill v0.51.295 entries

- v0.51.296: #3731 remote-workspace blocked-root rejection.
- Backfill the v0.51.295 release block (the #3739 model-pick entry + promote the
  #3570 revert out of [Unreleased]) which a stage-rebuild dropped from the prior
  release's CHANGELOG. git-describe versioning makes CHANGELOG-after-tag acceptable.

---------

Co-authored-by: hinotoi-agent <paperlantern.agent@gmail.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 13:49:59 -07:00
65c4bc9fa2 Release v0.51.295 — stage-3739/3742 (model-pick revert fix #3739 + session-status revert #3742) (#3743)
* fix: honor explicit model pick, suppress silent revert on cross-family selection (#3737)

When a user changes the model in the composer dropdown and sends,
_resolve_compatible_session_model_state previously had no way to
distinguish an explicit user pick from stale session state. The
profile-aware branch (v0.51.290, PR #3448) and the legacy block
both rewrote bare cross-family models to the profile default, and
the client unconditionally applied effective_model — silently
discarding the user's choice.

Backend: accept explicit_model_pick flag (default False) on
_resolve_compatible_session_model_state. Guard both the
profile-aware branch (routes.py:2024) and the legacy block
(routes.py:2124) to skip cross-provider normalization when set.
_handle_chat_start extracts the flag and passes it through.

Frontend: consult _readPendingSessionModel (sessionStorage, 10-min
window) to detect explicit picks and include the flag. Add a toast
as defense-in-depth when the server still returns effective_model.

Closes #3737

* fix: tighten explicit-pick detection and add regression tests (#3737)

Greptile P2-1: compare model_provider in pending pick detection,
not just model name, to avoid false-positive flag when the
session provider changes between pick and send.

Greptile P2-2: only show the defense-in-depth toast when an
explicit pick was actually overridden — stale-session
normalizations are expected behavior and should be silent.

Add two regression tests for the profile-branch guard:
- explicit_model_pick=True → cross-family model survives
- explicit_model_pick=False → existing normalization preserved

* revert(sidebar): remove manual session status labels (#3570)

The manual per-session status labels (Todo / In Progress / Done) added in
v0.51.284 (#3570) stored state only in browser localStorage keyed by session
id, with no server-side backing — so labels did not persist across browsers
or devices (a user who labeled sessions on one machine saw none after moving
to a laptop). They also rendered as three flat top-level entries in the
session context menu, crowding the root menu.

Per maintainer decision, remove the feature entirely for now. It can be
reintroduced later with proper server-side persistence and a less intrusive
menu treatment.

Removes:
- JS state/cycle helpers + SESSION_MANUAL_STATUS_KEY (static/sessions.js)
- context-menu status entries + sidebar status badge render
- .session-manual-status* CSS (static/style.css)
- session_status_* locale strings across all locales (static/i18n.js)

Full suite: 8084 passed, 0 failed. ESLint runtime gate: clean.

reverts #3570

* fix(#3737): keep explicit-pick marker until send consumes it (Codex catch)

Codex found the explicit_model_pick flag never engaged in the normal flow: boot.js
modelSelect.onchange cleared the pending-pick marker right after /api/session/update,
so by the time send() ran _readPendingSessionModel returned null, _explicitPick was
false, and the server's profile-provider branch still reverted the cross-family pick
(the exact #3737 bug). The flag only worked in the rare race where send beat the
session-update round-trip.

Fix (Codex prescription): do NOT clear the marker in onchange; clear it in send()
immediately after reading a matching pending pick, so it's consumed for that send only.
onchange still RECORDS the pick (_rememberPendingSessionModel) — only the premature
clear is removed.

* test(#3737): lock client clear-timing wiring (onchange records, send consumes)

Static source guards for the Codex clear-timing fix: onchange must record the
pending pick and NOT clear it post-session-update; send() must consume (clear) it
only after reading a matching _explicitPick, and send the flag only when truthy.
Complements the author's resolver-level tests in test_provider_mismatch.py.

* test(#3737): realign refresh-persistence test to the moved pending-pick clear

The Codex clear-timing fix moved the pending-pick clear out of modelSelect.onchange
into send() (consume-on-send). test_model_selection_records_pending_state_before_async_session_update
asserted the OLD onchange-clears behavior (assert _clearPendingSessionModel in body).
Updated to assert the NEW correct behavior (onchange must NOT clear it — it survives to
send). The test's core refresh-survives invariant (marker recorded before the async
session-update; reapplied on load) is unchanged and still passes; only the stale
clear-location assertion is flipped. Not a regression-blessing: the refresh-survives
feature is intact, the marker lifecycle is more correct.

---------

Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 13:39:14 -07:00
e3a7c93dc6 [HELD — independent review pending] Release v0.51.294 — stage-3401 (live-to-final redesign #3401 + 4 deep-review fixes) (#3741)
* Harden interrupted recovery control filtering

* Redesign live-to-final assistant replies

* Fix live activity anchor test fixture

* Fix CI lint issues for live reply tests

* Strengthen live progress prompt contract

* Recover PR #3401 refresh on origin/master

* Repair live-to-final refresh regressions

* Fix live worklog refresh regressions

* Show live footer timer on initial stream start

* Restore live stream shell after reload

* Preserve per-frame live SSE replay cursors

* Preserve reasoning as Worklog Thinking cards

* Quiet Worklog Thinking card styling

* Align Worklog Thinking card styling

* Scope live Worklog Thinking cards by segment

* Suppress exact duplicate settled Thinking

* Close #3401 merge review test gaps

* fix(#3401): resolve 4 deep-review regressions (inline-think, reconnect-dup, neon skin, busy-gate worklog)

Deep review (Codex diff-vs-master + live-browser drive) of the live-to-final refactor
surfaced 4 regressions vs master that the rewritten suite no longer guarded:

1. Inline <think>…</think>answer reasoning vanished — _assistantReasoningPayloadText
   used $-anchored regexes so a leading think block + visible answer extracted nothing
   and the Thinking card never rendered. Removed the 3 $ anchors to match the
   (non-anchored) display stripper. Live: inline-think thinking-only turn now renders.
2. (CORE) reconnect/reload duplicated the live reply — _rememberRunJournalCursor advanced
   a closure-local seq but never wrote INFLIGHT[activeSid].lastRunJournalSeq, so a reload
   replayed the journal from after_seq=0 over restored lastAssistantText. Now mirrors the
   cursor onto INFLIGHT + schedules a throttled persist.
3. Neon skin silently broke — PR deleted the :root[data-skin="neon"] CSS but left Neon in
   the picker. Restored the neon CSS block from master.
4. Settled tool-worklog rebuild gated purely on !S.busy — dropped every prior settled
   turn's worklog when renderMessages re-ran during an active stream (switch-back to an
   in-progress session). Restored master's !S.busy || (S.toolCalls && S.toolCalls.length).
   Live: busy re-render now preserves tool cards (4→4, was 4→0).

Live-verified all 4 + confirmed #3709/#3592 invariants still hold (1 thinking card, none
below footer; distinct siblings preserved). + tests/test_issue3401_deep_review_fixes.py (7).

* test(#3401): realign 3 stale source-shape assertions to the deep-review fixes

Fix commit changed two source literals that existing stage tests scanned for:
- test_live_activity_timeline.py (x2): split anchor 'if(!S.busy){' → the restored
  'if(!S.busy || (S.toolCalls&&S.toolCalls.length)){' guard (fix 4).
- test_run_journal_frontend_static.py: 'after_seq=0' not in source — fix 2's comment
  contained that literal; rephrased the comment to 'the zero floor (after_seq of 0)'.
Intent of all three assertions unchanged; only the matched string updated. No code
behavior change.

* docs(changelog): v0.51.294 — Release JJ (stage-3401, #3401 live-to-final redesign)

---------

Co-authored-by: Frank Song <franksong2702@gmail.com>
Co-authored-by: Nathan-Hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 12:12:37 -07:00
nesquena-hermesandnesquena-hermes 32d46f4450 Release v0.51.293 — Release JI (stage-s5 — thinking card no longer renders twice #3709) (#3715)
* fix(#3709): thinking card no longer renders twice (in Activity + below answer)

The #3592 inline-render branch (v0.51.258) emitted a thinking card for a
thinking-only message even when a sibling tool-message in the same turn already
built an Activity group carrying that turn's thinking — so the card showed twice,
the second one stranded below the answer + 'Done in …' footer (insertAdjacentHTML
'beforeend' on a segment that already had body+footer).

Fix (keeps #3592, does NOT revert it):
- A1: precompute turnsWithActivityGroup (turns whose segments have tool cards);
  the inline branch only renders when the anchor turn is NOT in that set.
- A2: when it does render inline, insert 'beforebegin' the .msg-body/.msg-foot so
  the card sits above the answer, not orphaned below the footer.
- B: strip thinking against the TURN's combined visible answer
  (_turnVisibleTextByRawIdx), so a trailing thinking-only message that echoes the
  answer gets de-duped even though its own body is empty.

Live-verified in browser: #3709 repro (tool+trailing-thinking) → exactly 1 card in
Activity, above footer; #3592 repro (thinking-only) → exactly 1 inline card, not
buried in a collapsed group. + regression test tests/test_issue3709_*.

Supersedes #3708 (which deleted the inline branch outright, re-breaking #3592).

* fix(#3709): merge suppressed sibling thinking into the Activity group (Codex re-gate)

Codex caught a content-loss edge in the first cut: when A1 suppresses a
thinking-only sibling's inline card (its turn has an Activity group), the group
only rendered assistantThinking.get(aIdx) for the TOOL message — so a sibling
with DISTINCT reasoning was neither inline nor in the group → dropped.

Fix: aggregate all of a turn's thinking (turnThinkingParts, de-duped, index
order) and render that merged text once per turn in the Activity group
(_renderedTurnThinking guard). Live-verified: tool-thinking A + distinct
sibling-thinking B → 1 merged node carrying both, no loss. + regression test.

* fix(#3709): shared anchor resolver so inline-suppression & group placement agree (Codex re-gate #2)

Codex caught a fallback-anchor mismatch: turnsWithActivityGroup was populated only
from assistantSegments.get(tcIdx) (direct segment), but the group-render path falls
back to a nearby earlier segment when a tool's assistant_msg_idx has no directly
rendered segment (legacy/rebased). So a fallback-anchored group's turn wasn't in
turnsWithActivityGroup → the sibling rendered inline AND the group rendered → dup
again. Fix: one shared _anchorRowForActivityIdx(aIdx) helper (direct-or-fallback)
used by the precompute, the inline branch, and the group render — they now agree.
Live-verified all three repros still pass.

* test(#3709): update test_compact_activity assertion to mergedThinking var

The brittle source-scan asserted _thinkingActivityNode(thinkingText, false) — the
#3709 fix renders the turn's MERGED thinking via _thinkingActivityNode(mergedThinking,
false) into the same Activity body. Intent (settled thinking renders inside the
Activity disclosure alongside tools) unchanged; only the source variable. Updated to
assert the new variable, kept all intent assertions.

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 00:19:29 -07:00
9720e0b03d Release v0.51.292 — Release JH (stage-s4 — compression-exhausted turns surface as errors #3316 fixes #3315) (#3705)
* fix(#3315): surface compression-exhausted/no-final-answer turns as errors (#3316)

When Hermes Agent exhausts context compression in a long tool-heavy turn, the
streamed result can end on a tool result / assistant(tool_calls) turn with no
final assistant answer. WebUI was finalizing that as a completed response.
Now _session_lacks_final_assistant_answer() + _agent_result_terminal_failure()
classify these as terminal failures and surface an apperror instead. The
compression session-id migration + pre-compression snapshot now run BEFORE the
terminal-failure return (ordering bug from the prior hold) so state stays
consistent when exhaustion fires after the agent rotated session_id.

Co-authored-by: Frank Song <franksong2702@gmail.com>

* docs(changelog): v0.51.292 — Release JH (stage-s4, #3316 fixes #3315)

---------

Co-authored-by: Frank Song <franksong2702@gmail.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-05 22:45:09 -07:00
nesquena-hermesandnesquena-hermes 534280e058 Release v0.51.291 — Release JG (stage-s2 — preserve live turn content on switch-away #3668) (#3704)
* fix(#3668): snapshot live turn before stream teardown on session switch

The 'stays gone' variant: switching away from a streaming session during a quiet
window (mid tool-exec / silent thinking, between content SSE events) left a
stale/absent live-turn snapshot, so restoreLiveTurnHtmlForSession() failed on
switch-back and loadSession()'s fallback rebuilt with an empty appendThinking(),
permanently losing streamed thinking/tool content (only the elapsed clock
survived). closeLiveStream() now snapshots the live-turn DOM via
snapshotLiveTurnHtmlForSession(sessionId) BEFORE closing the source + tearing
down LIVE_STREAMS, so switch-back always restores the exact state shown at
switch-away. + regression test asserting snapshot precedes teardown.

* docs(changelog): v0.51.291 — Release JG (stage-s2, #3668)

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-05 22:26:43 -07:00
47020925d5 Release v0.51.290 — Release JF (stage-s1 — profile provider/model resolution #3448 fixes #3405) (#3703)
* fix(#3405): respect profile provider/model in session resolution (#3448)

Profile-bound sessions now resolve their provider/model from the profile
instead of silently falling back to the global active provider — fixes wrong
credentials/billing and silent context truncation. Repairs stale models under
the profile provider (incl. the openai-codex + openai/ slash-model case) while
preserving native slash IDs on openrouter/custom.

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* docs(changelog): v0.51.290 — Release JF (stage-s1, #3448 fixes #3405)

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-05 22:17:27 -07:00
nesquena-hermesandnesquena-hermes d83de226fe docs(changelog): stamp v0.51.289 (sidebar ReferenceError #3696 hotfix + scope gate) (#3699)
The v0.51.289 tag ships the #3696 sidebar-crash hotfix + the scope_undef_gate
(merged in #3698, commit da5bf69a). This stamps the CHANGELOG [Unreleased]
section to the v0.51.289 release header. Docs-only.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-05 20:56:16 -07:00
nesquena-hermesandnesquena-hermes da5bf69aee fix(sidebar): hoist _sessionAttentionState to fix ReferenceError crash (#3696) + scope-undef prevention gate (#3698)
* fix(sidebar): hoist _sessionAttentionState to top-level scope (#3696)

_sessionAttentionState was declared inside renderSessionListFromCache() and
relied on function hoisting, but the top-level function _sidebarRowHasVisible
Messages (reached via renderSessionListFromCache -> _partitionSidebarSessionRows)
called it bare. Hoisting is scoped to the enclosing function, so every sidebar
cache-render threw 'ReferenceError: _sessionAttentionState is not defined' and
the session list went blank. Regressed in #3672 (v0.51.269) when _sidebarRow
HasVisibleMessages was extracted to top level.

Fix: move _sessionAttentionState to top-level scope (it is pure — only uses its
arg plus the i18n global t), so both the visibility predicate and the nested
per-row renderer can reach it.

Prevention (the durable half): add scripts/scope_undef_gate.py — models the
classic-<script> shared global scope (union of all static files' top-level
symbols) and runs ESLint no-undef per file, flagging a function defined nested
but called from a sibling scope. Wired into CI (.github/workflows/tests.yml lint
job) alongside the existing no-const-assign runtime gate, plus an in-suite test
(test_static_js_scope_undef.py) and a focused structural regression test
(test_issue3696_session_attention_scope.py). RED/GREEN-validated against the
broken tree.

* fix(streaming): thread source param into stale-stream bailout; tighten scope gate

Opus review of #3698 found the new scope_undef_gate's 'source' allowlist entry
was masking a real same-class bug: _bailOutOfTerminalEventsFromStaleStream
(declared inside attachLiveStream, params activeSid/streamId/uploaded/options)
called _closeSource(source) against a 'source' not in its lexical scope. All 5
call sites are inside _wireSSE(source), but JS scope is lexical not dynamic, so
the helper would throw ReferenceError: source is not defined on the stale-stream
terminal-event path (user back in an active session whose old stream finalizes
late).

Fix: thread source as an explicit parameter (declaration + all 5 call sites),
the same make-the-dependency-explicit fix as #3696 — and REMOVE the 'source'
allowlist entry so the gate stays gated against that name (it now passes because
the bug is fixed, not because it's allowlisted). Added the documented
false-negative classes from Opus's review to the gate docstring (name-collision
shadowing, destructuring-regex gap, exposure escape hatches, name-keyed
allowlist) and a focused regression test.

This is the prevention gate catching a real latent bug on its first outing.

---------

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-05 20:49:45 -07:00
d4f26f5156 Release v0.51.288 — Release JD (stage-r24 — collapsible approval card #3515) (#3697)
* feat(approval): make the approval card collapsible (#3515)

Adds a collapse toggle to the approval card header so users can shrink it
to a thin header strip and keep the tool-call rationale/transcript above
readable. Full ARIA (aria-expanded/controls/label), chevron swap, and
transcript reflow that preserves near-bottom scroll. Closes #3007.

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* docs(changelog): v0.51.288 — Release JD (stage-r24)

* fix(approval): clear collapsed state for a distinct queued approval (#3515)

Codex regression-gate finding: showApprovalCard's sameApproval check didn't
include approval_id and didn't clear .collapsed in the !sameApproval branch, so
a NEW/parallel approval arriving while the card was already collapsed could
render collapsed with its command + action buttons hidden. Add approval_id to
the signature; clear .collapsed for a distinct approval before syncing. +2 regression tests.

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-05 19:52:52 -07:00
0b223e91bc Release v0.51.287 — Release JC (stage-r22 — WeCom session classification #3653 + worker-profile picker hiding #3662) (#3695)
* feat(sessions): classify WeCom gateway sessions as messaging (#3653)

Co-authored-by: Frank Song <franksong2702@gmail.com>

* feat(profiles): hide worker profiles from chat picker (#3662)

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* docs(changelog): v0.51.287 — Release JC (stage-r22)

---------

Co-authored-by: Frank Song <franksong2702@gmail.com>
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-05 18:56:43 -07:00
2736f443b1 Release v0.51.286 — Release JB (stage-r21 — sidebar tab reordering #3067) (#3694)
* feat: allow sidebar tab reordering via drag (#3067)

Drag-reorder for sidebar tab chips in Settings, persisted via a sanitized
tab_order setting (collapses duplicates, rejects chat/settings, strips
non-strings).

Co-authored-by: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>

* docs(changelog): v0.51.286 — Release JB (stage-r21)

---------

Co-authored-by: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-05 18:39:42 -07:00
e9bb354908 Release v0.51.285 — Release JA (stage-r19 — update-reload server-identity race fix #3654) (#3693)
* Fix update reload readiness race — poll /health server identity before reload (#3654)

Replaces the raw-uptime comparison (couldn't distinguish a fresh old process
from the restarted one) with a stable server_started_at identity read before
the update POST; reloads only when the identity changes. Both the force-update
and regular apply paths read + pass the baseline. (#874, #3654)

Co-authored-by: Frank Song <franksong2702@gmail.com>

* docs(changelog): v0.51.285 — Release JA (stage-r19)

---------

Co-authored-by: Frank Song <franksong2702@gmail.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-05 18:13:35 -07:00
988348682c Release v0.51.284 — Release IZ (stage-w4 — sidebar status labels + cron-sessions toggle #3570 #3514) (#3692)
* feat(sidebar): add show_cron_sessions toggle to surface cron sessions (#3514, #2841)

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* feat(sidebar): add manual session status labels (#3570)

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* docs(changelog): v0.51.284 — Release IZ (stage-w4)

* fix(settings): persist show_cron_sessions in the explicit Save Settings path too (#3514)

Codex regression-gate follow-up: the autosave path (_preferencesPayloadFromUi)
included show_cron_sessions but the explicit saveSettings() button path read/saved
show_cli_sessions and dropped the cron checkbox — clicking Save Settings silently
omitted it. Read settingsShowCronSessions + add body.show_cron_sessions (gated on
CLI sessions, mirroring autosave).

* fix(settings): gate show_cron_sessions identically in BOTH save paths (#3514)

Codex round-2: my saveSettings() gate exposed that the autosave path
(_preferencesPayloadFromUi) posted the raw cron checkbox state ungated, so
show_cli_sessions=false + show_cron_sessions=true could persist via autosave.
Gate autosave on showCliCb too; update the regression test to assert both
paths gate on settingsShowCliSessions.

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-05 17:48:27 -07:00
a7241f75fc Release v0.51.283 — Release IY (stage-w2 — composer queue hint during auto-compaction #3512) (#3690)
* feat(composer): surface that messages queue during auto-compaction (#3512, #3079)

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* docs(changelog): v0.51.283 — Release IY (stage-w2)

* fix(composer): restore placeholder on ALL compaction-exit paths, not just clearCompressionUi (#3512)

Codex+Opus both caught: setCompressionUi(done) and the live-anchored SSE
window._compressionUi=null paths bypassed clearCompressionUi, leaving the
'will queue' placeholder stuck after compaction. Factor restore into
_restoreCompressionPlaceholder() + call from every compaction-exit path.

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-05 16:52:28 -07:00
nesquena-hermesandrodboev b26bb559d5 feat(ux): surface memory/skill saves in Activity summary (#3544)
Absorbs contributor PR #3544 (@rodboev, closes #3340) with two fixes:

1. DETECTION VOCAB (would never fire): the original gated on action names
   {save,create,update,upsert}, which don't match the real agent tool enums —
   memory.action is add|replace|remove, skill_manage.action is
   create|patch|edit|delete|write_file|remove_file. Split into per-tool
   predicates with the correct vocabularies: _isMemorySave gates memory on
   {add,replace}; _isSkillUpdate gates skill_manage on {create,patch,edit,
   write_file}. Deletions excluded so the saved/updated verbs stay accurate;
   running/errored excluded.

2. SNAPSHOT/RESTORE PERSISTENCE (Codex catch): classification lived only on the
   row._tcData JS property, which does NOT survive the outerHTML/innerHTML
   snapshot+restore the live tool-call group uses on session switch/restore —
   a restored memory/skill row would be re-counted as a generic tool and the
   suffix would silently vanish. buildToolCard now also stamps durable
   data-memory-save / data-skill-update attributes, and _syncToolCallGroupSummary
   counts them as a fallback when _tcData is absent. Verified live across a real
   outerHTML round-trip: label identical before/after.

Replaces the PR's static source assertions with a node-driven behavioral test
(11 cases) covering the real action vocabularies, exclusions, case-insensitivity,
null-arg safety, and the durable-attribute persistence guard.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-05 23:09:00 +00:00
nesquena-hermesandrodboev 10413b4d65 Release v0.51.281 — Release IW (stage-verdigris — Verdigris emerald/bronze skin #3602)
Adds the Verdigris dark-only appearance skin (emerald/forest-green + bronze-gold),
renamed from the contributor's 'Hermes Agent' to a descriptive material name per
maintainer naming convention. Registered across all 5 skin sites (config allowlist,
boot.js swatch, index.html FOUC map, i18n in 12 locales, scoped CSS palette) + test.
Also fixes the zeus i18n test (zeus is no longer the trailing skin token).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-05 22:18:47 +00:00
ffc1ab6fd6 Release v0.51.280 — Release IV (stage-p3i — Windows self-update restart fix #3647) (#3687)
* fix(updates): Windows self-update restart via detached Popen + bind-retry (os.execv doesn't replace proc on Windows) (#3647)

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

* docs(changelog): v0.51.280 — Release IV (stage-p3i)

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: jja881 <jja881@users.noreply.github.com>
2026-06-05 14:54:59 -07:00
b5caf83ff9 Release v0.51.279 — Release IU (stage-p3h — preserve Activity/streaming turn on mid-stream scroll #3665) (#3686)
* fix(streaming): preserve Activity + streaming turn when loading earlier messages mid-stream (#3665, #3346)

Co-authored-by: mysoul12138 <839465496@qq.com>

* docs(changelog): v0.51.279 — Release IU (stage-p3h)

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: mysoul12138 <839465496@qq.com>
2026-06-05 14:45:31 -07:00
8f89b4f825 Release v0.51.278 — Release IT (stage-p3g — repair inline PDF preview #3652) (#3684)
* fix(ui): repair inline PDF preview (blob module loader + CSP worker-src) (#3652, #3649)

Co-authored-by: sky <example@email.com>

* docs(changelog): v0.51.278 — Release IT (stage-p3g, #3652 only); widen CSP test window

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: sky <example@email.com>
2026-06-05 14:27:38 -07:00