Commit Graph
100 Commits
Author SHA1 Message Date
nesquena-hermes 8fff266aaa Release v0.51.460 — recognize command STT providers in voice probe (#4312) (#4318)
Release v0.51.460 — recognize command STT providers in voice probe (#4312)
2026-06-16 10:27:46 -07:00
nesquena-hermes b73e18cc23 Release v0.51.459 — normalize repair-merged user context (#4089) (#4317)
Release v0.51.459 — normalize repair-merged user context (#4089)
2026-06-16 10:15:12 -07:00
nesquena-hermes dea606bf24 Release v0.51.458 — O(D²) context-backfill, prevents server hang (#4314) (#4316)
Release v0.51.458 — O(D²) context-backfill, prevents server hang (#4314)
2026-06-16 10:06:07 -07:00
nesquena-hermes 971b89bcf6 Release v0.51.457 — gateway watcher non-blocking startup (#4297) (#4315)
Release v0.51.457 — gateway watcher non-blocking startup (#4297)
2026-06-16 09:54:28 -07:00
nesquena-hermes 232963791f Merge pull request #4250 from nesquena/contributors-refresh-20260615
docs: weekly contributor tally refresh (20260615)
2026-06-16 09:02:29 -07:00
nesquena-hermesandnesquena-hermes 44032ab5ab Release PQ (v0.51.456): start.sh detects an already-running server (#4308) (#4309)
* Release PQ (v0.51.456): start.sh detects an already-running server (#4308)

start.sh now probes /health (best-effort, 2s, curl/wget) before launching
bootstrap.py; if a server already answers it prints a clear 'already running +
how to restart' message and exits 0 instead of spawning a child that fails to
bind. Brings the detached start.sh path to parity with ctl.sh's double-start
refusal. Container path (server.py direct) unaffected.

Co-authored-by: tomas-forgac

* gate fix (Codex CORE): parse $@ for --host/--host=/positional port in the start.sh preflight

bootstrap.py accepts a positional port + --host that override the env defaults;
the preflight probed env/defaults only, so './start.sh <altport>' while a server
ran on the env-default port falsely reported 'already running' and refused the
legitimate launch. Now scans $@ the same way bootstrap.py's argparse does before
building the /health probe URL.

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-16 07:24:50 -07:00
50e7589a92 Release PP (v0.51.455): age-gate stale background-process completions (#4306, #4029) (#4307)
_drain_webui_process_notifications now drops (consumes, never requeues) a
completion whose completed_at is older than a configurable cap
(HERMES_WEBUI_STALE_COMPLETION_MAX_AGE_SECONDS, default 6h; 0 disables).
Fail-open: events without completed_at (legacy agent) are never dropped.
Additive (+192/-0). Closes #4029.

Co-authored-by: allenliang2022

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-16 06:58:38 -07:00
fadb4fdf32 docs(README): correct server stop instructions — start.sh runs detached, not foreground (#4303) (#4304)
start.sh exec's bootstrap.py without --foreground, which double-forks into a
detached daemon, so it belongs in the 'find PID + kill' row, not the Ctrl-C
foreground row. Docs-only, no version stamp.

Co-authored-by: tomas-forgac

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-16 03:40:26 -07:00
dc90ec9be4 Release PO (v0.51.454): complete the Japanese UI translation (#4301) (#4302)
Translated 69 remaining English fallbacks in the ja locale block to parity
with English; proper nouns/abbreviations intentionally left as-is. Values only,
no key changes (key parity verified). 265 locale/i18n tests pass.

Co-authored-by: koshikai

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 23:00:54 -07:00
ecf637c66d Release PN (v0.51.453): bridge approval controls to the gateway runs API (#4229, #4203) (#4299)
Approve/deny on a remote-gateway backend now forwards to the gateway runs API
(run_id server-derived, existing auth+CSRF preserved, capability-probed + 60s
cached). Non-gateway/local approval path unchanged — relay only fires when a
session has an active gateway run (_STREAM_RUN_IDS). Old gateways get a clear
upgrade message instead of a silent no-op.

Co-authored-by: rodboev

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 22:25:24 -07:00
2cc2c80074 Release PM (v0.51.452): reject symlinked memory-file writes (#4242) (#4298)
Refuse to write memory (MEMORY.md/USER.md/SOUL.md) through a symlinked target
file, extending the skills/plugins symlink-rejection hardening (#4217/#4234/#4240).
Parent-dir symlink intentionally allowed per maintainer decision (only the
concrete target file is guarded). + 3 regression tests.

Co-authored-by: zapabob

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 22:10:27 -07:00
nesquena-hermesandnesquena-hermes 2fa67ad699 Release PL (v0.51.451): show named-profile external sessions in the all-profiles list (#4067) (#4296)
* Release PL (v0.51.451): show named-profile external sessions in the all-profiles list (#4067, #4065)

All-profiles session enumeration now scans across profiles only when the
request explicitly asks (all-profiles view); cross-profile import resolves
to the active profile's state.db for unqualified requests (foreign id -> 404),
mirroring the #3982/#3991 detail/export profile-scoping gate.

Co-authored-by: rodboev

* gate fix (Codex CORE + Opus): close cross-profile leak on the import_cli refresh/existing-session branch

The already-imported branch of _handle_session_import_cli forced allow_all_profiles=True
whenever the globally-stored session carried a profile, with no active-profile visibility
gate — so an unqualified request from profile A could read/refresh a session imported under
profile B's live state.db (both advisors reproduced the foreign-transcript leak). Now gated
exactly like the /api/session detail + export endpoints: unqualified requests require
_session_visible_to_active_profile; explicit all_profiles requires a matching profile.
+ 3 regression tests.

* gate fix (Codex SILENT, re-gate): thread source_filter into the all-profiles CLI scan (models.py:4113)

The all-profiles branch keys its cache on source_filter but called
_load_cli_sessions_uncached without it, so /api/sessions?all_profiles=1 with a
source filter returned every-source sessions under a filtered key. + regression test.

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 21:55:31 -07:00
792adc653a Release PK (v0.51.450): preserve custom provider namespace prefixes in model normalization (#4278) (#4294)
* Release PK (v0.51.450): preserve custom provider namespace prefixes in model normalization (#4278)

Token-boundary check in _normalize_provider_id (exact token or prefix
followed strictly by ':' or '/'); explicit aliases for hyphenated
first-party names (google-gemini, google-ai-studio, claude-code); bare-prefix
family detection no longer fires on namespaced model IDs.

Closes #4278. Supersedes #4280 (rodboev) whose '-' boundary re-collapsed
claude-relay->anthropic.

Co-authored-by: b3nw

* gate fix (Codex CORE): add openai-api to _PROVIDER_ALIASES so the :/ token boundary doesn't drop the registered openai-api provider id to '' (would silently repair OpenAI API sessions to the profile default) + 2 regression tests

* gate fix (Opus): gate the streaming send-path bare-prefix loop on '/' not in model so custom namespaces (gemini_cli/, gpt_proxy/, claude-relay/) survive the send path — same #4278 collision Opus found in _apply_profile_provider_context_to_streaming_model; + regression test + CHANGELOG

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 21:06:08 -07:00
nesquena-hermesandnesquena-hermes 796edf56d3 test-infra: per-process test port for true parallel-run safety (fixes the local ConnectionRefused cascade) (#4292)
* test-infra: per-process free port + port-keyed state dir (true parallel safety)

ROOT CAUSE of the cascade-under-concurrency: the test port was a fixed
md5(repo_path)%10000, so two concurrent pytest runs from the SAME worktree (e.g.
a dev's suite + a Codex/Opus gate running ./scripts/test.sh to verify) bound the
SAME port — and the session-fixture's _kill_port_owner(TEST_PORT) at setup reaped
the OTHER run's server mid-suite, cascading every HTTP test with ConnectionRefused.

FIX: when HERMES_WEBUI_TEST_PORT isn't pinned, _auto_test_port now grabs a FREE
OS-assigned ephemeral port per process (bind :0, read back, release). The state
dir name is keyed on that port too (webui-test-<repohash>-<port>), so concurrent
runs get fully distinct port + state dir + db — no shared resource, no mutual kill.

VERIFIED: two FULL suites run concurrently from the same worktree → BOTH
9150 passed / 0 failed, 0 ConnectionRefused, 0 dirs leaked into ~/.hermes. This is
the parallel-safe world: Codex + Opus gates + local suite can all run at once.

* test-infra: address Codex CORE findings on the per-process-port fix

1. Don't _kill_port_owner an AUTO (unpinned) port: fuser -k on an ephemeral-range
   port can match a client socket or a concurrent run's process, killing the wrong
   thing. Track TEST_PORT_PINNED; only reap pinned ports (which may be stale).
   Auto ports are freshly confirmed-free, so nothing to reap.
2. Cap auto port at <=64535 so offset-deriving tests (test_server_port_exclusivity
   adds up to +905 to TEST_PORT) can't overflow past 65535 (OverflowError).

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 20:13:00 -07:00
1286ea7b3d Release PJ (v0.51.449): stop recovered turns replaying into later context (#4283) (#4291)
* stage-4286: stop recovered turns replaying into later context (fixes #4283)

* Release PJ: stop recovered turns replaying into later context (#4283)

rodboev's #4286. Codex SAFE + Opus net-positive-and-safe + 9152/0 clean suite.
Single-row prefix allowance gated to _recovered+role==user+offset0.

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 20:02:57 -07:00
nesquena-hermesandnesquena-hermes ee7fda019f test-infra: hard-isolate test state from production + resilient teardown (#4289)
* test-infra: hard-isolate test state from production + robust teardown

Two production-safety fixes to tests/conftest.py + tests/_pytest_port.py:

1. TEST_STATE_DIR now defaults under the OS temp dir (/tmp/hermes-webui-tests/)
   instead of HERMES_HOME/webui-test-<hash> — which had been writing test state
   INTO the real ~/.hermes/profiles/webui/ (144 leaked webui-test-* dirs found).
   Added a hard-stop guard: refuse to run if the resolved test state dir is
   inside the production Hermes home, so a misconfigured env can never let tests
   wipe/clobber production profiles/sessions/credentials.

2. _rmtree_retry is now resilient on Linux (was a bare shutil.rmtree): retries
   transient OSError [Errno 39] Directory-not-empty races (a background daemon
   writing into the tree during teardown) + a final ignore_errors sweep, and
   warns instead of raising so a benign cleanup race can't spuriously ERROR a
   test (the failure mode seen near #4283 context-reconciliation tests).

* test-infra: anchor production-proximity guard on literal ~/.hermes (not $HERMES_HOME)

The first guard compared TEST_STATE_DIR against $HERMES_HOME, which is overridden
to TEST_STATE_DIR itself during a run -> false-tripped collection (6 modules) with
'test dir X is inside production home X'. Now anchor on the literal user-home
~/.hermes and explicitly allow any dir under the OS temp root, so the temp default
never trips while a real ~/.hermes target still hard-stops.

* test-infra: close TMPDIR-under-~/.hermes guard hole (Codex)

Codex flagged: the 'temp is always allowed' exception could be exploited if
TMPDIR itself is set under ~/.hermes — then a 'temp' path is a production path and
_under_temp would wrongly suppress the guard. Now the temp exception only holds
when the temp root is itself OUTSIDE the production home. Verified: normal /tmp
default allowed; TMPDIR=~/.hermes/tmp now trips the guard.

* test-infra: mirror production-proximity guard into _pytest_port.py (Opus defense-in-depth)

* test-infra: remove ~/.hermes production-path fallbacks from 4 test files (Codex)

Codex's kickback found the real gap: test_sprint5, test_sprint45,
test_onboarding_existing_config, test_regressions each hardcoded a
~/.hermes/webui-mvp-test fallback for their state dir (used when running
standalone or if the env var is unset). Repointed all to the shared isolated
TEST_STATE_DIR from _pytest_port (temp-rooted). Also refreshed stale
~/.hermes/webui-mvp-test references in test_gateway_sync comment + ARCHITECTURE.md.
Now ZERO production-path fallbacks remain in tests.

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 19:04:16 -07:00
32458c442b Release PI (v0.51.448): fix blank transcript viewport regression (#4277) (#4288)
* stage-4281: restore virtualized transcript viewport anchors (fixes #4277, #3714 regression)

* Release PI: fix blank transcript viewport regression (#4277, #3714 follow-up)

rodboev's #4281. Codex SAFE + Opus net-positive-and-safe + 20 virtualization/
scroll tests green. Viewport-anchor restore + blank-state recovery.

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 18:43:14 -07:00
nesquena-hermesandnesquena-hermes 70b7cf865c Release PH (v0.51.447): toolset selector shows active-profile defaults (#3100) (#4287)
* stage-3632: surface active-profile toolset defaults in selector (#3100)

* Release PH: toolset selector shows active-profile defaults (#3100) — CHANGELOG

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 18:00:13 -07:00
nesquena-hermesandnesquena-hermes 113d6d6bb8 test(gateway-watcher): make profile-switch tests hermetic against auth-state bleed (#4284)
test_profile_switch_restarts_watcher_best_effort and
test_profile_switch_response_survives_watcher_restart_failure passed in isolation
but failed in the full suite (HTTP 409 != 200) when an auth-enabling sibling test
left is_auth_enabled() True in-process: /api/profile/switch -> build_profile_cookie
-> sign_profile_cookie_value raises (no session) -> RuntimeError -> 409. Added an
autouse fixture forcing api.auth.is_auth_enabled -> False for this module (these
tests exercise watcher restart, not auth). Verified: full suite 9139 passed, 0
failed. Test-only.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 17:56:05 -07:00
0b0e2a3213 Release PG (v0.51.446): model picker show-all overflow for large catalogs (#3691) (#4282)
* stage-3712: model picker show-all overflow + fix per-row count leak (#3691)

Self-rebased rodboev's #3712 onto master. FIX (Nathan-caught): the per-row
provider chip stripped the '(N of M)' overflow count — that count belongs on the
group heading only, and was reading as nonsense post-expand (row 30 showing
'15 of 30'). Updated test to assert heading-carries-count + rows-do-not.

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

* stage-3712: + dedupe overflow expand (Codex SILENT) — reuse existing injected option

Codex caught: select hidden overflow model from search → reopen → Show all could
duplicate the row (_appendOverflowOptionsToGroup blindly appended every extra
model without checking the select for an already-injected option). Now moves/reuses
an existing same-value option instead of re-creating it.

* stage-3712: + fix expanded-heading double-count (Opus) — strip decoration, count rendered rows

Opus caught: post-expand the heading showed 'Nous (2 of 4) (4)' (appended its own
count onto the already-decorated backend label) and used modelCount (incl hoisted-
configured) vs rendered rows. Now strips the '(N of M)' suffix when expanded +
counts groupRows.length. Test asserts no double count on out[expanded].

* stage-3712: + include extra_models in Settings/Cron/Profile/Aux native selectors (Codex CORE)

Codex caught: the server overflow split moves models>15 into extra_models for ALL
providers, but the 4 native <select> builders in panels.js (settingsModel:6690,
cronFormModel:1112, profileFormModel:5784, _auxProviders:8287) read g.models only
— silently dropping the overflow tail for large providers. Now concat
models+extra_models at all 4 sites. Test guard added.

* Release PG: model picker show-all overflow for large provider catalogs (#3691)

Self-rebased rodboev's #3712 onto v0.51.445; full browser-tested (desktop+mobile,
before/after) + Nathan screenshot-approved. 4 gate-caught issues fixed inline:
per-row count leak (Nathan), overflow-expand dedup (Codex), heading double-count
(Opus), and native Settings/Cron/Profile/Aux selectors dropping the overflow tail
(Codex CORE). Codex SAFE + Opus SHIP + 41 picker tests green.

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 17:16:36 -07:00
40591f146f test(#3664): Windows pytest-harness batch #3 (#4274) (#4276)
rodboev test-only #4274 (test_issue609, test_sprint3, + 2 others). Linux
assertions stay at full strictness. 63 tests pass. No version stamp (tests-only).
Note: #4273 was NOT included — it touches api/workspace.py runtime (home-expansion
in the blocked-roots boundary), needs a separate full gate.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 15:57:18 -07:00
1bf2ef9f5e Release PF (v0.51.445): scope gateway watcher to active profile (#2848) (#4275)
* stage-3629: scope gateway watcher to active profile (profile-keyed registry), rebased onto master

* stage-3629: + stop-race-safe subscribe() (Codex CORE) + regression test

Codex caught a TOCTOU between get_watcher() and subscribe(): a watcher reaped as
subscriber-free during a concurrent profile switch left a late subscriber's queue
without the None sentinel -> SSE loop hangs open (keepalives, no events). Opus
independently found the same race (rated follow-up). subscribe() now checks
_stop_event under _sub_lock after appending and self-enqueues the sentinel.
test_subscribe_after_stop_gets_sentinel_immediately pins it.

* stage-3629: + reconnect gateway SSE to new profile on switch (Codex CORE #2)

Second Codex finding: after a profile switch this tab's _gatewaySSE stayed
subscribed to the PREVIOUS profile's watcher (switchToProfile never restarted it;
the probe reattach is gated on !_gatewaySSE which is false while the old ES is
open). switchToProfile() now calls startGatewaySSE() after S.activeProfile is set
— it closes the old ES + reconnects with the new profile cookie, self-gating on
_showCliSessions.

* Release PF: scope gateway watcher to active profile (#2848)

rodboev's #3629, re-pushed to fix both bounce findings (client SSE reconnect +
singleton→profile-keyed registry). Two Codex CORE races caught + fixed inline:
(1) stop/subscribe sentinel race, (2) tab's own SSE staying on the old profile
after switch. Codex SAFE + Opus SHIP (architecture) + gateway-watcher tests green.

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 15:41:21 -07:00
fe62f445cf test(#3664): Windows pytest-harness batch #2 (#4263, #4266) (#4272)
Two more rodboev test-only PRs continuing #3664 Windows-harness path
normalization (test_bootstrap_python_selection, test_issue513_wsl_autostart,
test_title_aux_routing). Linux assertions stay at full strictness. Codex SAFE +
full suite 9114 green. No version stamp (tests-only).

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 14:56:31 -07:00
nesquena-hermesandnesquena-hermes 3323b9120b docs(changelog): restore v0.51.439-444 release headings (#4271)
Same-day releases (OZ/PA/PB/PC/PD/PE) cut each stage before the prior merged, so
sequential squash-merges collapsed their entry content under one heading and
dropped the per-version ## [v0.51.NNN] separators (changelog jumped to 438). All
entry text was intact; this re-inserts the 439-444 headings to match git tags +
moves the tests-only #3664 note to [Unreleased] (no tag). Docs-only.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 14:51:18 -07:00
b875db776e Release PE (v0.51.444): invalidate sidebar session-list cache on WAL + settings updates (#4268) (#4270)
* stage-4268: invalidate sidebar cache on WAL + settings updates (#4268)

* Release PE: invalidate sidebar session-list cache on WAL + settings updates (#4268)

rodboev's #4268 (the sidebar-cache fix split out of #4133). Adds state.db-wal +
SETTINGS_FILE to _session_list_cache_source_stamp so uncommitted-WAL writes and
settings changes refresh the cached /api/sessions list. Codex SAFE + Opus SHIP
+ 9 cache tests green.

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 14:47:10 -07:00
2a3baa71b8 Release PD (v0.51.443): [security] scope session by-id reads + exports to active profile (#3982, #3991) (#4269)
* stage-3982-3991: [security] scope session detail-reads + exports to active profile (paired, re-cut onto v0.51.442, conflict-resolved properly)

* Release PD: [security] scope session by-id reads + exports to active profile (#3982, #3991)

Paired Hinotoi-agent security PRs, re-cut onto v0.51.442 (avoided the stale-base
revert of the #4171 passkey gate that Codex caught). Both /api/session and
/api/session/export now apply _profiles_match→404. Codex SAFE + Opus SHIP +
49 tests green; passkey gate verified intact.

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>
2026-06-15 14:22:47 -07:00
4d90577e25 Release PC (v0.51.442): [security] require auth for passkey enrollment (#4171) (#4267)
* stage-4171: [security] require auth for passkey enrollment, rebased onto master

* Release PC: [security] require auth for passkey enrollment (#4171)

Self-rebased Hinotoi-agent's #4171 onto v0.51.441; Nathan-approved security
posture. Gates the first-passkey bootstrap like first-password onboarding
(loopback/private or explicit opt-in; remote rejected; socket-based locality,
no header spoof). Codex SAFE + Opus SHIP + 17 passkey tests green.

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>
2026-06-15 13:56:30 -07:00
4817e3a6ab Release PB: honest notification-permission controls (#4118) (#4265)
ai... rodboev's #4133, notification-feature ONLY (dropped the unrelated
'invalidate sidebar cache on WAL' commit — that's a separate good fix to split
out). Button now reflects live Notification.permission instead of silent no-op.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 13:42:46 -07:00
e47f685b89 Release PA (v0.51.440): advanced per-model options + auxiliary model routing (#3097) (#4264)
* stage-3097: advanced model options (re-push fixes both no-ops), rebased onto master

* Release PA: advanced per-model options + auxiliary model routing (#3097)

ai-ag2026's #3097, re-pushed to fix both prior silent no-ops:
- base_url no longer clobbered (advanced applied after resolver write)
- extra_body now reaches the agent via request_overrides (+ cache-key); main
  modal hides unsupported timing fields (aux-only). Functionally verified live
  (gear→modal→save→persists). Codex SAFE + Opus SHIP + suite green.
Note: also adds 3 aux task slots (kanban_decomposer/profile_describer/triage_specifier).

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-06-15 13:16:34 -07:00
2b68fe049d Release OZ (v0.51.439): Insights dashboard polish (#766) (#4262)
* stage-3937: dashboard CSS polish, rebased onto v0.51.438 (resolved #3634 conflict + dropped dead insights_model_team keys)

* Release OZ: Insights dashboard polish (#766)

Self-rebased rodboev's #3937 onto v0.51.438 (resolved conflict with shipped
#3634, dropped dead insights_model_team keys — matches the PR's own test).
CSS-only polish. Codex SAFE + Opus SHIP + insights tests green.

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 12:54:22 -07:00
5a316b7ac4 test(#3664): Windows pytest-harness compatibility (batch: #4254/4255/4256/4257/4259) (#4261)
* stage-3664: Windows pytest harness hardening (test-only batch #4254/4255/4256/4257/4259)

* test(#3664): Windows pytest-harness compatibility batch

Combines 5 rodboev test-only PRs (#4254/#4255/#4256/#4257/#4259), all under tests/.
Linux assertion paths preserved at full strictness; relaxations + new markers are
Windows/no-fork gated. Codex SAFE + Opus SHIP + changed tests green (cascade-only
full-suite noise is the box artifact). No version stamp (tests-only, Nathan rule).

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 12:42:33 -07:00
b01f3d5930 Release OY (v0.51.438): model cost/health comparison table on Insights (#691) (#4260)
* stage-3634: static model-health comparison table on Insights (#691, rodboev)

* stage-3634: model-health table + pt i18n keys + per-locale test (Codex fix)

* Release OY: model cost/health comparison table on Insights (#691)

Self-rebased rodboev's #3634 onto v0.51.437; Nathan-approved screenshot;
full Codex+Opus+suite gate. Codex fix applied: added the 4 insights_model_health_*
keys to the pt locale (was English-fallback) + tightened the i18n test to a
per-locale check (was a weak count>=8).

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 12:12:16 -07:00
cdcede8ce7 Release OX (v0.51.437): blur composer on Escape for j/k nav (#3952) (#4258)
* stage-3955: blur composer on Escape for j/k nav + IME guard (Opus nit)

* stage-3955: update test to match IME-guarded blur branch

* Release OX: blur composer on Escape for j/k message nav (#3952)

Self-rebased TomBanksAU's #3955 onto v0.51.436; full Codex+Opus+suite gate.
Escape now blurs the focused composer so j/k session-nav works, after higher-
priority Escape actions run; dropdown-Escape stops propagation; IME-composition
guarded (Opus nit). Also restored the v0.51.434/435 CHANGELOG headings that an
earlier batch left folded under v0.51.436 (hygiene fix).

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: TomBanksAU <TomBanksAU@users.noreply.github.com>
2026-06-15 11:55:47 -07:00
a004e49ebf Release OW (v0.51.436): cron sessions mark unread on new runs (#3460) (#4253)
* stage-3921: cron-session unread disambiguation (#3460, rodboev)

* Release OW: cron sessions mark unread on new runs (#3460)

Self-rebased rodboev's #3921 onto v0.51.435; full Codex+Opus+suite gate.
Fixes cron-session unread cross-attribution: resolver now considers all known
job ids and attributes each session to the longest matching cron_<jobid>_ prefix
(provably unambiguous). Read-only state.db scan, fires only on real completions.

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 10:52:52 -07:00
e775fb3355 Release OV (v0.51.435): supported local pytest runner (#3908) (#4252)
* stage-3908: supported local pytest runner + symlink-guard on venv create/clear (Codex CORE fix)

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

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

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

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

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-15 10:25:32 -07:00
4abf3bf5ef fix(skills): reject symlinked SKILL.md on save (#4240) (#4245)
* fix(skills): reject symlinked skill saves

* chore(release): stamp v0.51.434 (Release OU) for #4240 skill-save symlink guard

---------

Co-authored-by: 峯岸 亮 <1920071390@campus.ouj.ac.jp>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 04:22:17 -07:00
60ad36dab7 fix(file): reject symlinked entries in /api/file/save (#4234) (#4244)
* fix(file): reject symlink saves

* chore(release): stamp v0.51.433 (Release OT) for #4234 file-save symlink guard

---------

Co-authored-by: 峯岸 亮 <1920071390@campus.ouj.ac.jp>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 04:05:29 -07:00
f927fc3a42 fix(sidebar): treat TUI sessions like CLI for discoverability (#4213) (#4237)
* Fix TUI session sidebar discoverability

* chore(release): stamp v0.51.432 (Release OS) for #4213

---------

Co-authored-by: Dennis Soong <dso2ng@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 00:48:56 -07:00
4c5e0af4d2 fix(server): make request logging non-fatal so a closed stdout can't abort a response (#4188) (#4235)
* Make request logging nonfatal

* chore(release): stamp v0.51.431 (Release OR) for #4188

---------

Co-authored-by: MC <mchughson@sportsculture.io>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 00:28:51 -07:00
d29da34c42 feat(sessions): carry changed session_id in session-list-changed events (#4221) (#4233)
* fix(sessions): include target session in session events

* chore(release): stamp v0.51.430 (Release OQ) for #4221

---------

Co-authored-by: Dennis Soong <dso2ng@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 00:10:01 -07:00
8dc12713ca fix(sidebar): keep multiple messageful imported/CLI sessions discoverable (#4218) (#4232)
* Fix imported lineage sidebar discoverability

* chore(release): stamp v0.51.429 (Release OP) for #4218

---------

Co-authored-by: Dennis Soong <dso2ng@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-14 23:51:22 -07:00
44ead2a73d fix(context): bound non-git project-context file walk at the workspace (#4164) (#4231)
* fix(#4164): bound project-context file walk in non-git workspaces

_project_context_candidates mirrored the agent's _find_hermes_md exactly,
including the bug that 'stop_at = git_root' is None in a non-git tree —
so the walk 'for directory in [cwd, *cwd.parents]: ... if stop_at and
directory == stop_at: break' never broke and ran to filesystem root.
A workspace at /tmp/x/project/subdir could surface /tmp/x/HERMES.md
(a file outside the workspace) in the Memory > Project Context tab.

Fix: when no git root is found, treat the workspace itself as the stop
boundary. The cwd is still scanned (preserving in-workspace AGENTS.md /
HERMES.md behavior), but parents are skipped — we never walk into the
user's home directory or /tmp.

Git workspaces are unchanged (still walk up to the git root). The
existing test_3866 walk-to-git-root test still passes.

The issue notes the agent-side walk in agent/prompt_builder._find_hermes_md
should be bounded the same way for full parity; that lives in a separate
repo (hermes-agent), so this PR fixes only the WebUI side. Until the
agent ships the same bound, the WebUI tab will under-report context
files that live above a non-git workspace — strictly less surprising
than the previous over-report.

tests/test_issue4164_bound_non_git_project_context_walk.py (4 tests) pins:
- non-git workspace does not leak a parent HERMES.md
- non-git workspace still reads in-workspace context files
- git workspace walk-to-git-root is unchanged
- workspace-level HERMES.md wins, parent copy doesn't appear even shadowed

All 10 existing test_3866_project_context_memory_section tests still pass.

Closes #4164.

* chore(release): stamp v0.51.428 (Release OO) for #4164

---------

Co-authored-by: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-14 23:22:29 -07:00
ed5afd2ae3 Release ON: show_cli_sessions default-on for new installs (#3988) + symlink delete/rename guard (#4217) (#4230)
* fix(#3988): default show_cli_sessions to true for new installs, grandfather existing OFF

Combines the best of #3997 (rodboev) and #4222 (Sanjays2402):
- config.py default flipped True so NEW installs surface CLI/TUI/Telegram/
  Discord/Hermes One sessions in the sidebar without hunting for the toggle.
- load_settings() backfill grandfathers ESTABLISHED installs OFF: when the saved
  settings predate the flip (onboarding_completed True + key absent), pin False so
  an existing user's sidebar doesn't silently change. Fresh installs get True.
- Default-true hydration at every read site (config.py / boot.js / panels.js
  checkbox) via the !==false idiom — no config-ON-but-UI-OFF mismatch (#4006 class).
- 10 tests: config default, bool-key round-trip, grandfather matrix
  (fresh/mid-onboarding/established-absent/explicit-true/explicit-false/
  save-preserves-grandfather), and both frontend hydration sites.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: Sanjays2402 <Sanjays2402@users.noreply.github.com>

* fix(#4217): guard /api/file/delete and /api/file/rename against symlink targets

* chore(release): stamp v0.51.427 (Release ON) for #3988 + #4217

* chore: drop unused imports (ruff F401) in absorbed test files

* fix(#4217,#3988): address review — dangling-symlink ordering + boot.js catch fallback

Greptile review on #4230 + the related-PR reviews surfaced two fixes on the
shipped code:
1. (#4217) _handle_file_delete/_handle_file_rename checked target.exists()
   BEFORE is_symlink(), so a DANGLING workspace symlink resolved to a missing
   target -> misclassified as 404 'File not found' and left permanently
   undeletable/unrenameable. Moved the no-follow is_symlink() guard before the
   follow-based exists() check in both handlers. +2 dangling-symlink tests.
2. (#3988) boot.js settings-load-FAILURE catch block hardcoded
   _showCliSessions=false, inconsistent with the new True default (the #4006
   catch-block-fallback class). Now defaults true. +1 test pinning the fallback.

Both confirmed by the new tests (20/20 in the two files).

* fix(#3988,#4217): address Opus review — broaden grandfather signal + move-handler parity

Opus #4230 ship-review surfaced 2 LOW findings, both now fixed in-line:
1. (#3988) The grandfather keyed only on onboarding_completed, so a CLI-configured
   user who tweaked a WebUI setting but never ran the wizard would get flipped ON
   (and onboarding.py later auto-persisting onboarding_completed=True would make it
   permanent). Now: established = show_cli_sessions absent AND (onboarding_completed
   OR any persisted setting other than a falsy onboarding flag). A genuinely new /
   still-mid-onboarding file still falls through to True. +1 test.
2. (#4217) _handle_file_move still checked exists() before is_symlink() — the same
   dangling-symlink-404 bug just fixed in delete/rename. Reordered for parity so a
   dangling symlinked source is rejected 400, not 404.

22 tests across the two files pass; ruff clean.

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: Sanjays2402 <Sanjays2402@users.noreply.github.com>
2026-06-14 23:06:22 -07:00
e7e856cd79 fix(providers): keep custom provider when model id has known-provider prefix (#4210) (#4220)
* fix: keep custom provider when model id has known-provider prefix (#4210)

Siblings of #3872 (v0.51.349, custom provider with a configured base_url
preserves intrinsic routing prefixes). The base_url unset variant of
the same bug was missed: with provider=custom or custom:<slug> and no
base_url set, a model id like 'x-ai/grok-2' or 'google/gemma-2' was
hijacked to provider='openrouter', base_url=None. The backend then
failed to initialize with 'RuntimeError: No LLM provider configured'
because the user had no OpenRouter credentials configured.

resolve_model_provider() now applies the same _is_custom exemption the
config_base_url branch already carries to the cross-provider redirect
at the end of the slash branch. The custom provider keeps the request
and the full model id; real first-party providers pointed at an
OpenAI-compatible proxy (e.g. provider=openai + base_url=litellm) are
unaffected because their strip path is in the base_url branch and the
'prefix == config_provider' branch above the change.

Verified:
- test_model_resolver.py 32 passed (30 existing + 2 new RED tests)
- test_custom_provider_bare_model_reasoning.py 34 passed
- Pre-existing F401 (api/config.py:23,24,28) are not introduced by
  this change; same 3 errors exist on upstream/master.

Refs #4210

* chore(release): reposition #4210 CHANGELOG entry under v0.51.426 (OM)

---------

Co-authored-by: b3nw <b3nw@users.noreply.github.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-14 19:56:12 -07:00
d0fb2aa22b Release OL: empty-pending session-save guard (#4205) + cron-reply fix (#3975) (#4219)
* fix: guard active pending session saves

* fix: allow replies to cron sessions

* chore(release): stamp v0.51.425 (Release OL) for #4205 + #3975

---------

Co-authored-by: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
Co-authored-by: TomBanksAU <246584738+TomBanksAU@users.noreply.github.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-14 19:39:26 -07:00
d769acf43f fix(sessions): paginate tool-heavy session tails by visible messages (#4069) (#4215)
* fix: bound paginated session tails by visible messages

* fix(#4070): always merge state.db tail rows (drop the stale-tail timestamp shortcut)

Deep-review finding: the sidecar-only timestamp shortcut returned the sidecar
verbatim whenever state.db had no strictly-newer rows, but a state.db row
at-or-before the sidecar's newest timestamp (recovery / edited-in-place /
missing-timestamp) is still absent from the sidecar -> the paginated tail
diverged from the full merge path (empirically 3/6 adversarial cases). Always
run the cheap in-memory append-only merge; the real latency win (skipping the
lineage-parent DISK load) is preserved.

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

* fix(#4070): keep matching trailing tool-result row in paginated window

Codex ship-gate finding: _message_window_for_display pulled end_idx back to the
last renderable (user/assistant) row, dropping a trailing role:tool result row
even when it's the result of the newest assistant tool-call. The renderer
rebuilds tool cards from these rows (CLI-origin / empty S.toolCalls path,
static/ui.js resultsByTid), so the card lost its result snippet. Now extend
end_idx forward through trailing tool rows whose tool_call_id matches a tool-call
present in the window; orphan trailing tool rows (no match) are still skipped to
preserve the visible-row budget. +2 regression tests.

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

* chore(#4070): remove now-dead _max_message_timestamp + its import

Opus gate nit: _max_message_timestamp was only called by the removed
sidecar-timestamp shortcut, and the _message_timestamp_as_float import existed
solely to feed it. Both removed; _message_timestamp_as_float still lives in
api/models.py for its real callers.

* chore: add #4069 CHANGELOG entry (visible-message tail pagination)

* chore(release): stamp v0.51.424 (Release OK) for #4069

---------

Co-authored-by: t3chn0pr13st <technopriest@live.ru>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: t3chn0pr13st <t3chn0pr13st@users.noreply.github.com>
2026-06-14 19:18:11 -07:00
fe12eebbf3 perf(chat): virtualize long message transcripts (#500) (#4214)
* perf(chat): virtualize long message transcripts (#500)

* fix(chat): preserve virtual height cache on append (#500)

* fix(chat): bound virtual tail follow-up (#500)

* fix(chat): surface older history and bound virtual rerenders (#500)

* fix(chat): stabilize virtual history fallback (#500)

* fix(chat): preserve virtual compression anchors (#500)

* Keep virtualization regressions aligned with rebased sources (#500)

* fix(chat): preserve virtual heights across prepends (#500)

* test(update): isolate restart timing checks (#814)

* fix(chat): restore render contracts around virtual cache

* fix(chat): preserve legacy assistant anchor predicates

* fix(#3714): restore worklog-disclosure capture after HTML-cache early-return

Deep-review finding: the capture of _captureWorklogDetailDisclosureState was
moved before the HTML-cache fast-path return, failing the invariant test
test_render_rebuild_preserves_worklog_detail_disclosure_click_state (it must
not traverse the previous-session DOM when the cache path returns early).
Moved it back to just before inner.innerHTML='' (after the cache return).

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

* fix(#3714): restore Transparent Stream rehydration in HTML-cache fast path

Full-suite gate caught test_issue3820_chat_activity_display_mode failure: the
virtualization rewrite of the renderMessages HTML-cache fast-path block dropped
the _rehydrateTransparentStreamDom(inner) call (master has it; PR had 2 of 3
occurrences). On a cache-restore session switch, Transparent Stream controls
lost their property-assigned handlers. Restored the call in the same position
master has it (right after _sessionHtmlCacheSid=sid).

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

* fix(#3714): preserve live turn on scroll-rebuild even with zero streamed text

Opus ship-review residual (now fixed, not deferred): the live-turn re-attach
after a scroll-triggered virtualized rebuild was gated on _preservedLen>0, so a
turn showing only Activity/tool/worklog blocks with no streamed text yet (mid
tool-call) would not be preserved — scrolling up into history on a >80-row
transcript mid-stream could blink those live-only blocks for a frame. Broaden
the gate to also restore when the preserved turn has MORE structural blocks than
the rebuilt (lagging-S.messages) turn. Hoisted the _structuralCount helper above
the gate; whole-turn-vs-segment restore logic unchanged.

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

* chore(release): stamp v0.51.423 (Release OJ) for #500 transcript virtualization

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-14 19:11:32 -07:00
87fa0a2ace feat(workspace): optional Todos tab in workspace panel (#3564) (#4209)
* feat(workspace): add Todos tab to workspace panel behind a setting (#2942)

* fix(workspace): use DOM attribute to check active tab across script scope

* Prefer the live todos snapshot in the workspace tab

* Fill the missing workspace Todos locale keys

* feat(workspace): add optional Todos tab to workspace panel (#3564)

Rebased rodboev's #3564 onto current master + CHANGELOG entry.

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

* fix(#3564): apply workspace Todos tab visibility on cold boot

Codex gate: boot.js set window._workspaceTodosTab from saved settings but
never called _applyWorkspaceTodosTabVisibility(), so a previously-enabled
Todos tab stayed hidden on a normal page reload (only re-appeared after
opening Settings). Call the visibility helper after assigning the flag in
both the settings-apply and the fallback/default paths.

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

* chore(release): stamp v0.51.422 (Release OI) for #3564

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-14 17:31:13 -07:00
92ecf8edb4 fix(models): preserve colon-suffixed model IDs in normalization (#3959, ships #4196) (#4204)
* fix(models): preserve colon-suffixed model IDs in normalization (#3959)

* fix(models): align configured-model normalizer parity (#3959)

* chore(release): stamp v0.51.421 (Release OH) CHANGELOG for #3959/#4196

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-14 16:27:39 -07:00
6c6444396d fix(#3750): preserve LM Studio auth on reasoning probe (self-rebased #3837 + credential hardening) (#4202)
* fix(#3750): preserve LM Studio auth on reasoning probe

Rebased rodboev's #3837 onto current master (master reworked
resolve_model_reasoning_efforts via #3431 with a combined hermes_cli
import + _nested_route_reasoning_denied guard). Threaded the api_key
resolution + the _lmstudio_model_reasoning_options wrapper + the
HTTP-fallback probe into master's current structure; intent preserved
byte-identically (+164/-24, same as the original PR diff).

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

* fix(#3837): gate LM Studio probe credential to configured endpoint + no-redirect opener

Codex security review (CORE) on the rebased #3837: GET /api/reasoning accepts
a caller-supplied base_url, and the new probe attached the configured LM Studio
API key as a Bearer token; urllib also re-sends Authorization across redirects.
Together that could exfiltrate the stored credential to an attacker-controlled
host. (master only escalates here because master probed WITHOUT a key.)

Fixes:
1. Only forward the LM Studio credential when the probe target normalizes to
   the configured LM Studio base URL (reuses _normalize_base_url_for_match);
   a caller-supplied non-matching base_url is probed keyless.
2. _lmstudio_reasoning_probe_options_fallback uses a no-redirect opener
   (_NoRedirectHandler) so a 3xx can't forward the Authorization header onward.
3. Two regression tests: caller-supplied base_url drops the key; redirect is
   not followed (credential never reaches the redirect target).

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

* fix(#3837): route ALL credentialed LM Studio probes through the no-redirect probe

Codex re-gate caught that the first fix only hardened the built-in fallback;
in production hermes_cli IS importable, so the preferred path called
hermes_cli.models.lmstudio_model_reasoning_options(..., api_key=...) which uses
plain urlopen and still follows redirects with the credential.

Fix: _lmstudio_model_reasoning_options now short-circuits to the built-in
no-redirect probe whenever api_key is set; the hermes_cli path is used ONLY for
keyless probes (no credential to leak). Reworked tests accordingly:
- test_credentialed_probe_never_calls_hermes_cli: credentialed probe bypasses CLI
- test_keyless_probe_logs_signature_mismatch_before_fallback: keyless CLI degrade
- test_credentialed_probe_does_not_follow_redirects_end_to_end: E2E through
  resolve_model_reasoning_efforts with real hermes_cli; redirect target never hit

Verified both security tests fail without the guard and pass with it.

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

* chore(release): stamp v0.51.420 (Release OG) in CHANGELOG

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-14 15:46:35 -07:00
nesquena-hermes 5fa92729c6 Merge pull request #4201 from nesquena/stage-of
Release OF (v0.51.419): settled-stream message-count consistency (#4192)
2026-06-14 13:48:21 -07:00
nesquena-hermes 836f29951b Merge pull request #4200 from nesquena/stage-oe
Release OE (v0.51.418): slash-command autocomplete after non-ASCII prefix + multiple slashes (#4194)
2026-06-14 13:13:33 -07:00
nesquena-hermes db9803946f Merge pull request #4199 from nesquena/stage-od3
Release OD (v0.51.417): preserve sidebar issue numbers (#4193) + guard external-refresh poll (#4195)
2026-06-14 12:36:32 -07:00
nesquena-hermes 541b8a931d Merge pull request #4198 from nesquena/stage-oc
Release OC (v0.51.416): cache + single-flight /api/sessions payloads (#3881)
2026-06-14 12:07:45 -07:00
nesquena-hermes 73451c23d5 Merge pull request #4191 from nesquena/stage-ob
Release OB (v0.51.415): keep complete sidebar metadata on index fastpath (#4166)
2026-06-14 11:46:51 -07:00
nesquena-hermes d7c8a0c780 Merge pull request #4189 from nesquena/stage-na
Release OA (v0.51.414): fix misleading regenerate-title error message (#4186)
2026-06-14 11:08:19 -07:00
nesquena-hermes ae6d0b3e14 Merge pull request #4187 from nesquena/stage-nz
Release NZ (v0.51.413): restore approval pending-fallback reliability (#4111)
2026-06-14 10:58:05 -07:00
nesquena-hermes b983a57dc3 Merge pull request #4185 from nesquena/stage-ny
Release NY (v0.51.412): gateway reasoning previews (#4148) + regenerate-title for CLI/TUI sessions (#4184)
2026-06-14 10:47:12 -07:00
nesquena-hermes d4d4b1e041 Merge pull request #4182 from nesquena/stage-4179
Release NX (v0.51.411): offline recovery verifies unreliable browser offline reports (#4179)
2026-06-14 10:16:10 -07:00
nesquena-hermes 544005c7ae Merge pull request #4174 from nesquena/stage-uxnight
Release NW (v0.51.410): chat Mermaid lightbox (#4075) + workspace CSV table preview (#4025)
2026-06-13 23:21:12 -07:00
nesquena-hermes b461f2680c Merge pull request #4173 from nesquena/stage-titlegen
Release NV (v0.51.409): provider-gate title-gen reasoning extra_body (#4161/#2083, consolidates #4162+#3944)
2026-06-13 22:43:32 -07:00
nesquena-hermes dfb4007ae8 Merge pull request #4169 from nesquena/stage-4165
Release NU (v0.51.408): reasoning selector for nested Gemini custom-provider routes (#4165)
2026-06-13 21:53:10 -07:00
nesquena-hermes 5b96673f4b Merge pull request #4168 from nesquena/stage-3601b
Release NT (v0.51.407): nest forked sessions under their parent (#3224)
2026-06-13 21:42:13 -07:00
nesquena-hermes d637d27fa7 Merge pull request #4163 from nesquena/stage-4144
Release NS (v0.51.406): project-context file in the Memory tab (#3866)
2026-06-13 20:52:27 -07:00
nesquena-hermes ac216a5c0e Merge pull request #4160 from nesquena/stage-4158
Release NR (v0.51.405): Transparent Stream live prose stays chronological (#4096)
2026-06-13 20:06:37 -07:00
nesquena-hermes 1643d660b5 Merge pull request #4157 from nesquena/stage-4156
Release NQ (v0.51.404): PWA multi-window connection-pool saturation fix (#4151)
2026-06-13 18:36:34 -07:00
nesquena-hermes ebf778edd3 Merge pull request #4155 from nesquena/stage-4143
Release NP (v0.51.403): notification click reuses existing chat tab (#4109)
2026-06-13 17:47:27 -07:00
nesquena-hermes ca4b82c126 Merge pull request #4149 from nesquena/stage-4112b
Release NM (v0.51.400): skill bundles in WebUI slash commands (#4087)
2026-06-13 16:25:30 -07:00
nesquena-hermes 3e87b620f5 Merge pull request #4147 from nesquena/stage-3940v2
Release NL (v0.51.399): per-home provider probe-worker pool, race-safe (#3787)
2026-06-13 15:15:34 -07:00
nesquena-hermes 4eec7c3fec Merge pull request #4145 from nesquena/stage-3998v2
Release NK (v0.51.398): auto-generate titles for imported CLI sessions (#3987)
2026-06-13 15:02:56 -07:00
nesquena-hermes b2d32924a4 Merge pull request #4142 from nesquena/stage-4072
Release NJ (v0.51.397): wire /credits through WebUI command dispatch (#4071)
2026-06-13 14:28:01 -07:00
nesquena-hermes 1557f80f83 Merge pull request #4141 from nesquena/stage-4139
Release NI (v0.51.396): longer timeout for full-history session loads (#4139)
2026-06-13 13:53:12 -07:00
nesquena-hermes eeda18c764 Merge pull request #4140 from nesquena/stage-4011
Release NH (v0.51.395): push source filters into agent session scans (#3930)
2026-06-13 13:33:24 -07:00
nesquena-hermes 5c2b5c1b7b Merge pull request #4136 from nesquena/stage-4131
Release NG (v0.51.394): document-title attention badge for pending prompts (#4121)
2026-06-13 12:35:02 -07:00
nesquena-hermes 42e5d1b02e Merge branch 'master' into stage-4131 2026-06-13 12:31:15 -07:00
3ee2f5d693 docs: refresh contributor tally for v0.51.390 (248 contributors) (#4132)
* docs: refresh contributor tally for v0.51.390 (248 contributors)

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

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

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

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

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

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

---------

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-06-13 12:18:21 -07:00
nesquena-hermes e389107954 Merge pull request #4135 from nesquena/stage-4127
Release NF (v0.51.393): run /yolo immediately while busy (#467 follow-up)
2026-06-13 12:14:58 -07:00
nesquena-hermes ebe9fff809 Merge pull request #4134 from nesquena/stage-4130
Release NE (v0.51.392): align WebUI reasoning efforts to agent set, drop max (#3505 follow-up)
2026-06-13 12:02:05 -07:00
nesquena-hermes 80cb179625 Merge pull request #4129 from nesquena/stage-4126
Release ND (v0.51.391): Stable Assistant Turn Anchors renderer snapshot adapter (inert) (#4126)
2026-06-13 11:40:05 -07:00
nesquena-hermes 5838f8c770 Merge pull request #4128 from nesquena/stage-4124
Release NC (v0.51.390): don't snap long final answers to bottom on PWA refresh (#4123)
2026-06-13 11:17:32 -07:00
nesquena-hermes bba5631a69 Merge pull request #4125 from nesquena/stage-4103
Release NB (v0.51.389): surface dirty-install state in update check (#4085)
2026-06-13 10:48:38 -07:00
nesquena-hermes c76e219076 Merge pull request #4122 from nesquena/stage-4120
Release NA (v0.51.388): Stable Assistant Turn Anchors dual-run reconciler (inert) (#4120)
2026-06-13 10:35:22 -07:00
nesquena-hermes e3d8a3dc30 Merge pull request #4115 from nesquena/stage-4108
Release MZ (v0.51.387): Stable Assistant Turn Anchors live shadow feed (inert) (#4108)
2026-06-13 09:36:44 -07:00
nesquena-hermes ae90cf620b Merge pull request #4106 from nesquena/stage-4016b
Release MY (v0.51.386): voice mode survives dropped speechSynthesis onend (#3983)
2026-06-13 00:47:20 -07:00
nesquena-hermes 1ae56799cb Merge pull request #4105 from nesquena/stage-4028
Release MX (v0.51.385): profile-cookie env var aligned to HERMES_WEBUI_ prefix (#803)
2026-06-13 00:36:33 -07:00
nesquena-hermes abe89f3afa Merge pull request #4104 from nesquena/stage-3899
Release MW (v0.51.384): no false streaming / activity-timer reset on session switch (#3900)
2026-06-13 00:19:07 -07:00
nesquena-hermes 0f9b62370b Merge pull request #4102 from nesquena/stage-4094
Release MV (v0.51.383): desktop tab title keeps active session name (#4086)
2026-06-12 23:21:13 -07:00
nesquena-hermes 3f2eb8d362 Merge pull request #4101 from nesquena/stage-4093
Release MU (v0.51.382): Stable Assistant Turn Anchors activity-scene projection (inert) (#4093)
2026-06-12 23:02:04 -07:00
nesquena-hermes 2847bd98a0 Merge pull request #4100 from nesquena/stage-4092
Release MT (v0.51.381): Stable Assistant Turn Anchors settled final-answer projection (#4092)
2026-06-12 22:40:31 -07:00
nesquena-hermes c2ecb47554 Merge pull request #4099 from nesquena/stage-3823
Release MS (v0.51.380): tool-iteration-limit stops surfaced explicitly (#3821)
2026-06-12 22:19:06 -07:00
nesquena-hermes da70ea47f8 Merge pull request #4097 from nesquena/stage-4063
Release MR (v0.51.379): Worklog detail collapse survives live refresh (#4062)
2026-06-12 21:56:30 -07:00
nesquena-hermes d1b6e62eb6 Merge pull request #4095 from nesquena/stage-4006
Release MQ (v0.51.378): scroll auto-follow toggle + don't-yank-while-reading (#4006)
2026-06-12 21:37:07 -07:00
nesquena-hermes d60fa993fc Merge pull request #4091 from nesquena/stage-3920
Release MP (v0.51.377): Firefox post-stream scroll jitter (#3920)
2026-06-12 20:47:38 -07:00
nesquena-hermes b2c41658d6 Merge pull request #4090 from nesquena/stage-3971
Release MO (v0.51.376): Hide Thinking also hides Worklog reasoning (#3903)
2026-06-12 20:22:48 -07:00
nesquena-hermes d1a9e9ffbb Merge pull request #4037 from franksong2702/franksong2702/stable-assistant-turn-anchors-slice3-registry
Add assistant turn anchor registry and shadow source harness
2026-06-12 19:45:46 -07:00
nesquena-hermes dc909043cf Merge branch 'master' into franksong2702/stable-assistant-turn-anchors-slice3-registry 2026-06-12 19:41:20 -07:00
nesquena-hermes bb33e76568 Merge pull request #4084 from nesquena/stage-4024
Release MN (v0.51.375): Transparent Stream activity display (#3820 / #4024)
2026-06-12 19:19:57 -07:00
nesquena-hermes 75ea7abef4 Merge pull request #4082 from nesquena/stage-mm
Release MM (v0.51.374): custom-provider context-length probes carry the API key (#4059)
2026-06-12 17:35:50 -07:00
nesquena-hermes 6354a46e0d Merge pull request #4081 from nesquena/stage-ml
Release ML (v0.51.373): sidebar lineage segments survive streaming refresh + source-count alignment
2026-06-12 17:10:37 -07:00
nesquena-hermes dee23a3528 Merge pull request #4079 from nesquena/stage-mk
Release MK (v0.51.372): markdown link-label inline code + /use skill autocomplete + mobile Worklog overflow
2026-06-12 16:41:00 -07:00