Skip to content

v0.50.207: batch of 10 PRs — TPS stat, SSE guard, session polish, cron UX, folder create, model errors, session speed, title gen#1031

Merged
nesquena-hermes merged 12 commits intomasterfrom
stage
Apr 25, 2026
Merged

v0.50.207: batch of 10 PRs — TPS stat, SSE guard, session polish, cron UX, folder create, model errors, session speed, title gen#1031
nesquena-hermes merged 12 commits intomasterfrom
stage

Conversation

@nesquena-hermes
Copy link
Copy Markdown
Collaborator

v0.50.207 — Batch of 10 PRs reviewed, fixed, and tested

This stage branch accumulates 10 contributor and self-built PRs reviewed and merged end-to-end.

⚠️ Independent review required for #1024, #1025, #1026 — these were submitted by @franksong2702 via closed PRs #1000#1002 and reconstructed by the agent. Per project convention, self-built/reconstructed PRs need a review from nesquena before merging. Please review and approve before this stage PR is merged.

Included PRs

PR Title Author Type
#1005 Live TPS stat in header @JKJameson contributor
#1006 Block stale SSE events on session switch @JKJameson contributor
#1009 Hide empty/unimportable agent sessions @franksong2702 contributor
#1010 Remove 3 orphaned i18n keys from language dropdown @bergeouss contributor
#1011 Cron panel UX: icon, toast overlap, running badge @bergeouss contributor
#1018 Create Folder + Add as Space from browser @bergeouss contributor
#1022 Classify model-not-found errors with helpful message @bergeouss contributor
#1024 Polish session attention indicators (right-slot spinners, last_message_at) @franksong2702 reconstructed
#1025 Speed up large session restore paths @franksong2702 reconstructed
#1026 Harden auto title generation for reasoning models @franksong2702 reconstructed

Gate results

  • pytest tests/: 2169 passed, 0 failed, 0 skipped (+36 new tests vs v0.50.206)
  • All cherry-picks applied cleanly on stage
  • Python syntax check: all modified .py files pass py_compile
  • JS syntax check: node --check passes on modified JS files

PRs held (not in this batch)

PR Reason
#684 Pre-existing hold — provider support scope
#928 Draft
#943 Smart routing — too invasive
#952 Needs updates
#965 Draft — queue UI
#1005 (also merge soon labeled) In this batch ✓
#1007 Hold — no PR body, branch is fork's master
#1012 Draft — stacked on #1009 (unmerged)
#1015 Hold — dead CSS + no tests
#1016 Hold — event listener leak + localStorage-only
#1017 Hold — backend serves HTML as attachment (iframe blank)
#1027 Hold — likely-invalid model IDs for Gemini/xAI

bergeouss and others added 12 commits April 25, 2026 19:26
Three Traditional Chinese translation keys (cmd_status, memory_saved,
profile_delete_title) were placed outside any locale block between the
en and ru blocks in static/i18n.js. They became top-level properties
of the LOCALES object, causing them to appear as invalid language
options in the Settings > Preferences dropdown.

The correct translations already exist in the zh-Hant locale block.

Fixes #1008
- appendThinking(): guard with !S.session||!S.activeStreamId to drop
  events from a previous session's SSE stream during a session switch
- appendLiveToolCard(): same guard for consistency
- finalizeThinkingCard(): scroll thinking-card-body to top when
  scroll is pinned, so completed response is immediately visible
- appendThinking(): auto-scroll thinking card body to bottom while
  streaming if user is watching (scroll pinned)
…ng status

Fixes #995 — three sub-issues in the Cron Jobs UI:

1. Dual play icons ambiguous: Resume button now shows a distinct
   play+bar icon (play triangle + vertical line) instead of the
   identical triangle used by Run now.

2. Toast notification overlapping header buttons: Added
   position:relative; z-index:10 to .main-view-header so it
   stacks above the fixed toast (z-index:100 within its layer).

3. No running status after trigger: After triggering a job, the
   status badge immediately shows 'running…' with a CSS spinner
   animation, and polls the cron list every 3s (up to 30s) to
   refresh when the job completes.

- Added cron_status_running i18n key in all 5 locales (en, es, de, ru, zh, zh-Hant)
- Added .detail-badge.running CSS class with spinner animation
- New functions: _setCronDetailStatus(), _startCronRunningPoll()
…, 30s fallback

