Commit graph

563 commits

Author SHA1 Message Date
Brooklyn Nicholson
ac4f596ca2 feat(desktop): pointer session drag/drop + row/tab menus with close others/right/all 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
e6fea77d14 feat(desktop): multi-session tiles — per-profile state, tile pane, pane mirror 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
f1379bd6c2 feat(desktop): routes, nav, and command palette as contributions 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
7ed7170960 feat(desktop): plugin manager, runtime loader, and plugins settings 2026-07-13 17:57:53 -04:00
ethernet
ef61436967 test(desktop): fix React act() warnings across all desktop test files
Add vitest.setup.ts with IS_REACT_ACT_ENVIRONMENT=true + auto-cleanup,
and wrap render()/fireEvent() calls in act() across 8 test files:

- provider-config-panel.test.tsx: wrap renderPanel + fireEvent in act
- providers-settings.test.tsx: wrap renderProvidersSettings + fireEvent
in act
- use-prompt-actions/index.test.tsx: add actRender helper, wrap all 38
render
  calls, wrap Harness handle methods (submitText/cancelRun/steerPrompt/
  restoreToMessage) in act at the onReady callback level
- attachments.test.tsx: make renderWithI18n async + wrap in act
- skills/index.test.tsx: make renderSkills async + wrap fireEvent in act
- messaging/index.test.tsx: make renderMessaging async + wrap fireEvent
in act
- gateway-connecting-overlay.test.tsx: wrap all render/rerender in act
- preview-pane.test.tsx: wrap render calls in act, make tests async

