Commit graph

18447 commits

Author SHA1 Message Date
Alex Fournier
ba4f5b893a Merge upstream main into feat/hermes-relay-shared-metrics 2026-07-27 17:28:56 -07:00
Teknium
373632e338 fix(state): recover from read-only database files at startup
Port from Kilo-Org/kilocode#12508: a stray read-only state.db / -wal /
-shm (sudo run, restored backup, copied dotfiles) previously killed
SessionDB init with an opaque 'sqlite3.OperationalError: attempt to
write a readonly database' raised from deep inside _init_schema —
naming no file and no fix — and the obvious wrong 'fix' (deleting the
-wal) silently loses committed transactions.

New preflight_db_writability() runs before the first connection on both
DB open paths (SessionDB.__init__ and hermes_cli.kanban_db.connect):

- files inside the Hermes home tree are repaired with chmod u+rw (the
  safe scope: Hermes owns them, and the OS makes chmod fail on files
  the user doesn't own, which bounds the repair exactly);
- anything else (root-owned files, read-only mounts, custom paths)
  fails fast with an error naming the exact file and the exact chmod
  command, plus an explicit 'do NOT delete the -wal' warning;
- WAL sidecars are never deleted or truncated — once writable, the
  normal open path checkpoints committed frames into the DB.

Proven live on main first: chmod 444 state.db -> SessionDB() raises the
opaque readonly error. With the fix: in-home DBs self-heal; out-of-home
DBs get the actionable message. Sabotage run confirms the integration
tests fail without the wiring (2 failed / 10 passed).
2026-07-27 17:27:38 -07:00
brooklyn!
f800aa3cae
Merge pull request #72995 from NousResearch/bb/edit-composer-submit
fix(desktop): send a message edit when the arrow is clicked
2026-07-27 19:27:34 -05:00
hermes-seaeye[bot]
c89d21189a
fmt(js): npm run fix on merge (#72992)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 00:21:21 +00:00
Brooklyn Nicholson
c880672846 fix(desktop): send a message edit when the arrow is clicked
Clicking the edit composer's send arrow did nothing — the edit only went
through via revert. On macOS a <button> takes no focus on mousedown, so the
arrow-click blurred the contenteditable, the blur's 80ms timer cancelled the
edit and tore down the assistant-ui composer core, and the click's send() then
threw "Composer is not available" against the dead core. submitEdit had already
set submitting=true, so the arrow wedged and only revert worked.

Guard focus on the send button with onPointerDown preventDefault, the same way
the restore button does, so the click never blurs the editor. Also wrap the
send() and the blur-timer cancel() in the #49903 unguarded-core swallow so a
raced teardown can never wedge the arrow or leak an uncaught renderer error.
2026-07-27 19:17:36 -05:00
brooklyn!
67e592b015
Merge pull request #72987 from NousResearch/bb/context-menu-parity
Mirror every desktop kebab menu to right-click
2026-07-27 19:12:04 -05:00
Alex Fournier
e23ef9f312 Merge upstream main into feat/hermes-relay-shared-metrics 2026-07-27 17:09:18 -07:00
brooklyn!
66a94dc177
Merge pull request #72985 from NousResearch/bb/tooltip-delay
fix(desktop): delay Tip hover-open by 200ms
2026-07-27 19:08:52 -05:00
Brooklyn Nicholson
84a25212de feat(desktop): mirror every kebab menu to right-click
Wire the shared actions-menu into the remaining kebabs so right-clicking a
row opens the same actions as its ⋯ button: project rows (appearance moves
to a submenu via the new shared ProjectAppearancePicker), worktree lanes,
the composer branch bar, and settings credential rows.
2026-07-27 19:04:45 -05:00
Brooklyn Nicholson
0d14864b94 refactor(desktop): extract a shared kebab + right-click actions-menu primitive
Promote the session row's inline MenuKit device into components/ui/
actions-menu.tsx: one ActionsMenu (kebab) + ActionsContextMenu (right-click)
pair driven by a single items(kit) render function, so a row's dropdown and
its context menu can't drift. Refactor the session menu onto it and let
StatusRow forward ref/onContextMenu so any row can host a context menu.
2026-07-27 19:04:39 -05:00
Brooklyn Nicholson
705b20a57d fix(desktop): delay Tip hover-open by 200ms so tips stop flashing 2026-07-27 19:02:23 -05:00
teknium1
cf0c42fa0b fix(agent): never replay chain-of-thought in the active-turn redirect checkpoint
A /steer redirect during a thinking phase serialized the streamed
reasoning into the persisted assistant checkpoint ('Reasoning shown
before the interruption: ...'). An assistant turn exposing its own
chain-of-thought reads to Anthropic's output classifier as
reasoning-injection/prefill jailbreak, so every subsequent call on the
session deterministically returned 'Provider returned an empty
response' — and because the checkpoint is persisted and replayed, no
retry, nudge, or empty-recovery branch could ever escape it. Four
sessions were permanently bricked this way in the week of Jul 21-27
(42+ blocked calls; every reasoning-free checkpoint that week was
untouched — same mechanism as the prefill.json incident).

Class fix: streamed reasoning is now display-only state. The
_current_streamed_reasoning_text accumulator is removed entirely
(producer in _fire_reasoning_delta, resets, and init), so no future
path can serialize CoT into replayable content. The checkpoint keeps
only the visible response text; the model regenerates its reasoning on
the retried turn. Invariant documented in _apply_active_turn_redirect.

Regression tests: CoT never appears in either checkpoint shape,
reasoning-only interrupts produce a bare checkpoint, reasoning deltas
stay display-only.
2026-07-27 16:58:55 -07:00
Alex Fournier
d7b8a63eb7 Merge upstream main into feat/hermes-relay-shared-metrics 2026-07-27 16:24:46 -07:00
hermes-seaeye[bot]
96db67b849
fmt(js): npm run fix on merge (#72967)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-27 23:12:24 +00:00
brooklyn!
a19cfa4531
Merge pull request #72960 from NousResearch/bb/desktop-statusbar-toggle
feat(desktop): let the status bar be hidden
2026-07-27 18:10:28 -05:00
Alex Fournier
c07f2e023a Merge upstream main into feat/hermes-relay-shared-metrics 2026-07-27 16:10:24 -07:00
brooklyn!
9f02bb207d
Merge pull request #72965 from NousResearch/bb/resume-freetext-search
fix(desktop): keep /resume's free-text search typeable
2026-07-27 18:09:50 -05:00
brooklyn!
fbd8d1a93d
Merge pull request #72963 from NousResearch/bb/artifacts-real-preview
Unify the preview rail onto one tab list
2026-07-27 18:04:18 -05:00
Brooklyn Nicholson
3c74f463d0 fix(desktop): keep /resume's free-text search typeable
/resume was classified as an options command, but its argument is a
free-text query the picker fuzzy-matches against session titles and
previews. Its completion list also always ends in a "Browse all
sessions…" action row, so Space-to-accept never fell through to an
empty list: the first space in a multi-word query emptied the composer
and threw the user into the overlay, query and all.

Classify it as mixed so spaces type through, matches keep narrowing as
you refine, and Tab or arrow-then-Enter still accept a highlighted
session.
2026-07-27 18:03:07 -05:00
Brooklyn Nicholson
f4e042f466 style(desktop): sit every pane tab strip on the sidebar surface
The strips painted with `--theme-card-seed` — the raw, unmixed seed rather
than a surface token — so they read as their own band beside the sidebar and
titlebar, which share `--ui-bg-sidebar`.

Fixed at the default in `PaneTab` rather than per strip. The right-rail
preview strip never set the vars at all, so its inactive tabs fell through
to the seed even though its container was already on the sidebar surface;
correcting the fallback fixes that one for free and keeps the next strip
from regressing. With the default right, the two zone strips no longer need
to redeclare the var and paint the token directly.
2026-07-27 18:02:20 -05:00
brooklyn!
7de4fbd493
Merge pull request #72956 from NousResearch/bb/slash-freetext-enter
fix(desktop): don't let Enter swap a free-text slash argument for a completion
2026-07-27 17:56:45 -05:00
Brooklyn Nicholson
cd25a9f405 fix(desktop): open a preview without dragging the file tree open
The preview pane shares a collapsible column with the file tree, and
`revealTreePane` un-collapses a column through that column's bound store —
which on the right is `$fileBrowserOpen`, the tree's own ⌘J toggle. So
every preview open literally called `setFileBrowserOpen(true)` and the tree
came with it.

`revealPreview` now un-collapses the column directly and leaves the toggle
alone. The tree pane's visibility binding gains `$fileBrowserOpen` to match,
since its presence was tracking only the column's collapse — without that it
would still render the moment anything opened the column.
2026-07-27 17:55:46 -05:00
Brooklyn Nicholson
003ff53fb4 fix(desktop): scope composer and transcript state to their own session
`$activeSessionId` only ever holds the primary chat's session, but surfaces
that render once per transcript were reading it as if it meant "the session
on screen." A preview produced inside a session tile was recorded under the
main chat's key and surfaced in the main chat's composer, which is what
prompted this.

The tool row now records under its own `SessionView`, and the same fix
applies to the other readers of that atom that render per surface:
attachment pills and inline preview links resolve relative paths against
their session's cwd, composer voice and auto-speak read and subscribe to
their own transcript, and the thread's compaction label, prompt-wait gate
and turn timer follow the session that mounted them. `ComposerScope` now
carries a `$messages` atom rather than a read closure so both the
imperative read and the subscription come from one place.
2026-07-27 17:55:46 -05:00
Brooklyn Nicholson
96999b116b refactor(desktop): put every preview on one rail tab list
The right rail held two things at once: a list of file tabs, and a
privileged "live preview" slot with a hardcoded `preview` tab id backed by
a separate session-keyed registry. The two were written under different
session-id rules and reconciled against each other, so an `open_preview`
from a session whose stored id hadn't landed yet was set and then
immediately cleared — the pane flashed and vanished. Artifacts arrived as
a third list with their own pane and renderers.

Now everything the rail can show is a `PreviewTarget` in `$previewTabs`,
and `openPreview` is the only way in. `$previewTarget` is a computed read
of the active tab, the session registry and its reconciler are gone, and
artifacts render in the real preview pane through the shared mode switcher
and source view instead of a parallel one. Artifact tabs stay memory-only
since the registry rebuilds from the transcript.
2026-07-27 17:55:27 -05:00
Brooklyn Nicholson
43571601aa fix(desktop): don't let Enter swap a free-text slash argument for a completion
`/goal` keeps its completion popover open across arbitrary prose so its
subcommands stay reachable. The popover highlights its first row on open, and
Enter accepted that highlight unconditionally — so pressing Enter to send
`/goal ship the redesign` would replace the sentence with a row the user never
chose. Space was already guarded; Enter and Tab were not.

Enter now accepts only after the user has arrowed to a row deliberately, so
the highlight never lies about what Enter will do. Tab stays an unconditional
accept, since it means nothing else in the composer.

This is latent rather than reproducible today: `/goal` is absent from
`SUBCOMMANDS` (its `args_hint` pipes are spaced, so the extraction regex
misses them), so the backend returns no arg completions and the branch never
runs. Giving `/goal` the subcommands it already advertises would resurrect
the #71963 symptom in a worse form — losing the prose instead of chipping it.
2026-07-27 17:50:18 -05:00
Brooklyn Nicholson
e3acdfb21d refactor(desktop): lift the completion-accept decision out of the keydown ladder
Which keys accept the highlighted completion was an inline condition in the
composer's keydown god-function, untestable without a DOM harness. Move it to
a pure helper beside the other slash-query utilities.
2026-07-27 17:50:12 -05:00
Brooklyn Nicholson
58c8d86bd5 feat(desktop): let the status bar be hidden
The bar is always-on chrome today. Hiding it is `⌘⇧S`, the ⌘K palette, or
the bottom row of its own right-click menu — VS Code's set of doors, minus
their unbound default (they ship `toggleStatusbarVisibility` with no
keybinding and Hermes has no chord dispatcher for a `⌘K ⌘S` two-stroke).

Hidden unmounts the bar rather than hiding it, so the 15s status poll and
the per-turn readouts stop with it. Visibility persists per window profile
and defaults on.
2026-07-27 17:47:45 -05:00
Gille
b429194478
refactor(desktop): simplify free-text slash mode check (#72815) 2026-07-27 17:45:03 -05:00
Alex Fournier
5efeb73b9f Merge upstream main into feat/hermes-relay-shared-metrics 2026-07-27 15:13:47 -07:00
brooklyn!
7c532e1006
Merge pull request #72889 from NousResearch/bb/composer-at-paths
Fix `@` path navigation, folder completion, and chip baseline in the composer
2026-07-27 17:13:00 -05:00
brooklyn!
42c308ecdc
Merge pull request #72897 from NousResearch/bb/desktop-drift-fixes
Desktop: fix diff color drift, replayed notifications, stall timing, and quit-on-active-work
2026-07-27 17:12:49 -05:00
Alex Fournier
3666e3417e Merge upstream main into feat/hermes-relay-shared-metrics 2026-07-27 15:12:47 -07:00
brooklyn!
14cb0507a7
Merge pull request #72912 from NousResearch/bb/desktop-project-count
Drop the leftover session counts inside an entered project
2026-07-27 17:12:39 -05:00
Alex Fournier
70d8db7409 Merge upstream main into feat/hermes-relay-shared-metrics 2026-07-27 15:08:25 -07:00
Gille
dbc18c6d62
fix(desktop): preserve live model after settings save (#72903) 2026-07-27 17:52:37 -04:00
Teknium
731aa0ccc9 fix(browser): stop stale cdp_url from stalling every startup by 10+ seconds
Tool-schema assembly at CLI/Desktop startup runs the browser-family
check_fns (browser, browser_cdp, browser_dialog, browser_vision). Each
of those gates called _get_cdp_override(), which resolves the configured
endpoint over HTTP (GET /json/version, timeout=10) — so a *stale*
browser.cdp_url pointing at a dead debug browser cost ~7 serial blocking
socket connects before the banner rendered. Measured on a real Windows
install with a dead http://[::1]:9222 config: 15.1s of an 18s launch,
with no warning or error — just mystery slowness. The value is easy to
leave behind: /browser connect writes a session-scoped env override, but
'hermes config set browser.cdp_url' persists forever while the debug
Chrome it pointed at dies on the next browser restart.

Split the helper:

- _get_cdp_override_raw() — returns the configured value (env var or
  config.yaml) with zero network I/O. Used by every is-it-configured
  gate: check_browser_requirements, _browser_cdp_check, _is_local_mode,
  _is_local_backend, _navigation_session_key, _should_inject_engine
  (via _is_local_mode), and the hermes doctor chromium-skip check.
- _get_cdp_override() — unchanged contract (raw + /json/version
  resolution), now only called on paths that are about to connect:
  session creation and the dialog-supervisor attach.

This follows the existing rule in check_browser_requirements ('do not
execute agent-browser --version here') and the browser.manage status
path, which already banned _get_cdp_override for exactly this reason
(test_browser_manage_status_does_not_call_get_cdp_override): schema
assembly must not perform blocking I/O.

A/B on the same machine, same dead endpoint: get_tool_definitions()
15.08s unpatched -> 1.89s patched, with browser_cdp/browser_dialog
still advertised (gate now keys off configuration, not reachability —
matching the documented lazy-supervisor contract in
_browser_dialog_check).

Adds a regression test asserting the browser_cdp check_fn never touches
the network.
2026-07-27 14:32:05 -07:00
brooklyn!
c63be0daf7
Merge pull request #72900 from NousResearch/bb/desktop-home-project
Add a Home project at the top of the desktop sidebar
2026-07-27 16:29:57 -05:00
Brooklyn Nicholson
e04ed64637 style(desktop): drop the session counts inside an entered project
The sidebar's flat session list lost its `x/<total>` chip in #72336, but
the project drill-in kept counting: WorkspaceHeader still rendered a
SidebarCount for every repo and every branch/worktree lane. Entering a
project put a number next to each label again.

Remove the header's count slot and both call sites, along with the now
dead repo total.
2026-07-27 16:25:51 -05:00
Brooklyn Nicholson
e00901259c feat(desktop): Tab into a folder from the @ popover
Tab and Enter shared one branch, so picking a folder always committed a
chip and closed the menu — the list could show `apps/` but never open it.
Reaching a nested path meant typing every segment by hand.

Split the two intents. Tab re-types the token as a bare path so the next
completion lists that folder's children; Enter still commits the folder
itself as a chip. Files ignore the distinction — there's nowhere deeper
to go. Backspace mirrors the descent, dropping one path segment per press
instead of one character, so climbing out costs the same as going in.
2026-07-27 16:20:58 -05:00
Brooklyn Nicholson
579b66336f fix(desktop): let automated teardown quit past the active-work prompt
Playwright closes the app with a turn still in flight, so the new quit
confirmation waited on a click nobody was there to make and the E2E
worker died on a 90s teardown timeout.
2026-07-27 16:19:27 -05:00
hermes-seaeye[bot]
a88e27e9e5
fmt(js): npm run fix on merge (#72902)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-27 21:15:26 +00:00
brooklyn!
1d76a15188
Merge pull request #72899 from NousResearch/bb/desktop-recede-chrome-seams
Let the desktop chrome recede, and mark the active tab instead
2026-07-27 16:07:08 -05:00
Brooklyn Nicholson
5a5d7b9386 feat(desktop): pin Home to the top of the project sidebar
Home leads the overview above the active project and outside any drag
order, drills in to a flat chat list (it has no repo or worktree
structure), and overlays live sessions so a brand-new detached chat
appears instantly. Starting a chat from inside Home stays detached
instead of picking up the configured default project dir. Rename and
delete are hidden — there's no record behind the row.
2026-07-27 16:05:19 -05:00
Brooklyn Nicholson
ef0f4763e3 i18n(desktop): name the project-less bucket "Home"
The sidebar row is a place you enter, not a status line, so it reads as a
destination rather than "No project".
2026-07-27 16:05:10 -05:00
Brooklyn Nicholson
60b6ea237f feat(gateway): group unplaced sessions into a Home bucket in the project tree
Sessions with no cwd — or whose folder can't be promoted to a project (the
bare home dir, a deleted workspace, HERMES state) — were dropped from the
project tree entirely, so the grouped sidebar silently showed fewer chats
than flat Recents. Collect them into a synthetic `__no_project__` node at
the head of the list. It carries one lane purely to hold the rows, and is
omitted when empty so a project-less install stays blank.
2026-07-27 16:04:57 -05:00
Brooklyn Nicholson
2a7da2b549 style(desktop): fade split sashes until hover
The seam hairline sat at full strength on every split, so an empty
workspace read as a wireframe. Hold it at 0.1 and bring it up with the
grab band already on hover.
2026-07-27 15:58:34 -05:00
Brooklyn Nicholson
35a002a441 feat(desktop): mark the active pane tab with a primary underline
The active tab was defined by absence: the strip painted a rule and the
tab covered it, so inactive tabs stopped a pixel short to let it show.
Draw the state instead. The tab carries its own 2px --theme-primary
underline and the strip's rule goes away, which lets tabs run full height
and removes PANE_TAB_STRIP_LINE along with it.
2026-07-27 15:58:22 -05:00
Brooklyn Nicholson
d8b5bbf607 style(desktop): drop the titlebar and statusbar edge rules
The window chrome bracketed the workspace with a 1px rule top and bottom.
Both bars already paint the sidebar surface, so the rules divided one
continuous color rather than separating two.
2026-07-27 15:58:12 -05:00
Brooklyn Nicholson
c7ef4c192d refactor(desktop): name the overlay z-index ladder
DESIGN.md already said app-wide surfaces must not compete through ad-hoc
z-index literals, and the code disagreed: three overlapping numbering
schemes, and comments narrating the fight ("defaults to z-130, renders
UNDER the onboarding overlay (z-1300) ... bump it above with z-[1310]").
Picking a number meant reading someone else's near miss.

Name the rungs — modal, over-modal, switcher, and the boot chain — and
point the call sites at them. Every rung keeps the exact value it had, so
nothing moves; what changes is that the next overlay has a name to reach
for instead of a number to guess. Local stacking within a component stays
on plain z-10/z-20.
2026-07-27 15:48:09 -05:00
Brooklyn Nicholson
9ae3bd73c9 feat(desktop): confirm before quitting with a turn in flight
Cmd-Q went straight through to teardown, killing the backend mid-tool-call
— the turn is gone and whatever the agent was part-way through writing
stays part-way written, with nothing on screen to warn about it.

Renderers now report which chats are mid-turn; before-quit merges the
reports and asks, naming them, defaulting to Keep Running. Update, swap,
and uninstall relaunches skip the prompt: those are the app replacing
itself, and a modal there would strand the detached script waiting on a
PID that never exits.
2026-07-27 15:47:57 -05:00