Commit graph

804 commits

Author SHA1 Message Date
Teknium
b14d75f8af
fix(update): prevent and self-heal half-updated venvs on Windows (#57659)
Root-causes the July 2026 Windows incident chain (locked _brotlicffi.pyd /
_sodium.pyd during install, then 'No module named annotated_doc' with
'hermes update' insisting 'Already up to date!'):

- hermes update: probe venv core imports even when the checkout is current;
  a half-updated venv (dep sync killed mid-flight by a locked .pyd) is now
  detected and repaired instead of being reported as up to date
- hermes update (Windows): after pausing gateways, refuse to mutate the venv
  while other processes run from the venv interpreter (the Desktop backend
  runs as python.exe so the hermes.exe shim guard never saw it); --force
  keeps the old behavior
- install.ps1 venv stage: disarm gateway autostart Scheduled Tasks before
  the kill sweep (they respawn the gateway inside the kill->delete window),
  make the sweep a bounded loop requiring 3 clean passes, and rename-then-
  delete the old venv (a rename succeeds even with mapped DLLs) with stale-
  dir cleanup on the next run
- desktop updater: 'venv shim still locked after 15s' now ABORTS the update
  hand-off (restarting our backend, surfacing the holder to the user)
  instead of 'proceeding anyway (force)' into guaranteed venv corruption;
  the unlock wait also re-kills respawned backends each poll tick
2026-07-03 03:24:08 -07:00
brooklyn!
44cb0ea9e6
Merge pull request #57658 from NousResearch/bb/readtitle-race
fix(desktop): guard link-title readTitle against destroyed windows
2026-07-03 05:16:23 -05:00
Brooklyn Nicholson
359518beac fix(desktop): guard link-title readTitle against destroyed windows
Grace and timeout timers in runRenderTitleJob can call getTitle after
finish() tears down the hidden BrowserWindow, throwing in the main
process when the Artifacts page resolves many link titles concurrently.
2026-07-03 05:13:23 -05:00
Brooklyn Nicholson
dfb28cc631 refactor(desktop): only poll the transcript when it's a messaging session
The active-transcript poll armed a 5 s timer for every selected session
and no-op'd inside the tick for local chats (already live over the
websocket). Derive activeIsMessaging and gate the effect on it so local
chats never spin an idle timer.
2026-07-03 04:38:45 -05:00
Brooklyn Nicholson
52d0d671e7 fix(desktop): poll messaging sessions so platform traffic appears live
Inbound Telegram/WeChat/Discord messages are written by the background
gateway, not the desktop websocket that drives local chats. Without
explicit polling the messaging sidebar and the open transcript stay
frozen until the user manually refreshes.

Desktop:
- MESSAGING_POLL_INTERVAL_MS (10 s): interval poll of the messaging
  session list so new platform sessions surface automatically.
- ACTIVE_MESSAGING_SESSION_POLL_INTERVAL_MS (5 s): poll the currently-
  viewed messaging transcript and re-hydrate the chat state when the
  FNV-1a signature changes (hash covers role + timestamp + content).
- sameCronSignature now compares lineage_root_id / source / profile /
  preview / message_count / last_active / ended_at so stale previews
  and activity times are no longer silently ignored.
- sessionMatchesStoredId helper de-dups the id / _lineage_root_id check.
- refreshMessagingSessions exposed from useSessionListActions so the
  controller can use it in the poll effect.

Gateway:
- SessionStore._compression_tip_for_session_id: look up the latest
  compression continuation for a session id.
- SessionStore._heal_compression_tip_locked: rewrite a stale entry to
  the compression child before returning it, so a restart or failed send
  no longer leaves the store pinned to the compressed parent.

