Skip to content

feat(agent): app-wide agent side panel - #437

Merged
lightningpixel merged 15 commits into
devfrom
feat/agent-side-panel
Nov 12, 2026
Merged

lightningpixel merged 15 commits into
devfrom
feat/agent-side-panel

Conversation

@lightningpixel

@lightningpixel lightningpixel commented Nov 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

The agent leaves the Generate page. It now lives in a right-side panel available on every page, and it can act on the whole app without changing page. Design: docs/superpowers/specs/2026-11-12-agent-side-panel-design.md.

Closes #438
Closes #305

Panel

  • Placement: the panel is mounted in MainLayout, next to <main>, so it pushes the app instead of covering it.
  • Opening: the Agent button in the TopBar or Ctrl+L / Cmd+L toggles it, with a 220 ms slide-and-fade animation. Users with reduced motion get no animation.
  • Size: drag the left edge to resize it (320–640 px), or use the arrow keys; double-click resets it.
  • Persistence: the open state and the width are saved.
  • Stays mounted when closed: a streaming turn or a running workflow keeps going.
  • Generate: the chat mode of the left panel is removed and ChatPanel moves to src/shared/components/agent/AgentChat.tsx.
  • Composer: restyled after Claude's. The box holds the text, a + to attach an image and the send button; the model picker and the thinking mode sit underneath.

Conversations

  • Storage: one JSON file per conversation in <userData>/agent/conversations/.
    • Access goes through IPC: agent.conversations.list/read/save/remove.
    • Ids are validated.
    • Writes go to a temp file, then a rename.
  • Panel: the panel lists past conversations, can open or delete them, and starts new ones. The latest one reopens at startup.
  • Locking: while a turn, a workflow or a generation is running, switching conversation is blocked.
  • Failed saves: the conversation stays marked as unsaved and blocks switching away.

App-wide agent

  • Client tool bridge:

    • The backend emits client_tool events and waits for POST /agent/tool-result.
    • The tool runs in the app (stores and IPC).
    • The result comes back as a real role: tool message, in the same turn.
  • New client tools:

    • extensions: install_extension, uninstall_extension, reload_extensions
    • model weights: download_model_weights, delete_model_weights
    • workflows: open_workflow, delete_workflow
    • settings: set_setting, set_storage_folder
    • mesh history: undo_mesh, redo_mesh
    • pages: navigate, only when the user explicitly asks
  • New server tools: list_extensions, get_settings, generate_from_image. The last one generates from the image attached to the message, into the Generate viewer. The generation path is extracted into src/shared/utils/generationRunner.ts.

  • Confirmation: these tools show a Confirm / Cancel card and the agent waits for the click:

    • installing an extension (it runs code from GitHub)
    • uninstalling an extension
    • deleting model weights
    • deleting a workflow
    • changing a storage folder

    The app enforces the confirmation itself, whatever the server's flag says. A card that the server stopped waiting for is cancelled, and a queued card opens the panel.

  • Context: every request sends the current page, the mesh, workflows, extensions, downloaded models, storage folders and UI settings.

  • Storage folder changes: set_storage_folder uses the same rules as Settings > Storage: storageEntries, checkStorageLocation, the move, and a report of the items left behind.

Test plan

  • npm run test:node: 240 tests, 239 pass, 0 fail
  • python -m unittest discover -s tests (api): 415 tests, OK (5 skipped)
  • ESLint is clean on src and electron. tsc reports no new errors; the remaining errors (assetLibrary, workflowRunStore and TS5097 in the tests) are already on dev.
  • Manual:
    • Open the panel on each page and check it pushes the page.
    • Change page during a conversation; it stays.
    • Restart Modly; the last conversation comes back and is in the list.
    • "Liste mes extensions", then "Recharge les extensions".
    • "Supprime le workflow X": Cancel, then ask again and Confirm.
    • Install an extension from a GitHub URL; the card shows before the install.
    • "Cache l'indicateur de RAM" and "Passe l'interface en large".
    • Attach an image and ask "Génère un modèle 3D"; it appears in the Generate viewer.
    • With a mesh loaded: "Annule la dernière modification".

Tools the app must run itself (extensions, model weights, workflows,
settings, mesh undo/redo, navigation) are declared in CLIENT_TOOLS. The
tool loop hands them to the app as a client_tool event and waits for the
answer on POST /agent/tool-result; destructive ones ask the user first
and wait up to 10 minutes. Adds list_extensions, get_settings and
generate_from_image, and renders the page, models, workflows, storage
folders and UI options into the system prompt.
Adds window.electron.agent.conversations (list/read/save/remove), one file
per conversation in <userData>/agent/conversations. Ids are checked against
/^[a-z0-9-]{1,64}$/ and writes go through a temp file plus rename.
Pure title/sort/settle helpers live in src/shared/utils/agentConversations.
The chat leaves the Generate page's basic/chat toggle and becomes the body
of an AgentPanel next to <main> in MainLayout, so it pushes the app. The
panel toggles from a TopBar button or Ctrl/Cmd+L, resizes from its left
edge (320-640px) and remembers its open state and width.

Messages move to agentConversationStore: they survive page changes and
panel close, save to disk after 500ms of quiet, and the latest
conversation reopens at startup. The header lists past conversations
with open and delete.
…ation

runGeneration and createGenerationApi hold the from-image submit and status polling without React, so the agent can start a generation outside a hook. useGeneration and useApi now delegate to them with unchanged behaviour.
clientTools.ts runs the client_tool calls of the agent (extensions, weights, workflows, settings, storage folders, mesh undo/redo, navigation) through injected dependencies, asks for confirmation when required and posts the result to /agent/tool-result. ConfirmCard.tsx renders the Confirm/Cancel card and its promise queue. agentContext.ts builds the request context from the stores.
…panel

# Conflicts:
#	package.json
#	src/shared/hooks/useApi.ts
#	src/shared/hooks/useGeneration.ts
#	src/shared/utils/agentStream.ts
…to the chat

The chat runs client_tool events through the client tool registry (after a Confirm click for destructive ones), starts generate_from_image after the turn, and sends the app-wide context. Undo/redo checks read the mesh history depths.
…he app

install_extension runs code downloaded from GitHub, so it now needs a Confirm click showing the URL. The app also asks for destructive tools and installs whatever the server's confirm flag says, so a backend slip cannot bypass the card.
- set_storage_folder moves what Settings would (storageEntries, so a workflows folder of JSON files moves too), refuses what Settings refuses, and reports items left behind.
- set_setting declares uiScale by its scale names, matching the app.
- A queued confirm card opens the panel; a card the server stopped waiting for is cancelled; answered cards are cleared per turn and per conversation.
- An agent-started generation keeps the conversation busy, so its result cannot land in another one.
- A failed conversation save stays dirty and blocks switching away; temp files get unique names.
The panel's slot grows to its width while the panel slides and fades in from the right (220 ms), and the reverse on close. No transition while resizing, none with reduced motion. Closed, it stays mounted but invisible.
A lighter rounded box with the text on top, a plus to attach and the send button inside; the model picker and the thinking mode sit as quiet text controls under it. The Shift+Enter hint moves to the send button's tooltip.
Agent tools:
- generate_from_image refuses while a generation is already running
- the tool-result endpoint no longer doubles the "Error: " prefix
- a confirmed tool waits for the click (10 min), then gets tool_timeout
  for its run, via the new POST /agent/tool-confirmed
- the non-streaming /agent/chat no longer offers client tools
- uninstall_extension rejects "ext/node" ids so the card can't mislead

Conversations:
- every disk operation goes through one queue: a save in flight is always
  awaited, and a delete can't race the write of its file
- a failed delete keeps the pending changes
- init retries after a failure and skips unreadable conversations
- rename retries on transient Windows locks; orphan .tmp files are cleaned
@lightningpixel
lightningpixel merged commit 5ee3a0b into dev Nov 12, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant