703 Commits

Author SHA1 Message Date
Michael Lam 78c09e1fd9 fix: keep shell route errors html 2026-05-07 18:41:13 +00:00
nesquena-hermes 740e5412a5 Stage 315: PR #1838 — show auto-compression running state by @Michaelyklam 2026-05-07 18:41:13 +00:00
Michael Lam e31b7e72d6 fix: show auto-compression running state 2026-05-07 18:41:13 +00:00
Nathan Esquenazi f3b56d8793 fix(kanban): avoid double 404 when bridge already sent error response
PR #1837's new `_kanban_unknown_endpoint` wrapper was triggered for any
falsy bridge return — but `handle_kanban_*` returns `None` (not `True`)
when an inner handler calls `bad(...)` to send an error response. The
wrapper then sent a SECOND 404 on top of the bridge's response, producing
concatenated JSON bodies on the wire.

Concrete reproducer (caught by behavioural harness, not the merged tests):

    GET /api/kanban/tasks/<missing-id>/log
    →  '{"error":"task not found"}{"error":"unknown Kanban endpoint: GET ..."}'

This affected every `bad(...)`-shaped error path in the bridge:
- task-not-found returns from `_task_log_payload` / `_task_detail_payload`
- exception handlers for ImportError (503), LookupError (404),
  ValueError (400), RuntimeError (409) across all four method handlers
- the `_handle_events_sse_stream` board-resolution failure path

The fix: distinguish an explicit `False` (truly unmatched path) from
`None` (handled, response already sent). Only `False` should trigger
the unknown-endpoint diagnostic.

Adds a regression test that exercises the task-not-found path through
`routes.handle_get` and asserts only one JSON body is on the wire.

Follow-on to #1837 (already merged into master at v0.51.20).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:35:57 -07:00
hermes-agent d69d0eb35b stage-314 absorb: isolate CODEX_HOME in v0.51.19 codex provider card tests
PR #1827 introduced _read_visible_codex_cache_model_ids() merging
into the providers card live-fetch path. The two v0.51.19 tests in
tests/test_issue1807_codex_provider_card_live_models.py predate that
helper and didn't isolate CODEX_HOME, so the dev machine's real
~/.codex/models_cache.json (which contains entries like
gpt-5.3-codex-spark from #1680) was leaking into their assertions.

Add CODEX_HOME isolation in the existing _configure_codex helper —
matches the pattern PR #1827's own test already uses. Test-only fix;
production code unchanged. Caught by pre-release pytest gate.
2026-05-07 18:09:40 +00:00
ai-ag2026 72982db94b fix: add workspace heading root actions 2026-05-07 18:00:35 +00:00
Michael Lam 48773e8ff7 fix: allow no-agent cron edits without prompt 2026-05-07 17:59:23 +00:00
hermes-agent 0ed63968b6 Stage 314: PR #1827 — sync Codex provider card models with picker by @Michaelyklam
Note: PR #1827 was branched before v0.51.19 shipped #1812, which
introduced an initial (pure live-fetch) Codex provider card hook in
api/providers.py at the same line range. The contributor's PR was
filed AFTER #1812 shipped but their diff didn't yet account for it.
Stage 314 absorbs the contributor's intent (visible Codex cache
merge for gpt-5.3-codex-spark visibility) by replacing the v0.51.19
hook with the richer merged version directly in stage. Production
code change ≡ what the contributor's PR would have produced if
rebased onto current master. Test file + pr-media adopted verbatim.
Marker commit so the stage log makes the absorption visible.
2026-05-07 17:58:52 +00:00
ai-ag2026 36de8f1fc6 fix: hide workspace file tree cruft by default 2026-05-07 17:57:10 +00:00
Michael Lam bb75707331 fix: surface stale Kanban client recovery 2026-05-07 17:57:09 +00:00
hermes-agent 1f702c7569 stage-313 absorb: gate _resolve_configured_provider_id alias resolution + harden bootstrap test isolation
Two in-stage fixes for v0.51.19 batch:

