Commit graph

18607 commits

Author SHA1 Message Date
Brooklyn Nicholson
ab741ed331 fix(desktop): drop a session's tile when it loads into the main tab
A session is meant to be either the main thread or a tile, never both.
openSessionTile enforced this from the tile side (refusing to tile the
selected session), but the main side never dropped an existing tile — so
any path that routes main to a session that's already tiled (cold-start
remembered-session restore, a pasted/Cmd-K route, a notification jump)
painted the same transcript twice: the workspace pane from the route and
the tile pane in parallel, both fighting one runtime.

resumeSession is the single chokepoint every load-into-main path funnels
through, so close the redundant tile there once the session becomes the
selection. The warm cache/runtime binding survives for main to reuse.
2026-07-28 04:16:28 -05:00
brooklyn!
d2cdb21633
Merge pull request #73250 from NousResearch/bb/sys-event-rows
Type a resumed interrupted turn as a timeline event, not a user message
2026-07-28 04:04:44 -05:00
Brooklyn Nicholson
ff7418315f fix(tui,cli): render a resumed interrupted turn as an event
Desktop already did; the TUI transcript and the CLI resume recap fell
through to the default and printed the raw system note.
2026-07-28 03:54:25 -05:00
Brooklyn Nicholson
7ab5567953 fix(gateway): read an untyped recovery note as a timeline event
Pass the display type into the turn so the row is born typed, and
recognize the note's fixed prefix in _history_to_messages so the
untyped rows already on disk stop painting as user bubbles.
2026-07-28 03:54:21 -05:00
Brooklyn Nicholson
bf0871fbf8 fix(agent): type a synthesized user turn when its row is written
The auto-continue recovery note was typed only after run_conversation
returned, so its row sat untyped for the whole turn — and permanently
when the continuation was itself killed, which is the case it exists
for. persist_user_display_kind stamps the type on the live message
before the crash persist writes it, in the same insert as the content.
The flush also carries display_metadata through, which it was dropping.
2026-07-28 03:54:16 -05:00
brooklyn!
48cadf197e
Merge pull request #73229 from NousResearch/bb/slash-catalog-cache
Cache slash completions and show skills on a bare /
2026-07-28 03:34:45 -05:00
Brooklyn Nicholson
3ae9b16928 test(desktop): cover skills-only completions for a mid-message slash 2026-07-28 03:25:00 -05:00
brooklyn!
99d766fcac
Merge pull request #73220 from NousResearch/bb/subagent-runs
Show a delegation as its subagents, live
2026-07-28 03:22:43 -05:00
Brooklyn Nicholson
341f95b249 feat(desktop): cache slash completions and list skills on a bare /
The composer re-asked the gateway for the command catalog on every open
and for complete.slash on every keystroke. Both scan the skills dir on
the backend, so opening the menu cost a round trip against data that
only moves when a skill is added, removed, or toggled.

Hold both behind a one-hour cache keyed per query, and invalidate it
where the skill set actually changes: hub install/uninstall/update, a
Capabilities toggle, bulk toggle, archive, the agent's own skill_manage
call, and a profile switch. A cached query also skips the debounce and
the spinner, so a warm menu opens in the same frame.

The bare-slash list showed no skills at all: the backend categorizes
registry commands but appends skills to the flat pairs list only, and
the popover prefers the categorized layout. Re-add the uncategorized
leftovers under a Skills header, so /clean and /work are visible on /
and not just after typing enough of the name to match.
2026-07-28 03:19:51 -05:00
Brooklyn Nicholson
dc50ddbbd6 style(desktop): hold the delegation card to three-quarter width
A list of short rows — goal, model, timer — reads better narrow than stretched across the full reading column. Scoped in styles.css rather than as a utility: the rule above it sets width on every tool block and wins over one.
2026-07-28 03:15:20 -05:00
brooklyn!
e4564586bc
Merge pull request #73218 from NousResearch/bb/focused-zone-tabs
fix(desktop): make the tab verbs follow the focused zone like ⌘1-9
2026-07-28 03:14:03 -05:00
brooklyn!
fd39696ccf
Merge pull request #73216 from NousResearch/bb/desktop-thin-scrollbars
fix(desktop): thin chrome scrollbars without platform chunk
2026-07-28 03:08:06 -05:00
Brooklyn Nicholson
8fcad214ae chore: kick CI 2026-07-28 03:05:46 -05:00
Brooklyn Nicholson
2818c9e514 feat(desktop): render delegate_task as its live subagent list
A fan-out showed up in the transcript as one grey row and a JSON blob, so the several agents it started were invisible until they finished. Give the call its own card: one line per child with the goal, the model running it, and a live timer, over a single ticking line of that child's relayed activity.
2026-07-28 03:05:14 -05:00
brooklyn!
63841210d5
Merge pull request #73121 from NousResearch/bb/desktop-dev-cdp-port
feat(desktop): let the agent inspect the desktop app it's developing
2026-07-28 03:03:26 -05:00
Brooklyn Nicholson
ed67b20888 fix(desktop): make the tab verbs follow the focused zone like ⌘1-9
⌘1…⌘9 resolves its tab strip through $activeTreeGroup (the interacted
zone), but ⌘W, ⌘T, ⌘⇧T and the strip's "+" all hardcoded the workspace
pane's group. In a layout with a second chat zone the number keys worked
and every other tab verb acted on main instead — and that zone's strip
had no "+" at all.

