Commit Graph

4089 Commits

Author SHA1 Message Date
hermes-seaeye[bot] 19591aa390 fmt(js): npm run fix on merge (#89501)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-18 21:51:44 +00:00
seref 359e09fd65 feat(desktop): one-click plugin install via hermes:// deeplinks
Adds a reviewable in-app install path for Hermes plugins:
hermes://plugin/install?repo=owner/repo (and Settings -> Plugins ->
Install from Git) opens a confirmation modal showing the repo identity
and source links, shallow-clones to probe for agent and/or desktop
plugin artifacts, lets the user pick components, then installs — agent
side through the gateway's new plugins.manage `install` action (wrapping
the existing dashboard_install_plugin), desktop side through a new
Electron git-install module with subdir-escape guards, a 60s clone
timeout, non-interactive git env, and insecure-scheme warnings. Never
auto-installs; hybrid repos get one dialog. Legacy plugin-agent /
plugin-desktop deeplinks route into the same modal.

Salvaged from PR #82735 by @serefyarar (net diff applied onto current
main as a single authored commit; the branch carried merge commits).
The preview screenshot PNG from the original branch was intentionally
not carried over — images live in PR bodies, not the repo.
2026-08-18 14:45:23 -07:00
hermes-seaeye[bot] 72f9e01497 fmt(js): npm run fix on merge (#89492)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-18 21:38:17 +00:00
ethernet ae162f7e5c fix(desktop): clear the theme preview at palette close start
The revert to the committed theme lagged behind Escape. The palette
body stays mounted through the whole exit animation, and the preview
was cleared at unmount. So the repaint waited for the fade.

Subscribe to the palette open store in the body and clear the preview
the moment the store flips to closed. The unmount clear stays as the
backstop for a body that dies without a close.
2026-08-18 17:31:47 -04:00
ethernet 1e3106197b fix(desktop): read the palette highlight from the cmdk store
The highlight preview did not fire. cmdk calls the root onValueChange
only in controlled mode, when the value prop is set. The palette is
uncontrolled, so the preview callback never ran.

Add a HighlightWatcher child that subscribes to the cmdk store with
useCommandState. The store reports the highlight in both modes. The
watcher replaces the dead root prop.

The new test renders a real uncontrolled cmdk root. It proves that
the watcher fires and that the root prop stays silent. If cmdk later
fires the prop in uncontrolled mode, the second assertion fails, and
the watcher becomes removable.
2026-08-18 17:31:47 -04:00
ethernet 40fccbf082 feat(desktop): live-preview themes from the palette highlight
The theme rows in the Cmd-K picker applied a theme only on select.
Now the highlighted row paints its theme immediately.

cmdk reports the highlighted row through onValueChange on the root.
A new optional onHighlight callback on PaletteItem receives it. The
theme rows preview through a new previewTheme function on the theme
context. The preview is not persisted. A highlight on a row without
onHighlight, a page change, a palette close, or a commit clears the
preview. Then the committed appearance returns.
2026-08-18 17:31:47 -04:00
Teknium d354af5e12 feat(desktop): unified Sessions list shows every connected gateway's chats (#88880)
The global SESSIONS sidebar only aggregated local profiles and v1 per-profile
remote overrides. Sessions living on v2 registry connections (remote/cloud/ssh
gateways) never appeared — the remote API returned the rows, but the renderer's
Sessions component received an empty array (#88880).

- electron/profile-session-routing.ts: fetchRegistrySessionRows reads each
  CONNECTED registry gateway's session list (ssh backends natively, shared
  remote/cloud hosts via one cross-profile aggregate with a legacy flat-list
  fallback), tagging rows with connection_id + owning profile.
  spliceRegistrySessionRows dedupes them into the unified list and extends
  per-profile totals. Reads never pass include_hidden, so Bot Mode's hidden
  canonical chats stay OUT of the global list, same as local sessions.
- electron/main.ts: mergeRemoteProfileSessions splices registry rows; the
  /api/profiles/sessions[+/sidebar] intercepts also fire when registry
  gateways are pooled (previously only v1 remote overrides). Only
  already-pooled backends are read — a sidebar refresh never dials or spawns
  a backend (the roster-respawn trap), and a dead gateway contributes nothing.
- types/hermes.ts + use-session-actions: SessionInfo carries connection_id;
  resuming a registry-owned row activates its connection-scoped gateway
  (ensureGatewayAgent) instead of a same-named local profile.
- store/gateway.ts: ensureActiveGatewayOpen rides out an in-flight secondary
  activation (bounded 8s) instead of failing instantly — the Sessions "+"
  during remote wake no longer errors "Hermes gateway is not connected".

Tests: 5 new registry-source/splice unit tests (tagging, shared-host
aggregate + legacy fallback, dead-gateway isolation, hidden-flag contract,
dedupe/totals) and a sabotage-verified activation-wait regression test.
2026-08-18 14:27:15 -07:00
Teknium aae96913df fix(desktop): register plugin notify handlers only after guards pass; re-resolve activate at the IPC boundary
Two hardening follow-ups on the salvaged #84192 work:

- dispatchNativeNotification now reports whether the notification actually
  reached the OS bridge, and dispatchPluginNativeNotification registers its
  onActivate/onAction closures only on true. Previously a throttled,
  disabled, or baseline-suppressed notification registered handlers that no
  click could ever clear, leaking them for the window's lifetime.
- The renderer's onNotificationActivate handler re-resolves the activate
  payload through resolveHermesOpenPath instead of trusting the pre-IPC
  validation, keeping path validation in one funnel for any future
  hermesDesktop.notify caller.

Adds a regression test covering the throttled and suppressed cases.
2026-08-18 14:26:28 -07:00
seref 73ddf6665c feat(desktop): rich plugin OS notifications with deeplink activation
Extends ctx.os.notify (the curated plugin OS door from #78685) with icon,
action buttons, and a serializable `activate` target. Body/action clicks
focus the window and navigate to the plugin's screen; activation paths
share one resolver (hermes-open-target.ts) with hermes:// OS deep links,
so `hermes://index-network/intent/1`, `/index-network/intent/1`, and
{ path, params } all land on the same hash-router route. Approval
notifications keep their existing session-scoped channel.

Salvaged from PR #84192 by @serefyarar (net diff of the PR branch applied
onto current main; branch carried merge commits so a single authored
commit preserves attribution).
2026-08-18 14:26:28 -07:00
hermes-seaeye[bot] 6a1fb37c94 fmt(js): npm run fix on merge (#89485)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-18 21:26:10 +00:00
whisky0809 edb9f96d6d fix(desktop): keep escaped dollars from ending a shielded math span
The inline branch of MATH_SPAN_SPLIT_RE excluded `$` from the body outright,
so a `\$` inside inline math — a literal dollar sign, valid TeX — broke the
span match and the shield silently didn't apply. `$\sqrt[3]{8} + \$5$` still
lost its index.

Step over escape pairs instead, matching the escaped-delimiter rule
findClosingSingleDollar already applies via isEscapedAt. The two body
alternatives are disjoint on their first character, so the added quantifier
can't backtrack ambiguously.

Reported by Copilot in review.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPGjEQ2yrS4nWiooUEdYti
2026-08-18 14:19:39 -07:00
whisky0809 21fc7c5a14 fix(desktop): shield math spans from the visible-prose rewrites
`$\sqrt[3]{8}$` renders as a plain square root — the index is gone. It is
not a KaTeX layout problem: the index never reaches KaTeX. CITATION_MARKER_RE
strips `[3]` as a citation marker, because its lookbehind accepts any letter
and the `t` of `\sqrt` qualifies. That runs inside normalizeVisibleProse,
which splits out inline code spans but not math, so TeX is fed to rewrites
written for prose.

Numeric-only, which is why `\sqrt[n]{8}` survives and made this look like a
layout edge case rather than a preprocessing one.

Shield math the same way inline code is already shielded: split each prose
part on math spans and rewrite only the segments between them. That also
takes math out of the reach of the other rewrites in that pass
(autoLinkRawUrls, LOCAL_PREVIEW_URL_RE, the ``` stripper, linkifySessionRefs),
any of which can corrupt TeX the same way with different input.

The split is capturing, and math segments are identified by index parity
rather than a leading `$`, so a prose run that merely opens with a stray
dollar cannot be mistaken for math. Escaped `\$` delimiters stay prose, which
is what keeps `$5 and $10` escaping intact.

Verified in the real renderer through the desktop mock-backend E2E harness:
`.katex .root` (the span KaTeX emits for a radical index) goes from 1 to 4 on
the same four-radical reply, and the MathML annotations show KaTeX receiving
`\sqrt[3]{8}` intact rather than `\sqrt{8}`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPGjEQ2yrS4nWiooUEdYti
2026-08-18 14:19:39 -07:00
whisky0809 47066f5ea0 fix(desktop): normalize hugging multi-line display-math delimiters
Multi-line display math whose $$ delimiters hug the body
(e.g. $$\begin{aligned}...\end{aligned}$$) renders as raw error text.
remark-math's flow-math construct is fence-shaped: text after the
opening $$ on the same line is read as an info string and discarded,
and the closing $$ is only recognized alone on its own line. So the
block never closes and KaTeX paints the remains via its error fallback.

splitHuggingDisplayMath moves those delimiters onto their own lines. It
runs AFTER normalizeMathDelimiters because that rewrite is itself a
source of the hugging form: a multi-line \[...\] comes out of it as
$$\begin{aligned}...\end{aligned}$$, so the same bug reached users who
never typed a $$ at all.

Single-line $$...$$ is left alone (it routes through the inline
math-text construct and already renders), container prefixes are
replayed onto the delimiter lines, and both patterns anchor $$ to the
start of the line, which keeps them from firing inside an inline code
span.

Verified end to end: the repro emits katex-error through
remark-math + rehype-katex before this change and not after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:19:39 -07:00
Teknium ae6578aff8 fix(desktop): route session RPCs to the profile that owns the session (#89206)
Bot Mode wake-ups died on a routing split-brain: session.resume /
session.activate / session.usage were dispatched on whatever socket was
active at request time, while the bot's own backend sat healthy and idle
(zero traffic until the idle reaper killed it). Two divergence sources,
both fixed:

1. Registry-owned route truth. applyActive() now publishes the active
   route's bare profile ($activeGatewayRoute + onActiveRouteChanged), and
   use-gateway-boot mirrors it into $activeGatewayProfile. Previously,
   eviction fallbacks (idle reap, connection removal, profile delete)
   moved the SOCKET back to the primary while the profile atom kept
   naming the evicted bot — ensureGatewayProfile's "already active" fast
   path then trusted the stale atom and skipped the re-swap forever.

2. Request-time routing for session-scoped RPCs. resumeSession's RPCs go
   through requestForSessionProfile (store/session-request-router.ts):
   when the active route serves the session's owning profile the ambient
   dispatcher is kept (reauth-aware reconnect); when it diverges — a
   concurrent switch won the mutex, a failed dial left the old socket
   active, an eviction re-pointed the route — the RPC is pinned to the
   owning profile's own socket via requestGatewayForProfile.

Diagnosed from zero trust's debug bundle (loki/hulk/teknium-kun backends
READY then idle-reaped, renderer stuck on "Waking up… → retries gave
up") and DanBennettUK's #89206 trace (profile socket accepts, closes
with messages=0, no resume RPC observed).

Both layers sabotage-proven: reverting the route publish fails the
lockstep/eviction tests; reverting the request-time routing fails the
wrong-socket dispatch test.
2026-08-18 14:09:08 -07:00
Teknium ced900a57d fix(desktop): file-path links in chat now open through the preview pane (#82140)
Assistant messages that link a file the agent wrote —
[report](/home/user/report.md), file://…, ~/…, C:\… — rendered as dead
anchors: file:// is blocked in the renderer, Streamdown's URL hardening
turns file:/~/ hrefs into "[blocked]" spans, and on a remote gateway the
path isn't on the viewer's disk at all. Issue #82140 proposed exposing
the Desktop connection mode to skills/MCP/plugins so EXTENSIONS could
emit different output per viewer; this fixes the symptom at the right
layer instead — the viewer surface resolves paths at VIEW time, so
extension output stays surface-agnostic and the same transcript works
from every machine that opens it.

- markdown-preprocess: routeFileLinksToPreview() rewrites filesystem-path
  links in prose to the renderer's existing hash-href doors —
  #preview/… (PreviewAttachment) for documents, #media:… for
  audio/video/image extensions. These pass URL hardening by design and
  resolve through normalizeOrLocalPreviewTarget / resolveMedia*Src:
  local connections read the file directly, remote connections fetch
  over the authenticated /api/fs bridge. Image syntax, fences, inline
  code, anchors, relative and http(s) links untouched.
- markdown-text: MarkdownLink routes any filesystem href that still
  reaches it (bypassing preprocess) to PreviewAttachment/MediaAttachment
  instead of a bare dead <a>.
- media.ts: export isFileMediaPath.

Live E2E (built app, CDP-driven, fixture session with links to a real
gateway-side file):
- BEFORE: [report.md] = dead <a href="/home/…"> (click: nothing),
  [notes](file://…) = "notes [blocked]" span, 0 preview affordances.
- AFTER: both render as attachment rows; Open preview shows the file's
  content in the preview pane; zero blocked spans; screenshots verified.

Closes #82140. Supersedes PR #82187 (connection-mode API): with view-time
resolution the extension layer no longer needs to know where the viewer
sits.
2026-08-18 14:09:01 -07:00
Teknium cb7dd6d2b8 fix(desktop): MEDIA-delivered .md opens in the preview rail, not as a download link
A `.md` delivered via MEDIA has no entry in MEDIA_BY_EXT, so mediaKind()
classified it as a generic 'file' and MarkdownLink rendered a download-style
anchor. Markdown is renderable content: route markdown document paths to
PreviewAttachment (source='tool-result'), which opens them in the right-rail
preview pane — where .md already renders with a rendered/source toggle and,
since #89381, full KaTeX math, tables, images, and links.

Resolves #84951 (the MEDIA delivery half; the rail-side rendering half landed
in #89381).
2026-08-18 14:08:52 -07:00
ethernet 1c4dc4cf5f fix(desktop): collapse the docked Bots tab with the sessions sidebar on narrow viewports
The Bots pane docks into the sessions zone but did not declare
collapsible. Below the sidebar-collapse breakpoint, the sessions pane
left the grid and the zone kept a stranded BOTS tab on screen.

The Bots pane now declares collapsible, so it leaves the grid with
its zone. The narrow edge overlay now mirrors the zone's tab strip
when the revealed pane has collapsed zone-mates. Without the strip,
only the first pane of the zone was reachable while collapsed. A
lone pane keeps the stripless overlay form.
2026-08-18 16:58:25 -04:00
hermes-seaeye[bot] f94cead337 fmt(js): npm run fix on merge (#89453)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-18 20:40:02 +00:00
ethernet 542e146b05 feat(desktop): show a hover close button on pane tabs
Each closeable horizontal tab now shows a close button when the
pointer is on the tab. A small gradient fades the button into the
tab surface, so long labels fade under it instead of a hard clip.
The gradient reads the tab's effective surface color. This color
tracks the hover and selection washes, so the fade is correct on
every theme.

Middle-click and Cmd-click still close the tab. Vertical rail tabs
keep those gestures and do not get the button.
2026-08-18 16:27:41 -04:00
Teknium 3701435306 test(desktop): align preview test comment with math-only normalizer 2026-08-18 13:18:41 -07:00
Peter 034589fa22 test(desktop): add behavior tests for .md preview math/table/img/link rendering
Export MarkdownPreview and cover the regression this PR fixes: KaTeX output present with no raw $$ delimiters left, GFM table structure, image alt/src, and external links opening with noopener noreferrer. Aligns the import order (perfectionist/sort-imports) and documents the mathPlugin module-scope setup to match the chat renderer.
2026-08-18 13:18:41 -07:00
Peter 6a3ef22346 fix(desktop): render math and missing markdown elements in .md file preview
The preview renderer for .md files was missing the math plugin, table/image/link components, and the markdown preprocessing pipeline that the chat transcript renderer has. Add KaTeX math rendering (inline $...$ and block $$...$$), table, image, and link support so file previews match the chat rendering.
2026-08-18 13:18:41 -07:00
Topher Ross c445cc4ebd test(desktop): fix lint in file-preview math render test 2026-08-18 13:18:41 -07:00
Topher Ross 0fa1212c97 test(desktop): prove file-preview math renders to KaTeX
DOM-level render test mounting the exact file-preview pipeline
(normalizeFilePreviewMath -> Streamdown + memoized math plugin) and asserting
`.katex` output for inline $.x.$, display $$..$$, and \(..\) delimiter math,
plus that a code-fence $.HOME.) stays code.
2026-08-18 13:18:41 -07:00
Topher Ross 93a2fae4e6 fix(desktop): render LaTeX math in file preview
The right-rail file preview rendered markdown through Streamdown with no
plugins and no math preprocessing, so $...$ and \(...\) stayed as raw
source text. Wire the memoized KaTeX plugin (same one the chat transcript
uses) into the preview and preprocess prose with a math-only normalizer that
skips chat-only transforms (reasoning-block stripping, session-ref linking,
preview-target stripping, URL autolinking, citation stripping) so a file's
prose, code fences, and inline code spans are never mangled.

- preview-file.tsx: import + module-scope createMemoizedMathPlugin, pass
  plugins={{ math }} to Streamdown, preprocess text with
  normalizeFilePreviewMath before render
- markdown-preprocess.ts: add exported normalizeFilePreviewMath
- markdown-preprocess.file-preview.test.ts: 8 tests (currency escaping,
  delimiter normalization, fence/inline-code preservation, verbatim
  citations/URLs/reasoning blocks)
2026-08-18 13:18:41 -07:00
Teknium 9005ad1ff7 test(desktop): pin selectable-text opt-in on group chat bodies
Follow-up to the root-cause fix: comment documenting why the opt-in
exists, plus a source-contract test proven to fail without the
attribute (sabotage run).
2026-08-18 12:37:38 -07:00
lazy-idler 56ddc036e4 fix(desktop): make group-chat message text selectable
Group-chat message bodies in the hermes-bots plugin render without the
data-selectable-text attribute, so they inherit the app-wide
body { user-select: none } and cannot be drag-selected or copied.
1:1 chat messages already carry the equivalent marker
(aui_assistant-message-content), so this aligns group chat with that
behavior by adding data-selectable-text="true" to the message body
wrapper.
2026-08-18 12:37:38 -07:00
HarishDarko 6d1e6113ac feat(desktop): add copy on Bot Mode group chat messages
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 12:37:38 -07:00
HarishDarko fac2b127ee test(desktop): require copy control on Bot Mode group chat lines
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 12:37:38 -07:00
hermes-seaeye[bot] d07be6e165 fmt(js): npm run fix on merge (#89403)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-18 19:28:51 +00:00
Teknium ce751ff584 fix(desktop): resume cold-opened Bot Chats even before selection settles (#89206 class)
The salvaged gate only requested an explicit session.resume when the target
stored session was ALREADY selected — but the field failure (#89206) is the
cold open, where the persisted route points at the bot's session while
selection/runtime/transcript are all unsettled. The precondition skipped the
resume exactly when it was needed, and the hydration wait timed out into a
blank pane.

- sdk/index.ts: judge the main surface AFTER openSession() navigates, and
  request a sequenced resume whenever the surface is not healthy (selected +
  runtime bound + expected transcript present). Redundant requests are
  consumed as no-ops by the route-resume effect.
- hermes-bots plugin: widen the fix to the sibling open path — the profile
  session browser (openProfileSession) now opens with the same
  awaitHydration/expectHistory contract as canonical Bot Chats, so a stale
  main surface gets the same explicit resume instead of a silent blank pane.
- Regression test for the cold-open shape, proven failing against the
  pre-fix gate (sabotage run) and passing with it.
2026-08-18 12:22:44 -07:00
citizendev9c d758fdbce5 fix(desktop): make Bot Mode switches hydrate canonical chats 2026-08-18 12:22:44 -07:00
Teknium 4349cbbb23 feat: group chats get an editable name and room picture, at creation and after
Bot Mode group chats were named once at creation and could never be
renamed, and rooms had no picture — only the fanned member faces.

- New Group Chat dialog: optional room picture (upload from device or
  image.generate, same 256px normalize pipeline as bot avatars).
- Room header: gear button opens Group settings — rename the group or
  set/replace/remove the picture after creation.
- renameGroupChat re-keys the room record (log, watermarks, sessions,
  members, picture), swaps the name in every local member's ui_meta
  groups list, follows open views to the new name, and rejects
  collisions instead of silently suffixing. Stored member sessions keep
  resuming by sid, so no history is lost.
- Room picture persists in the durable room record, hydrates on window
  load, and renders in the roster row (over the face pile), and the
  room header.
2026-08-18 11:41:25 -07:00
hermes-seaeye[bot] ff3c65f757 fmt(js): npm run fix on merge (#89375)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-18 18:38:22 +00:00
Teknium d8e2386912 feat: Capabilities view configures the selected profile on its own gateway
A profile belongs to one gateway, but the Capabilities surface (Skills /
Tools / MCP) always read and wrote through the window's active backend —
scoping to a remote-owned profile silently edited the wrong machine.

- hermes.ts: capability REST helpers accept a ProfileScope
  (string | {connectionId, profile}); ambient path now also carries the
  active registry connection tag (same contract as the cron helpers,
  #87882); profileScopeKey namespaces cache keys per connection.
- SkillsView: scope selector lists (profile, device) rows from the union
  agent roster on multi-connection desktops; new fixedConnection prop
  pins the whole view to a registered connection (plugin door), with a
  probe-able SkillsView.supportsFixedConnection flag.
- MCP tab: live reload.mcp RPC withheld for cross-backend scopes (it
  rides the active gateway socket and would reload the wrong machine).
- Bot Mode: remote-target drafts now get the live Capabilities tab
  pinned to the target machine via fixedConnection, feature-detected so
  older desktops keep the staged checklists.
- Config-record/hub-action stores accept scopes; cache keys fold in the
  connection id so two gateways' same-named profiles never share rows.
2026-08-18 11:31:43 -07:00
xxxigm d28f2ed05b test(desktop): cover remote Files panel download action
Lock the remote-file-only menu gate and the save-bridge success, cancel, and error paths.
2026-08-18 22:19:24 +07:00
xxxigm 6a843f95c8 fix(desktop): let Files panel download remote backend files
Remote mode only offered Copy Path, which is a Linux server path and useless on the local machine. Reuse the existing gateway save bridge so a selected file can land on this computer.
2026-08-18 22:19:14 +07:00
Brooklyn Nicholson 8911e2e0ed feat(desktop): route agent-opened and typed URLs through loopback reach
Both entry points into the browser pane now ask for a reachable URL
first, so the dev server an agent names over a remote gateway actually
loads, and typing that address by hand behaves the same.

Every fallback keeps the original URL, which leaves the pane free to
explain an address it still cannot reach.
2026-08-18 08:52:36 -05:00
Brooklyn Nicholson 956642c4fc feat(desktop): resolve preview URLs through main, which knows the transport
Only main can answer whether a loopback address is reachable: it holds
the live SSH connection and the profile precedence that picks it. The
renderer asks over IPC instead of guessing.

Forwards are scoped to the connection that authorized them and dropped
whenever it changes, so a new host never inherits a tunnel into the old
one. A failed forward logs and returns the original URL rather than
breaking the load path.
2026-08-18 08:52:36 -05:00
Brooklyn Nicholson 0600738f6d feat(desktop): loopback reach for the in-app browser over an SSH gateway
Opens a local->remote forward on demand so a gateway-side dev server URL
resolves to the gateway, not the laptop rendering the webview.

One lease per remote port, reused across pages and expired after 15
minutes; the port allowlist is deliberately absent, since the transport
we are already authenticated on is the security boundary and a curated
list just means the next framework default silently fails.

Lease and capability shape adapted from #87243.

Co-authored-by: tuancookiez-hub <tuancookiez-hub@users.noreply.github.com>
2026-08-18 08:52:36 -05:00
brooklyn! 2d579af869 feat(desktop): drop the Tools & Keys in-page search — ⌘K already finds credentials (#89192)
The scoped cmdk bar on Tools & Keys was a second search UI for the same
catalog ⌘K now serves. Cards stay listed; deep links still expand, scroll,
and flash the matching credential.
2026-08-18 13:47:59 +00:00
hermes-seaeye[bot] 8a754104ad fmt(js): npm run fix on merge (#89185)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-18 13:37:43 +00:00
brooklyn! eb6922a8cd Settings search is ⌘K: deep field/credential results everywhere, scoped palette + edge pill on Settings (#89167)
* feat(desktop): add scoped credential command search

* feat(desktop): add global settings search

* feat(desktop): settings search lives in the command palette, deep everywhere

Replace the PR's sidebar search input with a settings page inside the ⌘K
palette (the subPages mechanism themes/pets already use). ⌘K on the
Settings overlay opens the palette scoped to settings; Back/Esc/Backspace
step out to the root. The deep catalog — schema-driven config fields,
appearance controls, stored credentials — now also serves the ROOT
palette's typed results, replacing the static section-key list, so ⌘K is
the same settings search everywhere. The input refocuses on page swaps,
and a seed store carries the keystroke that opened the palette so
type-to-search never loses the first character.

* feat(desktop): search pill rides the Settings card's top edge and hands off to ⌘K

The trigger is a fake pill — pure chrome, not an input — centered on the
overlay card's top border, half off the card (a new OverlayView edgeBadge
slot; a sibling of the card since the card clips its own corners). Click
or just start typing to open the scoped palette; while the palette is up
the pill scales up and fades out, then fades back when it closes.
Type-to-search forwards printable keys (outside editable targets) with
the keystroke seeded into the palette filter.

* feat(desktop): plugins are searchable — ⌘K 'kanban' lands on Settings → Plugins with the row flashed

The catalog stopped at config fields and credentials; installed plugins
were invisible to search. Both stores now feed it — desktop plugins from
$pluginRecords, agent plugins via the same plugins.manage RPC the Plugins
page fires (deduped by the store's inflight guard, filtered by the shared
desktop-relevance curation, which moves to the store so both callers use
one predicate). Rows deep-link as ?tab=plugins&plugin=<id|key> and the
page scroll-flashes the row via the shared useDeepLinkHighlight hook.

* style(desktop): satisfy perfectionist import/prop ordering

* test(desktop): add setApiRequestProfile to KeysSettings hermes mock

---------

Co-authored-by: Adolanium <94890352+Adolanium@users.noreply.github.com>
Co-authored-by: Adolanium <adolanium@users.noreply.github.com>
2026-08-18 13:28:39 +00:00
hermes-seaeye[bot] 593ad08496 fmt(js): npm run fix on merge (#89178)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-18 13:19:44 +00:00
Brooklyn Nicholson 3e9859beca fix(desktop): explain why a remote agent's localhost URL won't load in the browser pane
The webview always runs on the user's machine, never on the gateway
host. Against a remote gateway an agent's http://localhost:5173 resolves
here — where that port is usually nothing, or somebody else's service —
and the pane just said 'Server not found'.

The load error now names the mismatch when the address is loopback and
the gateway is remote. It stays quiet on a local gateway and for
non-loopback hosts, which fail for ordinary reasons.

This explains the limitation rather than lifting it; forwarding the port
is a separate, larger job (see #87243 for the SSH case).
2026-08-18 08:13:08 -05:00
Brooklyn Nicholson a9a4a04070 feat(desktop): open the browser with a hotkey instead of only through chat
The Browser tab could only be summoned by asking the agent or clicking a
link. Adds Cmd+Shift+L and a command-palette row.

L is the location-bar chord every browser shares; plain Cmd+L is
already the terminal's selection shortcut, hence the shift. Pressing it
re-fronts whatever page is loaded rather than resetting it, so it reads
as 'show me the browser' and not 'start over'.
2026-08-18 07:51:43 -05:00
hermes-seaeye[bot] e02d1e41fc fmt(js): npm run fix on merge (#89135)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-18 11:22:45 +00:00
Brooklyn Nicholson 4d1cdf83c9 fix(desktop): claim Cmd/Ctrl+R in before-input-event so it works off macOS
The application menu is only installed on macOS (#77845) — everywhere
else it is set to null, which strips the role accelerators with it. A
menu-only Cmd+R meant Windows and Linux got no page reload at all.

Moves the claim into the same before-input-event hook Cmd+W already uses
for exactly this reason, and drops the accelerator from the menu item so
one keypress can't fire both paths on macOS.
2026-08-18 06:16:29 -05:00
Brooklyn Nicholson fae8e27a49 fmt(desktop): eslint --fix import and JSX-prop ordering 2026-08-18 06:16:29 -05:00
Brooklyn Nicholson d48b441506 feat(desktop): open web links in the in-app browser, Cmd-click for the system one
Clicking a link in the transcript, a url chip, or the terminal left
Hermes entirely. Now a web page opens in the browser pane — no context
switch to read a doc, and it's the surface the agent can see — while
Cmd/Ctrl-click and middle-click still escape to the real browser.

Routing lives in one helper so every link surface agrees. Anything that
isn't a web page (mailto:, file:, custom schemes) always hands off to
the OS, as do billing, OAuth, and local media, which need a real
logged-in browser.

The terminal keeps its own chord: it already spends Cmd/Ctrl on
activating the link at all, so Shift is what escapes there.
2026-08-18 06:16:29 -05:00