1) api/config.py — add resolve_alias=False param to
   _resolve_configured_provider_id() and pass it from
   resolve_model_provider(). The PR #1818 swap from
   _resolve_provider_alias() to _resolve_configured_provider_id()
   was correct for active-provider/badge surfaces but broke #1625's
   local-server-provider literal-preservation contract: 'ollama' →
   'custom' and 'lm-studio' → 'lmstudio' alias-collapse caused
   _LOCAL_SERVER_PROVIDERS membership check to miss, breaking the
   model-id full-path preservation for LM Studio/Ollama. The new
   flag preserves the raw provider value when called from
   resolve_model_provider, and named-custom-slug + base-url
   fallback both still run unchanged.

2) tests/test_bootstrap_discover_agent.py — pin Path.home() in
   _isolate_discover_agent_dir so the hard-coded
   'Path.home() / .hermes / hermes-agent' / 'Path.home() /
   hermes-agent' candidates in discover_agent_dir() can't pick up
   the dev machine's real install. The original PR #1817 isolation
   helper covered HERMES_HOME, HERMES_WEBUI_AGENT_DIR, and
   REPO_ROOT but missed the Path.home() leak.

Both surfaced on full pytest pre-release gate, fixed in stage,
ship in v0.51.19. Tests: full suite green.
2026-05-07 17:07:48 +00:00
Frank Song 8bc2677691 fix: repair file picker and html preview interactions 2026-05-07 16:59:00 +00:00
Michael Lam f704fb52e8 fix: make error toasts copy-friendly 2026-05-07 16:59:00 +00:00
ai-ag2026 9633ed345b fix: preserve context card render ordering 2026-05-07 16:58:59 +00:00
ai-ag2026 ae22a80238 fix: hide workspace metadata in user bubbles 2026-05-07 16:58:59 +00:00
Dennis Soong fbc023bb17 fix: keep approval and clarify prompts session-owned 2026-05-07 16:58:40 +00:00
ai-ag2026 7d5704c3bc fix: keep cross-surface session continuations visible 2026-05-07 16:58:39 +00:00
Michael Lam 20861b6721 fix: preserve first-turn sidebar row during refresh 2026-05-07 16:58:39 +00:00
nesquena-hermes 5e01b00b8b Stage 313: PR #1809 — dedupe workspace-prefixed user turns after compaction by @ai-ag2026 2026-05-07 16:58:16 +00:00
ai-ag2026 256866ace6 fix: dedupe workspace-prefixed user turns after compaction 2026-05-07 16:58:16 +00:00
Frank Song f7902776d4 fix: use live Codex models in providers card 2026-05-07 16:58:16 +00:00
nesquena-hermes db7b72596e Stage 313: PR #1805 — provider account quota cards by @franksong2702 2026-05-07 16:58:15 +00:00
Frank Song b763f22f36 fix: clarify Codex quota window labels 2026-05-07 16:58:15 +00:00
Igor Tarasenko b7ed4dca3e fix(bootstrap): clarify shebang fallback precedence + tighten test setup
Addresses review feedback on PR #1817:

1. Extend the `_agent_dir_from_hermes_cli` docstring to spell out that
   the shebang fallback is a last-resort discovery step, not an override.
   Stale clones in known candidate paths still win — same precedence as
   today, but now documented so a future maintainer doesn't get the
   wrong idea.

2. Drop the misleading "install exists but no run_agent.py" comment in
   `test_returns_none_when_shebang_interpreter_does_not_walk_to_run_agent`.
   The test exercises a shebang pointing at /usr/bin/python3 whose
   parents never reach a run_agent.py — it doesn't actually need a fake
   install dir at all. Renamed for accuracy and removed the unused
   _make_agent_install call.
2026-05-07 16:57:13 +00:00
Igor Tarasenko 9f72472896 fix(bootstrap): discover agent dir via hermes CLI shebang
`discover_agent_dir()` only checked four hard-coded layouts:

  - HERMES_WEBUI_AGENT_DIR
  - $HERMES_HOME/hermes-agent
  - <webui-parent>/hermes-agent
  - ~/.hermes/hermes-agent / ~/hermes-agent

Users who clone hermes-agent somewhere else (e.g. ~/Projects/GitHub/hermes-agent)
hit:

    [bootstrap] ERROR: Python environment cannot import both WebUI dependencies
    and Hermes Agent. Set HERMES_WEBUI_PYTHON to the Hermes Agent venv Python
    or install the WebUI requirements into that environment.