Reduces act warnings from 44 to 4 (remaining are fake-timer + pre-render
store mutation edge cases). All 146 test files / 1180 tests still pass.
2026-07-13 17:22:17 -04:00
ethernet
999d63b517 test(desktop): fix a handful of broken tests 2026-07-13 17:22:17 -04:00
ethernet
7c98c65163 cleanup(desktop): lint&fmt all 2026-07-13 17:22:17 -04:00
ethernet
0fac4cd8e9 test(desktop): fix session preview registry tests fails
clearing the registry sets localstorage values, so we must clear it
afterwards
2026-07-13 17:22:17 -04:00
ethernet
92025df393 test(desktop): fix attachment list test not querying the selector correctly 2026-07-13 17:22:17 -04:00
Brooklyn Nicholson
da52ffea14 fix(desktop): pin unscoped streams + clear view sync on switch
Live deltas from session A were attaching to session B after New Session
when events arrived without session_id — fallback used the newly focused
activeSessionId (#47709).

Pin unscoped stream events to the session that received message.start
(#48281). Also reset RAF-pending view staging on new/resume/create so a
stale background flush cannot repaint over the switched chat (#47743).

Co-authored-by: Ray <rayjun0412@gmail.com>
Co-authored-by: zapabob <1920071390@campus.ouj.ac.jp>
2026-07-13 16:10:33 -04:00
ethernet
6f7ee72be5 feat(desktop): base-branch picker for new worktree dialog
The sidebar "New worktree" button branched off whatever HEAD you were
on — now a filterable Popover+Command combobox lets you pick any local
or remote-tracking branch as the base, defaulting to origin/HEAD.

Backend listBaseBranches() queries refs/heads + refs/remotes via
for-each-ref, flags origin/HEAD (falling back to the local default for
no-remote repos). Mirrored on the Python REST API side
(base_branch_list + /api/git/base-branches route).
2026-07-13 14:53:34 -04:00
Brooklyn Nicholson
3615545bca fix(desktop): keep draft fallback rows across autosave echo
Add fallback only updates local editor state; complete pairs are filtered
before onChange. The post-#7b5ba205 resync effect then saw the unchanged
persisted chain and wiped the draft — button looked dead.

Ignore value updates that match the last chain we emitted; still resync
on real external changes (profile/config reload).

Co-authored-by: HexLab98 <liruixinch@outlook.com>
2026-07-13 12:57:34 -04:00
Teknium
d48bf743f2 fix(approval): scope smart deny owner overrides to one operation
Co-authored-by: Sergei Ivanov <kavi@local.hermes>
2026-07-13 04:31:55 -07:00
Luigi Razon
3510b18814 feat(desktop): add profile-aware approval mode control 2026-07-13 03:00:43 -07:00
emozilla
8c288760d0 fix(desktop): stop the submit drift guard from aborting every new chat
The #54527 context pin (7acaff5ef) snapshots the selected stored session
and route token at submit entry and aborts when either changes mid-flight.
But a NEW chat's create pipeline legitimately moves both: on success,
createBackendSessionForSend re-homes selection and navigates to the chat
it just minted. Judged against the pre-create draft baseline that read as
a user switch, so every first send of a new chat aborted before
prompt.submit — message dropped, no DB row persisted (row creation is
lazy, server-side in prompt.submit), and the window stranded on a route
whose REST reads 404 "Session not found" forever.

Fix: after a successful create, verify no one re-homed during create's
post-commit await via the active-session ref (a non-null return
guarantees create set it; every switch path retargets it synchronously),
then re-pin the drift baseline to the created chat. A mid-create switch
still aborts through create's own null return, or through the active-ref
check for the post-commit window. Re-pinning also restores the correct
stored-id association for the optimistic-message state updates, which the
pinned pre-create null had degraded.

Tests: red-first regression for the new-chat send, an abort case for a
switch landing in create's post-commit window, and the sleep/wake
new-chat stub made faithful to the real create (it sets the active ref
before returning — the inert stub is what let this ship green).
2026-07-13 01:55:39 -04:00
teknium1
7b5ba20547 fix(desktop): resync fallback editor after config reload
Some checks are pending
CI / Detect affected areas (push) Waiting to run
CI / Python tests (push) Blocked by required conditions
CI / Python lints (push) Blocked by required conditions
CI / TypeScript (push) Blocked by required conditions
CI / Docs Site (push) Blocked by required conditions
CI / Deny unrelated histories (push) Blocked by required conditions
CI / Check contributors (push) Blocked by required conditions
CI / Check uv.lock (push) Blocked by required conditions
CI / Lint Docker scripts (push) Blocked by required conditions
CI / Build&Test Docker image (push) Blocked by required conditions
CI / Supply-chain scan (push) Blocked by required conditions
CI / OSV scan (push) Waiting to run
CI / All required checks pass (push) Blocked by required conditions
CI / CI timing report (push) Blocked by required conditions
Deploy Site / deploy-vercel (push) Waiting to run
Deploy Site / deploy-docs (push) Waiting to run
2026-07-12 04:33:00 -07:00
Mark Vlcek
bf3667aeec test(desktop): cover the Fallback Models editor
Asserts each {provider, model} entry renders as its own row (the bug
produced "[object Object]"), that removing a row emits the remaining
entries, that adding a blank row never persists a partial pair, and the
empty-state hint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 04:33:00 -07:00
Mark Vlcek
21781d54ec fix(desktop): structured Fallback Models editor
Settings → Model rendered `fallback_providers` (a list of `{provider,
model}` objects) through the generic `list` config field, which does
`value.join(', ')` and stringified each entry to `[object Object],
[object Object]`.

Add a dedicated provider+model row editor (add/remove), sourced from the
same `getGlobalModelOptions()` the composer picker uses, that reads and
writes the `{provider, model}` chain. Half-filled rows are kept in local
state so the config autosave never persists a partial entry, and an
out-of-catalog model stays selectable so existing custom entries render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 04:33:00 -07:00
Brooklyn Nicholson
29c9dd99a4 fix(desktop): autosave Mixture-of-Agents preset edits
MoA was internally inconsistent: preset-level ops (set default / add /
delete) persisted on click, but reference-model and aggregator slot edits
sat behind a manual Save button. Debounce-persist slot/aggregator edits
like the rest of settings and drop the redundant button, so MoA is
uniformly autosave.
2026-07-12 06:03:25 -04:00
Brooklyn Nicholson
e0a650fa7d refactor(desktop): text-only workspace status menu + attribution
Align the workspace status-bar dropdown with the rest of the status bar: drop
the per-item icons (they mixed lucide size-4 with a Codicon 1rem glyph and were
the only status-bar menu carrying item icons), leaving text-only items on the
shared DropdownMenuItem primitive with default typography. The status-bar
trigger keeps its FolderOpen glyph, consistent with sibling items.

Also map true@supersynergy.de → Supersynergy in AUTHOR_MAP.
2026-07-12 04:56:24 -04:00
Maxim M
5fc08c0e0d feat(desktop): add workspace path status action 2026-07-12 04:55:29 -04:00
brooklyn!
8895335453
Merge pull request #63077 from NousResearch/bb/salvage-61950-nongit-groups
fix(desktop): preserve legacy non-git workspace groups (supersedes #61950)
2026-07-12 03:51:48 -05:00
harjoth
a5c0715835 fix(desktop): preserve sidebar workspace targets across new drafts
Squashed salvage of #45744 (@harjothkhara), rebased onto current main and
resolved against #58241 (which swapped the new-session cwd fallback to the
project-aware resolveNewSessionCwd).

An explicitly clicked sidebar workspace stays authoritative until session.create:
a one-shot $newChatWorkspaceTarget (null → detached, string → that folder) plus a
generation counter so a stale async `config.get project` normalization can't
overwrite a newer draft target. The start-workspace-session action is extracted
out of desktop-controller.tsx into a testable workspace-session-target module.

Integrated with #58241: the no-explicit-target branch now falls through to the
project-aware resolveNewSessionCwd() instead of the old workspaceCwdForNewSession.

Co-authored-by: harjoth <harjoth.khara@gmail.com>
2026-07-12 04:43:43 -04:00
Brooklyn Nicholson
ceb179163d fix(desktop): mirror Windows path identity in live overlay + WSL spelling
Addresses @teknium1's review of #61950:

- The desktop live overlay (workspace-groups.ts) matched cwd membership
  case-sensitively, so a fresh mixed-case/separator Windows session missed
  its explicit/auto project until the next backend tree refresh. Mirror the
  backend identity (isWindowsPath/comparisonSegments/pathKey) in isPathUnder,
  liveSessionProjectId, and overlayRepoLanes lane matching. Comparison-only —
  emitted ids/labels keep their spelling. POSIX stays case-sensitive.
- Backend _is_windows_path missed root-relative `\wsl.localhost\...` (single
  leading backslash), leaving that historical spelling case-sensitive. Classify
  any backslash-rooted path as Windows.

Tests: WSL-spelling collapse + explicit-project precedence (project_tree),
Windows/WSL live-overlay membership + POSIX case-sensitivity (workspace-groups).
2026-07-12 04:33:05 -04:00
brooklyn!
095b9eed38
Merge pull request #58241 from tianma-if/codex/fix-desktop-project-session-cwd
fix(desktop): preserve project cwd for new sessions
2026-07-12 03:29:07 -05:00
Brooklyn Nicholson
f8152d2323 feat(desktop): embeddable Gateway settings panel
Add an `embedded` flag to GatewaySettings (and a `bare` variant to
SettingsContent) that drops the page title/intro, Diagnostics row,
"Save for next restart", and the page gutters — so the same panel can be reused
inside a tighter surface without a second connection form to maintain. No change
to the standalone Settings → Gateway page (defaults off).
2026-07-12 04:05:52 -04:00
Brooklyn Nicholson
302a0dade8 chore(desktop): drop the dev-only "soft switch" preview from gateway settings
Remove the DEV-gated "Dev · soft switch" ListRow and its previewGatewaySwitch
helper. It was a temporary review affordance for exercising the soft-switch
reconnect; dead-stripped from production, but it doesn't belong in the tree.
wipeSessionListsForGatewaySwitch (the real path) and $gatewaySwitching stay.
2026-07-12 03:34:57 -04:00
Teknium
7550c594ce
feat(reasoning): add max and ultra effort levels (#62650) 2026-07-12 00:26:49 -07:00
brooklyn!
2043436af1
Merge pull request #63029 from NousResearch/bb/salvage-62022-tooltip
fix(desktop): stop empty mispositioned tooltip on terminal rail hover
2026-07-12 01:59:25 -05:00
Brooklyn Nicholson
be2b73e253 fix(desktop): stop empty mispositioned tooltip on terminal rail hover
A block-level label child (e.g. `flex`) collapses TooltipContent's inline
`box-decoration-clone` wrapper, so Radix measures a zero-size chip and parks
an empty black rectangle in the panel corner instead of by the trigger
(#62022). The terminal rail's hotkey labels and the preview row's two-line
label both hit this.

Harden the shared wrapper (`[&>*]:!inline-flex`) so any call site's direct
child renders inline-flex, add a reusable `TipHintLabel` for the common
text+hotkey label, and keep the preview row's label explicitly inline-flex.

Salvages #62139 (shared-component hardening + TipHintLabel) and #62073
(inline-flex call-site fixes + rail/preview coverage).

Co-authored-by: alelpoan <alelpoan@proton.me>
Co-authored-by: zapabob <1920071390@campus.ouj.ac.jp>
2026-07-12 02:53:03 -04:00
Brooklyn Nicholson
1a3b220651 fix(desktop-terminal): reopen terminal tabs in the last-used directory
A reopened tab restarted the shell in its original launch dir, so the fresh
prompt showed the wrong folder after a prior `cd` (the issue's "separate
thing" note). Track the shell's working directory and restart the PTY there.

Two independent signals feed a persisted per-tab restoreCwd:
- a main-side PTY cwd probe (shell-agnostic; /proc on Linux, lsof on macOS;
  Windows has no cheap per-process query so it falls back to the launch dir)
- cwd-reporting OSC sequences parsed in the renderer (OSC 7 file URIs, OSC 9;9
  ConEmu/Windows-Terminal paths) for shells configured to emit them

On relaunch the fresh shell boots in restoreCwd, falling back to the launch
cwd (then home) when it no longer exists.
2026-07-12 02:49:26 -04:00
Brooklyn Nicholson
817969f5a2 fix(desktop-terminal): trim trailing idle prompt on no-separator shells
cleanReviveSnapshot only dropped the trailing prompt when a blank separator
sat above it (starship add_newline), so shells that print the prompt with no
preceding blank line — default PowerShell (PS C:\..>), bash user@host:~$ —
kept the idle prompt in the saved buffer and showed a duplicate under the
fresh boot prompt on every relaunch of an *active* session.

An interactive shell always reprints its prompt after a command, so the tail
of an idle buffer is the prompt, never history. Drop the short block after a
blank separator when present, otherwise drop the trailing single-line prompt.
Command output is preserved; the fresh shell reprints the live prompt on boot.
2026-07-12 02:48:48 -04:00
Brooklyn Nicholson
164d9126cc fix(desktop-terminal): stop idle prompt accumulation across relaunches
An idle terminal tab (no command ever typed) grew one extra copy of the
shell's boot prompt on every close/reopen: persistSnapshot re-serialized a
buffer that was just the replayed old prompt plus the fresh shell's new
prompt, and cleanReviveSnapshot's blank-line trim can't strip prompts on
shells like default PowerShell that print no separator line.

Track real user input (keystrokes/paste, drag-and-drop paths, injected
commands) and, when a session had none, skip re-serializing. If the buffer
we loaded carried no real scrollback (empty or only a repeated prompt),
clear it so the next launch shows a single fresh prompt and any existing
accumulation heals; otherwise leave the prior snapshot untouched so real
history from an earlier active session survives an idle reopen.

Salvages #61584 (activity tracking) and #61577 (clearing content-free idle
buffers) into one path: it also heals already-polluted buffers, counts
drag-and-drop and injected input as activity, and never discards genuine
short command history (only empty/all-identical buffers are cleared).

Co-authored-by: alelpoan <alelpoan@proton.me>
Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
2026-07-12 02:48:48 -04:00
HexLab
f8054601a8
Merge pull request #62600 from HexLab98/fix/desktop-cron-no-agent-editor
fix(desktop): allow editing script-only (no_agent) cron jobs without a prompt
2026-07-12 02:32:29 -04:00
izumi0uu
ce5c1f9f79 fix(desktop): keep model picker switches session-scoped
Desktop active-session picker calls already pass a session_id, but the gateway's model switch persistence is controlled by parsed model flags. Add --session so the shared parser keeps live-session selections, including MoA virtual provider presets, out of profile config.yaml.

Constraint: config.set model values are parsed by hermes_cli.model_switch before persistence is decided.

Rejected: backend special-case for desktop session_id | it would duplicate existing --session semantics and widen the gateway surface.

Confidence: high

Scope-risk: narrow

Directive: Keep desktop model picker active-session switches explicit with --session; do not rely on session_id alone for persistence.

Tested: npm run test:ui -- src/app/session/hooks/use-model-controls.test.tsx src/app/shell/model-menu-panel.test.tsx

Tested: npm run typecheck

Tested: git diff --check

Not-tested: full pytest suite; change is desktop TypeScript/UI routing only.
2026-07-11 08:39:17 -07:00
HexLab98
7acaff5ef2 fix(desktop): pin session context during async prompt submit (#54527)
Snapshot the selected stored session and route token for the full async submit
pipeline so a mid-flight session switch cannot resume the wrong chat or
misroute the user's text. Includes regression tests.
2026-07-10 19:35:05 -07:00
joaomarcos
2afa92c74f fix(desktop): pin composer draft scope to the swap-effect owner, not the render ref
Fixes #54527 — a message typed into one TUI session could be silently
misrouted into (or overwritten by) another concurrently-open session.

Root cause: activeQueueSessionKeyRef is written on every render, but the
debounced draft-persist timer, the pagehide flush, and dispatchSubmit's
reject-restore path all read it lazily at async-resolve time instead of
capturing the scope that was active when the operation started. A session
switch landing between capture and resolve relabels one session's text
under the other session's key. A large paste widens the window (slower
synchronous render), which matches the original report.

Fix: introduce draftScopeRef, written only by the draft-swap effect (so it
always reflects the session whose text is actually loaded in the editor)
and read it instead of the render-time ref at both async write sites.
dispatchSubmit's restore() now uses the submittedScope already captured at
dispatch instead of re-reading the live ref.

Also adds isPendingDraftPersistCurrent as defense-in-depth: before the
debounce timer commits a write, it verifies its captured {scope, text}
pair is still the one on file. This is a no-op under the fix above (a
session swap or a newer keystroke already clears/replaces the pending
entry via clearTimeout), but turns any future regression that reintroduces
a stale/live-ref read at this call site into a dropped write instead of a
silent cross-session misroute.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 19:35:05 -07:00
brooklyn!
291eae63b7
Merge pull request #62016 from NousResearch/bb/desktop-vibe-hearts
feat: vibe reactions — floating hearts on affection, across CLI/TUI/desktop
2026-07-10 16:14:27 -05:00
Brooklyn Nicholson
1fa3886bce chore(desktop): remove the DEV Shift+H heart preview
The real trigger (core `reaction` event on affectionate messages) is live, so
drop the dev-only hotkey and its always-mounted listener.
2026-07-10 16:09:23 -05:00
ethernet
f7c9feb395 fix(desktop): only show slash popover when / is first char
The SLASH_TRIGGER_RE regex used (?:^|[\s]) as its left anchor, so typing
a / anywhere in the message (e.g. "hello /") opened the slash command
popover — even though slash commands only execute at the beginning of a
message. Anchor the regex strictly at position 0 (^) so the popover only
appears when / is the first character, matching the actual execution
semantics. The @-mention trigger is left untouched since those work
anywhere in the text.
2026-07-10 16:14:50 -04:00
Brooklyn Nicholson
3aaf7e3876 feat(desktop): TikTok-style vibe hearts on a reusable particle system
Add a glyph-agnostic ParticleField (float-up + organic sway/bank + springy
pop-in), skinned as pink pixel hearts. Hearts play on the pet when one is out
(in-window or popped out) and celebrate alongside; otherwise they rise from the
composer. A generic $petReaction bus mirrors the burst to the pop-out overlay
window so it reacts even while the app is minimized.

Consume the core `reaction` event to fire hearts on affectionate messages. DEV
Shift+H previews a burst.
2026-07-10 05:42:14 -05:00
Brooklyn Nicholson
b3bde1fbee feat(desktop): soft gateway switch + gateway-settings polish
Switching connection mode (local / cloud agent / remote) no longer
full-window-reloads into the cold-boot CONNECTING screen. The primary
backend is torn down in place (no renderer reload); the shell + Settings
stay up while session lists are wiped so sidebar skeletons retrigger, then
the socket re-dials and config/sessions refresh. Cold-boot CONNECTING
latches off after the first successful boot; the intentional teardown
suppresses the backend-exit toast. Dev affordance: a "Preview soft switch"
button under Gateway diagnostics (Electron has no ?query= entry).

Gateway settings UI brought in line with the rest of Settings:
- Mode cards use the shared selectableCardClass on an equal-height
  auto-rows-fr grid, stacking 1→3 (never an orphaned 2+1); titles wrap
  instead of truncating.
- Remote gateway's auth detail moves into a ? tooltip in the title; drop
  the redundant "connects to the one you choose" from the cloud card.
- textStrong buttons force px-0 so the underline sits flush with the label.
- Tooltip chip uses box-decoration-break: clone so the background hugs each
  wrapped line (bg only on the text), capped at max-w-64.

Fully i18n'd (en + zh; ja/zh-hant inherit via defineLocale).
2026-07-10 02:57:02 -05:00
Brooklyn Nicholson
24f6ed53fc simplify(desktop): inline the cloud setup link like the rest of the app
Match the sibling pattern (pet-generate/generate-unavailable.tsx): inline the
portal URL literal in the ExternalLink href instead of a one-off named const.
2026-07-10 02:01:40 -05:00
Brooklyn Nicholson
1c7f31a577 simplify(desktop): hardcode the Hermes Cloud setup link
Drop the portalBaseUrl→IPC→useState plumbing I added for the "create an agent"
link. HERMES_PORTAL_BASE_URL is a dev/staging-only override; threading it
through cloud.status() into React state just to build one link isn't worth it —
in prod it's always portal.nousresearch.com. Module-level constant instead.
2026-07-10 01:59:47 -05:00
Brooklyn Nicholson
703487d7a6 feat(desktop): point the no-agents link at the Hermes Cloud instance-setup page
Per review: the empty-state "create an agent" link went to the generic portal
agents list; point it at the Hermes Cloud create-instance flow
({portal}/cloud?setup=instance) instead. Derive the host from the portalBaseUrl
that cloud.status() already echoes so it honors HERMES_PORTAL_BASE_URL rather
than hardcoding a second copy of the portal host. Link text/copy → "Hermes
Cloud" (en + zh).
2026-07-10 01:55:44 -05:00
Brooklyn Nicholson
0ff097439e refactor(desktop): DRY the cloud helpers
Tighten the salvaged Hermes Cloud code with no behavior change:
- main: one `trimCloudOrg` projection reused by the success-echo and the 409
  org list (drop the duplicated map), and a `cloudLoginError()` factory for the
  three needsCloudLogin throw sites.
- renderer: a `cloudLoginLapsed()` predicate for the duplicated
  needsCloudLogin→signed-out check.
2026-07-10 01:47:46 -05:00
Brooklyn Nicholson
2d315d30f8 polish(desktop): normalize cloud-URL highlight match + correct signedIn doc
Cleanups on top of @ben's Hermes Cloud salvage:
- isConnectedAgent normalized both sides of the cloud-URL comparison (trim +
  drop trailing slash + lowercase). The saved URL is host-lowercased by
  normalizeRemoteBaseUrl but the discovered dashboardUrl is raw from NAS, so
  a host-casing difference could silently break the connected-highlight.
- DesktopCloudStatus.signedIn doc said "AT-or-RT"; it actually reflects the
  Nous portal Privy session (privy-token), not the gateway cookies.
2026-07-10 01:37:54 -05:00
Ben
c101207b99 feat(desktop): Hermes Cloud connection mode — one sign-in, agent discovery, silent connect
Adds a third "Hermes Cloud" gateway mode to the desktop app: one portal
sign-in auto-discovers the agents on your account and connects to any of
them with no second interactive prompt.

- Electron: widen connection mode to 'local' | 'remote' | 'cloud', routed
  through a centralized modeIsRemoteLike() so every resolution site treats
  cloud exactly like remote; portal discovery (GET /api/agents over the
  OAuth partition), Privy-cookie liveness, multi-org picker (NAS 409), and a
  silent per-agent /oauth cascade (load protected root, not /login).
- Persist a cloudOrg on the cloud block; unselect cloud on mode switch.
- Renderer: Hermes Cloud ModeCard + agent picker (signed-out/loading/empty/
  list), org picker, Change-org, connected-highlight + Connected pill.
- i18n (en + zh full; ja/zh-hant inherit via defineLocale), Cloud icon.
- IPC: hermes☁️{status,login,logout,discover,agent-sign-in}.

Salvage of #55402 onto current main: the original branch predates the
desktop electron .cjs -> .ts migration (39d09453f), so the electron half
was re-authored against the .ts files. Authorship preserved.

cloud-auto-discovery Phases 3 + 4.
2026-07-10 01:37:43 -05:00
ethernet
56a8e81d33 cleanup(desktop): npm run fix for fmtting
we should run this as part of merges at some point :)
2026-07-08 16:24:16 -07:00
ethernet
39d09453f9 feat(desktop): ts-ify everything 2026-07-08 16:24:16 -07:00