feat(agents): tile-based workspace + Electron desktop shell#4276
Open
feat(agents): tile-based workspace + Electron desktop shell#4276
Conversation
Plans a VS Code/Cursor-style splittable workspace where the workspace is a recursive tree of Splits → Groups → Tiles, each tile rendered through a pluggable view registry (Chat, State Explorer, future Logs/Inspector/etc.). Splits and views stay orthogonal — splitTile() and setTileView() are the two primitives every menu item composes from. URL strategy is hybrid: clean default URL (active tile only) plus localStorage layout persistence per server, with an opt-in ?layout=<DSL> import param for shareable layouts. Migration ships in five sequential PRs starting with the view registry, then workspace skeleton, SplitMenu, drag-and-drop, and finally persistence + URL polish. Co-authored-by: Cursor <cursoragent@cursor.com>
…Explorer views
Stage 1 of the tile-based layout refactor (see TILE_LAYOUT_PLAN.md).
Views are now first-class, registered into a tiny in-memory registry at
app boot:
- 'chat' → ChatView (polymorphic on entity type;
embeds CodingSessionView when
applicable, else generic timeline)
- 'state-explorer' → StateExplorerView (thin wrapper over the existing
StateExplorerPanel)
Adding a new view is a single registerView({…}) call plus a *View.tsx
file — no changes to routing or chrome.
EntityHeader's bespoke 'Show state explorer' toggle is replaced by a
generic, registry-driven view-switcher: an inline icon strip plus
matching menu items, generated automatically from listViews(entity).
The /entity/$splat route gains a ?view=<id> query param so non-default
views are deep-linkable. The default view (chat) is implicit and never
shown in the URL.
Stage 1 still renders one view at a time — splits arrive in stage 3.
The bespoke right-drawer / statePanelWidth splitter in router.tsx is
removed; the State Explorer temporarily opens in-place via view-swap
until the workspace skeleton (stage 2) and SplitMenu (stage 3) bring
proper splits back.
Typecheck + tests green.
Co-authored-by: Cursor <cursoragent@cursor.com>
Stage 2 of the tile-based layout refactor (see TILE_LAYOUT_PLAN.md).
Introduces the recursive workspace data model and renders entities
through it, replacing the bespoke route handler that previously
rendered a single entity directly.
Data model (src/lib/workspace/types.ts):
- Workspace { root: WorkspaceNode | null, activeGroupId }
- WorkspaceNode = Split | Group
- Split { direction, children: { node, size }[] }
- Group { tiles: Tile[], activeTileId }
- Tile { entityUrl, viewId }
Reducer (workspaceReducer.ts) is pure and side-effect-free, with
invariants enforced on every mutation:
- splits with ≤1 child collapse / unwrap
- nested same-direction splits flatten
- empty groups are removed; sibling sizes re-normalised
- activeGroupId always references a group present in the tree
Covered by 15 Vitest cases for the tricky paths (open / close last
tile / move-tile / split-with-view / resize / active bookkeeping).
Components (src/components/workspace/):
- Workspace — top-level renderer + URL ↔ workspace sync
- NodeRenderer — pure dispatch from node kind to container
- SplitContainer — N panes + N-1 splitters, fractional sizing
- Splitter — drag-to-resize with px → fraction conversion
- GroupContainer — tab strip + active tile body, with
focus-follows-click group activation
- TabStrip — tabs (hidden when group has only one tile),
middle-click closes
useWorkspace (src/hooks/useWorkspace.tsx):
- WorkspaceProvider — wraps useReducer
- useWorkspace — exposes { workspace, dispatch, helpers }
- helpers wraps dispatch for ergonomics + computes activeTile
Router (src/router.tsx):
- WorkspaceProvider mounted under SearchPaletteProvider
- /entity/$splat ?view=<id> route delegates entirely to <Workspace>;
the route component is now a one-liner returning <Workspace />
URL behaviour preserved from Stage 1 (single-tile workspaces look
identical to before): URL → workspace effect refocuses the matching
tile, swaps view in place when same-entity-different-view, or opens
a new tile in the active group otherwise. Workspace → URL effect
mirrors the active tile back to the URL with replace:true to avoid
double-pushing history entries.
Stage 2 ships with single-tile workspaces by default — splits
become user-driven in Stage 3 via the SplitMenu.
Typecheck + tests green (21 passing).
Co-authored-by: Cursor <cursoragent@cursor.com>
Stage 3 of the tile-based layout refactor (see TILE_LAYOUT_PLAN.md).
Adds the unified per-tile '…' menu (SplitMenu), driven entirely by
two reducer primitives — setTileView() and splitTileWithView() — so
every menu item composes from those.
SplitMenu structure (matches the Cursor screenshot):
- Inspect (entity JSON dialog)
- View ▸ <viewId> ▸ Open here / Split right / down / left / up
- parent-row click runs each view's defaultSplit (e.g. 'right'
for State Explorer, restoring the muscle-memory of "drawer
pops out to the right" from before stage 1)
- Split right / down / left / up (duplicates the active tile)
- Move tile to ▸ Group N (only shown when ≥2 groups)
- Copy URL · Pin · Fork subtree (entity-level actions)
- Close tile (⌘W)
- Kill entity (with confirmation dialog)
EntityHeader is now display-only:
- title + status + view-toggle icon strip
- a generic `menu` slot that the workspace fills with <SplitMenu>
The Inspect / Kill confirm dialogs and all entity-action props
(onKill, onFork, pin) move into SplitMenu. EntityHeader no longer
knows about tiles, groups, or splits.
Workspace hotkeys (useWorkspaceHotkeys, mounted in RootShell):
- ⌘D Split active tile right
- ⇧⌘D Split active tile down
- ⌘W Close active tile
- ⌘\ Cycle to next group
- ⌘1..9 Focus group N
State Explorer regains its "drawer to the right" UX as the *default*
action of `View ▸ State Explorer` thanks to defaultSplit: 'right' on
its registry entry — clicking the parent row splits it right; the
deeper sub-menu lets power users put it elsewhere or open in place.
Typecheck + tests green.
Co-authored-by: Cursor <cursoragent@cursor.com>
…erlay)
Stage 4 of the tile-based layout refactor (see TILE_LAYOUT_PLAN.md).
Native HTML5 drag-and-drop, no react-dnd. Two payload kinds carried
under a custom `application/vnd.electric-tile+json` MIME type:
- sidebar-entity { entityUrl }
- tile { tileId, sourceGroupId }
DropOverlay (per-group):
- mounted on every group, position:absolute
- pointer-events default to none; window-level dragstart/dragend
listeners arm/disarm the overlay so splitter drags / text selection
in the body aren't interrupted when no drag is in progress
- on dragover, computes the active zone (centre 25% inset square +
4 edge slabs joined at the centre) and highlights it
- on drop, dispatches:
- moveTile(tileId, { groupId, position }) for tile drags
- openEntity(entityUrl, { target: { groupId, position }})
for sidebar drags
- silently no-ops when dropping a tile back onto its source group's
centre (avoids a redundant reducer round-trip)
Sidebar rows:
- now `draggable` with the sidebar-entity payload
- ⌘/Ctrl-click + middle-click open the entity in a new split right of
the active group (matches VS Code's "open to side")
- routed through `helpers.openEntity({ target: { position: 'split-right' }})`
in RootShell
Tabs (TabStrip):
- now `draggable` with the tile payload
- middle-click already closes (preserved from stage 2)
- click activates (preserved from stage 2)
GroupContainer:
- gains a position:relative wrapper for the overlay
- adds a subtle inset ring on the active group when there's >1 group
(so the user knows where new tiles will land for a sidebar click)
Drop semantics summary:
- centre → append as new tab (or replace if dropping on self)
- north → new horizontal split, this tile on top
- east → new vertical split, this tile on the right
- south → new horizontal split, this tile on the bottom
- west → new vertical split, this tile on the left
Typecheck + tests green.
Co-authored-by: Cursor <cursoragent@cursor.com>
Stage 5 of the tile-based layout refactor (see TILE_LAYOUT_PLAN.md).
Implements the §3.4 hybrid URL strategy end-to-end.
Layout codec (lib/workspace/layoutCodec.ts):
Compact, human-readable, URL-safe DSL for serialising the workspace
tree. Distinct separators remove parse ambiguity:
',' = split-sibling ';' = group-tab '.' = entityUrl/viewId
Examples (canonical encoded form):
horton%2Ffoo.chat
horton%2Ffoo.chat;horton%2Ffoo.state-explorer@1
H(horton%2Ffoo.chat:60,horton%2Ffoo.state-explorer:40)
H(horton%2Ffoo.chat,V(horton%2Fbar.chat,horton%2Fbaz.logs))
Encoder strips the conventional leading '/' on entity URLs and
omits sizes that match the natural even share — keeps URLs short.
Decoder mints fresh ids (so two decodes don't collide) and
renormalises malformed sizes. 10 Vitest cases cover round-trips,
nesting, error paths, and id freshness.
Persistence (hooks/useWorkspacePersistence.ts):
- key: `electric-agents-ui.workspace.<encoded-server-url>.v1`
- value: `{ v: 1, workspace: <Workspace> }` (versioned envelope)
- 250ms debounced write on every workspace change
- one-shot hydration per (server, mount); restores only when the
current workspace is empty so it doesn't fight the URL → workspace
effect
- prune-on-load: tiles whose entity is no longer in the live
entitiesCollection are dropped; cascade-collapses empty
groups / single-child splits / dead root
- silently no-ops in environments where localStorage throws
(Safari private browsing, sandboxed iframes)
URL hydration (Workspace.tsx):
- ?layout=<DSL> takes priority over localStorage; once consumed we
navigate({ replace: true }) to strip the param so the address bar
settles back to "active tile only" — passes the
open-shared-link-then-clean-URL acceptance check from the plan
- entity route's validateSearch now accepts both `view` and `layout`
Copy layout link (SplitMenu):
- new menu item between "Copy URL" and the separator
- builds a `?layout=<encoded>` URL relative to the current hash
history; copies to clipboard
Wired up in RootShell:
- useWorkspacePersistence() runs alongside useWorkspaceHotkeys()
Typecheck + tests green (31 passing). Build passes.
Co-authored-by: Cursor <cursoragent@cursor.com>
…tile Replace the group-and-tabs layout model with a flat Split | Tile tree: each leaf is a single tile, no tabs within a tile, and dividers / drop targets share the sidebar's hairline + accent-on-hover styling. The new-session screen is now a first-class standalone tile rather than a separate route page. View registry distinguishes entity views (chat, state-explorer) from standalone views (new-session); standalone tiles carry entityUrl: null and render a tile chrome (header + split menu + drop overlay) just like an entity tile. Both / and /entity/$ mount the same Workspace component and the URL <-> workspace sync maps standalone tiles back to /. Drag-and-drop covers all three sources (sidebar entity row, sidebar new-session button, existing tile header) into the four edge quadrants of any tile. openTile now focuses the freshly-created tile in all paths so drop-to-side gives immediate visual feedback and the URL follows. Multiple new-session tiles can coexist via drag (the click flow keeps focus-existing-or-replace semantics). The SplitMenu's view rows render inline with [->][down] icon buttons for split-this-view-to-the-side; entity-only items (Inspect, Pin, Fork, Kill, Copy URL) and "Close tile" (when sole tile) are hidden contextually. The State Explorer's draggable divider switched to the shared Splitter component for visual consistency. Persistence layer: SCHEMA_VERSION bumped to v2; pruneNode keeps standalone tiles intact. Layout codec encodes standalone tiles as ".viewId" (empty entity-path segment) so layout links can carry mixed standalone+entity workspaces. Co-authored-by: Cursor <cursoragent@cursor.com>
…ver/chip surfaces
Aligns the agents UI's typography and palette with the marketing site
(self-hosted OpenSauceOne + Source Code Pro, website surface ladder
mapped onto --ds-bg / --ds-bg-subtle / --ds-surface / --ds-surface-raised,
website text/accent values).
Introduces two centralised semantic tokens to fix muddy dark-mode
surfaces:
--ds-chip-bg — solid raised surface for pill triggers, inline
code chips, kbd keys, code wells etc. (matches
the marketing site's --vp-code-bg pattern)
--ds-bg-hover — universal interactive hover lift. Per-theme:
light = --ds-gray-a3 (alpha-black tint composes
cleanly on warm-white surfaces), dark = solid
#2d3142 (one clean cool-grey step above
--ds-surface-raised, no muddy compositing on
navy page bg).
Routes drop-down items, sidebar rows, search palette, ghost icon
buttons (Button.module.css ghost variant + tone-neutral soft fill),
chip triggers, code wells and similar through these tokens so every
surface in the same family reads consistently.
Ports chat-log + markdown rhythm refinements from the projects branch
(without bringing across the 13→14px body bump or the Figtree font
swap):
- EntityTimeline statusPill switches from a centred chip to a
left-bordered log line; jump-to-bottom moves to bottom-right as
a small bordered surface
- Composer gets a hairline shadow + border-1 edge; user bubble
border lightened
- Markdown rhythm: container gap 12→14, list padding 1.5→1.75em,
li gap 4→6, heading top margins bumped (h1 4→10, h2 4→8, h3 2→6)
- Tool blocks get a softer shadow, mono header strip with a faint
band, recessed code well, new .sectionLabel small-caps style
(consumed by ToolCallView for Command/Output/Content/Input)
- MarkdownCodeBlock strips the trailing empty line Shiki appends
when source ends with a newline
Sidebar rows: type label drops to 10px lowercase (cap-height ≈ title
x-height) with a 1px translateY so it shares a baseline with the
title; title line-height bumped to 1.3 so descenders aren't clipped
by the ellipsis box.
Also drops the projects/tagging feature from this branch (App,
Sidebar, NewSessionPage, useProjects hook) — this branch is being
reset to a fresh-app baseline before re-landing those features.
Co-authored-by: Cursor <cursoragent@cursor.com>
…de blocks Shiki's `codeToTokens` defaults to `defaultColor: 'light'`, which emits the light theme's hex directly on each token's `color` property and only sets `--shiki-dark` as a CSS variable. `--shiki-light` was therefore never set, so the `var(--shiki-light, inherit)` rule in `markdown.css` was falling back to `inherit` and tokens rendered uncoloured in light mode. Pass `defaultColor: false` so Shiki emits BOTH themes as CSS variables, letting the existing per-theme CSS rules pick the right one via `[data-theme]`. Co-authored-by: Cursor <cursoragent@cursor.com>
…n-app Brings the tile-based workspace (view registry, splittable tiles, drag-and-drop layout, persistence, shareable layout URLs) into the electron-app branch on top of the website-aligned theme. Conflict resolution: - Sidebar.tsx: keep the `treeProps` spread cleanup from electron-app and add `onOpenEntityInSplit` to the spread (introduced by tile branch for ⌘/Ctrl-click + middle-click into a new split). - views/NewSessionView.tsx (renamed from NewSessionPage.tsx): adopt the tile branch's workspace flow (`useWorkspace` + `helpers.openEntity` with `tileId` `replace` target, `StandaloneViewProps`) and drop the `CODING_SESSION_ENTITY_TYPE` / `CodingSessionSpawnForm` references — the coder entity was removed on main (#4272) so those imports no longer resolve. - router.tsx: replace the in-router `EntityPage` + `GenericEntityBody` with the tile branch's tiny `WorkspacePage` shell that just renders `<Workspace />` (which now owns all entity rendering and URL ↔ tile syncing). - views/ChatView.tsx: drop the `CODING_SESSION_ENTITY_TYPE` / `CodingSessionView` polymorphism for the same reason as above; chat view is now a single generic timeline + composer. Verified with a clean `pnpm -C packages/agents-server-ui build`. Co-authored-by: Cursor <cursoragent@cursor.com>
Picks up minor version bumps that were already resolved during install (`@antfu/ni`, `@react-grab/cli`, `ora`, `log-symbols` and their transitive deps). No package.json changes. Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a new `@electric-ax/agents-desktop` Electron package that reuses `@electric-ax/agents-server-ui` as its renderer and bundles the `BuiltinAgentsServer` runtime from `@electric-ax/agents` so a local Horton runtime can register against any Agents server selected in the UI. The Electron main process owns servers/active-server/working-dir settings (persisted to userData), spawns multiple windows from a tray/menu-bar icon, and orchestrates the runtime lifecycle. UI integration in `agents-server-ui`: - New `build:desktop` Vite mode that emits `dist-desktop/` with relative `base` and stamps `<html data-electric-desktop="true">` so desktop-only CSS matches from the first paint (more reliable than preload's isolated-world DOM mutation). - `window.electronAPI` typings + `loadDesktopState` / `saveActiveServer` / `onDesktopStateChanged` helpers so the existing `useServerConnection` hook transparently uses the IPC bridge in Electron and `localStorage` on the web. - `SettingsMenu` shows a desktop runtime status group (status, URL, errors, restart/stop actions) when running in Electron. - macOS `hiddenInset` titlebar with traffic lights positioned to align with the existing 44px header. `SidebarHeader` and every tile's `MainHeader` become `-webkit-app-region: drag` strips with a 84px left inset on the leading edge so the toggle/search icons sit beside the lights; buttons / links / inputs / `data-no-drag` opt back out so they stay clickable. `AGENTS_DESKTOP_PLAN.md` documents the architecture, scope, and the phased rollout (this is phase 1: bundled runtime only, server stays remote). Co-authored-by: Cursor <cursoragent@cursor.com>
Builds out the Electron shell on top of the bundled-runtime base: - Application menu (File/Edit/View/Window/Help) and tray menu wired to a `desktop:command` IPC channel so menu items, on-screen buttons and hotkeys all go through the same renderer actions. Window submenu rebuilds on focus/blur and lists open windows by their session document title (driven by a new `useDocumentTitle` hook). - Custom branded About dialog (a small frameless `BrowserWindow`) so the app icon and copy are consistent across platforms — the native macOS panel ignores `iconPath`. App icon, tray template icon (1x/2x black-on-transparent), and `app.dock.setIcon` round out the branding. - First-launch API keys dialog: on startup the renderer asks main for the saved/suggested key set; if no Anthropic or OpenAI key is configured it pops a modal pre-filled from `process.env.*` (snapshot taken at launch). Saved values are persisted in `settings.json`, mirrored back into `process.env` for Horton's `createBuiltinAgentHandler`, and the runtime is restarted so the next request picks them up. Optional `BRAVE_SEARCH_API_KEY` is captured in the same flow. - Localhost server discovery: main probes a focused port set (4437/4438/4439/3000/4000/8080) for `GET /_electric/health` on a 30 s background loop and broadcasts the set via `desktop:state- changed`. The renderer's `ServerPicker` polls every 5 s while its menu is open and surfaces matching servers as one-click "add" rows under the saved-server list (no header, consistent row height with the trash-button rows). - Bug fix: `Add server` dialog cancel was disabled whenever no servers were saved — a holdover from the web build's auto-seeded `This Server` fallback that doesn't exist in desktop. Cancel / Esc / backdrop click now always dismiss. Co-authored-by: Cursor <cursoragent@cursor.com>
The active-entity lookup was using a raw `===` comparison inside `.where()`, which TanStack DB rejects (the predicate evaluates to a boolean at build time instead of producing a query expression). The trailing `.limit(1)` then tripped the "LIMIT/OFFSET require ORDER BY" guard once the predicate was fixed. Switch to `eq(e.url, activeEntityUrl)` and drop the limit — `url` is the primary key, so the predicate already constrains the result to at most one row, the same pattern the other entity-by-url queries (Workspace, TileContainer) already use. Co-authored-by: Cursor <cursoragent@cursor.com>
Restructure the settings cog dropdown into a launcher with cascading submenus (Theme, Local Runtime) plus a "Settings…" link that opens a full settings screen at /settings/<category>. The screen mirrors the macOS System Settings layout: a categories sidebar on the left and bordered section cards on the right. - Categories: General (provider API keys), Appearance (theme tile picker), Local Runtime (status badge + start/restart/stop, desktop only). - Extract ApiKeysForm into a shared component reused by the first-launch modal and the General page. - RootShell swaps the workspace sidebar for the settings sidebar while on /settings/* so the experience reads as part of the same shell. - useDocumentTitle now recognises the settings route so the Electron Window menu reflects the active settings page rather than the previously-active session. Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the hand-rolled tsdown + electronmon + wait-on chain with vite-plugin-electron, which builds main + preload in watch mode and manages the Electron child process with proper debouncing — no more restart loop. - agents-server-ui's Vite dev server runs in `--mode desktop` on a pinned port (5183) so the desktop main process can wait on it deterministically and load the renderer with full React Refresh / CSS HMR. The desktopHtmlMarker plugin runs in dev too, so `data-electric-desktop="true"` is on `<html>` from the first byte. - Main / preload now build via Vite. All bare imports are externalised so Node resolves `node_modules` at runtime — fixes the jsdom→canvas build error and keeps `dist/main.js` at ~94 kB. - `setActiveServer` no longer calls `restartRuntime()` when the active server is unchanged, so the renderer mount (which always fires `saveActiveServer(active)`, doubled by React 19 StrictMode in dev) doesn't trigger a triple Horton bootstrap on every window open. - DevTools no longer auto-open on each window — multi-window setups get noisy fast. The standard View → Toggle Developer Tools menu item (Cmd+Opt+I / Ctrl+Shift+I) still works in every window. Drop tsdown / electronmon / cross-env devDeps; add vite + vite-plugin-electron. Concurrently + wait-on are kept for orchestrating the parallel UI / desktop dev servers. Co-authored-by: Cursor <cursoragent@cursor.com>
New ≡ dropdown between the server picker and settings cog. Group the session list by Date / Type / Status, hide noisy types or statuses via Show submenus, and Expand/Collapse all in one click. Prefs persist to localStorage. Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a way to choose a `workingDirectory` spawn arg for each new
Horton session, without touching the runtime's global cwd.
• horton: accepts an optional `workingDirectory` spawn arg and
routes it into the system prompt + filesystem tools, with
fallback to the runtime's configured cwd.
• agents-desktop: new `desktop:pick-directory` IPC for one-shot
native folder picks (no persistence, no runtime restart).
• new-session composer: a `WorkingDirectoryPicker` pill sits
next to the model / reasoning controls, defaulting to the
most-recently-used path.
• sidebar filter menu: adds a "Working dir" group-by mode
backed by `groupByWorkingDirectory` in `sessionGroups`.
• shared UI: new `Combobox` primitive (Base UI wrapper, types
mirror `Select<V extends string>`) used by the picker for
typeahead + recents + native browse, with ServerPicker-style
row geometry and a check-↔-IconButton swap on hover for
removing recents.
Co-authored-by: Cursor <cursoragent@cursor.com>
Store provider tool-call IDs on tool call events and use them to match overlapping tool starts and completions reliably. Co-authored-by: Kyle Mathews <mathews.kyle@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Keep tool_call/tool_result pairs valid under context budget truncation and merge adjacent assistant history blocks so resumed prompts remain API-compatible. Co-authored-by: Kyle Mathews <mathews.kyle@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Align worker tool selection and prompts with the actual web_search tool name so spawned agents receive consistent instructions. Co-authored-by: Kyle Mathews <mathews.kyle@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Use the full-radius token for badges so status labels render as rounded pills. Co-authored-by: Kyle Mathews <mathews.kyle@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Warm entity stream connections through the route loader on sidebar intent so session timelines can reuse a preloaded StreamDB when opened. Co-authored-by: Kyle Mathews <mathews.kyle@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Use the static hero title from the projects design and remove duplicate new-session title chrome from standalone tiles. Co-authored-by: Cursor <cursoragent@cursor.com>
Pick one of the hero title phrases when the new-session view mounts, without rotating or animating after load. Co-authored-by: Kyle Mathews <mathews.kyle@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…dings
Two related polish passes:
• Sidebar working-dir grouping now labels each bucket with a
tildified, head-truncated path (`~/Code/electric`,
`…/projects/acme`) instead of the bare basename, with the
full absolute path surfaced as a `title` tooltip on hover.
Truncation drops *leading* segments so the project folder
stays visible — CSS ellipsis would lose it from the end.
Path helpers extracted to `lib/pathDisplay.ts` and shared
with `WorkingDirectoryPicker`.
• Removed `text-transform: uppercase` (and paired
`letter-spacing`) from every section/group heading across
the app — sidebar, search palette, new-session screen,
state-explorer headers, split menu, dropdown group labels.
Bumped `font-weight: 500` (and 10 → 11px on the small
sidebar / search labels) to keep heading prominence without
relying on uppercase letterforms.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Patch release across the four touched packages: new `@electric-ax/agents-desktop` Electron shell, the tile-based workspace + dropdown / settings rework in `agents-server-ui`, Horton's new `workingDirectory` spawn arg, and the runtime tool-pair / event-matching fixes surfaced while building it. Co-authored-by: Cursor <cursoragent@cursor.com>
❌ 1 Tests Failed:
View the top 1 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add resolve aliases to compile workspace packages from source TS - Invert externalization: bundle all deps except native addons (better-sqlite3, sqlite-vec), optional native peer deps (canvas, bufferutil, utf-8-validate), filesystem-dependent (jsdom), and worker-thread-based (pino, pino-pretty) - Switch main process output from ESM to CJS (array-wrap output config to override vite-plugin-electron's forced ESM format) - Add externalized packages as direct deps for runtime resolution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
05e5c48 to
5068685
Compare
Generate unified diff patches in the edit and write tools using the diff package, and render them in the UI with syntax-colored lines. Replaces the old separate "Removed"/"Added" blocks with a single diff view. Edit and write tool calls now default to expanded. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When no STREAMS_DATA_DIR env var is set, persist embedded durable
streams data under ${cwd}/.streams-data instead of leaving the
directory undefined.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A long-running branch that lands two related shipments on the Electric Agents stack:
1. Tile-based workspace in
@electric-ax/agents-server-uiReplaces the single-pane chat surface with a Cursor / Zed-style tile workspace:
localStorageand encoded into shareable layout URLs.View ▸menu — split horizontally / vertically, focus next pane, close pane, reset layout.WorkingDirectoryPickerin the new-session composer with recents (localStorage) and a native folder picker on Electron, backed by a newComboboxUI primitive that mirrorsSelect's typing.2. New
@electric-ax/agents-desktoppackage — Electron shellA desktop app that bundles a local Horton runtime (no Postgres / Electric / agents-server bundled — talks to those over HTTP):
https://electric.ax/agents/) + Window menu listing open windows by active session.userData/settings.jsonand mirrored intoprocess.env.vite-plugin-electron(replaces the brittletsdown+electronmonsetup that caused restart loops in StrictMode).Supporting fixes pulled in
@electric-ax/agents— Horton accepts an optionalworkingDirectoryspawn arg so each session can run against its own project root without restarting the runtime;web_searchworker tool name fix.@electric-ax/agents-runtime— preserve tool pairs during compaction, match tool-call events by id (both surfaced while building the desktop UI).useDocumentTitle— useseq()from@tanstack/react-dband drops alimit(1)that needed anorderBy.Changeset
.changeset/electron-desktop-and-agents-ui-tiles.md— patch bumps for@electric-ax/agents-desktop,@electric-ax/agents-server-ui,@electric-ax/agents-runtime, and@electric-ax/agents. (@electric-ax/agents-serveris in afixedgroup with the UI so will track automatically.)Test plan
pnpm -C packages/agents-server-ui typecheck— clean.pnpm --filter @electric-ax/agents-desktop dev— Electron window opens against a local agents-server, tray icon shows runtime status, HMR works on UI edits.pnpm -C packages/agents-server-ui build) still works — desktop-only chrome stays out of the web bundle.Made with Cursor