…even though the `hermes` CLI is on PATH and works fine. The CLI is a
console-script with a venv-relative shebang:

    #!/path/to/hermes-agent/venv/bin/python3

After the explicit candidates miss, fall back to introspecting that shebang
and walking up parents until we find `run_agent.py`. That's a reliable
pointer to the install root regardless of where the user cloned the repo.

Tests cover happy path, no `hermes` on PATH, missing/invalid shebang,
shebang pointing outside any agent install (e.g. /usr/bin/python3), and
explicit candidates winning over the shebang fallback.

Verified end-to-end: with hermes-agent at a non-standard path,
`uv run bootstrap.py` now succeeds without any HERMES_WEBUI_AGENT_DIR
override.
2026-05-07 16:57:13 +00:00
nesquena-hermes 1706bbdcef Stage 313: PR #1815 — venv symlinks=True for shared-library Python by @Saik0s 2026-05-07 16:57:12 +00:00
nesquena-hermes 6ab384618a Stage 313: PR #1818 — named custom provider routing by @franksong2702 2026-05-07 16:56:49 +00:00
nesquena-hermes 63e85f2626 Stage 313: PR #1811 — workspace user turn repair script by @ai-ag2026 2026-05-07 16:56:49 +00:00
ai-ag2026 4c03fdfaa8 fix: add workspace user turn repair utility 2026-05-07 16:56:49 +00:00
Michael Lam 1192a0a766 fix: preserve inaccessible workspace entries 2026-05-07 16:56:48 +00:00
Igor Tarasenko 4ae28a685a fix(bootstrap): note Windows fallback + add symlinks regression test
Addresses review feedback on PR #1815:

1. Extend the inline comment to note that CPython's venv falls back to
   copy mode when symlink creation fails (e.g. older Windows without
   SeCreateSymbolicLinkPrivilege), so symlinks=True is safe to set
   unconditionally — no platform branching needed.

2. Add a regression test that asserts EnvBuilder is called with
   symlinks=True. Cheap insurance against a future "simplify" pass
   removing the flag without realising it's load-bearing on macOS.
2026-05-07 18:35:00 +02:00
Frank Song 3ac89c2696 fix: route named custom provider model selections 2026-05-07 21:40:23 +08:00
Frank Song a6b88c8c1e feat: show account limits in provider quota 2026-05-07 17:36:04 +08:00
nesquena-hermes b49c3cbd43 fix(ux): rail tooltips, +new-conversation clipping, context-menu hover, rename pre-fill
Four small UX bugs Nathan caught while dogfooding the v0.51.17 release on
desktop. All independently reproduced with browser_console + browser_vision
on a fresh worktree before fixing.

(1) **Left-rail icon tooltips never appeared.** The rail was migrated to the
    new `.has-tooltip` system in #1782, but the legacy suppression rule
    `.rail .nav-tab:hover::after { content: none }` survived the migration.
    Its specificity (0,3,1) outweighs `.has-tooltip:hover::after` (0,2,1),
    and `content: none` removes the pseudo-element entirely on hover — so the
    new tooltip system silently no-op'd on every rail icon. Fix: drop the
    suppression rule and scope the legacy `data-label` tooltip to
    `.sidebar-nav .nav-tab` (mobile) only, so it doesn't fire on rail buttons
    that carry no `data-label` (which would render an empty styled box).

(2) **`+ New conversation` tooltip clipped at panel right edge.** The button
    sits flush with the chat panel's right edge but used `--bottom` which
    centers the tooltip on `left:50%` — half the label overflowed past the
    panel edge ("New convers..."). New `.has-tooltip--bottom-right` variant
    anchors the tooltip's RIGHT edge to the trigger so the label extends
    inward. Reusable for any future right-edge panel-head button.

(3) **Workspace right-click menu items had no hover state.** The five sites
    in `_showFileContextMenu` (Rename / Reveal / Copy path / Delete) and two
    in `_showProjectContextMenu` set `style.background = 'var(--hover)'`. The
    custom property `--hover` is undefined anywhere in the codebase. An
    undefined `var()` falls back to the property's initial value
    (`transparent` for `background`) → no visible hover feedback. The defined
    variable is `--hover-bg` (`rgba(255,255,255,.06)`), already used by every
    other hover state in the app. One-letter typo, seven sites.

