fix: batch v0.50.232 — fuzzy match, codex detection, workspace reload, timestamp sync#1198
Merged
nesquena-hermes merged 10 commits intomasterfrom Apr 28, 2026
Merged
fix: batch v0.50.232 — fuzzy match, codex detection, workspace reload, timestamp sync#1198nesquena-hermes merged 10 commits intomasterfrom
nesquena-hermes merged 10 commits intomasterfrom
Conversation
… set (#1189) openai-codex has a full static model list in _PROVIDER_MODELS (9 models) and a display name in _PROVIDER_DISPLAY, but no env-var detection path. The only way it appeared was via manual 'providers:' config in config.yaml. OPENAI_API_KEY authenticates both the 'openai' and 'openai-codex' groups — they share the same credential. Added openai-codex to the env-var detection block alongside openai so the group appears automatically for any user who has OPENAI_API_KEY configured. One-line change, no logic side effects. Users who don't want the Codex group in the picker can override via the existing providers: section in config.yaml. Closes #1189
3 source-level + sanity tests verifying: 1. The OPENAI_API_KEY detection block adds both 'openai' and 'openai-codex' to detected_providers 2. _PROVIDER_MODELS['openai-codex'] is non-empty (so detection actually surfaces models, not an empty group) 3. _PROVIDER_DISPLAY['openai-codex'] has a label Test docstring records the cross-tool nuance: hermes-agent's openai-codex default uses oauth_external (chatgpt.com), so the simple OPENAI_API_KEY shortcut is a UX-positive convenience for users who have BOTH key+OAuth, but users with only the env var will see picker entries that error at use-time. The fix is still net-positive — the alternative is silent absence requiring manual config.yaml. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…alse positives (#1188) Step 3 of _findModelInDropdown() used a truncated 'base' (target with last version segment stripped) as the prefix to match against dropdown options. For 'gpt-5.5', target='gpt.5.5' and base='gpt.5', which incorrectly matched '@nous:openai/gpt-5.4-mini' (norm: 'gpt.5.4.mini') because it starts with 'gpt.5'. The chip would then show 'GPT-5.4 Mini (via Nous)' for a session that stores 'gpt-5.5'. Fix: use the full target as the prefix when base has meaningful content (length > 4 and base !== target). Only fall back to the shorter base when it is a bare root word ('gpt', 'claude', etc.) where stripping the version segment would be a no-op. 'gpt-5.5' with prefixTarget='gpt.5.5': 'gpt.5.4.mini' does NOT start with 'gpt.5.5' → returns null (correct — no false match). 'gpt' with prefixTarget='gpt' (useBase=true): still finds 'gpt.5.4.mini' via the shorter base → prefix match for bare roots preserved. Closes #1188
9 tests run the live _findModelInDropdown function via Node so the real regex/normalization rules are exercised (no Python mirror to drift). Two locked-bad cases (gpt-5.5 → gpt-5.4-mini, claude-opus-4.7 → claude-opus-4.6) reproduce on master and pass on the PR. Seven preserved-good cases (bare-root prefix match, exact match, unrelated) ensure the tighter check doesn't regress legit fuzzy lookups. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
After PR #1187 fixed the workspace panel persisting on empty-session reload, a secondary bug remained: the file tree went blank on the *second* reload of an empty new conversation. Root cause: the ephemeral guard (message_count=0) called localStorage.removeItem('hermes-webui-session') which removed the stored session ID after the first blank-page refresh. Sequence: Refresh 1 — saved=session_A_id in localStorage → loadSession(A) runs → loadDir('.') → files populate the DOM → guard fires: localStorage key REMOVED, S.session=null → files still visible (DOM populated by loadDir above) ✅ Refresh 2 — saved=null (key was removed) → falls to 'no saved session' path → loadDir() is never called in that path → file tree is empty / never populated ✗ Fix: remove the localStorage.removeItem() call. The session ID stays in localStorage so every refresh takes the same path: loadSession() → loadDir() → files populate → guard fires → S.session=null → workspace panel stays open with files visible ✅ The session ID remaining in localStorage is harmless: the ephemeral guard still fires, S.session is cleared in memory, the session stays invisible in the sidebar (server-side filter), and no phantom 'Untitled' entry appears. 2729 tests passing.
Three static checks that catch the exact regression PR #1196 fixes: 1. The empty-session guard MUST NOT remove 'hermes-webui-session' from localStorage — that's the line whose presence broke the second refresh (no-saved-session path doesn't call loadDir). 2. The guard MUST still clear S.session=null and S.messages=[] so the empty scratch pad isn't surfaced as the active conversation. 3. The guard MUST still read 'hermes-webui-workspace-panel-pref' so PR #1187's panel-persist behaviour remains intact. Together they pin the contract: the localStorage key stays (this PR), the in-memory state clears (#1182), and the panel pref restores (#1187). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Root cause: All relative-time calculations in the session sidebar
("2 hours ago", "Today", "Yesterday", etc.) used Date.now()
(client-side clock) as reference. When the browser clock and server
clock are out of sync (common with WSL clock drift, Docker containers
in UTC, remote access), session timestamps appear wrong.
Fix:
- Backend: /api/sessions now returns server_time (epoch seconds) and
server_tz (offset string like "+0800") alongside session data.
- sessions.js: Computes _serverTimeDelta = Date.now() - server_time
once per session-list fetch. All time helpers (_formatRelativeSessionTime,
_sessionCalendarBoundaries, _sessionTimeBucketLabel, _formatSessionDate)
now default to _serverNowMs() (Date.now() - delta) instead of Date.now().
- ui.js: _formatMessageFooterTimestamp and tsTitle tooltip now use
_serverTzOptions() to display timestamps in the server's timezone
(via IANA Etc/GMT offset format), falling back to browser timezone
when _serverTzOptions is not available.
Backward-compatible: passing explicit nowMs parameter still works
(used by tests), and when server_time is absent (no skew), behavior
is identical to before.
18 regression tests covering: backend response, skew compensation,
time formatting with skew, timezone conversion, and fallback behavior.
The Etc/GMT-X mapping in _serverTzOptions can only express whole-hour offsets — IANA Etc/GMT zones don't support fractional hours. Users in India (+0530), Iran (+0330/+0430), Newfoundland (-0330), Nepal (+0545), Sri Lanka (+0530), Afghanistan (+0430), and Burma (+0630) — collectively ~1.5 billion people — would see timestamps off by 30 minutes after this PR's whole-hour-only TZ conversion. That's actively WORSE than the pre-PR behaviour for them: pre-PR they saw browser-tz times (consistent with their wall clock), post-PR they'd see times 30 min off from the server. Fix: introduce _formatInServerTz(date, options) that uses offset arithmetic — shift the timestamp by the server's offset, then format with timeZone:'UTC' so no further conversion is applied. The formatted output reads as the wall-clock time in the server's timezone for ANY offset, including fractional. _serverTzOptions is kept for whole-hour fast-path callers that spread its result; updated to return undefined for fractional offsets so it can't silently produce off-by-30-min output via the spread path. ui.js: _formatMessageFooterTimestamp and tsTitle in renderMessages now go through _formatInServerTz with a fallback to bare toLocaleString when sessions.js hasn't loaded yet. Tests: - test_message_footer_timestamp_uses_server_tz: stubs _formatInServerTz with the same offset-arithmetic logic as sessions.js, asserts UTC+8 conversion produces the right wall-clock hour. - test_message_footer_timestamp_handles_fractional_offset: NEW. Stubs _formatInServerTz with +0530 (IST), asserts 02:00 UTC → 07:30 IST and explicitly NOT 07:00 (the broken Etc/GMT-5 result). - test_ui_js_message_timestamp_uses_server_tz: relaxed to accept either _formatInServerTz or _serverTzOptions reference. - test_sessions_js_has_format_in_server_tz_helper: NEW. Verifies the function exists and uses offset arithmetic + UTC formatting. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…oral - api/config.py: correct misleading comment (OPENAI_API_KEY does NOT auth the default Codex OAuth endpoint; hermes_cli.auth handles that path when oauth is set up) - tests: replace fragile 400-char source-scan with isolation-safe unit test that directly tests the detection logic rather than parsing source code
This was referenced Apr 28, 2026
JKJameson
pushed a commit
to JKJameson/hermes-webui
that referenced
this pull request
Apr 29, 2026
…, timestamp sync (nesquena#1198) Batch release v0.50.232 — 4 fixes. ## PRs included | PR | Author | Fix | |---|---|---| | nesquena#1192 | @nesquena-hermes | Model chip fuzzy-match false positive (nesquena#1188) | | nesquena#1193 | @nesquena-hermes | openai-codex not detected in model picker (nesquena#1189) | | nesquena#1196 | @nesquena-hermes | Workspace files blank after second empty-session reload | | nesquena#1197 | @bergeouss | Session timestamps wrong with server/client clock drift (nesquena#1144) | All four PRs independently reviewed and approved by @nesquena. ## Integration fixes applied **nesquena#1193:** Updated misleading comment — `OPENAI_API_KEY` does NOT authenticate the default Codex OAuth endpoint (that uses `chatgpt.com/backend-api/codex` and requires a separate OAuth flow). The comment now accurately states the known limitation. Also replaced a fragile 400-char source-scan test with an isolation-safe unit test. Note: OAuth-authenticated users already get detected via `hermes_cli.auth` — this fix only addresses the env-var fallback path. ## Test results **2764 passed, 2 skipped** (macOS-only workspace tests). Browser QA: **21/21**. `/api/sessions` confirmed returning `server_time` and `server_tz` fields.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Batch release v0.50.232 — 4 fixes.
PRs included
All four PRs independently reviewed and approved by @nesquena.
Integration fixes applied
#1193: Updated misleading comment —
OPENAI_API_KEYdoes NOT authenticate the default Codex OAuth endpoint (that useschatgpt.com/backend-api/codexand requires a separate OAuth flow). The comment now accurately states the known limitation. Also replaced a fragile 400-char source-scan test with an isolation-safe unit test. Note: OAuth-authenticated users already get detected viahermes_cli.auth— this fix only addresses the env-var fallback path.Test results
2764 passed, 2 skipped (macOS-only workspace tests). Browser QA: 21/21.
/api/sessionsconfirmed returningserver_timeandserver_tzfields.