Adds focusedSessionGroup() to the tree store, resolving the same zone
$activeTreeGroup names when it hosts a chat strip and falling back to
the workspace otherwise, so focus parked in files/terminal can't make ⌘W
close the file tree. ⌘W closes through it, unanchored openSessionTile()
(⌘T / ⌘⇧T) docks into it, and the "+" renders on any chat strip, noting
its zone on pointerdown so the tab lands where it was clicked.
2026-07-28 03:00:13 -05:00
brooklyn!
1cd8f8c37e
Merge pull request #73180 from NousResearch/bb/composer-popout-tabs
Composer pop-out is scoped to its layout zone
2026-07-28 02:58:33 -05:00
Brooklyn Nicholson
ef8f2e701c fix(desktop): thin chrome scrollbars without platform chunk
Chromium 121+ prefers scrollbar-width over ::-webkit-scrollbar and
ignores the latter when both are set, so our themed thumb never painted
and mac got the chunky platform "thin" bar. Gate standard scrollbar-*
for non-webkit engines only; ship a 4px webkit thumb on .scrollbar-dt
and portal menus.
2026-07-28 02:57:16 -05:00
Brooklyn Nicholson
88b6a7a49c fix(desktop): measure dock proximity against the chat surface, not the window
The dock target is the docked composer at the bottom-center of its own
surface. In a split, the viewport's bottom-center is somewhere else, so
dragging onto the real dock never registered.
2026-07-28 02:50:06 -05:00
Brooklyn Nicholson
df25d77131 fix(desktop): scope composer pop-out to its layout zone
Pop-out was one flag and one position for the whole window, and every
keep-alive-mounted tab re-clamped that position against its own rect and
wrote it back. So floating the composer in one pane floated it in every
pane, and N surfaces raced for one value with the last writer winning — a
drag in one tab was lost in the next.

State is now keyed by layout group, the scope users actually mean: tabs in
a zone share a float, a split zone beside them keeps its own. Within a
zone the stored position is intent, and each surface derives what it
renders through the pure clampPopoutPosition against its own rect.