(4) **Rename dialog didn't pre-fill the current filename.** The caller
    (`_inlineRenameFileItem`) passed `defaultValue: item.name` to
    `showPromptDialog`, but the dialog's input setter reads `opts.value`
    only — the param name was silently dropped, leaving only the placeholder
    visible (Nathan called it the "ghost name"). Fixed two ways for
    defense-in-depth:
    - Caller switched to canonical `value: item.name`.
    - Dialog now also accepts `defaultValue` as an alias for `value`, so
      future typos using the standard `HTMLInputElement.defaultValue` param
      name don't repeat the bug.
    Plus: added `selectStem:true` opt that selects the stem before the last
    `.` on focus (Finder-style: `report.txt` → selects `report`, extension
    preserved). Edge cases verified live: directories full-select,
    `.gitignore` full-selects (dot at index 0), `noextension` full-selects,
    `a.b.c.d` selects `a.b.c`.

## Tests

+12 new regression tests, +5 net (existing test_css_tooltips suite gained 5
class-based tests; new tests/test_workspace_context_menu_and_rename.py file
adds 7 more). Total: 4728 passed (was 4723 in v0.51.17), 4 skipped, 3
xpassed, 0 failed in 141s.

- `RailTooltipCascadeTests` — pins the killer rule's absence (with comment
  stripping so the explanatory note doesn't false-positive), pins the
  scoped `.sidebar-nav .nav-tab` form, walks every rail button to confirm
  `has-tooltip` + non-empty `data-tooltip`.
- `BottomRightTooltipVariantTests` — pins variant existence, mechanics
  (`right:0`, `left:auto`, `transform:none`), and `#btnNewChat` adoption
  (with mutual-exclusion check that it doesn't carry both `--bottom` and
  `--bottom-right`).
- `ContextMenuHoverBackgroundTests` — `var(--hover)` may not appear in
  ui.js or sessions.js (the bug shape); affirmative pin that
  `_showFileContextMenu` sets ≥4 items to `var(--hover-bg)` and
  `_showProjectContextMenu` ≥2.
- `ShowPromptDialogPrefillTests` — pins both `opts.value` and
  `opts.defaultValue` references; pins the `selectStem` mechanic
  (`lastIndexOf('.')` + `setSelectionRange(0, dot)`); pins the caller's
  use of `value:item.name` and `selectStem`.

## Verification

Live in browser at port 8789 (worktree-served):
- Rail Tasks tooltip renders 8px right of the icon at the same vertical
  level (math: btn at y=87-123, tooltip at left=44px = 36px width + 8px gap).
- New-conversation tooltip renders below + button with right edge aligned
  to button's right edge, extending leftward, fully visible.
- Right-click → Reveal in File Manager shows `rgba(255, 255, 255, 0.035)`
  background on hover (the `--hover-bg` value); was `rgba(0, 0, 0, 0)`
  (transparent) before.
- Right-click → Rename on `report.txt`: input shows `report.txt`,
  selectionStart=0, selectionEnd=6, selected text = "report". Edge cases:
  directory `docs` → full-select; `.gitignore` → full-select;
  `noextension` → full-select; `a.b.c.d` → selects `a.b.c`.

`node -c` syntax check passes on both modified JS files.

