Release v0.50.296 — 3-PR batch (TPS in headers + session save mode + Codex OAuth onboarding)
3 PRs from 1 contributor (@Michaelyklam). Closes #1406, #1617; refs #1362.
What ships
#1640 — show TPS in assistant message headers (closes #1617)
UX gate APPROVED by @aronprins (May 04 19:24 UTC) after the design discussion narrowed to default-off + Preferences toggle. Per-turn TPS rendered in the assistant message header (not the global titlebar) when show_tps is enabled. Default-off — no UI change for non-power users. Hot-applies on toggle without page refresh.
Backend now distinguishes real TPS readings from "no real reading available" (no more placeholder 0.0 chips). Live counting switched from character-count-derived to streaming-callback deltas. Final TPS computed from exact final output token usage / backend-measured turn duration when both signals are available.
#1648 — operator config knob for session save timing (closes #1406)
webui.session_save_mode accepts deferred (default — preserves v0.50.230 fix for #1171 orphan-Untitled files) or eager (materializes user message into s.messages before launching agent thread for crash-resilience on the first prompt). Implementation matches @nesquena-hermes's prescribed shape from the #1406 maintainer comment 1:1 — no Settings UI toggle (operator-level only), default stays deferred, threshold is "≥1 user message" not "did new_session() get called". Updates WAL/repair path + streaming context-build path to avoid double-counting the user turn in eager mode.
#1650 — first in-app OAuth flow: OpenAI Codex (refs #1362)
Three new endpoints (POST /api/onboarding/oauth/start, GET /api/onboarding/oauth/poll, POST /api/onboarding/oauth/cancel) with server-owned device-code lifecycle. Browser only sees flow_id, user_code, verification_uri, status — never raw OAuth lifecycle secrets (device_auth_id, code_verifier, authorization_code, access_token, refresh_token). 15-minute flow timeout. Atomic tmp+rename with chmod 0o600 BEFORE rename so final auth.json never has a world-readable window. Profile-scoped storage (writes to active profile's auth.json credential_pool.openai-codex). Allowlist _ALLOWED_ONBOARDING_OAUTH_PROVIDERS = {"openai-codex"}; explicit blocklist for anthropic/claude/nous/qwen/gemini/minimax/copilot (rejected with generic message — no internal triage state leaked).
Anthropic Claude OAuth is the planned v2 per @nesquena-hermes's prescribed roadmap.
Tests
4255 → 4284 passing (+29). 0 regressions. Full suite ~120s.
Pre-release verification
- Opus advisor on stage-296 combined diff: SHIP verdict. All 14 verification questions cleared, with focused OAuth security audit on #1650:
- In-memory
_OAUTH_FLOWSis the right shape (persistence would require writing sensitivedevice_auth_id+code_verifierto disk — strictly more attack surface than losing transient flows on restart) - Lock NOT held during network IO (concurrent flows don't block each other)
flow_id(UUID4, 128-bit) leakage analyzed — worst case is poll/cancel another flow, no credential exposure- Allowlist fail-closed (any truthy unknown string rejected)
- chmod-before-rename correctly implemented per the prior security-fix pattern
- Sensitive fields scrubbed on every terminal status transition via
_drop_sensitive_flow_fields - No internal triage state in error messages
- In-memory
- Two minor follow-ups absorbed in-release per <20-LOC defensive policy:
_get_active_hermes_home()exception fallback now logs alogger.warning(...)so silent profile-corruption fallback is observable in logs- Codex credential pool find-loop accepts both
source == "manual:device_code"(current code) ANDsource == "oauth_device"(legacy) so users with prior creds get their entry updated in-place rather than accumulating stale duplicate pool entries
- #1640 @aronprins UX-gate APPROVED.
- #1648 implements @nesquena-hermes's prescribed shape from the #1406 maintainer comment 1:1.
- #1650 implements @nesquena-hermes's prescribed shape from the #1362 maintainer comment 1:1.
- JS syntax: 5 modified
.jsfiles clean. - Browser API sanity: 11/11 endpoints OK on stage server.
Author
- @Michaelyklam — 3 PRs (#1640, #1648, #1650). Now 9 merged PRs across the v0.50.292-296 release window.
Trust boundary note
This release ships the first user-facing OAuth flow in the WebUI. Token storage path, atomic write semantics, chmod timing, server-side flow state, and the allowlist/blocklist pattern are all in scope for security reviewers. The Hermes Agent CLI's auth.json format is the source-of-truth contract — both the WebUI and CLI write the same credential_pool.openai-codex shape, so credentials added via either surface are usable by either surface.