Co-authored-by: lawyer112 <lawyer112@users.noreply.github.com>
2026-07-03 04:29:22 -05:00
Teknium
c7103c637c
feat(desktop): CLI/dashboard parity — skills hub, MCP test/toggle/catalog, maintenance ops, log filters (#57441)
* feat(desktop): CLI/dashboard parity — skills hub browser, MCP test/toggle/catalog, maintenance ops, log filters

Brings desktop GUI to parity with hermes skills/mcp/doctor/backup/debug-share/
curator/memory CLI commands and the dashboard's System + Skills-hub pages:

- Skills page: new Browse Hub tab (search official/GitHub/community sources,
  preview SKILL.md, security scan verdicts, install/update with live action log)
- MCP settings: connection test (tool listing), per-server enable/disable
  toggle, and a Catalog tab installing Nous-approved MCP servers with env prompts
- Command Center: new Maintenance section (doctor, security audit, backup,
  debug share links, curator status/pause/run, memory file status + reset)
- Command Center system logs: file (agent/errors/gateway/desktop), level, and
  substring filters instead of a fixed agent.log tail
- hermes.ts API client + types for all the above; en/zh locale strings (ja and
  zh-hant inherit via defineLocale)

* feat(desktop): backend model catalogs in toolset config — hermes tools parity

Completes the `hermes tools` parity gap: after picking an image/video
generation backend the CLI runs a model picker (e.g. FAL's multi-model
catalog with speed/strengths/price); the desktop toolset drawer now has the
same flow as a radio-card list.

- web_server: GET /api/tools/toolsets/{name}/models (catalog + current +
  default for the active or named provider row) and PUT .../model
  (validated write to image_gen.model / video_gen.model), reusing the CLI's
  plugin catalog helpers so GUI and `hermes tools` stay in lockstep
- desktop: ModelCatalogPicker in ToolsetConfigPanel — per-model cards with
  speed/strengths/price, in-use + default badges, disabled until the
  backend is the active one; provider selection now mirrors is_active
  locally so the catalog unlocks without a refetch
- tests: 3 backend endpoint tests (catalog shape invariants, persist +
  validation), 2 component tests, 2 API-contract tests; en/zh strings
2026-07-03 01:02:47 -07:00
Brooklyn Nicholson
42bc07d107 fix(desktop): cancel downloads triggered by link-title fetch window
The hidden BrowserWindow used by fetchLinkTitle to scrape page titles
had no will-download handler on its session.  When a link artifact URL
responds with Content-Disposition: attachment, Electron fires will-download
and the file is saved for real — explaining the spurious download on the
Artifacts page.

Add guardLinkTitleSession() (parallel to the existing audio-mute guard for
#49505) that installs a will-download handler which immediately cancels
every download item on the hermes:link-titles session.  Call it from
getLinkTitleSession() right after the request-type blocklist is wired up.
2026-07-03 01:46:27 -05:00
Brooklyn Nicholson
7e9e13fe55 fix(desktop): use symbol-namespace codicon for Model settings nav 2026-07-03 01:37:41 -05:00
Brooklyn Nicholson
88b720ebb4 fix(desktop): use gift codicon for update-available toast
Add optional notification icon override and use codicon-gift on the
update-ready toast so it reads as a present rather than generic info.
2026-07-03 00:56:16 -05:00
Brooklyn Nicholson
3e21cfdebb fix(desktop): clear stale active todos on turn end AND on rehydration
A turn that ends without a final `todo` update left the composer "Tasks N/M"
panel pinned with its last item stuck pending/in_progress, and it survived
restarts because the panel is read back from stored session history.

Two coupled fixes (the first alone is undone by the second path):

- Turn end: clear a still-active todo list on `message.complete` and on a
  terminal `error` (new `clearActiveSessionTodos` — active lists only; a
  finished list keeps its short linger so the last checkmark still lands).
- Rehydration: `hydrateFromStoredSession` runs *after* a turn completes, so an
  "active" stored list is stale, not in-flight. It now restores only a
  *finished* list (via new `todosForHydration`) and drops anything still
  active — otherwise it re-pinned the panel right after the turn-end clear and
  resurrected it on every restart.

Salvages #52996 (@0disoft): the fix shape (clearActiveSessionTodos on turn
completion, preserving the finished-list linger) is carried forward and ported
onto the current use-message-stream/ folder split (gateway-event.ts), then
extended to the rehydration path per review.

Co-authored-by: 0disoft <rodisoft1@gmail.com>
2026-07-02 21:22:45 -05:00
Brooklyn Nicholson
e40175f069 fix(desktop): stop macOS Tahoe misplacing the traffic lights
On macOS Tahoe (Darwin 25+), a nonzero titleBarOverlay height makes
setWindowButtonPosition() miscalculate the native traffic-light position
(electron#49183), shoving the lights into the left titlebar tools. Pass
height 0 there so the lights land at the configured inset; the renderer
paints its own drag strips, so nothing is lost. Pre-Tahoe is unchanged.

Gate on the truthful Darwin kernel major (25 = Tahoe) rather than the
product version, which macOS reports as 16 or 26 depending on build SDK.
2026-07-02 21:14:28 -05:00
Brooklyn Nicholson
66c3d595d1 feat(desktop): cap overlay inner-page width at 75rem
Add a shared PAGE_MAX_W (1200px) and center OverlayMain within its pane
so settings and command center bodies stay readable instead of sprawling
on wide/ultrawide displays.
2026-07-02 20:40:57 -05:00
Brooklyn Nicholson
4aad27b751 fix(desktop): extend startup long-timeout to the whole boot data burst
Broadens Tranquil-Flow's profile-startup timeout fix (#48518) from getProfiles
+ refreshActiveProfile to the rest of the calls the desktop fires during
connect: /api/config, /api/config/defaults, /api/model/info, /api/model/options,
/api/cron/jobs. On a profile-heavy or remote install any of these can exceed
the 15s DEFAULT_FETCH_TIMEOUT_MS while the backend is alive-but-busy (e.g.
list_profiles walks the skill tree per profile), surfacing as the spurious
"Timed out connecting to Hermes backend after 15000ms" that hangs the UI
(#48504).

Uses the surgical per-call mechanism (renamed STARTUP_PROFILE_REQUEST_TIMEOUT_MS
→ STARTUP_REQUEST_TIMEOUT_MS) rather than raising the global default (the
alternative in #48526): the liveness poll /api/status and all interactive/
runtime calls keep the short default, so a genuinely-dead backend is still
detected fast and the boot readiness probe (waitForHermes) is untouched.

Supersedes #48518 (carried as the base commit) and #48526 (global-default
raise). Fixes #48504.

Co-authored-by: YapBi <129007007+HeLLGURD@users.noreply.github.com>
Co-authored-by: Tranquil-Flow <66773372+Tranquil-Flow@users.noreply.github.com>
2026-07-02 19:36:43 -05:00
Tranquil-Flow
584d3ae532 fix(desktop): extend profile startup REST timeouts (#48504) 2026-07-02 19:36:43 -05:00
Brooklyn Nicholson
4d88facfc1 fix(desktop): let settings content use full pane width
Remove the max-w-4xl wrapper from SettingsContent so every settings
page can use the available overlay width.
2026-07-02 19:31:57 -05:00
Brooklyn Nicholson
5a6720b884 fix(desktop,tui-gateway,zai): stop thinking-off from reverting to medium
A Z.ai desktop user reported thinking reverting to medium after one turn,
burning ~200% of a week's credits in 4 days despite reasoning_effort: false
in config.yaml. Four compounding bugs:

- _session_info reported reasoning_effort "" for disabled reasoning,
  indistinguishable from unset — the desktop adopted it after the first
  turn, wiping its sticky "thinking off" pick so every later chat
  reverted to the default effort.
- config.set key=reasoning always wrote agent.reasoning_effort to global
  config.yaml, so every desktop model-menu selection (preset.effort ??
  'medium') clobbered the user's configured value. Now session-scoped
  like the messaging gateway's /reasoning, landing on
  create_reasoning_override so lazily-built sessions keep it too.
- YAML `reasoning_effort: false`/`off`/`no` (boolean False) was coerced
  to "" by every loader's `str(x or "")`, silently re-enabling thinking.
  parse_reasoning_effort now treats False/"false"/"disabled" as
  {"enabled": False}; loaders (tui gateway, gateway, cli, cron,
  delegate) pass the raw value through. The desktop config reader also
  crashed on the boolean (false.trim()), aborting voice/STT settings.
- The zai provider profile never sent thinking on the wire, and GLM-4.5+
  defaults to thinking ON server-side — so disabling reasoning was a
  silent no-op on direct Z.ai, the actual token burner. The profile now
  emits extra_body.thinking {"type": "enabled"|"disabled"} for
  thinking-capable GLM models, mirroring the DeepSeek profile.

Also: /new (session reset) now carries reasoning_config across the
rebuild like model_override; config.get reasoning prefers the session's
live value and maps a config False to "none"; Settings shows "Off"
instead of a blank select for hand-written false.
2026-07-02 15:23:47 -05:00
Tranquil-Flow
c3f06a8fda fix(desktop): refresh profile rail after deletion (#49289) 2026-07-02 15:18:49 -05:00
liuhao1024
c5e8a60b0a fix(desktop): skip ensureBackend after profile-delete teardown to prevent respawn loop
When the renderer sends a DELETE /api/profiles/{name} request, the IPC
handler tears down the profile's pool backend (or primary backend) via
prepareProfileDeleteRequest.  However, the very next line calls
ensureBackend(profile), which spawns a fresh pool backend for the just-
deleted profile.  The new backend's startup path calls ensure_hermes_home(),
which recreates the profile directory — defeating the deletion and leaving
the process as a zombie.

On the next Desktop restart the cycle repeats: the profile directory exists,
the Desktop spawns a backend, the backend recreates the directory after
deletion, and PIDs accumulate indefinitely.

Fix: make prepareProfileDeleteRequest return the torn-down profile name.
The IPC handler uses this to route the DELETE to the primary backend
instead of spawning a new pool backend for the deleted profile.

Fixes #52279
2026-07-02 15:18:49 -05:00
Jaaneek
5ef0b8acb0 feat(auth): make xAI Grok OAuth device-code-only, drop loopback login
Replace the loopback/PKCE-callback server and manual-paste fallback with
the RFC 8628 device-code flow as the only xAI Grok OAuth login path. The
flow works in headless/SSH/container sessions with no 127.0.0.1 listener,
shrinking the local attack surface.

- Poll the token endpoint with server-provided interval, honoring
  slow_down and expires_in; store tokens with auth_mode
  oauth_device_code.
- Adaptive proactive refresh skew for short-lived device-code JWTs;
  rotated tokens sync back to auth.json, the global root store, and the
  credential pool (no refresh-token replay).
- Clear source suppression on successful re-login (CLI + dashboard) and
  drop the duplicate dashboard pool entry so exactly one seeded
  device_code entry exists.
- Use the shared device_code source name for consistency with the
  nous/codex device-code providers.
- Desktop: remove the loopback OAuth flow states and dead type variants;
  pkce providers' sign-in URL selection is unchanged.
- Docs (EN + zh-Hans) rewritten for device-code login; drop the deleted
  --manual-paste flag from documented commands.
2026-07-02 13:17:41 -07:00
brooklyn!
6cffc37b5a
feat(desktop): collapse profile rail to a select past 13 profiles (#57306)
The colored-square rail stops scaling once a user racks up many profiles:
tiny drag targets and an endless horizontal scroll strip. Past a threshold
(13) the rail swaps the squares for a compact select dropdown — same active
tint + initial glyph, minus the drag-reorder / long-press-recolor / per-row
context menu that only make sense at small counts. Two render paths behind
one flag; the left default↔all toggle, the "+" create button, and Manage
stay put in both. Rename/delete/color remain reachable via Manage.
2026-07-02 19:15:07 +00:00
helix4u
a9b5598909 fix(desktop): load remote model options before session
Both Desktop picker surfaces (status-bar model menu, settings/onboarding
dialog) only asked the connected gateway's model.options once a session
existed; before that they fell back to the Desktop REST/global options, which
can't see virtual providers a remote gateway exposes — including the MoA
presets from #53817. Centralize the fetch rule in requestModelOptions(): prefer
the connected gateway whenever one exists (no session_id needed — the RPC
resolves disk config), REST only when no gateway is connected.

The status-bar MoA preset section now renders from the same model.options
payload (the virtual `moa` provider row) instead of the local /api/model/moa
REST config, so remote presets appear correctly; the row is filtered out of
the main provider groups so presets don't list twice. Preset selection keeps
the persistent switchTo path from #56417 and drops the vestigial session gate —
like regular model rows, a pre-session pick ships on the next session.create.

Fixes #53817.

Rebased and reconciled with #56417 (persistent MoA selection), which landed
after this PR was opened and covered its one-shot-/moa half.
2026-07-02 12:50:46 -05:00
Brooklyn Nicholson
fe82b3a774 fix(desktop): read attachment previews local-first in remote mode
attachImagePath fetched its thumbnail through readDesktopFileDataUrl, which in
remote mode routes every read to the gateway fs bridge. Paperclip picks,
clipboard saves, and OS drops always produce paths on the LOCAL machine, so the
gateway read 404s — toasting "image preview failed" and dropping the thumbnail
even though the attach itself works (upload reads local bytes via the Electron
bridge). Read the local bridge first and fall back to the remote facade, which
still serves in-app drags from the remote project tree. Local mode is
unchanged (the facade already reads locally there).

Follow-up to #56572, which restored the remote paperclip picker and made this
path reachable from the picker as well.
2026-07-02 12:36:03 -05:00
helix4u
c19bfb50ad fix(desktop): restore remote file picker attachments 2026-07-02 12:32:30 -05:00
Brooklyn Nicholson
8da0a56ba8 style(desktop): fix pre-existing import-order lint in use-prompt-actions 2026-07-02 12:28:30 -05:00
Brooklyn Nicholson
931e2356af feat(desktop): /journey opens the memory graph overlay instead of printing text 2026-07-02 12:27:52 -05:00
Brooklyn Nicholson
42ca438131 style(desktop): fix import ordering + padding lint in remote-artifact files 2026-07-02 12:22:47 -05:00
helix4u
03406ae255 fix(desktop): restore remote artifact rendering 2026-07-02 12:22:47 -05:00
David Metcalfe
9738870489 fix(desktop): call checkUpdates() in startUpdatePoller so version pill auto-populates
startUpdatePoller() only called checkBackendUpdates() — never checkUpdates().
The statusbar version pill reads $updateStatus (set by checkUpdates()), so the
commit-behind counter stayed null after restart. It only appeared when the user
manually clicked the pill, which triggered checkUpdates() via openUpdateOverlayFor.

Added void checkUpdates() in three places alongside the existing
checkBackendUpdates() calls:
- On startup in startUpdatePoller()
- In the 30-minute setInterval callback
- In the onFocus handler

checkUpdates() uses the Electron IPC bridge (local git check), not the gateway,
so no mode gating is needed. The existing $updateChecking atom guard prevents
double-fire on overlap.

Fixes #53079
2026-07-02 11:52:57 -05:00
Brooklyn Nicholson
fb44b519d7 fix(desktop): parse multiline slash commands + hand degenerate payloads back
parseSlashCommand used /^(\S+)\s*(.*)$/ where `.` can't cross a newline and
`$` anchors end-of-string, so any slash command whose arg contained a newline
(/goal <multi-line text>, a skill command with a long pasted context) failed
the whole match, parsed as an empty name, and rendered "empty slash command"
while the payload vanished — cleared from the composer and absent from the
Up-arrow history ring, which only derives from sent user messages.

- name now splits on any whitespace ([\s\S]* arg), matching the CLI and the
  gateway's split(maxsplit=1); multiline args flow to slash.exec intact
- the residual empty-name branch (bare "/", "/ text") restores the submitted
  text to the composer draft instead of eating it

Fixes #41323. Fixes #55510.
2026-07-02 11:36:21 -05:00
teknium1
36b7e5e9cc fix(desktop): guard configured-cwd override against active sessions
Follow-up to the #39227 salvage: config refreshes fire mid-session too
(gateway events, settings saves), so applying terminal.cwd
unconditionally would yank the workspace out from under an attached
session. Gate the override on activeSessionIdRef like the sibling
reasoning/tier settings, keep branch refresh on the live cwd, and add
coverage for the active-session path. Also lint-polish the new test
file (typed config mock, prettier formatting).
2026-07-02 01:58:19 -07:00
Sahibzada Allahyar
d2de9580e1 fix(desktop): prefer configured workspace cwd 2026-07-02 01:58:19 -07:00
Teknium
9be292f1e6
fix(desktop): make MoA preset selection persistent, not one-shot (#54670) (#56417)
The MoA preset section in the composer model dropdown presented presets like
persistent model selections, but selecting one dispatched the one-shot `/moa`
command (command.dispatch name=moa) — it ran a single turn through MoA and then
silently reverted to the prior model. The user saw MoA context for one message,
then it vanished with no indication.

Route MoA preset selection through the same persistent path real provider
selections use: onSelectModel({ model: preset, provider: 'moa' }) →
config.set model="<preset> --provider moa" → the gateway's switch_model. The
check mark now reflects the real current selection (currentProvider === 'moa'
&& currentModel === preset) instead of transient local state, and the
now-unused activeMoaPreset state is removed.

Tests: new model-menu-panel.test.tsx (2) — selecting a preset calls
onSelectModel with provider 'moa' (persistent), and the check renders on the
active preset. tsc -b clean.
2026-07-01 06:40:20 -07:00
Teknium
1641441837
fix(desktop): don't false-timeout long prompt.submit turns (MoA, deep reasoning) (#56411)
prompt.submit is fire-and-forget — turn completion is signaled by stream /
message.complete events, not the RPC return — but it inherited the generic 30s
default RPC timeout. A turn that legitimately takes >30s to ACK (MoA presets
running references + aggregator in series, deep reasoning, large tool chains)
popped a false 'request timed out: prompt.submit' toast at 30s while the turn
was still running and streamed its real answer in 60-120s later (#55024).

Add PROMPT_SUBMIT_REQUEST_TIMEOUT_MS (1_800_000 = the backend's
agent.gateway_timeout ceiling) and pass it on all four prompt.submit call sites
(submit, resume-recovery retry, regenerate, rewind), mirroring the existing
SESSION_LIST_REQUEST_TIMEOUT_MS opt-out precedent. Widen the GatewayRequest
type (+ the inline requestGateway prop type) to carry the optional timeoutMs the
runtime impl already accepts.

Tests: use-prompt-actions/index.test.tsx 34/34 pass; tsc -b clean.
2026-07-01 06:33:47 -07:00
brooklyn!
44ddc552f5
Merge pull request #56029 from NousResearch/fix/desktop-drop-folder-attach 2026-06-30 22:08:21 -05:00
emozilla
a488fcf107 fix(desktop): detect dropped folders so they attach as @folder refs
Dragging a folder from Explorer/Finder into the composer failed with "file
not found on gateway and no data_url provided", on local gateways too.

extractDroppedFiles tagged every OS drop as a File-bearing entry, so
partitionDroppedFiles routed the folder to the upload pipeline and
file.attach tried to read a directory's bytes — a directory has none, and
there is no data_url to send. This regressed in 4906dcfc25, which routed
OS drops through file.attach to reach a remote gateway but did not exclude
directories, which also carry a File handle.

Detect directories at drop time with DataTransferItem.webkitGetAsEntry(),
the only synchronous way to tell a dropped folder from a file. A dropped
directory now becomes a path-only entry with isDirectory set, which routes
to a @folder: ref exactly like the folder picker, instead of the file
upload path that cannot stage a directory.

Process transfer.items before transfer.files: webkitGetAsEntry lives only
on items, and claiming the folder's path there first lets the files
fallback dedup skip the same entry (Chromium lists a dropped folder in
both). Path-based dedup and the getPathForFile resolution are preserved.
2026-06-30 22:36:37 -04:00
Brooklyn Nicholson
c6ba4b229e fix(desktop): lift memory-graph dark-mode line + outline alpha
Dark-mode connector lines and ring outlines read too faint. Double the
two live knobs: MODE_DEFAULTS.dark.lineAlpha 0.12->0.24 and
RING_PARAMS.dark.ringAlpha 0.03->0.06. (MODE_DEFAULTS.ringAlpha is dead;
the outline is drawn from RING_PARAMS.)
2026-06-30 16:44:04 -05:00
brooklyn!
b77ebb5999
Merge pull request #55871 from NousResearch/bb/desktop-composer-trigger
refactor(desktop): extract the composer trigger/completion engine into useComposerTrigger
2026-06-30 15:31:23 -05:00
Brooklyn Nicholson
e774d7fbf9 refactor(desktop): extract the composer trigger/completion engine into useComposerTrigger
The last welded composer engine. The `@`/`/` trigger state, detection
(refreshTrigger), the adapter-driven item list + its effects, popover selection,
closeTrigger, commitTypedSlashDirective, and the contentEditable chip insertion
(replaceTriggerWithChip) move verbatim into hooks/use-composer-trigger.ts behind a
hook that takes the editor refs + the two completion sources (at/slash). ChatBar's
input/keydown/keyup paths + the popover render consume the returned API; the
keydown navigation block stays in place (no key-handling restructure), and
triggerKeyConsumedRef is exposed so keyup still skips its post-consume refresh.

ChatBar 1,248 → 1,047. Behaviour-preserving: typecheck 0 errors, eslint clean,
and the composer DOM repro suite (slash-nav, enter-submit, IME composition,
trigger-popover) is green — the documented IME/caret/focus edge fixes ride along
verbatim. (The 1 attachments.test.tsx failure is pre-existing on main.)
2026-06-30 15:27:36 -05:00
brooklyn!
7098c2b71e
Merge pull request #55868 from NousResearch/bb/desktop-fallback-model
refactor(desktop): split tool fallback-model into a folder with leaf modules
2026-06-30 15:26:26 -05:00
Brooklyn Nicholson
322138df51 refactor(desktop): split tool fallback-model into a folder with leaf modules
fallback-model.ts (1,696) folded into assistant-ui/tool/fallback-model/ with
three cohesive, self-contained leaf modules extracted (verbatim moves):

- types.ts (83)   — the shared tool-view types/interfaces.
- format.ts (133) — pure value formatting/parsing (isRecord, compactPreview,
  clampForDisplay, prettyJson, parseMaybeObject, unwrapToolPayload, numberValue,
  contextValue, formatDurationSeconds).
- targets.ts (75) — url/path/preview detection + disclosure ids (looksLikeUrl,
  findFirstUrl, hostnameOf, isPreviewableTarget, toolPart/GroupDisclosureId).

index.ts (1,434) keeps the tool-specific assembler (TOOL_META, titles, the count
machinery, subtitle/detail/diff, buildToolView) and re-exports the leaf modules,
so consumers importing `./fallback-model` are unchanged (folder index resolution)
— no importer or channel edits needed. The count/result/detail helpers reach
across each other around buildToolView, so they stay together to avoid a circular
split; the three leaves are the clean cut.

Behaviour-preserving: typecheck 0 errors, eslint clean, fallback-model test 24/26
(the 2 browser_navigate title failures are pre-existing on main — `hostnameOf`
intentionally includes the pathname; verified identical on the un-split file).
2026-06-30 15:24:33 -05:00
Brooklyn Nicholson
05ed553c53 fix(journey): satisfy desktop eslint sort rules in star map
The merged #55859 left the star-map NodeContextMenu import and the
canvas onContextMenu prop out of perfectionist's required order, failing
`npm run lint` in the desktop workspace. Reorder both.
2026-06-30 15:16:18 -05:00
Brooklyn Nicholson
bb67dad07a feat(journey): edit/delete in TUI overlay and desktop star map
TUI /journey gets d/e with confirm + $EDITOR; desktop gets a right-click
context menu with inline edit modal. Both refresh the graph after mutation.
Extract openInEditor into the shared TUI editor helper.
2026-06-30 15:07:24 -05:00
Brooklyn Nicholson
9e4ed4d7a9 feat(installer): redesign the Tauri setup shim — design system, OS theme, granular updates
Bring Hermes-Setup.exe's UI onto the shared design tokens (self-contained, no
desktop-component coupling) and add two capabilities:

- design: flat stage rows (running step opaque, rest muted), neutral check /
  destructive cross, running fourier-flow Loader, hairline --stroke-nous
  borders, fill-less log panel; ported BrandMark (nous-girl) + HackeryButton +
  Loader standalone; re-synced button variants; de-boxed success/failure.
- theme: follow the OS light/dark via the authoritative Tauri window theme
  (theme.ts + onThemeChanged, core🪟allow-theme), with Nous dark seed
  colors in styles.css so the --ui-*/--dt-* chain derives correctly.
- updates: split the monolithic "Updating" bar into handoff -> download ->
  rebuild (+ install on macOS) stages via a shared update_stages() builder, a
  live elapsed timer on the running stage, and a dev-only fake-boot preview
  (gated on import.meta.env.DEV, stripped from the shipped bundle).
2026-06-30 14:46:28 -05:00
Brooklyn Nicholson
8fe8c2d6c4 style(desktop): bring the install & update overlays onto the design system
Align the first-launch install overlay and the in-app update overlay to
apps/desktop/DESIGN.md, reusing in-bundle primitives only (no new deps):

- install overlay: Loader2 spinners -> Loader (fourier-flow); emerald check /
  AlertTriangle -> neutral Codicon check + canonical ErrorIcon; de-boxed the
  failure block; hairline (--stroke-nous) command/code chips; --ui-* tokens;
  BrandMark header; flat stage rows (only the active step opaque).
- update overlay: drop the redundant DialogContent border (base Dialog already
  supplies shadow-nous + --stroke-nous); de-box the changelog; hairline +
  primary-flash manual command block; on-brand BrandMark for "all set".
2026-06-30 14:46:28 -05:00
Brooklyn Nicholson
b48fcfa8bd test(desktop): unit-cover the composer URL-dialog engine
Adds hooks/use-composer-url-dialog.test.tsx (renderHook): @url: directive
fallback, host onAddUrl preference + clear/close, and the blank-input no-op.
First unit coverage for an extracted composer engine — previously none of this
logic was testable while welded into the DOM-coupled ChatBar.
2026-06-30 14:27:19 -05:00
Brooklyn Nicholson
8795dfa331 refactor(desktop): extract composer pop-out engine into useComposerPopout
Moves the docked↔floating state, dock/float/toggle actions, drag-gesture wiring,
and the on-screen re-clamp effect out of ChatBar into
hooks/use-composer-popout.ts, verbatim. ChatBar passes its composerRef in and
consumes the returned popout state/handlers; the secondary-window gate and the
shared persisted atom stay encapsulated in the hook.
2026-06-30 14:26:39 -05:00
Brooklyn Nicholson
a8f9d089f1 refactor(desktop): extract composer placeholder logic into useComposerPlaceholder
Moves the resting-placeholder state + the conversation-change re-roll effect +
the disabled/reconnecting/starting derivation out of ChatBar into
hooks/use-composer-placeholder.ts, verbatim. The hook owns its own i18n + browse
reset; ChatBar just reads the derived string.
2026-06-30 14:25:21 -05:00
Brooklyn Nicholson
2a84bcb149 refactor(desktop): extract composer "Add URL" dialog into useComposerUrlDialog
Moves the URL dialog's open/value state, autofocus-on-open effect, and submit
(host onAddUrl or an @url: directive) out of ChatBar into
hooks/use-composer-url-dialog.ts, verbatim. ChatBar just wires the returned
openUrlDialog into the context menu and the state into <UrlDialog>.
2026-06-30 14:24:32 -05:00
Brooklyn Nicholson
e009ba57ac refactor(desktop): extract global Esc-to-cancel into useComposerEscCancel
Moves the chat-focused Esc-cancel listener (the latest-handler ref + the
register-once window keydown effect) out of ChatBar into
hooks/use-composer-esc-cancel.ts, verbatim. Encapsulating the latest-closure ref
inside its own hook is the first of the plan's "delete the latest-closure refs"
cleanups: it's no longer a loose ref in the 1.4k-line component, just an
implementation detail of a focused side-effect hook keyed on busy/awaitingInput/
onCancel.
2026-06-30 14:23:35 -05:00
Brooklyn Nicholson
5fdc2acedc refactor(desktop): extract composer branch/worktree engine into useComposerBranch
Moves the CodingStatusRow hand-offs (openInWorktree + branch-off / convert /
list / switch) out of ChatBar into hooks/use-composer-branch.ts, verbatim. The
hook depends only on cwd + draftRef + clearDraft (backend coupling via the
projects store); nothing about ChatBar's render. Dead projects/composer-store
imports drop out of index.tsx.
2026-06-30 14:22:54 -05:00