Commit graph

2622 commits

Author SHA1 Message Date
nesquena-hermes
b7b6b3a4c8
Release exp-v0.52.43: trusted-header auth / reverse-proxy SSO (#5568, #3351) (#5956)
* feat(#3351): add trusted-header auth core

* fix(#3351): reconcile trusted sessions per request

* fix(#3351): preserve valid trusted group mappings

* fix(#3351): defer trusted logout reconciliation until after csrf

* fix(auth): clear _pending_set_cookies in reset_trusted_auth_request_state (keep-alive cookie cross-request leak)

Codex gate CORE finding: a Set-Cookie queued in request N but not flushed
survived onto the reused HTTP/1.1 handler and could be emitted by request N+1
— after trusted-identity rotation on logout this could overwrite a valid login
cookie and 401 the user. Reset the queue at the per-request boundary + add a
keep-alive regression test.

* docs(.env.example): document trusted-header auth / reverse-proxy SSO env vars

HERMES_WEBUI_TRUSTED_AUTH_HEADER + TRUSTED_PROXY_CIDRS (with exact-proxy-IP
security warning) + optional groups-header/JSON group-profile-map/logout-url.

* Release exp-v0.52.43: trusted-header auth / reverse-proxy SSO (#5568, #3351) + keep-alive cookie fix + docs

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-11 16:34:14 -07:00
nesquena-hermes
18793a1ba4
Release exp-v0.52.42: ?profile= deep links (#5730) + Russian locale refresh (#5778) (#5953)
* Refresh Russian localization bundle

* feat(#5682): honor profile query during boot

* fix(profile): refresh deep-link reasoning state (#5682)

* test(ci): initialize reasoning transition harness state

* test(ci): accept reasoning fetch context override

* fix(#5682): preserve retryable profile query intent

* test(#5682): accept profile switch result contract

* fix(#5682): bind reasoning fetches to profile transitions

* Release exp-v0.52.42: ?profile= deep links (#5730) + Russian locale refresh (#5778)

---------

Co-authored-by: DrMaks22 <DrMaks22@users.noreply.github.com>
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-11 15:58:47 -07:00
nesquena-hermes
9a1acbff2a
Release exp-v0.52.41: dedupe pending prompts across compaction boundary (#5920) (#5952)
* fix: dedupe pending prompt across compaction

* Release exp-v0.52.41: dedupe pending prompts across compaction boundary (#5920, starship-s)

---------

Co-authored-by: Hermes Agent <hermes-agent@users.noreply.github.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-11 15:26:05 -07:00
nesquena-hermes
20d258d552
Release exp-v0.52.40: ACP sessions in sidebar (#5939) + date-stamped Claude 3.0 reasoning fix (#5934) (#5951)
* fix(reasoning): stop date-stamped Claude 3.0 ids from reading reasoning-capable

_candidate_supports_reasoning captured a trailing date stamp as the minor
version so bare date-stamped Claude 3.0 ids like claude-3-opus-20240229
satisfied the major==3 and minor>=7 gate and wrongly surfaced reasoning-effort
controls. Cap the minor group to 1-2 digits with a (?!\d) guard so the date
stamp is not read as a minor version -- the same date-stamp defense
_is_pre_adaptive_anthropic already uses. Claude 3.0/3.5 now correctly report
no reasoning support; 3.7+ and 4.x (incl. date-stamped builds) are unaffected.

* test(reasoning): cover date-stamped Claude 3.0 not reasoning-capable

Locks the heuristic so claude-3-opus-20240229 (and sibling date-stamped 3.0
ids) report no reasoning support while 3.7+/4.x stay capable.

* fix(session): classify ACP adapter sessions into the CLI sidebar family

Gateway ACP adapter sessions (source='acp' in state.db — Zed, external
device bridges like a Rabbit R1) normalised to session_source 'other',
which fell through BOTH sidebar buckets: sidebar_source=webui skips the
state.db projection entirely (and ACP rows have no WebUI sidecar), while
sidebar_source=cli keeps only CLI-classified rows. Result: ACP
conversations were invisible and could never be clicked/imported.

Classify 'acp' as a CLI-family source, mirroring 'tui' (a local
interactive agent client): normalize_agent_session_source maps it to
session_source 'cli' with label 'ACP', is_cli_session_row recognises the
raw source, is_cli_session_row_visible keeps ended/untitled ACP rows
visible (they are always user-driven), and frontend _isCliSession
accepts raw === 'acp'. Zero-message ACP connect/reconnect stubs stay
hidden via the existing message-count guard. Claim policy is untouched:
ACP stays claimable like CLI/TUI per the documented 'future local agent
sources' allowance in _is_claimable_cli_source.

Rollback: revert this commit (pre-change file copies also in
~/.hermes/backups/acp-sidebar-fix-20260711/), then restart WebUI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(session): require a user turn before surfacing ACP rows

An ended ACP connection can record only assistant/tool/system messages
(replayed or aborted turn), which the blanket TUI-style visibility
exception would surface despite not being user-driven. Gate the ACP
branch on _count_user_turns(row) > 0, per Greptile review on #5939.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Release exp-v0.52.40: ACP sessions in sidebar (#5939) + date-stamped Claude 3.0 reasoning-control fix (#5934)

---------

Co-authored-by: 黄云龙 <76432572+nankingjing@users.noreply.github.com>
Co-authored-by: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-11 15:20:41 -07:00
nesquena-hermes
29f67b4289
Release exp-v0.52.39: transparent-stream mode-precedence fix (#5942/#5943) + hermetic OpenRouter test (#1426) (#5948)
* fix(transparent-stream): stop compact worklog from hijacking transparent live render (#5942, #5943)

Transparent Stream turns with tool calls alternated between the grouped compact
worklog summary ("Processed Ns") and the individual per-event row list (#5942), and
rebuilt every live row element each tick causing flicker (#5943, regression of #5367).

Root cause (bisected to the #5739 merge as a MERGE INTERACTION; #5700/#5739 timestamp
code is NOT causal): #5746 (final-answer-only mode) reworked renderLiveAnchorActivityScene
so a caller-supplied `requestedMode` (opts.mode) OVERRODE the user's active display mode.
Several live callers (appendLiveToolCard, ensureLiveWorklogShell, appendLiveCompressionCard,
appendLiveThinking, session restore in sessions.js) hardcode {mode:'compact_worklog'}.
Before #5746 the dispatch checked isTransparentStream() FIRST and ignored that hint; after,
those callers forced the compact grouped render onto a transparent turn. The compact pass
deletes the keyed transparent rows, so the next transparent tick's preserveByKey reuse
(the #5367 element-reuse fix) misses -> it rebuilds a grouped frame then tears it down and
recreates every row = alternating + flicker.

Fix (two layers, defence in depth):
1. renderLiveAnchorActivityScene: the USER's active chatActivityMode() is authoritative;
   opts.mode is only a fallback when there is no usable active mode. A compact_worklog hint
   can no longer override an active transparent_stream (restores the good-build precedence).
2. Removed the now-dead hardcoded {mode:'compact_worklog'} argument from all 8 current-view
   live/restore call sites (ui.js x5, sessions.js x3) so intent is explicit and the helpers
   resolve the active mode via chatActivityMode().

Compact_worklog folding, hide_all_activity suppression, the #5700/#5739 timestamp feature,
and #5860 lazy settled-worklog materialization are all unaffected.

Verified with the crown-jewel stream regression gate (private workspace): transparent_stream
goes rowElementSwaps 1105->0 and groupedFramesInTransparent 42->0; hide_all_activity clean.
Independently root-caused + fix-shape-confirmed by Codex adversarial review.

* fix(transparent-stream): also make projection mode active-authoritative in _renderLiveAnchorActivitySceneForStream (Fable advisory — kill latent requestedMode-wins drift)

* test(#5942/#5943): update brittle source-string assertions for removed {mode:'compact_worklog'} arg

test_live_to_final_anchor_visible_order.py asserted the literal presence of the now-removed
hardcoded compact-mode arg (in ensureLiveWorklogShell + the sessions.js restore path). The fix
correctly drops that arg so the helpers resolve the active display mode. Updated both to assert
the mode-free call / active-mode behavior instead of the removed literal. 63/63 in-file pass.

* revert wrapper projection-mode change (preserve #3820 explicit-mode contract)

The follow-up 8f654f0c0 made _renderLiveAnchorActivitySceneForStream projection-mode
active-authoritative, but that broke the #3820 contract (test_issue3820: explicit hide_all
honored when active=compact; hide_all-active always wins). Fable flagged this only as an
advisory/latent cleanup, NOT required — the real fix is the central renderLiveAnchorActivityScene
dispatch + call-site arg removal, both proven by the gate. Reverting the wrapper restores #3820
(109/109 targeted tests pass) with the bug fix fully intact.

* test(#1426): make OpenRouter live-fetch test hermetic by mocking curated fetch

test_openrouter_group_uses_live_fetch_when_available failed deterministically
in any environment where the hermes_cli agent package is installed AND the
network is reachable, e.g.:

    pytest "tests/test_issue1426_openrouter_free_tier_live_fetch.py::\
test_openrouter_group_uses_live_fetch_when_available" -p no:xdist

    assert 'minimax/minimax-m2.5:free' in
        ['anthropic/claude-sonnet-4.6', 'anthropic/claude-fable-5', ...]

Root cause — a gap in the test's mock, not a production regression:

get_available_models() populates the OpenRouter group from TWO live fetches:

  (1) hermes_cli.models.fetch_openrouter_models() — the curated catalog.
      This routes through hermes_cli's open_credentialed_url(), which builds
      its own opener and calls .open(); it does NOT go through the
      module-level urllib.request.urlopen symbol the test monkeypatches.
  (2) a direct urllib.request.urlopen(".../v1/models") free-tier augment —
      this one IS intercepted by the monkeypatch.

The test only patched urllib.request.urlopen, so fetch (1) hit the real
network and returned ~36 live curated models. Those filled the visible picker
cap (15), pushing the mocked free-tier entries (minimax/minimax-m2.5:free,
openrouter/elephant-alpha) into the group's extra_models bucket. The
assertion only inspected the visible `models` bucket, so it failed. In CI
(hermes_cli absent) fetch (1) raised ImportError, the curated base was empty,
the 2-3 mocked entries fit the visible cap, and the test passed — hence the
environment-dependent failure.

Production is correct: free-tier models DO surface in the picker (verified
they land in extra_models). The fix keeps the coverage intent intact:

  - Mock fetch (1) too (as sibling tests already do) so the test is genuinely
    hermetic — matching this module's docstring promise of "without depending
    on real network access" — with a small curated base so live data, not the
    fallback list, is exercised.
  - Assert the free-tier IDs surface across BOTH picker surfaces (`models` +
    `extra_models`), matching the sibling fail-closed and cap tests, so a
    large curated base overflowing the visible cap no longer flips the result.

No production code changed.

* Release exp-v0.52.39: transparent-stream mode-precedence fix (#5946/#5942/#5943) + hermetic OpenRouter live-fetch test (#5947/#1426)

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-11 14:40:59 -07:00
akay64
834ff49816 fix: reset context selection counter on clear and session switch 2026-07-11 11:34:26 +04:00
nesquena-hermes
45fe6b4f10 Merge #5917: clear stale sidebar unread dot on session visit 2026-07-11 06:20:50 +00:00
nesquena-hermes
cb94cecc1a fix(sessions): reset busy/stream flags before the visit-ack repaint (gate)
Gate finding: the idle-session S.busy/S.activeStreamId reset ran AFTER
_acknowledgeSessionVisit(), so the ack's sidebar repaint could inherit the
PREVIOUS session's busy state and manufacture a phantom unread/streaming
indicator on the freshly-loaded idle session. Moved the activeStreamId calc +
idle reset BEFORE the acknowledge call. Co-authored-by: neaucode-bot.
2026-07-11 05:52:56 +00:00
nesquena-hermes
05f6e8c7f4 fix(sessions): guard viewed-count clear in message-load + derive stream state from target session (gate findings)
Co-authored-by: neaucode-bot <neaucode-bot@users.noreply.github.com>
2026-07-11 05:39:19 +00:00
nesquena-hermes
b33c008de2 Merge salvage/5294-mobile-dictation 2026-07-11 05:27:11 +00:00
nesquena-hermes
42fcdb0090 Merge salvage/4750-composer-clear 2026-07-11 05:27:11 +00:00
nesquena-hermes
63d3efb6c4 fix(sessions): guard post-load unread re-ack on active view (gate finding)
Gate found the unconditional post-load _acknowledgeSessionVisit() cleared a
hidden-tab completion's unread marker: a completion landing while
_ensureMessagesLoaded() is in flight AND the tab then goes hidden is correctly
marked unread, but the final re-ack wiped it. Gate the re-ack on
_isSessionActivelyViewedForList(sid) so only an actively-viewed session clears.
Regression test added. Co-authored-by: neaucode-bot.
2026-07-11 05:15:01 +00:00
nesquena-hermes
bd1cdf24c7 Merge #5910: add 'max' reasoning effort level (opt-in, availability-gated) 2026-07-11 04:47:37 +00:00
nesquena-hermes
5d2740cf56 fix(send): clearPending:false + preserve file objects (gate round 2)
Re-gate found 2 issues in the prior fix: (1) [CORE] uploadPendingFiles still
defaulted clearPending=true and re-cleared S.pendingFiles after the await,
wiping any attachment staged during the upload window — pass clearPending:false
since we already detached the snapshot. (2) [SILENT] converting File objects to
name strings broke draft-restore signature matching ({value:name} vs canonical
{name,path,size,type}) — preserve the file objects with [..._submittedFiles].
2026-07-11 04:13:53 +00:00
nesquena-hermes
d6323bfff0 fix(send): snapshot files + clear draft pre-await (gate fixes)
#5912 gate found 2 defects the capture-clear salvage exposed:
- [CORE] uploadPendingFiles() read still-live S.pendingFiles, so a re-entrant/
  interrupt send during the upload window inherited + re-uploaded the first
  send's attachment. Now snapshot _submittedFiles before the await, detach from
  S.pendingFiles, and upload exactly that snapshot.
- [SILENT] the persisted-draft clear ran AFTER the await, cancelling a draft
  typed during the upload window. Moved it (and _composerDraftClearPromise)
  alongside the textarea clear, before any await. Failed-send restore still
  chains off the promise via the pre-await _failedSendFilesSnapshot.
Updated source-assertion tests for the new call shape + ordering.
2026-07-11 03:58:52 +00:00
nesquena-hermes
58af505cc6 fix(sessions): clear stale sidebar unread indicator on session visit (salvage of #4946)
Salvaged from #4946 by @neaucode-bot. Rebuilt fresh on master (repro-confirmed),
without regressing hidden-tab completion state or lineage-parent unread dots.

Co-authored-by: neaucode-bot <neaucode-bot@users.noreply.github.com>
2026-07-11 03:32:26 +00:00
Nathan Esquenazi
762fd01ae2 fix(review): reset mic restart budget on a productive dictation result (#5294)
_micRestartCount bounds the continuity auto-restart loop (_micMaxRestarts=20)
so a stolen audio session can't spin tight. But it only reset on
fresh-start / terminal-end / error -- never on a real onresult. So a long
mobile dictation with 20 natural pauses (each producing speech) hit the cap
mid-session and the mic silently stopped, undercutting the feature's whole
purpose ("keep dictation active through pauses").

Reset _micRestartCount=0 in sr.onresult: a real result proves the restarts
are PRODUCTIVE, not a failure spin. The cap still guards the failure case --
consecutive restarts that yield no speech (onend with no intervening
onresult) keep incrementing and trip the bound.

Regression test seeds the count AT the cap, fires a productive onresult, and
asserts the ensuing pause still restarts (count reset to 0 -> 1). Verified
fail-without / pass-with against boot.js.
2026-07-10 20:17:33 -07:00
nesquena-hermes
54efd6078e feat(workspace): opt-in 'start new chat when switching workspace' (#5473)
Salvage-rebuild of #5473 (@ruizanthony), reworked to OPT-IN per maintainer
decision. New default-OFF setting new_chat_on_workspace_switch: when enabled,
selecting a DIFFERENT workspace starts a fresh chat bound to that workspace
(leaving the current conversation on its original workspace, avoiding stale
cross-workspace context) instead of mutating the current session in place.
Same-workspace selection stays an in-place refresh. Default off preserves the
shipped behavior for everyone.

Wiring: config default + bool key; window._newChatOnWorkspaceSwitch set on
boot + autosave; Settings checkbox (Preferences); gated branch in
switchToWorkspace() that runs before the in-place /api/session/update; 3 i18n
keys across all 15 locales; regression tests (default-off, gated-branch-present,
flag-wiring, checkbox+i18n).

Co-authored-by: ruizanthony <ruizanthony@users.noreply.github.com>
2026-07-11 03:13:48 +00:00
nesquena-hermes
18845fd000 fix(#4732): keep mobile composer-mic dictation active through pauses (salvage of #5294)
Salvaged from #5294 by @brianmmaina. Rebuilt fresh on master with the maintainer gate fix-spec: continuous+restart gated to MOBILE (desktop stays one-shot), wake-lock async race serialized, error-path toasts + abort-loop bound.

Co-authored-by: brianmmaina <brianmmaina@users.noreply.github.com>
2026-07-11 03:05:43 +00:00
nesquena-hermes
55509e98c2 i18n(cs): add share_session/stop_sharing keys for Czech locale parity
Czech (#5546) was added after #5337 authored its share i18n keys, so the
strict cs-parity test failed on the 14 missing share_session/stop_sharing keys.
Added them to the cs block (English fallback values per locale convention).
2026-07-11 02:37:57 +00:00
nesquena-hermes
75153f5b79 fix(share): always-on redaction boundary + rebase/UX fixes (gate findings)
Adversarial security re-gate on the rebased head found 4 issues; fixed:

1. [CORE] api/shares.py public snapshot could leak credentials/paths/tool
   payloads. The sanitizer relied on redact_session_data (gated on the
   user-toggleable api_redact_enabled) — disabling it exposed API keys, a dict
   assistant content was stringified as raw tool data, and session.workspace
   survived in transcript text. Now the per-message sanitizer applies ALWAYS-ON
   credential redaction (_redact_fn_cached, force=True, ignores the setting) +
   scrubs known session/workspace/home paths, and refuses to stringify non-text
   structured content. Title is force-redacted too. Regression test proves the
   boundary holds with api_redact_enabled=False.
2. [SILENT] api/models.py: my rebase wrongly kept anchor_activity_scenes in
   METADATA_FIELDS, moving 250-480KB scene bodies before messages and undoing
   master's metadata fast-path (#5854). Removed; 17 #5854 tests pass again.
3. [SILENT] static/index.html: the Share/StopSharing/ExportHTML button triplet
   was double-applied by the rebase (duplicate IDs → only the first wired).
   Deduped to one each + a count assertion test.
4. tests: fixed the stale Session.load-after-/api/session/new setup (master
   keeps new sessions memory-only until first message).

Also (UX, found in the visual flow sweep): a clipboard-copy failure after a
SUCCESSFUL share showed a red 'Share failed' toast and skipped opening the page.
The copy is now isolated — the share succeeds, the page opens, and the toast
shows the link if the clipboard write fails, never a false failure.

Co-authored-by: MicoRobot.
2026-07-11 02:06:34 +00:00
nesquena-hermes
e061e8534c Rebase #5337 (public read-only conversation sharing) onto master
Rebased 363 commits forward (branch-point 2026-07-05). Applied the PR's code
diff cleanly across 16 files; resolved api/models.py additively (both master's
process_wakeup_pause/anchor_scene_index fields AND the PR's share_token/
share_created_at fields kept in the Session constructor, metadata list, and
to_dict). CI workflow (.github/workflows/tests.yml) intentionally NOT taken from
the stale branch — master owns it (the branch's apparent removal of Playwright/
Office steps was a PAT-workflow-scope revert; rebase restores master's CI).

Co-authored-by: MicoRobot. Concept approved by maintainer (opt-in public share,
2026-07-11). Note: 4 dynamic share tests fail identically on the original
unrebased head (pre-existing test-env Session.load fragility, not rebase-caused);
to be resolved during the security re-gate in a proper sandbox.
2026-07-11 01:39:42 +00:00
nesquena-hermes
67c9277ed4 fix(send): clear composer immediately after capture to prevent stalled-text race (salvage of #4750)
Salvaged from #4750 by @harryazj — pulling ONLY the atomic composer capture-and-clear fix from the larger PR. Prevents a re-entrant/interrupt-mode send from re-reading stale textarea DOM and double-sending.

Co-authored-by: harryazj <harryazj@users.noreply.github.com>
2026-07-11 01:39:19 +00:00
nesquena-hermes
d56123f796 feat: add max reasoning effort level (salvage of #4627)
Salvaged from #4627 by @perejaslav. Core Hermes already supports
reasoning_effort=max; this closes the WebUI parity gap (Anchor-A) — adds
max to VALID_REASONING_EFFORTS + dropdown/chip/reasoning-command, keeps
the degrade ladder for capped models.

Co-authored-by: perejaslav <perejaslav@users.noreply.github.com>
2026-07-11 01:13:36 +00:00
nesquena-hermes
bdb245346d Merge #5739: worklog event timestamps (+ gate fixes) 2026-07-11 00:43:24 +00:00
nesquena-hermes
97fe772fdb fix(worklog): add full-date tooltip + hide event-time under 420px (Fable UX)
Two non-blocking UX improvements from the Fable gate on #5739:
1. Full date+time tooltip (title attr) on the worklog event-time label — the bare
   clock is date-ambiguous when a settled session is reviewed later or a run crosses
   midnight. New _activityFullClockLabel (range-guarded so a bad epoch never yields
   'Invalid Date' in the tooltip) + test.
2. Hide the time label under 420px — its ~50px of fixed chrome squeezes the
   collapsed row's flexible preview toward zero on phones.

Co-authored-by: rodboev.
2026-07-11 00:38:23 +00:00
nesquena-hermes
a86324985e fix(worklog): persist event timestamps through settlement + reject invalid epochs
Two gate findings on the worklog-event-timestamp feature (#5700/#5739):

1. Thinking-row timestamps disappeared after settlement. Reasoning source events
   carry no server timestamp; the live DOM showed a fallback time but the settled
   scene row rebuilt with created_at:null. Now stamp created_at on the source event
   the first time it's seen (messages.js apply path) and carry row.created_at
   through snapshot hydration, so the timestamp survives live->settled.

2. A garbage huge numeric timestamp (e.g. 1e20) passed the finite/>0 checks and
   rendered literal 'Invalid Date'. _timestampSeconds now rejects epochs outside
   JavaScript's valid Date range. Added a regression test.

Co-authored-by: rodboev. Gate findings from adversarial Codex re-gate.
2026-07-11 00:23:48 +00:00
nesquena-hermes
0af0a5fcf0 Merge #5746: final answer only activity display mode 2026-07-10 23:50:28 +00:00
t
8949cad20f Merge #5792: keep media downloads visible in CSV previews
Co-authored-by: ruizanthony <ruizanthony@users.noreply.github.com>
2026-07-10 21:33:41 +00:00
t
78cc5557b0 Merge #5887: focus composer after URL prefill
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-10 18:45:20 +00:00
nesquena-hermes
07b3f411d7 fix: preserve same-session load ownership on redundant reload + guard hint cleanup
Gate review (Codex) reproduced two SILENT regressions in the generation-token
ownership change, both validated against PR head + merge base:

1. sessions.js:1428 — the no-op early-return did NOT fire for a redundant
   same-session load (currentSid===sid, !_loadingSessionId was false because a
   keep-stale refresh was in flight). The redundant loadSession() then bumped
   _loadSessionGeneration and CANCELLED the authoritative in-flight refresh —
   stale rows remained while message_count advanced, so later polling could not
   detect them. Restore the pre-existing behavior: also return when the in-flight
   load is for the SAME sid (!_loadingSessionId || _loadingSessionId===sid).

2. sessions.js:2781 — _ensureMessagesLoaded's finally cleared the
   transcript-width reload hint unconditionally, so a stale same-session response
   collapsed a long session to the default tail window. Guard the cleanup with
   _ownsLoad() so only the owning load clears the hint.

Both fixes retain the PR's cross-session, same-session-rejection, and INFLIGHT
race behavior (47 tests pass incl. the new isolation suite + the 4 edited files).

Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-07-10 16:52:37 +00:00
Hermes Agent
47c3fcf387 Fix stale session load ownership races 2026-07-10 09:26:36 -06:00
Rod Boev
2e39bb8418 fix(#5884): focus composer after URL prefill 2026-07-10 11:04:44 -04:00
nesquena-hermes
de0e6eb452
Release: centralize auxiliary-model task catalog (#5865) (#5870)
Auxiliary-model task definitions were maintained in BOTH Python and JS and could
drift (the retired session_search task lingered in one). Centralize to a single
backend catalog that panels.js renders+saves from exclusively; purge the retired
slot on reset while preserving other custom bindings; old configs still load.

Gate-pass: Codex SAFE (only session_search removed, 11 other tasks + defaults
unchanged, 22 keys match across 15 locales, legacy migrate preserves custom
bindings, executable DOM harness rendered all rows). 53 own + 887 regression green.

Co-authored-by: t <a@b>
Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-07-09 20:31:54 -07:00
nesquena-hermes
803b081506
Release: lazy-render collapsed worklog rows w/ post-process + disclosure preservation (#5860, #5839) (#5866)
* fix(#5839): post-process + restore disclosure on lazily-materialized worklog rows

Gate (live Chromium) found the deferred-render path skipped two things the eager
path does: (1) materialized Markdown rows bypassed postProcessRenderedMessages ->
lost syntax highlight / copy button / mermaid / katex / trees; (2) detail-disclosure
(open tool/thinking card + scroll) was lost across rebuilds because restore ran
before deferred rows existed. _materializeDeferredWorklogRows now rAF-schedules
_postProcessWithAnchorSuppression + re-applies stashed disclosure state; the rebuild
restore pass stashes the captured state on still-deferred groups. +2 regression tests.

* changelog: #5860 lazy-worklog frontend fix (#5839, #4633)

---------

Co-authored-by: t <a@b>
2026-07-09 19:25:11 -07:00
nesquena-hermes
1a64d7d313
Release: fix workspace image previews + downloads under subpath mounts (#5830) (#5856)
_workspaceRouteForPath now resolves app-relative /api/... routes against document.baseURI
so the direct consumers (previewImg.src, media/pdf/html frame src, download href,
window.open) work under a subpath mount instead of 404ing at the server root. Root-mount
deployment unaffected (identical final URLs); api() callers not double-prefixed.

Gate-pass: Codex SAFE (no root-mount regression, all 6 consumers verified), 825 workspace
regression tests + browser-smoke green. Nathan-approved.

Co-authored-by: t <a@b>
Co-authored-by: tomtong2015 <tomtong2015@users.noreply.github.com>
2026-07-09 14:38:53 -07:00
nesquena-hermes
46720a39dd
Release: Copy conversation link copies an openable URL (#5478) (#5851)
Was copying the internal session:// reference (not browser-openable); now copies
an absolute origin + /session/<id> URL (verified single-origin, no doubling —
_sessionUrlForSid returns a relative path, the caller prepends origin once),
tracking params stripped. Product/UX parity with major chat apps (Anchor C).

Nathan-approved. 17 copy-link regression tests green.

Co-authored-by: t <a@b>
Co-authored-by: djsavvy <djsavvy@users.noreply.github.com>
2026-07-09 13:24:45 -07:00
nesquena-hermes
8cc2d6f881
Release: model-picker Selected badge + Transparent Stream dupe-prefix suppression (#5757, #5758) (#5850)
Two visible render PRs, gate-passed (deep-review round), Nathan-approved to ship.

- #5757 (rodboev): mark active model with a Selected badge + open its provider group (#5748).
  Screenshot-verified (badge clear + correctly placed on active row).
- #5758 (rodboev): suppress the live-token prose row that is a strict prefix of the final
  answer (#5749). Provenance-gated (live-prose:* token rows after last tool row only) so it
  can't eat legitimate narration; reproduction test proves the dupe is gone.

Codex SAFE + full regression green (1576 render tests). 18 own tests pass together.

Co-authored-by: t <a@b>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-09 12:51:18 -07:00
nesquena-hermes
2b457f1646
Release: cap StreamChannel offline replay buffer (#5788) (#5849)
Bounded deque(maxlen) drop-oldest so a disconnect-without-cancel can't grow the
per-turn offline replay buffer unbounded (per-abandoned-turn OOM risk, #4633 family).
Dropped frames recovered via run-journal last_event_id replay path.

Deep-review gate: Codex SAFE (journal-recovery verified end-to-end), 20 own + 1106
regression tests green. Crown-jewel streaming path.

Co-authored-by: t <a@b>
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-07-09 12:26:05 -07:00
nesquena-hermes
7a1a122e6e
Release Batch C: hidden-tab dashboard poll + SSE write-deadline + rate-limit map bound (#5840, #5841, #5842) (#5847)
Phase 2 low-risk backend/perf batch (ai-ag2026 reliability cluster). Each byte-verified
vs PR head, Codex SAFE, 33 own + 720 regression tests pass, disjoint routes.py regions.

- #5840: skip dashboard status poll while tab hidden (perf; forced polls unaffected)
- #5841: arm write deadline on session-events invalidation SSE (stuck-client teardown)
- #5842: bound client rate-limit maps by sweeping stale keys (long-uptime memory)

Co-authored-by: t <a@b>
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-07-09 10:38:47 -07:00
nesquena-hermes
1e275d04ed
Release: queue gateway-owned steer fallback (#5823) (#5845)
Steering a live gateway-owned run had no process-local agent in SESSION_AGENT_CACHE,
so it errored with "No agent cached" and dropped the typed text. Now detects the live
gateway ACTIVE_RUNS stream and queues the steer as the next turn (model/provider/profile
snapshotted first), updates the queue badge, clears the draft, shows a toast — without
cancelling the stream. Gateway sibling of the shipped #5597 fallback.

Codex SAFE + Fable SHIP-UX; byte-identical to PR head; 66 steer/busy-input tests pass;
Nathan visual-approved (desktop + mobile screenshots).

Co-authored-by: t <a@b>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-09 10:04:21 -07:00
Isla-Liu
db082e8d92 fix: localize process wakeup label 2026-07-09 04:36:06 +00:00
ai-ag2026
0c132ba103 Fix login connectivity retry cleanup: clearInterval not clearTimeout
The connectivity probe schedules retries with setInterval(probe, 3000)
but tore the timer down with clearTimeout(retryTimer). Browsers share a
single numeric handle space between the timer families, so clearing an
interval handle via clearTimeout only worked by coincidence and is
semantically wrong. Match the teardown to how the timer was created.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 03:41:42 +00:00
Isla-Liu
bef8fbd3b6 fix: align wakeup notice with message rail 2026-07-09 03:21:44 +00:00
Isla-Liu
d1345e6ed0 fix: handle process wakeup attachments 2026-07-09 03:21:44 +00:00
Isla-Liu
fa04044e7c fix:[WebUI] 修正 background wakeup 訊息渲染邊界 2026-07-09 03:21:44 +00:00
Hermes Agent
69d0fc73c9 fix: derive service tier support from agent fast-mode metadata 2026-07-09 03:16:53 +00:00
Rod Boev
e592498664 fix(chat): keep live reasoning on one renderer path (#5720) 2026-07-09 03:12:37 +00:00
Rod Boev
837a552746 fix(chat): contain streaming code block layout (#5760) 2026-07-09 03:08:19 +00:00
sheldon-im
67f8414656 Keep closed workspace dropdown out of SR navigation 2026-07-09 02:40:36 +00:00