Commit graph

1456 commits

Author SHA1 Message Date
Pamnard
f24d633189 Fix skills detail markdown styling with preview-md wrapper
Skill detail and linked markdown files now use the same preview-md
pipeline as Memory/Notes, with code highlighting and KaTeX enhancement.
2026-05-31 23:02:23 +00:00
nesquena-hermes
1aed605fb6 fix(#3267): harden collapsed tool-preview secret filter (Codex gate MUST-FIX)
Codex regression gate found the exact-name hidden-key set leaked secret-shaped
args (apiKey/access_token/clientSecret/Authorization/cookie/...) into the
always-visible collapsed tool-card header. Replace with a normalized
case-insensitive _toolArgPreviewKeyIsHidden() predicate matching secret-bearing
substrings + camelCase variants. Adds 22 parametrized regression tests pinning
the secret-key denial + a legit-key-still-shown guard. Co-authored-by preserved.
2026-05-31 19:21:15 +00:00
ai-ag2026
69072ac34d fix: keep collapsed tool previews quiet 2026-05-31 19:12:31 +00:00
ai-ag2026
e34c632236 fix: localize WebUI tooltip quick wins 2026-05-31 18:26:31 +00:00
PINKIIILQWQ
aeda6add2b fix: suppress phantom sidebar refresh on gateway SSE reconnect
Shift from backend mtime-based detection to frontend SSE deduplication.

Backend: Revert gateway_watcher.py to original pure hash-based polling.
Remove _get_db_mtime, _detect_gateway_restart, and mtime tracking.
This is a no-op in behavior — the original was already hash-only.

Frontend: Add deduplication at the SSE event handler level.
- _gatewaySessionSnapshotKey(sessions): deterministic key from
  session_id + updated_at + message_count (same fields as backend hash)
- _isGatewaySessionForSnapshot(session): classify non-webui sessions
- _isDuplicateGatewaySessionSnapshot(sessions): compare SSE payload
  against current _allSessions, filtered to gateway subset
- SSE sessions_changed handler wraps renderSessionList() in dedupe:
  identical data → skip refresh

This directly addresses the real root cause: the SSE reconnect snapshot
(routes.py:7735) unconditionally pushes an initial snapshot, and the
frontend always re-renders. After this fix, a reconnect with unchanged
session data is correctly detected and the redundant redraw is skipped.

Previously submitted as #3259 (backend mtime approach, now closed per
maintainer review).
2026-05-31 18:26:30 +00:00
nesquena-hermes
d46d3a1411 fix: canonicalize ./ and ~/ prefixes in _normalizeArtifactPath (#3262)
Pre-release Codex regression gate caught that _normalizeArtifactPath()
did not strip ./ or ~/ prefixes, so a tool arg recorded as ./foo.md did
not match a file-tree-opened foo.md in _turnMutatedPreviewPaths — the
open preview was left stale after an agent edit via a ./-prefixed path.
Strip ~/ and leading ./ before ignore/membership checks. Node-driven
regression test pins foo.md == ./foo.md == ~/foo.md and confirms the
existing ignore-dir / URL / empty rejections still hold.

Co-authored-by: Pamnard <pamnard@users.noreply.github.com>
2026-05-31 17:03:07 +00:00
Pamnard
ee414144d3 Reload open workspace preview when agent mutates that file
Track write/edit tool paths per turn, refresh the open preview on
tool_complete and after preservePreview loadDir on stream done, without
closing preview for unrelated responses or wiping unsaved local edits.
2026-05-31 16:54:22 +00:00
Pamnard
9365f2d219 Fix workspace preview closing on chat stream done
Background file-tree refresh after a response must not call clearPreview();
preserve the open preview while still reloading the directory listing.
2026-05-31 16:53:57 +00:00
emanon312
e24ca105d7 fix: extend upward scroll intent timeout to prevent streaming scroll snap-back
Increase MESSAGE_UPWARD_INTENT_MS from 450ms to 2000ms to fix a race
condition where the user scrolls up during streaming, pauses to read
for >450ms, and then gets snapped back to the bottom.

The root cause: after the 450ms upward-intent window expires, DOM layout
changes from the streaming markdown parser (smd), tool card insertions,
or code re-highlighting can trigger scroll events that the handler no
longer recognizes as user-initiated. When the resulting position lands
inside the 250px near-bottom zone for two consecutive samples, the
hysteresis counter re-pins (_scrollPinned=true) and the next streaming
token's scrollIfPinned() call forces scrollTop to the bottom.

With a 2-second window, the user's upward intent persists through typical
streaming DOM churn. Downward scrolling and the scroll-to-bottom button
are unaffected — movedUp requires top < _lastScrollTop-2 which is false
for downward movement regardless of the intent timeout.

Refs: #1360 (macOS momentum protection), #1731 (direction-aware unpin)
2026-05-31 16:53:57 +00:00
nesquena-hermes
86a1ddc3a7 Merge PR #3247 into stage-batchE 2026-05-31 06:50:10 +00:00
nesquena-hermes
584a3f0bf1 Merge PR #3245 into stage-batchE 2026-05-31 06:50:10 +00:00
mysoul12138
f9ff6df883 fix: prevent browser autofill on clarify input (#clarify-autofill)
Chrome's password manager aggressively autofills the clarify card's
input field with saved credentials (e.g. provider base URLs) despite
autocomplete='off'. This causes two bugs:

1. 'Clarification closed. Your draft was kept in the composer.' appears
   on every session completion because _stashClarifyDraft reads the
   autofilled value and treats it as a user draft.

2. The autofilled URL gets injected into the main composer, confusing
   the user.

Fix: add readonly attribute to the clarify input element so Chrome's
autofill ignores it. When showClarifyCard() makes the card visible,
readonly is removed programmatically so the user can type normally.

Both the static HTML (index.html) and the dynamic DOM creation
(_ensureClarifyCardDom in messages.js) are patched.
2026-05-31 13:54:02 +08:00
Andy Kang
18e9a6b9c9 fix: distinguish identical clarify prompts by id 2026-05-31 14:12:15 +09:00
nesquena-hermes
0cceb3bebf fix(#3169): pin capture backend at mic start (_activeCaptureMode) — toggle-mid-record safety (Codex review #2)
Codex round-2: _stopMic and mediaRecorder.onstop read the CURRENT _rawAudioMode
to choose backend/dispatch, but the recording was started on the OLD mode — so
toggling Settings→Sound mid-recording could stop the wrong backend (orphaning
the other) or dispatch raw-vs-transcribe wrongly. Pin _activeCaptureMode
(speech | media-raw | media-transcribe) at start; _stopMic + onstop use it.
Adds front-end source-invariant regression tests.

Co-authored-by: lucasrc <lrclucas@gmail.com>
2026-05-31 04:54:56 +00:00
nesquena-hermes
c74a019a8a fix(#3169): honor explicit Send-click (_micPendingSend) in raw-audio mode (Codex review)
Codex pre-release gate: clicking Send while raw-audio recording with text in the
composer attached the audio but never sent — btnSend sets _micPendingSend=true
before _stopMic, but _sendRawAudio only called send() when the textarea was empty.
Mirror the transcribe path: if _micPendingSend is set, clear it and send()
regardless of composer text; otherwise keep the empty-composer auto-send + toast.

Co-authored-by: lucasrc <lrclucas@gmail.com>
2026-05-31 04:47:04 +00:00
Lucas Coutinho
038bf9924f fix: _stopMic skips recognition.stop() when raw audio mode is active
Bug: when raw audio mode is enabled, SpeechRecognition is
skipped and MediaRecorder is used instead. But _stopMic()
checked for a truthy 'recognition' reference (which exists
even when unused) and called recognition.stop() — a no-op
since speech recognition was never started — never reaching
the mediaRecorder.stop() branch. Recording could never be
stopped by the mic button.

Fix: add !_rawAudioMode guard to the recognition.stop() branch.
2026-05-31 04:34:17 +00:00
Lucas Coutinho
5ebf98479d fix: rename _updateMicBadge → _updateMicTooltip, use monkeypatch in tests
- 'Badge' naming was misleading since the visual badge was removed
- Use monkeypatch.setitem with pytest fixture instead of manual
  import sys + del sys.modules (safer cleanup, no inline import)
- All 9 tests pass
2026-05-31 04:33:52 +00:00
Lucas Coutinho
6258afbaef fix: tooltip conflict with RAW badge — use span instead of ::after
The .has-tooltip CSS uses ::after to show the tooltip text.
Using ::after for the RAW badge overwrote the tooltip content.
Fixed by adding a <span class="mic-raw-badge"> child element via JS.

Also: use _setButtonTooltipAndKey for consistent i18n tooltip management.
2026-05-31 04:33:52 +00:00
Lucas Coutinho
f6b658c7a2 fix: use _setButtonTooltipAndKey for mic tooltip to update data-i18n-title
The global _setButtonTooltip only sets data-tooltip, not data-i18n-title.
The i18n system reads data-i18n-title for locale-aware tooltip updates.
Switched to _setButtonTooltipAndKey which manages both attributes.
2026-05-31 04:33:52 +00:00
Lucas Coutinho
41a8b1deb0 feat: add raw audio upload mode — send audio as attachment instead of transcribing
- Settings toggle in Sound section (after voice mode button)
- Mic button shows 'RAW' badge when raw audio mode is active
- Raw mode: record → pending file → send() (auto-send if textarea empty)
- Dictation mode: unchanged (transcribe → textarea)
- 12 locales with full i18n keys and translations
- 4 backend tests for upload + transcribe regression

PR: Thinking Path
- Hermes WebUI has dictation via Web Speech API / MediaRecorder → /api/transcribe
- Telegram sends raw audio as attachment, agent decides what to do
- Raw audio mode enables external STT, emotion/noise analysis, multimodal models

What Changed
- static/index.html: settingsRawAudio checkbox in Sound section
- static/boot.js: raw audio preference, send raw blob as pending file
- static/style.css: badge RAW on mic button
- static/i18n.js: 12 locales with translations
- tests/test_raw_audio_upload.py: 4 tests for upload + regressions

AI Usage
- Provider: opencode-go
- Model: deepseek-v4-flash
2026-05-31 04:33:52 +00:00
Andy Kang
6091428791 fix: preserve scroll on CLI import refresh 2026-05-31 04:33:12 +00:00
nesquena-hermes
4fa051ea14 fix(security/#3234 + #3219): unify case-folded path checks, fix workspace over-block, protect raw <pre> (Codex review #6)
1. routes.py: hoist a single case-folded path helper (_norm/_within_ci/_equal_ci)
   used for ALL deny + carve-out comparisons (consistent macOS/Windows safety).
2. routes.py: split the deny into (a) dir-based denies that ALWAYS fire (even
   inside the active workspace — so a workspace overlapping a state dir cannot
   expose sessions/memories), and (b) filename denies relaxed only by the
   carve-out. Fix the over-block: a workspace that is a proper DESCENDANT of a
   Hermes root (e.g. STATE_DIR/workspace) is a legit project workspace and keeps
   the carve-out; only a root-itself / ancestor / $HOME / profiles / state-subdir
   workspace disables it.
3. ui.js: move the bare file:// media-stash pass after the raw-<pre> stash too,
   so file:// inside a raw <pre> block stays literal (not just fenced/inline code).
2026-05-31 03:24:35 +00:00
nesquena-hermes
5758d4a827 fix(security/#3234 + #3219): case-fold deny checks + make bare file:// rewrite code-fence-aware (Codex review #5)
1. api/routes.py: case-fold /api/media deny filename + dir containment checks
   (os.path.normcase + casefold) so STATE.DB / Sessions/ cannot bypass the
   state/secret deny on case-insensitive filesystems (macOS/Windows).
2. static/ui.js: move the bare file:// media-stash pass to run AFTER fenced-block
   and inline-code stashing, so a file:// inside a code block / backtick span
   stays literal text instead of becoming an auto-loaded <img>. The MEDIA: stash
   keeps its first-position precedent.
Adds behavioral renderer tests (real renderMd via node) for fenced + inline code
file:// staying literal, bare file:// becoming media, and anchors keeping the
link path. Closes the last Codex review items for #3234.
2026-05-31 03:17:10 +00:00
AJV20
aae3b418f0 fix: render file artifact links as media 2026-05-31 02:24:55 +00:00
nesquena-hermes
5134d896ba absorb #3201: set _streamFinalized immediately in SSE done handler (closes #3195)
Keeps the messages.js fix from @mysoul12138 (set _streamFinalized=true right
after the early-return guard, before the fade window, so a stream_end arriving
mid-fade can't call _restoreSettledSession and overwrite live messages with a
stale server snapshot). Drops the contributor's dead empty `else {}` block in
ui.js (no behavior). Adds a regression test pinning the immediate-finalize order.

Co-authored-by: mysoul12138 <mysoul12138@users.noreply.github.com>
2026-05-31 00:07:39 +00:00
nesquena-hermes
d6d74b5354 polish(#3203): preserve localized 'Forked from' base in fork tooltip
Opus advisor SHOULD-FIX: the tooltip rework dropped t('forked_from') in favor
of a hardcoded English string, a real i18n regression (the catalog key exists
in all locales). Restore the localized base while keeping the clearer
'<base>: <parent>' format. Adds a regression test.

The new lineage/child explanatory suffixes (additive English) and the
read-only title hint are deferred to a small follow-up — they need new locale
keys and the read-only state is still surfaced by the existing meta chip.

Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-05-30 21:51:17 +00:00
nesquena-hermes
cceeca89d0 polish(#3203): keep localized attention tooltip precedence over generic state tooltip
The merged hunk set state.title=_sessionStateTooltip(...) unconditionally,
two lines after assigning the localized attention.title (pending approval/
clarify, from #3190). That clobbered the attention tooltip and, for a
needs-attention session not currently streaming, blanked it to '' since
_sessionStateTooltip returns '' when neither streaming nor unread.

Make the attention title win, and apply the state tooltip only when non-empty.
Adds a regression test pinning the precedence.

Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-05-30 21:46:01 +00:00
nesquena-hermes
5cd2aef0d5 Merge #3203: clarify sidebar session tooltips
# Conflicts:
#	CHANGELOG.md
2026-05-30 21:43:03 +00:00
nesquena-hermes
4ef544cfa4 Merge branch 'pr-3190' into release/stage-batch58
# Conflicts:
#	CHANGELOG.md
2026-05-30 19:56:01 +00:00
Hermes Agent
01c1239fc9 refactor(sessions): simplify attention indicator to a color-coded dot
Per maintainer UX direction: drop the text badge pill ("APPROVAL" /
"N QUESTIONS") in favor of color-coding the existing right-side status dot
plus the colored left rail. Red dot (--error) for pending approvals, amber
dot (--warning) for pending clarifies; theme-driven, visible even when the
session is not streaming/unread. Approval dot gently pulses (disabled under
prefers-reduced-motion). Quieter and consistent with the existing pin/unread
dot family — the row no longer needs to truncate its title to fit a pill.

Updated test_session_attention_badges to assert the dot classes
(is-attention-approval/clarify) instead of the removed badge element/styles.

Co-authored-by: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
2026-05-30 19:54:51 +00:00
ai-ag2026
00e6d953c2 feat: add distinct attention request sound
Adds a distinct two-tone attention sound (880->660Hz) for approval and
clarify prompts so they are not confused with the existing completion sound,
plus sidebar attention badges + colored rails driven by `attention` metadata
on /api/sessions. Includes lock-safety note: the in-lock
publish_session_list_changed() calls in clarify.py are safe because publish()
only takes the leaf _SESSION_EVENTS_LOCK and never re-acquires clarify._lock
(verified by Opus advisor review).

Co-authored-by: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
2026-05-30 19:40:40 +00:00
ai-ag2026
d2aa066862 feat: surface session attention badges 2026-05-30 19:18:13 +00:00
ai-ag2026
8e44dc8609 feat: resolve session links in conversation filter 2026-05-30 19:13:49 +00:00
ai-ag2026
ce4026660e test: harden session link sanitization 2026-05-30 19:13:11 +00:00
ai-ag2026
d3ee17793a feat: add internal conversation links 2026-05-30 19:13:11 +00:00
ai-ag2026
c26e25414e fix: clarify sidebar session tooltips 2026-05-30 20:56:59 +02:00
nesquena-hermes
025dd738b9 Merge branch 'pr-3178' into release/stage-batch55
# Conflicts:
#	CHANGELOG.md
2026-05-30 18:25:55 +00:00
nesquena-hermes
91f33a50cd Merge branch 'pr-3192' into release/stage-batch55
# Conflicts:
#	CHANGELOG.md
2026-05-30 18:24:30 +00:00
nesquena-hermes
db952ac561 Merge branch 'pr-3187' into release/stage-batch55
# Conflicts:
#	CHANGELOG.md
2026-05-30 18:24:29 +00:00
ai-ag2026
9458df6f4d fix: dedupe sidebar completion session ids 2026-05-30 18:19:34 +02:00
ai-ag2026
b891e4fe77 fix: avoid double rebasing legacy tool cards 2026-05-30 16:10:52 +02:00
ai-ag2026
d57854807a fix: keep tool cards anchored during history paging 2026-05-30 14:57:00 +02:00
ai-ag2026
97dde9a48e fix: restore legacy tool card visuals 2026-05-30 10:31:35 +02:00
mysoul12138
1a991041f1 fix: use getModelLabel() in _formatSessionModelWithGateway fallback
When gateway_routing is absent, _formatGatewayModelLabel returns '' and
the fallback path returns raw s.model — which may contain an @provider:
prefix (e.g. @token-plan-cn.xiaomimimo.com:mimo-v2.5-pro).

getModelLabel() already handles stripping @provider: prefixes (ui.js:2855)
but was never called in this fallback path. Route through it so the
session list always displays a clean model name.

Closes #3172
2026-05-30 15:13:03 +08:00
Hermes Agent
a7c871d45e feat: add Neon cyberpunk skin
Cyberpunk-inspired dark-first skin with neon purple (#B347FF) accent
and cyan (#00DDFF) highlights. Full palette for both light and dark
modes with component-level overrides (glow effects, purple scrollbars,
tool cards, tree viewer, session badges, MCP status indicators).

- static/style.css: 37 CSS rules (light + dark palette + components)
- static/boot.js: Neon entry in _SKINS array
- static/index.html: neon:1 in skin allowlist

Follows the existing skin pattern (modeled after Nous).
2026-05-30 04:24:15 +00:00
AJV20
d34d11821b fix: label tool output cards persistently 2026-05-30 04:24:15 +00:00
AJV20
5cb82f9da4 fix: make SSE replay cursors run-aware 2026-05-30 04:09:20 +00:00
nesquena-hermes
e5b31caee1 fix(#3162): const→let in _ensureMessagesLoaded (brick-class mobile TypeError) + ESLint runtime-error guard
The #3018 carry-forward reassigns msgs but it was declared const, throwing a
TypeError that surfaced as 'Failed to load conversation messages' on every mobile
message (v0.51.161-166). Change to let.

Adds a static JS runtime-error lint guard (eslint.runtime-guard.config.mjs +
tests/test_static_js_runtime_lint.py) using no-const-assign/no-import-assign — the
exact class node --check and source-presence tests miss. Dev-only dependency; app
stays pure Python + vanilla JS.
2026-05-30 03:24:41 +00:00
nesquena-hermes
6791b1d5d8 Merge PR #2252 (swipe actions for touch devices + session list FLIP) onto master — stripped out-of-scope version-badge/_dirty_suffix changes 2026-05-30 02:33:18 +00:00
AJV20
b53bc1a2ea fix: harden webui passive performance paths 2026-05-30 01:39:54 +00:00