Commit Graph
7031 Commits
Author SHA1 Message Date
Nathan Esquenazi da00a5ae8c fix(review): unblock CI on the stale-unread-dot salvage (#4946)
Two CI-red issues, both fixed:

1. Lint (F401): the new tests/test_session_unread_dot_on_visit.py imported
   `re` but never used it. Dropped the import.

2. Test hang/timeout: loadSession() now routes its viewed-count/unread clear
   through the new _acknowledgeSessionVisit() helper, but the PRE-EXISTING
   tests/test_cross_session_message_load_isolation.py harness (which extracts
   loadSession and stubs its deps) didn't define it — so the extracted
   loadSession broke and the node driver hung to the 60s pytest-timeout.
   Stubbed _acknowledgeSessionVisit + _sessionVisitHasUnreadState to no-ops
   there, mirroring the pre-existing _setSessionViewedCount /
   _clearSessionCompletionUnread stubs they replaced. That harness covers
   cross-session load ordering + stale-reject, not unread-dot state, so a
   no-op stub preserves its intent; the real helper is exercised by the PR's
   own test_session_unread_dot_on_visit.py.

Verified: ruff forward gate clean; the timeout test passes in ~1s; the PR's
unread-dot suite 7/7.
2026-07-10 21:05:47 -07:00
nesquena-hermesandneaucode-bot 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
nesquena-hermes 101421ff67 Merge pull request #5914 from nesquena/release/stage-5337
Release exp-v0.52.31: public read-only conversation sharing (#5337)
2026-07-10 19:50:34 -07:00
nesquena-hermes 8c9d3635e2 lint: fix B904 raise-from in share session-resolve + drop unused importlib
CI ruff gate flagged 3 new violations: two api/routes.py KeyError re-raises in an
except-KeyError block (add 'from None') + an unused importlib import in the share
security test. All trivial.
2026-07-11 02:42:59 +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 82d3bfd51a Release exp: public read-only conversation sharing (#5337). Thanks @MicoRobot. 2026-07-11 02:22:13 +00:00
nesquena-hermes 455ab12dc1 Merge #5337: public read-only conversation sharing 2026-07-11 02:21:41 +00:00
nesquena-hermes a1312acc5d fix(share): reject dict-valued text-blocks and dict titles (re-gate edge)
The re-gate found the always-on sanitizer still had two str()-on-dict paths:
a list text-block whose 'text' is itself a dict (via /api/session/import), and
a dict-valued session title. Both are now type-checked to str; a dict title
falls back to 'Untitled'. Regression cases added. Co-authored-by: MicoRobot.
2026-07-11 02:14:07 +00:00
nesquena-hermesandMicoRobot. 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 91804b30fc Merge pull request #5908 from nesquena/release/stage-5739
Release exp-v0.52.30: worklog event timestamps (#5739)
2026-07-10 17:56:34 -07:00
nesquena-hermes 40db97733e Release exp: worklog event timestamps (#5739). Thanks @rodboev. 2026-07-11 00:43:53 +00:00
nesquena-hermes bdb245346d Merge #5739: worklog event timestamps (+ gate fixes) 2026-07-11 00:43:24 +00:00
nesquena-hermesandrodboev. 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-hermesandrodboev. Gate findings from adversarial Codex re-gate. 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 bc3bc31172 Merge pull request #5906 from nesquena/release/stage-5746
Release exp-v0.52.29: final answer only activity display mode (#5746)
2026-07-10 17:07:51 -07:00
nesquena-hermes d07e41d572 Release exp: final answer only activity display mode (#5746)
Stamp CHANGELOG for the #5746 ship. Thanks @rodboev.
2026-07-10 23:51:52 +00:00
nesquena-hermes 0af0a5fcf0 Merge #5746: final answer only activity display mode 2026-07-10 23:50:28 +00:00
nesquena-hermes 62a185e453 Merge pull request #5904 from nesquena/release/stage-5792
Release: CSV media download affordance (#5792)
2026-07-10 14:52:25 -07:00
t 0e2fe7c3eb changelog: #5792 restore CSV media download affordance 2026-07-10 21:34:04 +00:00
tandruizanthony 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
nesquena-hermes 63fe02e209 Merge pull request #5901 from nesquena/release/stage-5677
Release: resumable session SSE stream (#5677, #4812)
2026-07-10 13:56:20 -07:00
tandrodboev 3188f88599 fix(#4812): break live SSE loop on a terminal event even when deduped at cutoff
Codex round-3 finding #2 [CORE]: when reconciliation replayed the active run's
terminal event at the subscribe cutoff, the live queue's copy hit the cutoff/dedup
'continue' BEFORE the terminal 'break' check — so the handler never broke, stayed
blocked on the dead run's queue, and missed subsequent session runs. Check the
terminal event type before the already-delivered continue: a terminal always ends
the loop (after emitting if not already sent). Regression test asserts the handler
unsubscribes (doesn't hang) when the terminal is deduped. 24/24 SSE tests green.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-10 20:47:06 +00:00
tandrodboev 2b22690fa4 fix(#4812): capture resume cursor + journal baseline before SSE headers commit
Codex round-3 finding #1 [SILENT]: baselining after end_sse_headers() left a window
where a run completing between header commit and baseline was absorbed into the
baseline + silently lost. Move resume-id parse + fingerprint baseline before
send_response/headers (both side-effect-free: header read + stat-only fingerprint).

Codex round-3 also flagged finding #2 [CORE] (reconciliation terminal-event-at-cutoff
dedup keeps the handler attached to a dead run queue) — NOT addressed here; escalating
to Nathan per Codex-depth diminishing-returns (3rd round into the same SSE seam class).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-10 20:44:10 +00:00
tandrodboev 3c642e08f1 fix(#4812): hoist idle SSE fingerprint baseline to earliest point (before preliminary lookup + initial replay)
Codex re-gate found the baseline was still after the preliminary
_active_run_stream_for_session() lookup + the initial cursor read_session_run_events()
replay — a run completing during either window folded into the baseline and was
silently missed. Move the fingerprint baseline to immediately after resume_event_id
parsing, before both operations, so no pre-baseline completion window remains. Remove
the now-redundant later baseline. 23/23 SSE tests green.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-10 20:36:22 +00:00
tandrodboev 3ac19a5518 fix(#4812): capture idle SSE fingerprint baseline BEFORE first attach (TOCTOU)
Final-gate Codex caught a TOCTOU race in the prior idle-seam fix: the fingerprint
baseline was captured AFTER the first attach_active_stream() lookup, so a run
completing DURING that first attach folded into the baseline and was silently
missed (only : keepalive emitted, no snapshot). Move the baseline capture before
the first attach so any completion from that point on is detected. Add an ordering
regression test (baseline fp precedes the idle-loop attach + advance still emits
one snapshot). 23/23 SSE tests green.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-10 20:24:47 +00:00
tandrodboev 2829843a2a fix(#4812): re-sync idle no-cursor SSE subscriber on run completing during keepalive wait
Fable gate found a seam Codex's 3 boundary findings didn't cover: an idle no-cursor
subscriber to /api/sessions/{id}/events misses a run that starts AND finishes inside a
single keepalive tick — the wait loop only looked for a live in-memory STREAMS entry to
attach, never noticing the journal advanced, so the run's transcript was silently missing
until manual refresh.

Add a cheap bounded session_journal_fingerprint() (file-count/max-mtime/total-size from
stat only, never parses bodies) and, inside the idle attach-wait loop, emit a session_snapshot
re-sync whenever the fingerprint advances with no live stream to attach — the same honest
recovery contract used for a failed reconciliation. Re-baselines after each re-sync so a quiet
idle connection never spams snapshots. 2 regression tests; 22/22 SSE tests green.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-10 20:15:49 +00:00
tandrodboev 9600e1546a Merge #5677: resumable session SSE stream (#4812)
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-10 20:12:27 +00:00
nesquena-hermes 157714a1b7 Merge pull request #5898 from nesquena/release/stage-5732
Release: pause credential-exhausted process wakeups (#5732, #3929)
2026-07-10 12:37:06 -07:00
t d967131056 fix(#3929 ux): gate wakeup-pause disclosure on actual pause + cover both error paths
Codex re-gate caught 2 defects in the prior UX commit:
1. streaming.py:8921 — the disclosure fired for ANY process_wakeup failure (e.g.
   rate-limit) even when no pause was recorded -> falsely claimed 'retries paused'
   while process_wakeup_pause stayed empty. Now gated on the record_* return value.
2. streaming.py:10130 — the raised-exception credential_pool_empty path (exercised
   by the existing agent test) recorded the pause but omitted the disclosure. Added
   the same conditional disclosure there.
Both paths now keep _error_payload['hint'] in sync with the persisted bubble, and
the disclosure appears only when a pause was actually recorded. 69 tests green.
2026-07-10 19:28:06 +00:00
tandfranksong2702 c26bc21194 ux(#3929): disclose wakeup pause in error card + changelog
Fable UX-gate follow-up on #5732: after the first credential-exhausted error,
automatic wakeups are silently suppressed. Disclose it in the wakeup-source
error card ('Automatic retries for this conversation are paused until you send
a message, switch the model/provider, or fix the credentials.') so the silence
reads as intentional, not a stuck agent — Gmail/Dropbox account-needs-attention
convention. Scoped to the process_wakeup + credential_pool_empty seam only.

Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-07-10 19:16:48 +00:00
tandfranksong2702 669ddde4b0 Merge #5732: pause credential-exhausted process wakeups (#3929)
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-07-10 19:10:49 +00:00
Frank Song 3f0eebcbd8 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-11 02:59:26 +08:00
nesquena-hermes b6ebb72960 Merge pull request #5897 from nesquena/release/stage-5887
Release: focus composer after URL prefill (#5887)
2026-07-10 11:50:21 -07:00
t d690eb0cdb changelog: #5887 focus composer after URL deep-link prefill 2026-07-10 18:45:38 +00:00
tandrodboev 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
Frank Song 3c95b3a9d5 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-11 02:07:37 +08:00
nesquena-hermes e9caa7ba95 Merge pull request #5893 from nesquena/release/stage-5224
Release: stale-session-load pane isolation (#5889) + fail-closed OpenRouter pricing (#5882)
2026-07-10 11:02:47 -07:00
t 7f613d3c76 changelog: #5889 stale-session-load pane isolation + #5882 fail-closed OpenRouter pricing 2026-07-10 17:54:21 +00:00
tandstarship-s 4a4a6d4927 Merge #5889: fix stale session loads contaminating the active pane
Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-07-10 17:53:55 +00:00
tandigorroncevic b6c7a36750 Merge #5882: fail closed on incomplete OpenRouter pricing
Co-authored-by: igorroncevic <igorroncevic@users.noreply.github.com>
2026-07-10 17:53:55 +00:00
Hermes Agent 635d051e84 test: update session ownership assertions 2026-07-10 11:00:49 -06:00
nesquena-hermesandigorroncevic 89408b7b2d test: skip fail-closed pricing test on openrouter-group pollution
The pricing3-null case failed on one CI shard with StopIteration: pytest-shard's
partitioning interleaves this test with modules that mutate config.cfg away from
the openrouter active provider, so get_available_models() omits the openrouter
group and next(...) raised. Make the group lookup resilient (next(..., None) +
pytest.skip) — the exact defensive guard the sibling live-fetch tests in this
file already use for the same pollution. Passes both with and without hermes_cli.

Co-authored-by: igorroncevic <igorroncevic@users.noreply.github.com>
2026-07-10 16:54:26 +00:00
nesquena-hermesandstarship-s 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
nesquena-hermesandigorroncevic 2dc6530f2b test: guard optional hermes_cli import in fail-closed pricing test
The new test_openrouter_free_tier_pricing_fails_closed did an unguarded
'from hermes_cli import models', but hermes_cli (the agent package) is an
optional dependency not installed in the WebUI CI test environment, so the
test failed with ModuleNotFoundError on 4-5 shards (master green, PR head red).

Wrap the import in the same try/except guard the sibling tests in this file
already use — when hermes_cli is present we force its live-fetch to empty so
the fail-closed static path is exercised; when absent, that static path is
already the only one and the api/config.py fix under test still runs via the
mocked urlopen. Verified: 10/10 with hermes_cli, 5/5 with it blocked.

Co-authored-by: igorroncevic <igorroncevic@users.noreply.github.com>
2026-07-10 16:49:18 +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
Rod Boev 4cbc70c778 fix(#4812): satisfy replay boundary lint gate 2026-07-10 10:51:34 -04:00
Rod Boev 956c276362 fix(#4812): close session SSE replay recovery gaps 2026-07-10 10:46:47 -04:00
Frank Song 4c2400261a Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 21:21:47 +08:00