Reported by: Nathan via screenshots (rail tooltips missing, + button
clipped tooltip, Workspace right-click no hover, rename dialog blank).
2026-05-07 06:25:18 +00:00
Michael Lam eeedccec58 fix: preserve sidebar scrolling while streaming 2026-05-07 06:25:17 +00:00
Michael Lam ee5600e46c fix: keep workspace open from preview breadcrumb 2026-05-07 06:25:17 +00:00
Michael Lam 048f1fa24e fix: keep assistant-only stream deltas on current turn 2026-05-07 06:25:16 +00:00
Sanjay Santhanam 064d14c85b fix(config): custom provider + :free/:beta/:thinking suffix mis-resolution (#1776)
PR #1762 fixed the rsplit grammar collision for plain @openrouter:model:free
qualifiers, but skipped the fallback whenever the provider hint started with
'custom:' on the assumption that custom providers route directly. That left
'@custom:my-key:some-model:free' broken: rsplit yields
provider='custom:my-key:some-model', bare='free' → custom guard skips the
split-fallback → returns provider='custom:my-key:some-model', model='free'.

Detect the over-split structurally instead of using a known-suffix allowlist:
custom hints carry exactly one segment after 'custom:' (constructed at
api/config.py:1363 as 'custom:' + entry_name). So any rsplit result of
'custom:<a>:<b>' with bare model '<c>' has eaten one model segment — peel
it back with a second rsplit and prepend it to the bare model.

This is robust for :free / :beta / :thinking / :preview / any future
OpenRouter suffix without an allowlist to maintain.

Adds 5 regression tests covering the matrix (free/beta/thinking/preview/
slashed-model). All 7 existing #1744 tests still pass; #1228 tests
unaffected.

Co-authored-by: Cake <51058514+Sanjays2402@users.noreply.github.com>
2026-05-07 06:25:16 +00:00
nesquena-hermes 56d88723cf fix(ux): add has-tooltip--left variant for right-edge buttons + fix tests
(1) Send-button tooltip clipping fix:
    The send button (btnSend) sits at the right edge of the composer area.
    Its side-positioned tooltip extended 'Send message' (~95px wide) past
    the viewport edge, leaving only 'Se' visible in some viewports —
    confirmed by maintainer screenshot review.

    Added a new `.has-tooltip--left` variant that flips the tooltip to
    the LEFT side of the trigger via `right: calc(100% + 8px)` instead
    of `left: calc(100% + 8px)`. Applied to btnSend in index.html.
    Browser-verified: full 'Send message' text now readable to the left
    of the gold Send button, no clipping.

(2) Test compatibility for the tooltip coverage expansion:
    5 pre-existing tests hardcoded specific class strings or 'title='
    attributes that no longer apply after we added has-tooltip + replaced
    title= with data-tooltip= on 11 high-traffic icon buttons.

    - tests/test_issue1488_composer_voice_buttons.py:
      - test_dictation_button_has_dictate_i18n_key: accept either
        title='Dictate' or data-tooltip='Dictate' as the static fallback.
      - test_buttons_have_distinct_static_titles: extracted helper
        _static_tooltip() that prefers data-tooltip over title.
    - tests/test_sprint20.py::test_mic_button_has_mic_btn_class:
      regex tolerant to additional utility classes between icon-btn and
      mic-btn (now 'icon-btn mic-btn has-tooltip').
    - tests/test_sprint20b.py::test_send_button_has_title_attribute:
      accept title= OR data-tooltip= per #1775.
    - tests/test_sprint20b.py::test_send_button_still_has_send_btn_class:
      regex tolerant to additional utility classes.
    - tests/test_workspace_panel_session_list.py::TestWorkspacePanelCollapsePriority::test_panel_header_no_longer_uses_space_between:
      panel-header was changed from overflow:hidden to overflow:visible
      so its tooltips can escape the header bar. The title-text ellipsis
      moved to the inner span (.panel-header > span:first-child) which
      already had its own overflow:hidden + text-overflow:ellipsis.
      Test now accepts either parent-level or inner-span overflow handling.

All 192 of the previously-failing or impacted tests now pass.
2026-05-07 04:30:02 +00:00
nesquena-hermes d41555cec6 fix(ux): polish CSS tooltips + clear native title + extend coverage
Stage 311 maintainer-side enhancements on top of @jasonjcwu's PR #1782,
addressing browser-verified issues + extending coverage to high-traffic
icon buttons:

(1) Clear native title when custom data-tooltip is present (the core bug fix):
    - static/i18n.js: when data-i18n-title runs against an element that has
      data-tooltip, sync data-tooltip AND removeAttribute('title'). Without
      this, the slow ~1.5s native browser tooltip co-fires alongside the
      fast custom CSS tooltip — exactly the bug #1775 reports.
    - static/ui.js _applyDashboardStatus: same treatment for the dashboard
      rail/mobile buttons (was setting btn.title=warning unconditionally).
    - static/boot.js: added _setButtonTooltip() helper, replaced 6 direct
      .title assignments (workspace toggle/collapse/clear, voice dictate,
      voice mode active/inactive) with calls through the helper.

(2) Extend coverage to high-traffic icon buttons in static/index.html:
    - Composer area (side tooltip): btnAttach, btnMic, btnVoiceMode,
      btnWorkspacePanelToggle, btnSend.
    - Workspace panel header (bottom tooltip): btnCollapseWorkspacePanel,
      btnUpDir, btnNewFile, btnNewFolder, btnRefreshPanel, btnClearPreview.
    - All 11 buttons gain has-tooltip[--bottom] class and data-tooltip,
      lose their native title=. Total covered surfaces: rail (12), sidebar
      nav-tabs (12), panel-head (31), composer/workspace icons (11) = 66.

(3) CSS polish (browser-verified visible improvement):
    - z-index 60 → 1500/1501 so the tooltip clears all sidebar/panel
      stacking contexts. Earlier verification showed the tooltip overlapping
      the Filter conversations search input.
    - background: var(--bg-strong, ...) → var(--surface) (solid #1A1A2E
      instead of falling back via undefined cascade).
    - color: var(--text, var(--accent-text)) → var(--text) (solid warm white
      #FFF8DC instead of gold which clashed at body-text size).
    - border: var(--accent-bg-strong) → var(--border) (#2A2A45 solid
      instead of gold at 0.15 alpha — the old border was barely visible
      and the arrow ::before triangle was invisible).
    - shadow: 4px/0.45 alpha → 6px/0.55 alpha + 0 0 0 1px ring fallback.
    - Added 150ms hover-onset delay (matches Cygnus's spec in #1775); 0s
      dismissal-delay so quick mouse-aways don't leave the tooltip behind.
    - Fixed has-tooltip--bottom arrow direction: was pointing down (wrong),
      now points up at the trigger (border-color order corrected).
    - Bumped offsets: side tooltip 10px → 12px (clearance from icon edge),
      bottom tooltip 8px → 10px.

(4) Test fixes (the 2 CI failures):
    - tests/test_cron_refresh_button_835.py: assertion accepts either
      title= or data-tooltip= per #1775 (was hardcoded title=).
    - tests/test_mobile_layout.py::test_profiles_sidebar_tab_present:
      regex tolerant to additional utility classes (has-tooltip).

(5) Regression tests added to tests/test_css_tooltips.py:
    - test_native_title_cleared_when_custom_tooltip_present: pins the
      removeAttribute('title') call so we don't regress to dual tooltips.
    - test_native_title_path_preserved_for_non_tooltip_elements: pins the
      el.title fallback for elements without data-tooltip.

Browser-verified: all 72 has-tooltip elements have zero native title at
runtime (was 94 with native, 2 stuck via dashboard JS path).

Co-authored-by: Jason Wu <jasonjcwu@users.noreply.github.com>
2026-05-07 04:00:40 +00:00
fxd-jason b86bdf9dc8 fix(ux): replace native title tooltips with custom CSS tooltips (#1775)
- Add .has-tooltip CSS utility class with 300ms delay (vs ~1500ms native)
  - Position-aware: right side for rail buttons, bottom for nav/panel buttons
  - Arrow indicator pointing back at trigger element
  - :focus-visible support for keyboard accessibility
  - prefers-reduced-motion: no animation for users who opt out
- Replace native title="" with data-tooltip="" on all rail-btn, sidebar
  nav-tab, and panel-head-btn elements in index.html
- Sync data-tooltip via data-i18n-title handler for locale switching
- 17 tests covering HTML coverage, CSS class definitions, and i18n sync

Closes #1775
2026-05-07 03:58:16 +00:00
fxd-jason a80b7695d8 fix(kanban): update stale read-only docstring + board_exists early-out in board counts
The bridge module docstring still described the API as 'deliberately
read-only' but it now exposes full CRUD (tasks, boards, comments,
links, SSE). Updated to list the supported operations.

For _board_counts_for_slug (the hot path for the board-switcher badge),
added a board_exists() early-out that mirrors the agent's own helper
in plugin_api.py (path.exists() before connect()). This avoids a
redundant init_db()+connect() schema pass per board per list refresh.
connect() already handles auto-init for fresh databases via its
needs_init check, so the extra init_db was unnecessary overhead on
the hot path that scales linearly with board count.

Tests:
- test_board_counts_returns_empty_for_nonexistent_board: verifies the
  early-out (no connect() call, returns {})
- test_board_counts_returns_real_counts_for_populated_board: verifies
  actual per-status counts are returned for existing boards
2026-05-07 03:58:16 +00:00
nesquena-hermes c38ee6c339 chore(release): stamp v0.51.16 — 3-PR batch (#1768, #1778, #1779)
Constituent PRs:
- #1768 (@franksong2702) serialize Anthropic env fallback reads. Closes #1736.
- #1778 (@Michaelyklam) preserve CLI session tool metadata. Closes #1772.
- #1779 (@Michaelyklam) reset model picker on session switch. Closes #1771.
  AUTO-FIX: Opus stage-310 caught a regression in the new !hasSessionModel
  branch — it dropped the deferModelCorrection guard that the parallel
  else-branch keeps. Fired spurious /api/session/update POSTs against
  imported/read-only CLI sessions whose model field reads 'unknown' (the
  exact surface #1778 introduces in this same release). Wrapped the new
  branch's _persistSessionModelCorrection call + state mutation in
  if(!deferModelCorrection). Added test_sync_topbar_does_not_persist_correction_while_model_resolution_deferred
  regression test covering both empty and 'unknown' fast-path interaction.

Tests: 4694 → 4702 collected (+8). 4695 passed, 4 skipped, 3 xpassed,
0 failed in 141.29s.

Pre-release verification:
- All 3 PRs CI-green individually.
- node -c clean on static/ui.js.
- 11/11 browser API endpoints PASS.
- Pre-stamp re-fetch: all PR heads match local rebases.
- Opus advisor: SHIP #1768 + #1778, #1779 SHOULD-FIX before merge — auto-fix
  applied at stage with regression test, re-verified clean.

Closes #1736, #1771, #1772.
2026-05-07 03:10:43 +00:00
Michael Lam 24f76bcf37 fix: reset model picker on session switch 2026-05-07 02:52:01 +00:00
Michael Lam 0bd65ef0bf fix: preserve CLI session tool metadata 2026-05-07 02:47:19 +00:00
Frank Song 91f99d8194 fix(oauth): serialize Anthropic env fallback reads 2026-05-07 02:47:19 +00:00
nesquena-hermes 516e5ad1f0 chore(release): stamp v0.51.15 — 4-PR batch (#1762, #1767, #1769, #1770)
Constituent PRs:
- #1762 (@bergeouss) openrouter/ prefix for tencent/hy3-preview:free. Closes #1744.
- #1767 (@Michaelyklam) use spawn for manual cron subprocesses. Closes #1754.
  AUTO-FIX applied: 2 tests skip on dev machines with editable hermes_agent
  install (the spawn child resolves the real cron.scheduler first instead of
  the fake one). Tightened detector to use importlib.util.find_spec origin
  check per Opus stage-309 SHOULD-FIX.
- #1769 (@nesquena-hermes, APPROVED by @nesquena) three context-menu
  essentials from #1764: Reveal-in-finder, Copy-path, Open-with-system.
- #1770 (@Michaelyklam) surface Codex usage exhaustion errors. Closes #1765.

Tests: 4662 → 4694 collected (+32). 4687 passed, 4 skipped (2 dev-only +
2 prong-2 noise), 3 xpassed, 0 failed in 135s.

Pre-release verification:
- All 4 PRs CI-green individually.
- node -c clean on all 4 changed JS files.
- 11/11 browser API endpoints PASS.
- Pre-stamp re-fetch: all PR heads match local rebases.
- Opus advisor: SHIP, all 5 verification questions clean, 0 MUST-FIX,
  2 SHOULD-FIX (one absorbed: detector tightening; one filed as #1776
  follow-up: custom provider + :free suffix edge case in #1762).

Closes #1744, #1754, #1764, #1765.
2026-05-07 02:04:36 +00:00
Michael Lam 2d20842450 fix: surface Codex usage exhaustion errors 2026-05-07 01:39:52 +00:00
nesquena-hermes f77a44fce2 feat(ux): three high-leverage context-menu essentials from #1764
Issue #1764 asked for a much larger surface (Reveal + Copy-path on
every UI surface that references a file path, plus Rename in session
menus). Per Nathan's curation we ship only the three highest-leverage
pieces in this PR — they cover the three concrete user-visible
frictions Cygnus reported, and leave the broader sweep for follow-up.

## 1. Copy file path in workspace tree right-click menu

The tree's right-click already had Rename and Reveal in File Manager.
Reveal is slow when the user just wants the path string for a
terminal/editor — and there was no Copy-path action anywhere.

Added "Copy file path" between Reveal and Delete. It POSTs to a new
`/api/file/path` endpoint that resolves the relative tree-rooted path
into the absolute on-disk path (the frontend can't compute it because
only the server knows the workspace root) and writes the result to
the OS clipboard via `navigator.clipboard.writeText()`. Falls back to
the legacy execCommand pattern on browsers where the modern Clipboard
API is gated.

The new endpoint deliberately does NOT require the target to exist:
copy-path on a recently-deleted file is still useful (paste into a
terminal to investigate). `safe_resolve` continues to gate path
traversal — the test suite pins this with a `../../../../../etc/passwd`
attempt that 400s.

## 2. Rename in session three-dot menu

Cygnus's specific ask: double-click rename in the sidebar is timing-
sensitive — the first click frequently registers as "open the chat"
before the second click arrives, so users open the conversation when
they meant to rename it. Putting Rename in the menu eliminates the
timing entirely.

Added Rename as the FIRST item in `_openSessionActionMenu` (above
Pin). It reuses the existing `startRename` closure attached to each
session row — no duplicated state, no second API call out of band
with the double-click path. Mechanism: the row builder now stores
`el._startRename = startRename` and `el.dataset.sid = s.session_id`,
so the menu can find the row by data-sid and call its closure
directly. This keeps all the `_renamingSid`/`oldTitle`/`applyTitle`
bookkeeping single-sourced.

Read-only imported sessions skip the menu item via the same
`_isReadOnlySession` gate the closure already uses.

## 3. Reveal-failed toast includes the resolved server-side path

Cygnus posted a screenshot of a "Failed to reveal: not found" toast
that dropped the path entirely. Without it the user can't tell which
file the system expected — useful when a stale session row still
references a deleted file.

Server-side fix in `_handle_file_reveal`: instead of returning
`bad(handler, "File not found", 404)`, return
`bad(handler, f"File not found: {target}", 404)` where target is the
resolved absolute path. Frontend toast also defends against err with
no .message: `(err.message||err)` instead of `err.message` alone.

Verified live: a missing-file reveal now produces:

    Failed to reveal: File not found: /home/hermes/workspace/missing-xyz.txt

Cygnus's exact diagnostic-friction is gone.

## Tests

* tests/test_1764_context_menu_essentials.py (new)
  - 13 source-level pinning tests
  - 6 live HTTP behaviour tests against the conftest test server

* tests/test_1466_sidebar_cancel_clarify.py
  - Two assertion-window bumps (3200→4400, 3600→4800) to accommodate
    the new Rename action prepended to _openSessionActionMenu. The
    test relied on a fixed-byte-window function-body slice — comments
    added explaining why the bumps were needed.

* All 9 locales got translations for the 5 new keys
  (copy_file_path, path_copied, path_copy_failed, session_rename,
  session_rename_desc) — locale parity tests pass.

## Verification

Full pytest suite: 4671 passed, 2 skipped, 3 xpassed (matches
pre-change baseline).

Live browser verification on port 8789:
- Right-click .git folder in workspace tree → menu shows
  Rename / Reveal in File Manager / Copy file path / Delete (red).
- Click Copy file path → clipboard gets "/home/hermes/workspace/.git",
  toast confirms "File path copied to clipboard".
- Open session three-dot menu → Rename conversation appears first
  with pencil icon, followed by Pin / Move / Archive / Duplicate /
  Delete in the same order as before.
- Trigger reveal on a non-existent file → toast reads
  "Failed to reveal: File not found: /home/hermes/workspace/<filename>".
  The resolved server-side path is now visible in the failure.

Refs nesquena/hermes-webui#1764.
2026-05-07 01:39:52 +00:00
Michael Lam 1fc8e83c90 fix: use spawn for manual cron subprocesses 2026-05-07 01:39:51 +00:00