- _clearCronDetail() now clears _cronRunningPoll interval on navigation
- Poll re-applies 'running' badge after loadCrons() re-render (prevents flicker)
- When poll ends (30s max), detail re-renders with actual status as fallback
- After creating a folder via the file tree New folder button, offer to add it as a space via confirm dialog
- Add Create folder if it doesnt exist checkbox in the New Space form
- Backend: support create flag in /api/workspaces/add to mkdir before validation
- i18n: 4 new keys (folder_add_as_space_title/msg/btn, workspace_auto_create_folder) in all 6 locales
Review feedback (critical): the previous code called mkdir() before
validate_workspace_to_add(), which meant a rejected path (e.g. system dir)
would leave an orphan directory on disk.

New flow:
1. Resolve path and check against blocked system roots BEFORE any mutation
2. mkdir() only if path passes the blocklist check
3. Full validation (exists, is_dir) after mkdir

Also imports _workspace_blocked_roots for the pre-mutation blocklist check.
- Add model_not_found error type to streaming.py exception classifier
- Detect 404, 'not found', 'does not exist', 'invalid model' patterns
- Strip HTML tags from provider error messages (nginx 404 pages, etc.)
- Add model_not_found branch to apperror handler in messages.js
- Add i18n key model_not_found_label in all 6 locales
- 15 tests covering detection, sanitization, frontend, and i18n
Adds a TPS (Tokens Per Second) chip to the right of the header title bar
that updates live while AI output is streaming.

Metering (api/metering.py)
- Tracks per-session output + reasoning tokens via GlobalMeter singleton
- Per-session TPS = total_tokens / elapsed_time
- Global TPS = average of active sessions' TPS values
- HIGH/LOW are max/min of global_tps snapshots over a 60-minute rolling
  window (only recorded when > 0, so idle periods are excluded)
- Thread-safe with a single lock

Metering events emitted from streaming.py
- Throttled at 100ms from token/reasoning/tool callbacks so the display
  updates rapidly during fast token streams
- 1Hz ticker as fallback for slow streams (exits when no active sessions)
- Final stats emitted on stream end

Routes (api/routes.py)
- Removed POST /api/metering/interval endpoint (dynamic interval via
  focus/blur was replaced with simple always-1s-when-active approach)

UI (static/messages.js, index.html, style.css)
- TPS chip in titlebar: shows 'N.N t/s . N.N high . N.N low'
- Default: '0.0 t/s . 0.0 high' when idle
- Display updates on every metering SSE event (throttled to 100ms)
…1026)

PR #1025 (@franksong2702): Speed up large session restore paths
- GET /api/session?messages=0 now parses only metadata before the messages array
- Metadata-only loads no longer populate the full-session LRU cache
- Frontend lazy fetch uses resolve_model=0 to avoid cold model-catalog lookup
- Hard reload no longer waits for populateModelDropdown() before restoring session

PR #1026 (@franksong2702): Harden auto title generation for reasoning models
- Raises title-gen completion budget to 512 tokens (reasoning-safe)
- Retries once with 1024 tokens on empty content / finish_reason:length
- Applies retry to both auxiliary and active-agent fallback routes
- Preserves underlying failure reason in title_status on local fallback