Re-placing is gated on pane visibility so a live drag can't force a reflow
in every background tab, and runs pre-paint so a revealed tab never shows
a stale frame. Storage is written on release rather than per drag frame,
dead zones are pruned against the live tree, and the pre-zone value seeds
one first read so an existing float survives the upgrade without leaking
into zones split later.
2026-07-28 02:50:02 -05:00
Brooklyn Nicholson
25ff775136 feat(desktop): let every chat surface float its composer
popoutAllowed was hardcoded false for session tiles, so only the primary
thread could pop out — a tab or a split had no way to undock its composer.
Secondary windows stay docked; that gate was the load-bearing one.
2026-07-28 02:49:51 -05:00
Brooklyn Nicholson
73e3e0860e feat(desktop): expose a pane's layout zone to its content
PaneGroupContext, alongside PaneVisibleContext: the zone hands each pane
the group id it's rendered in, so state that belongs to a stack of tabs
rather than to a pane can key off it — and follows a pane dragged between
zones, since the provider is whichever zone renders it.
2026-07-28 02:49:46 -05:00
brooklyn!
5771a6ebe6
Merge pull request #73200 from NousResearch/bb/tip-after-model-pick
fix(desktop): stop the model-pill tooltip popping open after a mouse pick
2026-07-28 02:41:16 -05:00
hermes-seaeye[bot]
9a5f102d0e
fmt(js): npm run fix on merge (#73197)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 07:27:44 +00:00
Brooklyn Nicholson
914c3f46c6 fix(desktop): stop the model-pill tooltip popping open after a mouse pick
Picking a model closed the menu and then flashed the pill's tooltip over
the fresh selection. The existing focus guard gated on `:focus-visible`
alone, which does not separate a mouse pick from a Tab: the dropdown
autofocuses its search field and keyboard-navigates its rows, so Chromium
is already in keyboard modality when the menu restores focus to the
trigger, and the guard never fired.

Track the device behind the last real interaction and use it to qualify
`:focus-visible`. A mouse pick reports `pointer` and stays silent; Tab
focus still opens the tip.
2026-07-28 02:27:11 -05:00
brooklyn!
a9c9467dd8
Merge pull request #73178 from NousResearch/bb/dupe-message-id
fix(desktop): stop a duplicate message id from crashing the workspace pane
2026-07-28 02:19:12 -05:00
Brooklyn Nicholson
14790234ff fix(desktop): stop a duplicate message id from crashing the workspace pane
A repeated id in the transcript reached assistant-ui's MessageRepository,
which throws on the second link and takes the whole workspace pane down to
the contribution error boundary — the pane the user is actually working in.

Dedupe where the repository export is built, so no upstream transcript bug
can crash the pane, and close the journal merge path that produced one: a
resume that replays a still-journaled turn appended rows the base already
held by id.
2026-07-28 02:03:05 -05:00
brooklyn!
4da7b9ee02
Merge pull request #73169 from NousResearch/bb/queue-drain-semantics
fix(gateway): a queue drain never becomes a live-turn correction
2026-07-28 01:58:31 -05:00
brooklyn!
48b21acb90
Merge pull request #73164 from NousResearch/bb/pins-out-of-lists
fix(desktop): keep pinned sessions out of the unpinned sidebar lists
2026-07-28 01:54:08 -05:00
Brooklyn Nicholson
ab68c5efec fix(gateway): a queue drain never becomes a live-turn correction
The desktop's queue promises "run this AFTER the current turn", but the
promise broke on a race the user can't see: a drain that fired when the
client observed idle while the server was still unwinding the turn landed
in _handle_busy_submit, which applied busy_input_mode — redirecting or
interrupting the live turn with text the user explicitly queued. That's
why force-sending the queue felt like a dice roll: the same gesture
steered, interrupted, or queued depending on a millisecond settle race.

prompt.submit now carries queued:true on every fromQueue drain (composer
auto-drain, send-now, background drain), and the gateway's busy path
honors it by forcing queue semantics — never steer, never redirect,
never interrupt. Lose the race and the text simply waits its turn, which
is what queueing meant all along.

Covered on both sides: a gateway test proving a queued drain cannot touch
redirect/steer/interrupt on the live agent, and the desktop drain tests
now assert the flag rides every prompt.submit shape (direct, background,
resume-retry).
2026-07-28 01:48:08 -05:00
hermes-seaeye[bot]
f2a4452c8a
fmt(js): npm run fix on merge (#73165)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 06:47:12 +00:00
Brooklyn Nicholson
c3a3814885 fix(desktop): keep a worktree lane whose only sessions are pinned
Filtering pins out of the project tree was deleting the lane along with
its last row, so pinning your only chat on a branch made the worktree
vanish from the sidebar — and a project whose sessions were all pinned
fell through to the "no sessions" empty state.

A lane is structure: it exists on disk and you can still start work in
it, which is why the git-worktree enhancer injects lanes that never had
a session at all. Keep emptied lanes, count a lane (not a row) as
project content, and teach the live overlay's prune to drop only the
lanes IT emptied.
2026-07-28 01:45:59 -05:00
Brooklyn Nicholson
7806e6a9a8 fix(desktop): keep pinned sessions out of the unpinned sidebar lists
A pin belongs to the Pinned section and nowhere else, but only the flat
recents list filtered them — the project overview, an entered project's
lanes, and the messaging platform sections all still rendered a pinned
row a second time.

Filter every unpinned group on a shared predicate that matches the live
id AND the durable lineage-root pin id, so a compression tip rotation
can't leak the row back in. A platform's "load more" count discounts its
pinned rows too, or it promises rows that never appear.
2026-07-28 01:39:20 -05:00
brooklyn!
cef85482fc
Merge pull request #73158 from NousResearch/bb/tiny-model-switches
fix(desktop): shrink model-visibility switches to xs
2026-07-28 01:38:56 -05:00
brooklyn!
e95ce5a0e1
Merge pull request #73146 from NousResearch/bb/steer-transcript-scaffolding
fix(agent): keep interrupt-checkpoint scaffolding out of the steered transcript
2026-07-28 01:36:15 -05:00
Brooklyn Nicholson
69a4f65164 fix(desktop): use xs switch size in model-visibility dialog 2026-07-28 01:32:24 -05:00
hermes-seaeye[bot]
c80b199f36
fmt(js): npm run fix on merge (#73155)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 06:29:04 +00:00
brooklyn!
4289a934b2
Merge pull request #73147 from NousResearch/bb/session-open-in-place
desktop: open sessions where they already are
2026-07-28 01:20:53 -05:00
brooklyn!
befff02e9b
Merge pull request #73150 from NousResearch/bb/session-tab-slots
desktop: ⌘1–9 maps to visible tabs
2026-07-28 01:18:52 -05:00
brooklyn!
ff06b47ab0
Merge pull request #73143 from NousResearch/bb/floating-panes
feat(desktop): floating pane placement
2026-07-28 01:18:36 -05:00
Brooklyn Nicholson
b6c7df6cb6 fix(desktop): sort session-row imports for eslint 2026-07-28 01:13:30 -05:00
Brooklyn Nicholson
319bcedfea desktop: open sessions where they already are
Notifications, ⌘K, switcher, session picker, cron/command-center, artifacts,
and @session refs all go through openSession. Plain click/Enter focuses the
existing tile or main; ⌘-click/⌘-Enter opens a new tab; ⇧⌘ still pops a window.
2026-07-28 01:13:30 -05:00
Brooklyn Nicholson
e67e4604db desktop: shared openSession door for focus-or-route
Centralize open-in-place / tab / window so every surface can jump to an
already-open tile or main tab instead of forcing main.
2026-07-28 01:13:30 -05:00
Brooklyn Nicholson
f65fa01926 desktop: ⌘1–9 slots map to visible tabs, not raw pane indices
Focus layout keeps `files` in the workspace group's panes while chrome-
hidden. Slot keys walked that raw array, so ⌘2 landed on the first session
chip and every later number was off-by-one (especially after a ⌘W main-tab
shift). Index the same shown filter the strip paints, and cycle the same.
2026-07-28 01:12:43 -05:00
Brooklyn Nicholson
589fd23947 test(desktop): cover floating pane adoption and live drag
Adoption: a floating contribution stays out of the tree while a docked
sibling in the same pass is adopted, so the exclusion is specific rather
than a broken run.

Live DOM: mounts FloatingPanes and drives real pointer + resize events —
anchored spawn, drag, persistence across remount, edge-riding on resize,
the titlebar floor, collapse, and the no-drag button opt-out.
2026-07-28 01:11:12 -05:00
Brooklyn Nicholson
aad7f128bb feat(sdk): expose FloatingAnchor to plugins
Plugins previously had to fake a floating pane by registering into a
chrome slot and rendering position:fixed themselves, which broke inside
any transformed ancestor and reimplemented drag, clamping, and
persistence each time.
2026-07-28 01:11:12 -05:00
Brooklyn Nicholson
f78521785f feat(desktop): render floating panes above the layout tree
Adds placement: 'floating' — the one non-tiling pane role. Adoption skips
those contributions so one can never become a track that steals width
from a zone; the tree renders them as fixed cards instead, draggable by
the header, with position and collapse persisted per pane id.

The card reuses HUD_SURFACE (command palette, session switcher) rather
than hand-rolling a second float surface.
2026-07-28 01:11:05 -05:00
Brooklyn Nicholson
6dd1c6c062 feat(desktop): floating pane geometry
Pure clamping/anchoring rules for a pane that lives outside the layout
tree: keep 48px grabbable at either horizontal edge, hard-bound the top
below the titlebar so the drag handle stays reachable, pin top-left
rather than invert when the card outgrows the viewport, and let an
edge-anchored card ride its edge as the window resizes.

No DOM, so the rules are testable directly.
2026-07-28 01:10:59 -05:00
brooklyn!
c8ec2a6f3c
Merge pull request #73141 from NousResearch/bb/figma-mcp-oauth
fix(mcp): Figma remote OAuth via DCR allowlist defaults
2026-07-28 01:05:28 -05:00
Brooklyn Nicholson
c883367bd2 fix(agent): keep interrupt-checkpoint scaffolding out of the steered transcript
A mid-stream steer persists an interrupted-turn checkpoint so the model knows
its reply was cut off. That scaffolding — "[This response was interrupted by a
user correction.]" and the "Visible response before the interruption:" header —
was written straight into message content, so every reload painted the raw
machinery as an assistant bubble (and merged it into the preceding tool-call
bubble). Steered transcripts became unreadable.

Reuse the existing display/replay split instead of inventing new surface:
- Carry the scaffolded form in the server-only api_content sidecar (the exact
  bytes replayed to the provider), keep content the user's/agent's real words.
- When nothing reached the screen there is no clean form, so mark the row
  display_kind=hidden — replayed to the model, dropped by every transcript
  surface, exactly like compaction-reference rows.
- Honor display_kind=hidden in the gateway's _history_to_messages projection
  (it only sniffed the [System: convention), so the checkpoint can't leak
  through the live/resume path to the TUI/CLI either.

The model still receives the full interrupted context on the wire; the
transcript shows the partial reply and the user's correction.
2026-07-28 01:05:02 -05:00