Commit graph

19613 commits

Author SHA1 Message Date
brooklyn!
8defb9fd60
Merge pull request #74833 from NousResearch/bb/status-stack-seam
fix(desktop): fuse the status stack to the composer again
2026-07-30 12:06:36 -05:00
Austin Pickett
1fd7548b49 test(desktop): query the profile row kebab by its own label
main now labels each panel row's kebab with the row's name
(menuLabel={profile.name}), so the hardcoded "Actions" default this test
relied on no longer exists. The name alone is ambiguous — the row-select
button carries it too — so match the menu trigger via `expanded`.

Neither side conflicts textually, so this only surfaced once main merged in.
2026-07-30 12:31:38 -04:00
Austin Pickett
95571de9d7 fix(desktop): fold delete dialog into shared, level up name field, test the view
Addresses review on #73013.

1. Manage Profiles used a hand-rolled delete Dialog next to the shared
   DeleteProfileDialog in the same folder. That copy missed the active-
   profile re-home fix (f764b0400): deleting the profile the gateway is
   on stranded it on a dead backend. Switch to the shared dialog, which
   owns the deleteProfile call and re-homes to default. Drops
   handleConfirmDelete, the deleting state, and the now-unused Dialog*
   imports.

2. The name field regressed to a plain Input during the create-dialog
   dedup, losing live slugging. Level both shared dialogs up to
   SanitizedInput sanitize={slug} so every entry point gets the behavior
   Manage Profiles had — the sanitize primitive means callers never
   validate-then-reject.

3. Nothing rendered ProfilesView, which is how the drift got in. Add a
   behavior test: create dialog exposes SOUL.md, deleting the active
   profile re-homes to default, deleting a non-active one does not.
2026-07-30 12:31:38 -04:00
Austin Pickett
4d9b7718d9 fix(desktop): use shared create-profile dialog on Manage Profiles page
The Manage Profiles page had its own local CreateProfileDialog/
RenameProfileDialog copies that predated the shared dialogs in
create-profile-dialog.tsx / rename-profile-dialog.tsx. The local
create copy lacked the SOUL.md textarea, so New Profile from the
sidebar rail and New Profile from Manage Profiles rendered different
modals.

Delete both local duplicates and reuse the shared self-contained
dialogs (they own the createProfile/renameProfile/updateProfileSoul
calls), so both entry points show the same modal including SOUL.md.
2026-07-30 12:31:38 -04:00
kshitij
14abd64b00 test: drop change-detector test, keep behavioral test
test_generated_script_contains_umask_else_branch asserted on shell
script text ('else', 'umask', '(0666 & ~0', 'chmod') rather than
behavior — a change-detector test per AGENTS.md. The behavioral
test (test_new_file_gets_umask_default_permissions) already
covers the actual behavior end-to-end via real subprocess.
2026-07-30 21:53:38 +05:30
webtecnica
fbfee8e405 fix(file_ops): apply umask-default permissions in _atomic_write for new files (#70856) 2026-07-30 21:53:38 +05:30
kshitij
9ceac1896e
Merge pull request #74902 from kshitijk4poor/chore/author-map-webtecnica-email
chore: add contato@webtecnica.com.br → webtecnica to AUTHOR_MAP
2026-07-30 21:47:06 +05:30
kshitij
ad12233f76 chore: add contato@webtecnica.com.br → webtecnica to AUTHOR_MAP
Required for PR #70888 salvage attribution audit.
webtecnica already has a noreply entry (75556242+webtecnica@users.noreply.github.com);
this adds their commit-email identity.
2026-07-30 20:52:19 +05:00
Kshitij Kapoor
acfd376d66 ci(docker): retry buildx setup on transient Docker Hub failures
The Docker Build, Test, and Publish workflow fails when
docker/setup-buildx-action can't pull the moby/buildkit:buildx-stable-1
image from Docker Hub. The failure happens during builder bootstrap at
the auth token exchange — a transient network blip (connection reset,
read timeout, rate limiting) that self-resolves on re-run.

Recent failure (run 30449230291, merge job):
  read tcp 10.1.0.171:45666->104.18.43.178:443: read: connection reset by peer

This has hit us before and will again — it's the same class of
transient Docker Hub flake that the merge job already retries for
imagetools create. But buildx setup had no retry, so a single network
hiccup killed the entire job (build, publish, or merge) even though
nothing was wrong with the code or the image.

Fix: wrap each of the 3 buildx setup steps (build, publish, merge jobs)
with continue-on-error + a conditional retry step. The maintained action
is preserved as-is — we just give it a second attempt if the first
fails. The action generates a unique builder name per invocation, so the
retry never collides with the failed first attempt. The second attempt
has no continue-on-error, so genuine persistent failures still fail the
job.

The docker/setup-buildx-action maintainer has explicitly said retry
belongs at the workflow level, not inside the action [1], and other
repos use this same continue-on-error pattern for this exact issue [2].

[1] docker/setup-buildx-action#510
[2] joshjhall/containers#688, ethpandaops/eth-client-docker-image-builder#391
2026-07-30 21:12:59 +05:30
Matt Ezell
7965462d6c fix(compression): choose summary role by template-visible alternation
The compaction summary's role was selected against the LITERAL
neighbouring messages (compressed[-1] / tail_messages[0]). Mistral-family
chat templates (Devstral, Mistral Small 3.x, Magistral) enforce
user/assistant alternation but exempt the tool flow (tool results and
assistant messages carrying tool_calls) from the check, so a protected
head ending [user, assistant(tool_calls), tool] pinned the summary to
role="user" while the last role the template counts is "user": the
backend rejects the whole request with a Jinja alternation error
(HTTP 500). The summary persists in the stored conversation, every
retry replays the identical poisoned history, and the session is
permanently unrecoverable. Fires on EVERY compaction against a
Mistral-strict backend, captured byte-exact via a tee-proxy in front of
a llama.cpp/llama-swap Devstral deployment.

Fix: compute both neighbour roles through _template_visible_role(),
which skips template-exempt messages. The #52160 (Anthropic user-first)
and #58753 (zero-user-turn) forced-user guards are preserved; their
forced shapes (summary-user followed only by exempt messages) are
alternation-safe. When the visible head ends "assistant" and the
visible tail opens "user", no standalone role can alternate and the
existing merge-into-tail fallback now correctly fires (the literal
logic emitted a standalone user summary there: a second poisoning
shape).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGN45sMMbwM8cW9T9ga4ou
2026-07-30 21:07:36 +05:30
ethernet
36e41c09ed
fix(nix): include new flat modules at the root (#74362) 2026-07-30 20:28:36 +05:30
Brooklyn Nicholson
77bdf932fc fix(desktop): fuse the status stack to the composer again
pb-2 on the in-flow stack wrapper opened an 8px gap under the card and
broke the shared seam the dock card is built for.
2026-07-30 07:47:02 -05:00
hermes-seaeye[bot]
b4f8c491d3
fmt(js): npm run fix on merge (#74827)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-30 12:46:25 +00:00
brooklyn!
fd90ef77be
Merge pull request #74815 from NousResearch/bb/composer-clear-parity
Clearing the composer empties it whichever way you do it
2026-07-30 07:36:28 -05:00
brooklyn!
466e6402f6
Merge pull request #74806 from NousResearch/bb/ref-parity
A sent reference renders as the chip the composer showed
2026-07-30 07:32:22 -05:00
hermes-seaeye[bot]
84d71fb88f
fmt(js): npm run fix on merge (#74814)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-30 12:31:53 +00:00
Brooklyn Nicholson
570337c099 fix(desktop): clearing the composer lands on the same empty state every way
Select-all + Delete cleared the composer; select-all + Cut left it holding
a draft. Both produce identical DOM, so the split was in the reader.

An emptied editor keeps a placeholder <br> \u2014 scaffolding
normalizeComposerEditorDom adds so the contenteditable doesn't collapse to
a sliver, not a line the user typed. composerPlainText read it as "\n",
and syncDraftFromEditor (session swap, pagehide) skipped the
normalize+sanitize its rAF twin runs. Cut's residue reached that reader;
Delete's went through the flush path and got cleaned.

Fixed where the two disagree rather than at each call site: an editor
holding nothing but its placeholder break reads as empty. A real
Shift+Enter break, a trailing break after text, and a nested lone <br>
are all unchanged \u2014 the exemption is scoped to the editor root by its slot
marker. syncDraftFromEditor now normalizes and sanitizes like the flush
path, so both readers see one truth.
2026-07-30 07:29:31 -05:00
brooklyn!
3eebb99ca8
Merge pull request #74804 from NousResearch/bb/tab-close-stay-right
Closing a tab leaves you on the one that fills its slot
2026-07-30 07:23:00 -05:00
brooklyn!
81aacdef4d
Merge pull request #74802 from NousResearch/bb/dismissed-projects-cmdk
Dismissed projects stay out of ⌘K
2026-07-30 07:17:01 -05:00
Brooklyn Nicholson
0c4a5d70f5 fix(desktop): a sent reference renders as the chip the composer showed
A reference whose value is backtick-quoted — `@url:` always, and any path
with a space — arrived in the sent bubble as a bare `@url:` followed by a
markdown code span. The composer showed a chip; sending it produced two
wrong things.

user-message-text scans inline code BEFORE handing the remaining text to
DirectiveContent, so it claimed the directive's quoting as a code span and
split the reference down the middle. Directives win that overlap: the
backticks are syntax the composer wrote, not something the user typed as
code.

The pattern itself lived in three identical copies (composer hydration,
sent bubble, and the one this fix needed), plus a fourth copy of the kind
list. They agreed today by luck. reference-kinds already owns what a
reference LOOKS like, so it now also owns what one IS: WIRE_REFERENCE_KINDS
and referenceRe(), a fresh matcher per call because a shared /g regex
carries lastIndex between callers.
2026-07-30 07:15:44 -05:00
brooklyn!
0567613497
Merge pull request #74790 from NousResearch/bb/worktree-diff-scope
fix(desktop): scope coding rail + review pane per worktree
2026-07-30 07:15:21 -05:00
Brooklyn Nicholson
ec1645a5ba fix(desktop): stay on the tab that fills a closed slot
Closing an active layout tab always selected the previous neighbor, so
focus jumped left every time. Prefer the right neighbor instead (left
only at the end) — same rule terminals and the preview rail already use.
2026-07-30 07:12:29 -05:00
brooklyn!
383829df2e
Merge pull request #74782 from HexLab98/fix/74761-update-marker-self-pid
fix(installer): adopt desktop-prewritten update marker (#74761)
2026-07-30 07:10:01 -05:00
Brooklyn Nicholson
ce6ecf306e fix(desktop): hide dismissed projects from ⌘K too
Remove-from-sidebar only filtered the overview; the command palette still
listed every auto project. Share one filter so both surfaces stay in sync.
2026-07-30 07:09:40 -05:00
Brooklyn Nicholson
87e14d1f2d fix(desktop): unclog lint on worktree-scoped rail PR
Perfectionist import order on the changed-files card and coding-status
tests, plus the padding blanks eslint wants in the new review scope tests.
2026-07-30 07:08:19 -05:00
Brooklyn Nicholson
2e3e9c1765 fix(desktop): scope coding rail + review pane per worktree cwd
Session tiles each live in their own worktree, but the coding rail's
branch/±LoC and the review pane both keyed off the main pane's global
cwd — and every refresh only re-probed that one tree. Cache status per
cwd, register on-screen rails, and pin the review pane to the surface
that opened it.
2026-07-30 06:21:55 -05:00
brooklyn!
14db1a99e2
Merge pull request #74781 from NousResearch/bb/pane-context-menus
Stop right-click showing a lone Select All on bare surfaces
2026-07-30 06:01:51 -05:00
HexLab98
67083c6dde test(installer): pin own-pid adopt for desktop-prewritten update marker
Regression for #74761: acquire must succeed when the marker already
names this process (desktop writeUpdateMarker raced ahead), and still
clean up on Drop.
2026-07-30 17:57:13 +07:00
HexLab98
160586ff8d fix(installer): adopt desktop-prewritten update marker with our own pid
Since #50238 the desktop writes .hermes-update-in-progress with the
spawned updater's PID before UpdateMarkerGuard::acquire runs. Without a
self-PID exclusion, live_marker_owner treated that as a foreign live
owner and every in-app desktop update aborted into a relaunch loop
(#74761). Treat our own PID as adoptable; keep refusing foreign live
updaters.
2026-07-30 17:57:13 +07:00
Brooklyn Nicholson
dba7bef5ce fix(desktop): stop right-click showing a lone Select All on bare surfaces 2026-07-30 05:55:30 -05:00
brooklyn!
3735ccee23
Merge pull request #74772 from NousResearch/bb/code-pre-padding
fix(desktop): tighten code-block padding and use the native mono font
2026-07-30 05:55:23 -05:00
Brooklyn Nicholson
1afe076dbf fix(desktop): use the native Menlo/Monaco mono stack for code 2026-07-30 05:48:20 -05:00
Brooklyn Nicholson
b7ee610dc9 fix(desktop): drop the doubled inset around highlighted code blocks 2026-07-30 05:41:00 -05:00
brooklyn!
937222f4ec
fix(desktop): keep a mid-turn model pick painted in the composer (#74759)
The gateway now queues a model switch made during a turn and applies it
at the next turn start (#74756), but the desktop still bounced the pill
back to the old model: the post-switch refetch answered with the model
still running and repainted over the pick.

Skip that refetch when the switch was deferred — the apply publishes
session.info when it lands, and that is what re-syncs every surface.
An older gateway that still refuses with 4009 keeps the pick too rather
than rolling back and toasting at a user who did nothing wrong; it is
what the next turn runs anyway. Real failures still roll back and report.

The 4009 predicate lives beside the other gateway-compat probes in
lib/gateway-rpc.
2026-07-30 10:34:48 +00:00
brooklyn!
d8a9c17dae
fix(tui): keep the pending model shown at turn end instead of blipping back (#74766)
A model picked mid-turn is applied at the next turn start, but the end-of-turn
session.info (_emit_settled_session_info) reads the still-live agent — the OLD
model — and clobbered the optimistic paint, so the UI showed the new model, then
snapped back to the old one when the turn ended, then switched for real on the
next turn. Report the queued pick's model/provider in _session_info while a
switch is pending (it IS the model the next turn runs), so the display stays on
the user's choice through the settle. Cleared once the switch applies.
2026-07-30 10:32:55 +00:00
brooklyn!
206eda50a5
Merge pull request #74734 from NousResearch/bb/tab-context-menu
Every tab strip gets the standard right-click tab menu
2026-07-30 05:23:06 -05:00
brooklyn!
f27d45e288
feat(tui): reach the model picker without wrecking your draft, and switch mid-turn (#74756)
* feat(tui): Ctrl+O opens the model picker without clearing your draft

Reaching the model picker meant typing /model, which forces you to wipe
whatever you'd already drafted. Bind Ctrl+O to open the same picker overlay
directly, leaving the composer untouched. Ctrl+O is added to the textInput
pass-through allowlist so the composer doesn't swallow it, mirroring the
existing Ctrl+X session-switcher path.

* feat(tui): apply a mid-turn model switch at the next turn instead of rejecting it

Picking a model while a turn was streaming hit a 4009 'session busy' reject:
switch_model() mutates the agent's model/provider/base_url/client in place and
the worker thread reads those every iteration. Now config.set queues the pick
in session[pending_model_switch] and _apply_pending_model_switch applies it on
the turn thread at the next turn start, before any model call — no race, no
interrupt, no waiting on the client rebuild. The TUI paints the pick optimistically
and notes '(applies next turn)'.
2026-07-30 10:18:30 +00:00
brooklyn!
dd4eadcf79
A finished turn ends on its changed files (#74732)
* feat(desktop): open the review pane on a given file

toggleReview is a toggle, so it can't back a "take me to the diff" affordance
-- pressing it when the pane is already up hides the thing you asked to see.
revealReview is the open-only half (toggleReview now calls it for its own open
branch), and openReviewForPath goes one further: refresh, then select the file.

A tool reports the path it wrote absolute while git reports repo-relative, so
the two are matched on the tail. fileEditPath is exported for the same reason
-- the caller needs the same path the tool row derives.

* feat(desktop): derive a turn's changed files from its tool parts

A finished turn already carries everything the summary needs: each file-edit
tool part holds the path it touched and the inline diff it produced. Folding
those into one row per file, with repeat edits to a file summed, means the card
costs no extra git probe.

Only landed edits count -- a call still running has no result, and a failed one
changed nothing.

* i18n(desktop): copy for the changed-files card

* feat(desktop): close a turn on its changed-files card

A turn that edited files now ends with a summary panel: one row per file with
its +/-, a Review action opening the diff pane, and a row click opening that
file's diff.

It rides only the newest message. The card describes a working tree, and that
tree has moved on by the next turn -- so rather than leaving a trail of stale
cards down the transcript, sending the next message retires it. While the turn
is still streaming the selector returns a stable empty list, so the tool rows
narrate the edits and the delta stream never re-renders the card.
2026-07-30 10:11:58 +00:00
hermes-seaeye[bot]
3b47e0c436
fmt(js): npm run fix on merge (#74751)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-30 10:11:53 +00:00
brooklyn!
9650f555d0
Merge pull request #74668 from NousResearch/bb/composer-attach
Attaching a file, folder, or link works like a picker, not a syntax
2026-07-30 05:01:45 -05:00
brooklyn!
19556db610
Merge pull request #74747 from NousResearch/bb/loading-bg
fix(desktop): match zone loading bg to chrome
2026-07-30 04:57:18 -05:00
brooklyn!
1ee75fc473
Merge pull request #74746 from NousResearch/bb/chat-widget-chrome
Inline chat widgets share one shell
2026-07-30 04:54:33 -05:00
Brooklyn Nicholson
7c59101c4a fix(desktop): give every tab strip the standard right-click tab menu
Right-clicking a tab that has no domain menu of its own — the main tab on a
fresh draft, the file tree, a terminal — fell through to the zone strip's
menu, which offered Split right/down/left/up and Hide header. Session tabs
never showed it (SessionTabMenu stops the event), so the split menu only ever
appeared on the surfaces least likely to want it.

ZoneMenu now renders the same verbs a session tab's menu does — Close, Close
others, Close to the right, Close all — over the shared ActionsContextMenu
kit, so both menus stay identical, above the strip's own header/minimize
toggles.

The Split actions were the only caller of splitTreeZone -> splitGroupZone, and
Move was the only caller of adjacentGroup; both chains are removed along with
the now-unused direction strings in every locale. Everything else the menu did
is still reachable: Move by dragging the tab, Hide header by double-tapping
the strip (and from a session tab's Hide tab bar), Minimize from the header
chevron.
2026-07-30 04:54:11 -05:00
brooklyn!
5e807390fd
Merge pull request #74487 from NousResearch/bb/update-eol-churn
fix(update): repair managed checkouts still running core.autocrlf=true
2026-07-30 04:53:24 -05:00
Brooklyn Nicholson
c999dc2e8e refactor(desktop): one .ref class, and the theme owns every accent
A reference had two styling systems: a Tailwind class string assembled in
TypeScript (`directiveChipClass`) and a separate `link-chip` for prose links,
each carrying its own color-mix(). Same concept, three appearances.

Now every inline reference — a composer chip, a sent message's mention, a
markdown link, a completion row's glyph — is `class="ref"` plus
`data-ref="<kind>"`, and styles.css owns the accent. No hex or color-mix()
ships from a component, so a skin restyles all of them at once.

Keying the accent on `[data-ref]` alone rather than `.ref[data-ref]` also lets
the popover's icon column take a kind's hue without inheriting its inline-text
layout.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
d83d296473 refactor(desktop): one reference vocabulary behind @ and / alike
`@` and `/` were two menus that happened to live in the same file: `@` rows
were horizontal with an icon, `/` rows were stacked with none, and each kept
its own hand-maintained icon map. Picking a file and picking a skill felt like
features from different apps.

Adds reference-kinds.ts — one table mapping every kind a reference can be
(file, folder, url, image, tool, line, terminal, session, git, diff, staged,
command, skill, theme, emoji) to its icon, accent, and section label. Both
surfaces that show a reference now read from it:

  - the popover row, browsing for one
  - the chip, having picked one

so a thing is the same colour with the same glyph wherever you meet it, and a
row looks like the chip it will become. `/` rows gain icons in the process,
which is what the shared layout gives them for free.

Chips lose their pill: no background, no padding, no border, just the icon and
coloured text. A filled badge turns every mention into a UI element the eye has
to step over, and the icon plus accent already carry the kind. Slash pills are
the same component — SLASH_CHIP_BASE_CLASS is now literally DIRECTIVE_CHIP_CLASS.

Emoji rows stay icon-less: the emoji is its own glyph.

Also drops three duplicated definitions (ICON_PATHS, SLASH_ICON_PATHS,
SLASH_CHIP_VARIANT) and an inline copy of DirectiveIcon inside SlashChip.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
c17053c4f7 perf(desktop): cache @ path completions like / completions already are
The `/` path has had a completion cache since the skills-scan work; the `@`
path never got one. Every keystroke was an uncached round trip behind the 60ms
debounce, so walking a tree — Tab in, Backspace out, retype a segment — paid
full price for paths it had just listed.

Measured in-process against this repo (8,036 files): `git ls-files` ~38ms,
ranking ~12ms. The backend already caches the file list for 5s, so the fix
belongs in the renderer: reuse the existing cache module with a short 15s TTL
(a directory listing, unlike the command catalog, can change under the user)
keyed on cwd + session + query, and wire `isCached` so a warm query skips BOTH
the debounce and the loading state.

That last part is what makes it feel instant rather than merely fast — a
spinner over an answer already in hand reads as latency the user isn't paying.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
47180dec83 fix(desktop): one label per reference, on every surface
Picking a folder showed three different names for it: the popover row said
`desktop/`, the editor mid-browse said `apps/desktop/`, and the committed chip
said `desktop`. Each surface derived its own label from the value.

Upstream keeps ONE label on the directive node and hands it to every consumer
verbatim (`DirectiveNode.__label = item.label`, rendered by `decorate()` and
carried through `:type[label]{name=id}`). Our wire format is `@kind:value`,
which can't carry a label, so the same invariant is held by deriving both ends
from refChipLabel: the popover row now shows exactly what the chip will show,
and the commit path passes the picked row's label into the chip rather than
letting it re-derive one.

refChipLabel keeps the directory for the reason it already keeps a URL's path —
a bare basename can't tell two references apart, and `src`, `index.ts`, and
`main.tsx` repeat all over a repo. Browsing into apps/desktop/ only to be
handed a chip reading `desktop` throws away the context you navigated for.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
1f72da4f88 test(desktop): cover the directive-scope contract
Nine cases against the real hook and a real contentEditable: the scope
surviving Tab-descend, Backspace climbing the path then dropping the scope
whole, a mid-message pick keeping its trailing prose, a paste consuming an
open scope, and the guards that keep @teknium1: / localhost:8080 from being
mistaken for a directive.

text-utils.test.ts picks up the additive `value` field and asserts the
scope/value split directly.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
cf5b6feae8 feat(desktop): show the active @ browse scope as a popover header
With the scope no longer sitting in the editor as raw syntax, the popover is
where it belongs: a FOLDERS / FILES / URL header above the list, so the filter
reads as the mode it is instead of as characters the user has to finish.

Reuses the existing group-header style the slash menu already renders — no new
chrome.
2026-07-30 04:52:06 -05:00