Skip to content

Request for Shaping Feedback: Addons for Agent Canvas UI #481

@DevinVinson

Description

@DevinVinson

Request for Shaping Feedback: Addons for Agent Canvas UI

I’ve been experimenting with local UI add-ons for Agent Canvas in this branch:

This is not ready as a merge PR yet. I’m opening this first to get shaping feedback on the direction, the user experience, and where add-ons should live long term.

What The Prototype Explores

The prototype adds a trusted local, frontend-only add-on system for Agent Canvas.

The current implementation discovers add-ons from a repo-local folder:

addons/<addon-id>/.openhands/addon.json

Then npm run make-addons generates a registry that Agent Canvas uses to
include the add-ons in the frontend app.

The important thing about the current prototype: add-on files are compiled by Agent Canvas as part of the Vite/React build. When an add-on is already registered, Vite HMR works well for source changes, which makes local iteration feel good. Adding/removing an add-on or changing manifest metadata still requires regenerating the registry and restarting/rebuilding the UI.

Proof Of Concept Examples

1. Routed UI Add-on

The first use case is a normal add-on page that appears in the Agent Canvas sidebar and mounts under:

/addons/:addonId/*

Example manifest:

{
  "name": "agent-status",
  "title": "Agent Status",
  "frontend": {
    "entry": "src/index.tsx"
  },
  "sidebar": {
    "order": 350
  }
}

The add-on exports a register(api) function and returns a React component for
the route.

This proves that a local add-on can add a dedicated UI page without adding backend routes or changing agent-server behavior.

2. Style-only Shell Add-on

The second use case is a route-less add-on that customizes the Agent Canvas shell with CSS but does not add a sidebar icon or page.

Example manifest:

{
  "name": "canvas-polish",
  "title": "Canvas Polish",
  "frontend": {
    "route": false
  },
  "sidebar": {
    "visible": false
  },
  "styling": {
    "appCss": ["src/agent-canvas.css"]
  }
}

The CSS can scope to the Agent Canvas root:

[data-agent-server-ui][data-agent-canvas-addons~="canvas-polish"]
  > [data-theme] {
  --oh-color-primary: #7dd3fc;
}

This proves that an add-on can enhance or customize the whole UI without introducing a new route.

The Next Unclear Use Case: Route-less JavaScript

The third case I’d like feedback on is a UI add-on that needs JavaScript but still should not add a route.

Examples:

  • global keyboard shortcuts
  • small floating UI elements
  • shell badges/status indicators
  • command-palette entries, if we add shell slots later
  • small app-wide UI behavior paired with CSS customization

I’m unsure which direction is better here.

Option A: Component-based Root Add-ons

A route-less add-on could still provide a normal .ts, .tsx, or .js entry:

{
  "name": "canvas-polish",
  "title": "Canvas Polish",
  "frontend": {
    "route": false,
    "entry": "src/index.tsx"
  },
  "sidebar": {
    "visible": false
  }
}

Then it could return a root-level React component:

export default function register(api) {
  return {
    AppComponent: CanvasPolishRoot,
  };
}

Agent Canvas would mount that component near the app root, outside the route outlet.

This keeps add-ons in the React/Vite/TypeScript world and preserves HMR in dev, but it still assumes Agent Canvas is compiling the add-on source.

Option B: Compiled Runtime Scripts

Another option is to support compiled JavaScript loaded on top of the running UI, for example an IIFE:

{
  "name": "canvas-polish",
  "title": "Canvas Polish",
  "frontend": {
    "route": false
  },
  "runtime": {
    "script": "dist/canvas-polish.iife.js",
    "style": "dist/canvas-polish.css"
  }
}

The script could register itself through a small global API.

This may fit future installs better because the running Agent Canvas app would not need to compile user source code. But it is less React-native, harder to typecheck, and opens more questions around cleanup, lifecycle, dependency duplication, and security.

Where Should Add-ons Live?

The prototype currently uses a repo-local addons/ folder because that was easiest for proving the concept.

For a real user-facing flow, I think local add-ons probably belong in:

~/.openhands/agent-canvas/addons/<addon-id>/

That would keep user customization with other local Agent Canvas/OpenHands data rather than mixing it into the Agent Canvas source checkout.

Possible layout:

~/.openhands/agent-canvas/addons/my-addon/
  .openhands/addon.json
  src/index.tsx
  src/styles.css

I’d like feedback on whether this is the right default.

Future Install Question

The current build-time model works naturally when developing Agent Canvas from source:

  • npm run dev
  • generate the add-on registry
  • Vite compiles add-on source
  • HMR works for already registered add-ons

But the future install story is less obvious.

If users install Agent Canvas from npm or run a prebuilt static package, then user-local .ts / .tsx add-ons cannot simply be compiled into the already-built app unless we add a specific addon development/build flow.

Possible directions:

  1. Source/dev mode only:

    • source add-ons work when running npm run dev
    • good for contributors and local experimentation
    • not a complete installed-user story
  2. Add-on dev mode:

    • support something like a dev-addons or
      ~/.openhands/agent-canvas/addons folder only in dev mode
    • HMR remains a local development feature
    • installed/static Agent Canvas would not promise source add-on compilation
  3. Local add-on build/cache:

    • Agent Canvas could eventually provide a command that compiles user add-ons
      into a local cache under ~/.openhands/agent-canvas
    • more complex, but could support installed usage without committing add-on
      source into the main repo
  4. Runtime compiled add-ons:

    • use precompiled JS/CSS assets loaded by the running app
    • possibly better for installed users
    • bigger API and security design

I’m not sure which of these should be the target yet.

Current Leaning

My current feelings on some of these:

Keep the first mergeable version focused on trusted local UI add-ons

For the first real PR, I think add-ons should be treated as local code that the user intentionally places on their own machine. They would not be sandboxed, reviewed, or installed from a marketplace.

Keep backend/agent runtime plugins out of scope

This proposal is only about the Agent Canvas browser UI. It should not add anything else.

Those may be useful someday, but they are a different design problem with a larger security and compatibility surface.

Support routed add-on pages and style-only shell add-ons

The first mergeable version should support the two use cases already proved in the prototype:

  1. Routed add-on pages:

    • add a sidebar entry
    • mount under /addons/:addonId/*
    • render a React component returned by register(api)
  2. Style-only shell add-ons:

    • add app-level CSS
    • do not add a route
    • do not add a sidebar icon
    • scope CSS through the Agent Canvas root marker

This keeps the first implementation understandable. Addons could be full single page apps with their own nav item and route which gives a lot of surface area when combined with the existing apis.

Move user add-ons toward ~/.openhands/agent-canvas/addons

The repo-local addons/ folder is useful for proving the concept, but it is probably not the right final location for user add-ons. Local user customization should likely live beside other Agent Canvas/OpenHands local state:

~/.openhands/agent-canvas/addons/<addon-id>/

This would keep user-installed add-ons out of the Agent Canvas source checkout and avoid making local add-on files show up as repo changes.

The open implementation question is how to do this cleanly while still letting Vite compile and watch those files. A polished version may need:

  • a configurable add-on root
  • Vite server.fs.allow support for the external directory
  • a generated registry that does not commit machine-local absolute paths
  • Docker path handling for the mounted ~/.openhands directory
  • clear docs for source/dev mode versus installed/static mode

Preserve Vite HMR for already registered add-ons in dev mode

One nice property of the prototype is that once an add-on is registered, editing its source files can update through Vite HMR. That makes local add-on development feel much better than a full rebuild loop.

I think that is worth preserving for npm run dev or any future add-on dev mode.

However, HMR should not be over-promised. Adding or removing an add-on, or changing manifest fields like route/sidebar/style metadata, would still require regenerating the add-on registry and restarting/rebuilding the UI unless we add a more dynamic discovery layer later.

Get feedback before deciding route-less JavaScript

I do not think route-less JavaScript should be rushed into the first merge unless there is clear agreement on the shape...but I would really like to see it.

The main options seem to be:

  • root-mounted React components returned from register(api)
  • compiled IIFE/runtime scripts loaded by the running app
  • explicit shell slots first, then add-ons can target those slots
  • no route-less JavaScript yet

This choice affects the install story, security model, cleanup lifecycle, typing, testing, HMR, and how much of Agent Canvas internals add-ons can touch.

I would like feedback on that direction before turning it into implementation. See below

Feedback Requested

I’d especially like feedback on:

  1. Does the general local UI addon direction make sense for Agent Canvas?
  2. Are routed add-ons under /addons/:addonId/* the right first scope?
  3. Should user add-ons live under ~/.openhands/agent-canvas/addons?
  4. Should source-based add-ons be dev/source-build only?
  5. What should the installed/npm package story be?
  6. For route-less JavaScript, should we prefer: root-mounted React components, compiled IIFE/runtime scripts, both eventually, or neither until we define shell slots?
  7. What security/support concerns would block this from becoming a real PR?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    Shaping

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions