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.
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.
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.
`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.
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.
Without symlinks=True, mise/asdf shared-library Python builds on macOS
default venv to copy mode. The copied python3 binary still references
@executable_path/../lib/libpython3.X.dylib in its load command, but the
dylib is never copied into .venv/lib — so any import in the new venv
(starting with ensurepip) aborts with SIGABRT.
Reproduces with mise's cpython 3.13.9 build:
[bootstrap] Creating local virtualenv at .../.venv
[bootstrap] ERROR: Command '[".../.venv/bin/python3.13", "-m",
"ensurepip", "--upgrade", "--default-pip"]' died with
<Signals.SIGABRT: 6>.
Symlinking the interpreter keeps @executable_path resolving back to the
original install where libpython lives. uv-managed Pythons already
symlink by default; mise's do not.
Detect IPv6 addresses (containing ':') in QuietHTTPServer.__init__ and set address_family to AF_INET6 before socket creation, fixing EAFNOSUPPORT when binding to :: or ::1.
Also updates the loopback check to recognize ::1 and the container warning to mention :: as the IPv6 equivalent of 0.0.0.0. Documents IPv6 usage in HERMES_WEBUI_HOST env var description.
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).