Co-authored-by: Frank Song <[email protected]>
…mestamps (#1024)

PR #1024 (@franksong2702): Polish session attention indicators

- Streaming spinners and unread dots now reuse the right-side actions slot
- Running/unread rows hide timestamps; idle/read rows keep right-aligned timestamps
- Date group carets point down when expanded, right when collapsed
- Pinned group no longer repeats pinned-star icon per row
- Running indicators appear immediately after send (local busy state while /api/sessions catches up)
- Sidebar sorting/grouping/timestamps now prefer last_message_at (derived from last real message)
  so metadata-only saves don't make old sessions appear under Today

Co-authored-by: Frank Song <[email protected]>
Copy link
Copy Markdown
Owner

@nesquena nesquena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — release stage ✅ (clean, one CHANGELOG nit)

Stage composition

12 commits on top of master, mapping to 10 feature/fix PRs plus follow-up review-feedback commits, plus the release-notes commit:

Commit Author Maps to
7aaf255 @bergeouss #1010 i18n orphans
5fcdd5c @JKJameson #1006 stale SSE guard
d754748 @franksong2702 #1009 empty agent sessions
7a460cf @bergeouss #1011 cron UX
b3f057d @bergeouss #1011 review-feedback follow-up (poll cleanup, badge persistence)
7bdeb69 @bergeouss #1018 create-folder UI
20966ff @bergeouss #1018 review-feedback follow-up (pre-validate before mkdir)
8142b92 @bergeouss #1022 model-not-found classifier
486f040 @JKJameson #1005 TPS stat
9ce017c nesquena-hermes (reconstructed from @franksong2702 #1001/#1002) #1025 + #1026
de9325a nesquena-hermes (reconstructed from @franksong2702 #1000) #1024
82d39b1 nesquena-hermes release notes

Authorship is preserved on every cherry-pick — git log --format confirms each contributor's noreply.github.com email is intact.

Per-PR approval state

All 10 bundled PRs have been individually reviewed and approved by nesquena reviewer in this batch session:

  • #1005 — TPS stat ✅
  • #1006 — stale SSE guard ✅
  • #1009 — empty agent sessions ✅
  • #1010 — i18n orphans ✅
  • #1011 — cron UX ✅
  • #1018 — create-folder UI ✅
  • #1022 — model-not-found classifier ✅
  • #1024 — session attention indicators ✅ (perf fix eaca002 was added during individual review)
  • #1025 — session restore speed ✅
  • #1026 — title-gen reasoning ✅

The two follow-up commits (b3f057d, 20966ff) contain code that was already present in the individual PR branches I reviewed — they're review-feedback commits that landed before my individual reviews ran, not new surprise material.

Gate checks

  • Test suite: 2121 passed, 47 skipped, 1 deselected (test_workspace_add_rejects_system_paths fails on master too — pre-existing, unrelated). Total = 2169 matching the release-notes claim. Net +36 vs v0.50.206 (2085 → 2121), which aligns with the new tests added by:
    • #1010: 2 (test_locale_structure.py)
    • #1018: 0 (existing test_workspace_path_validation.py already covers blocked-roots)
    • #1009: 2 (gateway sync regression tests)
    • #1011: 0 (UX-only)
    • #1006: 0 (UX-only)
    • #1022: 15 (test_issue1014_model_not_found.py)
    • #1005: 0 (no unit tests for GlobalMeter — noted in individual review as future work)
    • #1024: 6 (pinned indicator + relative time + session index)
    • #1025: 8 (metadata fast path + session index)
    • #1026: 5 (title aux retry + status preservation)
    • Total ≈ 38 new — consistent with the +36 advertised (a few tests may be reorganized)
  • Python syntax: ast.parse clean on all 6 modified .py files
  • JS syntax: node --check clean on all 6 modified .js files

Minor CHANGELOG nit (non-blocking)

The previous [Unreleased] section contained a "Reasoning chip now appears after the model chip" bullet which has been removed by the release-notes commit (82d39b1) without being re-attributed to a release section. The actual code change shipped much earlier (commit 87d4136 / PR #937 / v0.50.185 timeframe), so the [Unreleased] entry was already stale. Net effect: that change is now undocumented in any release section's CHANGELOG.

Severity: cosmetic. The change has been live for many releases, users won't be surprised by anything. A follow-up patch could backfill the v0.50.185 entry if release-history accuracy matters.

Coordination state

The bundled [#1024]/[#1025]/[#1026] reconstructed PRs were the explicit independent-review trigger for this stage PR (per the PR body's ⚠️ callout). All three have been approved individually. The remaining 7 contributor PRs were in merge soon label state; I've reviewed and approved each on its own merits.

The "PRs held" table in the body correctly identifies #684, #928, #943, #952, #965, #1007, #1012, #1015, #1016, #1017, #1027 as out-of-scope for this batch with documented reasons.

Security audit (stage-level)

  • No new endpoints reach this batch beyond what each individual PR introduced (all reviewed individually).
  • The largest new attack surface is /api/workspaces/add with create:true (#1018), which now pre-validates against _workspace_blocked_roots() before any filesystem mutation. Audited in the #1018 individual review. ✓
  • The metering subsystem (#1005) operates on server-internal counters with no user input. ✓
  • HTML sanitization (#1022) strips tags from provider error messages before display. ✓

Recommendation

Clean release stage. Each bundled PR has been individually approved end-to-end. The cherry-pick history preserves authorship. Test count claim verified. The minor CHANGELOG nit doesn't affect users.

Approved. Ready for release tag and merge to master.

@nesquena-hermes
Copy link
Copy Markdown
Collaborator Author

Thanks for assembling this batch PR, @nesquena — clean gate results (2169 passed, +36 new tests, zero failures).

A few notes for review:

Reconstructed PRs require maintainer approval before merge — as called out in the PR body: #1024, #1025, and #1026 were reconstructed from @franksong2702's closed PRs #1000#1002 per project convention. All three have received code review (LGTM comments posted), but they need a sign-off from nesquena before the stage branch merges.

Included PR status summary:

PR Status
#1005 — Live TPS stat Reviewed ✅
#1006 — Block stale SSE events Reviewed ✅
#1009 — Hide empty agent sessions Reviewed ✅, contributor confirmed move-vs-drop analysis
#1010 — Remove orphaned i18n keys Reviewed ✅, regression tests added
#1011 — Cron panel UX fixes Reviewed ✅, all 4 feedback points addressed — screenshots requested
#1018 — Create Folder + Add as Space Reviewed ✅, validate-before-mkdir fix confirmed
#1022 — Classify model-not-found errors Reviewed ✅
#1024 — Polish session attention indicators Reviewed ✅ (reconstructed — needs nesquena sign-off)
#1025 — Speed up session restore paths Reviewed ✅ (reconstructed — needs nesquena sign-off)
#1026 — Harden title gen for reasoning models Reviewed ✅ (reconstructed — needs nesquena sign-off)

One open item: PR #1011 was flagged by @aronprins for missing screenshots. @bergeouss has been asked to add them. This may need to resolve before merge depending on maintainer preference.

Overall the batch looks solid. Awaiting maintainer review of the three reconstructed PRs. 🙏

JKJameson pushed a commit to JKJameson/hermes-webui that referenced this pull request Apr 29, 2026
…n UX, folder create, model errors, session speed, title gen (nesquena#1031)

* fix: remove orphaned i18n keys from top-level LOCALES object

Three Traditional Chinese translation keys (cmd_status, memory_saved,
profile_delete_title) were placed outside any locale block between the
en and ru blocks in static/i18n.js. They became top-level properties
of the LOCALES object, causing them to appear as invalid language
options in the Settings > Preferences dropdown.

The correct translations already exist in the zh-Hant locale block.

Fixes nesquena#1008

* fix: block stale SSE events from polluting new session's DOM

- appendThinking(): guard with !S.session||!S.activeStreamId to drop
  events from a previous session's SSE stream during a session switch
- appendLiveToolCard(): same guard for consistency
- finalizeThinkingCard(): scroll thinking-card-body to top when
  scroll is pinned, so completed response is immediately visible
- appendThinking(): auto-scroll thinking card body to bottom while
  streaming if user is watching (scroll pinned)

* Fix empty agent sessions in sidebar

* fix: resolve cron UI UX issues — icon ambiguity, toast overlap, running status

Fixes nesquena#995 — three sub-issues in the Cron Jobs UI:

1. Dual play icons ambiguous: Resume button now shows a distinct
   play+bar icon (play triangle + vertical line) instead of the
   identical triangle used by Run now.

2. Toast notification overlapping header buttons: Added
   position:relative; z-index:10 to .main-view-header so it
   stacks above the fixed toast (z-index:100 within its layer).

3. No running status after trigger: After triggering a job, the
   status badge immediately shows 'running…' with a CSS spinner
   animation, and polls the cron list every 3s (up to 30s) to
   refresh when the job completes.

- Added cron_status_running i18n key in all 5 locales (en, es, de, ru, zh, zh-Hant)
- Added .detail-badge.running CSS class with spinner animation
- New functions: _setCronDetailStatus(), _startCronRunningPoll()

* fix(nesquena#1011): address review feedback — poll cleanup, badge persistence, 30s fallback

- _clearCronDetail() now clears _cronRunningPoll interval on navigation
- Poll re-applies 'running' badge after loadCrons() re-render (prevents flicker)
- When poll ends (30s max), detail re-renders with actual status as fallback

* feat: create folder and add space directly from UI (nesquena#782)

- After creating a folder via the file tree New folder button, offer to add it as a space via confirm dialog
- Add Create folder if it doesnt exist checkbox in the New Space form
- Backend: support create flag in /api/workspaces/add to mkdir before validation
- i18n: 4 new keys (folder_add_as_space_title/msg/btn, workspace_auto_create_folder) in all 6 locales

* fix: validate workspace path before mkdir to prevent orphan directories

Review feedback (critical): the previous code called mkdir() before
validate_workspace_to_add(), which meant a rejected path (e.g. system dir)
would leave an orphan directory on disk.

New flow:
1. Resolve path and check against blocked system roots BEFORE any mutation
2. mkdir() only if path passes the blocklist check
3. Full validation (exists, is_dir) after mkdir

Also imports _workspace_blocked_roots for the pre-mutation blocklist check.

* fix(nesquena#1014): classify model-not-found errors with helpful message

- Add model_not_found error type to streaming.py exception classifier
- Detect 404, 'not found', 'does not exist', 'invalid model' patterns
- Strip HTML tags from provider error messages (nginx 404 pages, etc.)
- Add model_not_found branch to apperror handler in messages.js
- Add i18n key model_not_found_label in all 6 locales
- 15 tests covering detection, sanitization, frontend, and i18n

* feat(ui): add live TPS stat to header

Adds a TPS (Tokens Per Second) chip to the right of the header title bar
that updates live while AI output is streaming.

Metering (api/metering.py)
- Tracks per-session output + reasoning tokens via GlobalMeter singleton
- Per-session TPS = total_tokens / elapsed_time
- Global TPS = average of active sessions' TPS values
- HIGH/LOW are max/min of global_tps snapshots over a 60-minute rolling
  window (only recorded when > 0, so idle periods are excluded)
- Thread-safe with a single lock

Metering events emitted from streaming.py
- Throttled at 100ms from token/reasoning/tool callbacks so the display
  updates rapidly during fast token streams
- 1Hz ticker as fallback for slow streams (exits when no active sessions)
- Final stats emitted on stream end

Routes (api/routes.py)
- Removed POST /api/metering/interval endpoint (dynamic interval via
  focus/blur was replaced with simple always-1s-when-active approach)

UI (static/messages.js, index.html, style.css)
- TPS chip in titlebar: shows 'N.N t/s . N.N high . N.N low'
- Default: '0.0 t/s . 0.0 high' when idle
- Display updates on every metering SSE event (throttled to 100ms)

* feat: session restore speed + title gen reasoning hardening (nesquena#1025, nesquena#1026)

PR nesquena#1025 (@franksong2702): Speed up large session restore paths
- GET /api/session?messages=0 now parses only metadata before the messages array
- Metadata-only loads no longer populate the full-session LRU cache
- Frontend lazy fetch uses resolve_model=0 to avoid cold model-catalog lookup
- Hard reload no longer waits for populateModelDropdown() before restoring session

PR nesquena#1026 (@franksong2702): Harden auto title generation for reasoning models
- Raises title-gen completion budget to 512 tokens (reasoning-safe)
- Retries once with 1024 tokens on empty content / finish_reason:length
- Applies retry to both auxiliary and active-agent fallback routes
- Preserves underlying failure reason in title_status on local fallback

Co-authored-by: Frank Song <[email protected]>

* feat: session attention indicators in right slot + last_message_at timestamps (nesquena#1024)

PR nesquena#1024 (@franksong2702): Polish session attention indicators

- Streaming spinners and unread dots now reuse the right-side actions slot
- Running/unread rows hide timestamps; idle/read rows keep right-aligned timestamps
- Date group carets point down when expanded, right when collapsed
- Pinned group no longer repeats pinned-star icon per row
- Running indicators appear immediately after send (local busy state while /api/sessions catches up)
- Sidebar sorting/grouping/timestamps now prefer last_message_at (derived from last real message)
  so metadata-only saves don't make old sessions appear under Today

Co-authored-by: Frank Song <[email protected]>

* docs: v0.50.207 release notes — 10 PRs, 2169 tests (+36)

---------

Co-authored-by: bergeouss <[email protected]>
Co-authored-by: Josh <[email protected]>
Co-authored-by: Frank Song <[email protected]>
Co-authored-by: nesquena-hermes <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants