Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
19a9e33
docs: add extensions system RFC
DevinVinson May 23, 2026
1a36465
Ensure navigation story is covered, plan out building an mvp poc
DevinVinson May 23, 2026
0b62a9d
Merge branch 'main' into dv/extensions-poc-v1
DevinVinson May 23, 2026
3f9d830
Updates after settings changes
DevinVinson May 23, 2026
3665108
Merge branch 'OpenHands:main' into dv/extensions-poc-v1
DevinVinson May 23, 2026
eff53f2
Update with a plan
DevinVinson May 23, 2026
e53f744
Merge branch 'main' into dv/extensions-poc-v1
DevinVinson May 24, 2026
1b8ae98
Merge branch 'OpenHands:main' into dv/extensions-poc-v1
DevinVinson May 30, 2026
a876346
Merge remote-tracking branch 'upstream/main' into dv/extensions-poc-v1
DevinVinson Jun 3, 2026
ef2b98a
docs: refresh extensions poc plan
DevinVinson Jun 3, 2026
1d1999d
Merge remote-tracking branch 'upstream/main' into dv/extensions-poc-v1
DevinVinson Jun 11, 2026
c8203eb
Update extension planning for visualizer work
DevinVinson Jun 11, 2026
dad50a4
Clarify Canvas Extensions surfaces
DevinVinson Jun 11, 2026
2b2530f
Rename management view to Extensions
DevinVinson Jun 11, 2026
f45959a
Clarify extension FAQ and theme contributions
DevinVinson Jun 11, 2026
fa2a325
Continue fine tuning, include ref to draft pr on visualizer registry
DevinVinson Jun 11, 2026
121058b
Reduce and reword so RFC is easier to digest
DevinVinson Jun 11, 2026
0c5543b
Challenge and clarify pass
DevinVinson Jun 11, 2026
fca9c31
Add in the example extensions
DevinVinson Jun 11, 2026
07e0b6f
Cover multi surface example
DevinVinson Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
773 changes: 773 additions & 0 deletions docs/ExtensionsSystemAgentExecutionPlan.md

Large diffs are not rendered by default.

443 changes: 443 additions & 0 deletions docs/ExtensionsSystemPoCPlan.md

Large diffs are not rendered by default.

1,528 changes: 1,528 additions & 0 deletions docs/ExtensionsSystemRFC.md

Large diffs are not rendered by default.

63 changes: 63 additions & 0 deletions examples/extensions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Example Canvas Extensions

Three minimal Canvas Extensions that each exercise one contribution surface from the
[Canvas Extensions RFC](../../docs/ExtensionsSystemRFC.md). They are repo fixtures
(`private: true`), not published packages, and ship hand-written browser ESM so they
install and run with **no build step**.

| Example | Surface(s) | Browser module | RFC |
|---|---|---|---|
| [`sunset-theme`](./sunset-theme) | A color theme in Settings > Application > Color Theme | none (manifest-only) | §12.2 |
| [`hello-page`](./hello-page) | A primary-nav page with placeholder content | `mount()` (view) | §12.1, §12.4 |
| [`app-panel`](./app-panel) | An "App" panel in the conversation view | `activate()` (route-less) | §12.4, §13.1 |
| [`shell-toolkit`](./shell-toolkit) | **Multi-surface:** a tool visualizer + a conversation panel + a nav view | `activate()` + `mount()` | §12.9, §12.4, §13.1 |

The first three cover the three module shapes in isolation — **no module** (theme-only),
**`mount()` only** (a view), and **`activate()` only** (a route-less registration).
`shell-toolkit` then combines them: one extension, one module, where `activate()`
registers a visualizer and a panel and `mount()` renders a view.

## Install

```sh
agent-canvas install ./examples/extensions/sunset-theme --yes
agent-canvas install ./examples/extensions/hello-page --yes
agent-canvas install ./examples/extensions/app-panel --yes
agent-canvas install ./examples/extensions/shell-toolkit --yes
agent-canvas list canvas-extensions
```

Lifecycle depends on launch mode (RFC §15.2):

- **Packaged / Docker / static** — restart-bounded. Enable/disable from the CLI, then
restart `agent-canvas`. The Extensions page is read-only.
- **Dev source stack** (`npm run dev` / `dev:minimal`) — live management. Enable/disable
from the Extensions page and pick up newly installed extensions without a restart.
Use `--dev` to register a source folder and iterate:

```sh
npm run dev
agent-canvas install ./examples/extensions/hello-page --dev
```

## Verify each one

- **sunset-theme** — Settings > Application > Color Theme shows **Sunset**; selecting it
recolors the app. Disabling/removing the extension falls back to the default theme.
- **hello-page** — a **Hello** entry appears in the left sidebar after Automations and
opens the placeholder page; the "Say hello" button fires a host toast.
- **app-panel** — open a conversation; an **App** tab appears beside Files / Browser /
Terminal and renders the panel (showing the active conversation id).
- **shell-toolkit** — a **Shell** nav entry, a **Commands** conversation tab, and a
terminal-style visualizer for shell-like MCP tool calls — all from one extension.

## How these map to the spec

- All three carry a `package.json` with `agentCanvas.manifest` and an
`agent-canvas.extension.json` manifest (RFC §10, §11).
- Browser modules are plain browser-ready ESM with no bare runtime imports; the only
imports are type-only JSDoc references that are erased (RFC §12.4, PoC §2.2).
- They use Canvas design tokens (`--oh-color-primary`, `--cool-grey-*`) instead of
importing Canvas internals, and only touch host-mediated context APIs.
- They are **trusted same-origin** code once enabled — not sandboxed (RFC §19). Install
only extensions you trust.
44 changes: 44 additions & 0 deletions examples/extensions/app-panel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# App Panel (example Canvas Extension)

Adds an **App** panel to the conversation work area, beside the built-in Files, Browser,
and Terminal panels.

This demonstrates the `conversationRightPanels` surface (RFC §12.4) and the route-less
registration lifecycle (RFC §13.1): the manifest declares only panel *metadata*, and the
browser module registers the panel *implementation* by contribution id during
`activate(context)` — independent of any view route being open.

## What it does

- Contributes a `conversationRightPanels[]` entry: `id: "example.app-panel.app"`,
`title: "App"`, an icon, and `order: 200`.
- On activation, calls `context.conversationPanels.registerRightPanel({ id, mount })`.
- The panel's `mount({ root, conversation })` renders placeholder content and shows the
active conversation id, degrading gracefully when no conversation is active.
- Returns a `dispose()` that unregisters the panel when the extension is disabled or
removed (so on the dev source stack it can disappear live, without a restart).

Canvas owns tab placement, collapsed/expanded behavior, focus, loading state, and the
per-panel error boundary; the extension only fills the panel body.

## Install

```sh
# packaged/restart-bounded:
agent-canvas install ./examples/extensions/app-panel --yes

# dev source stack (live):
npm run dev
agent-canvas install ./examples/extensions/app-panel --dev
```

Open a conversation and select the **App** tab among the right-hand panels.

## Assumed API shape

The RFC fixes the *manifest* contribution shape and the `registerRightPanel` entry point
but leaves the exact panel-mount params to implementation. This example assumes
`registerRightPanel({ id, mount({ root, conversation }) })` returning a disposable,
consistent with the DOM-island `mount()` contract used by views (RFC §12.4, §13).
If the implemented contract differs, only `dist/index.js` needs to change — the manifest
stays the same.
30 changes: 30 additions & 0 deletions examples/extensions/app-panel/agent-canvas.extension.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "https://schemas.openhands.dev/agent-canvas/extension.v1.json",
"schemaVersion": 1,
"id": "example.app-panel",
"displayName": "App Panel",
"version": "0.1.0",
"description": "Adds an 'App' panel to the conversation view, beside Files, Browser, and Terminal.",
"publisher": { "name": "Agent Canvas Examples" },
"license": "MIT",
"compatibility": {
"agentCanvas": ">=1.0.0-rc.6 <2.0.0",
"extensionApi": 1
},
"permissions": {
"ui": ["conversationPanels"]
},
"browser": {
"module": "./dist/index.js"
},
"contributes": {
"conversationRightPanels": [
{
"id": "example.app-panel.app",
"title": "App",
"icon": "./dist/app-panel.svg",
"order": 200
}
]
}
}
9 changes: 9 additions & 0 deletions examples/extensions/app-panel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@example/agent-canvas-app-panel",
"version": "0.1.0",
"type": "module",
"private": true,
"description": "Adds an 'App' panel to the conversation view beside Files, Browser, and Terminal.",
"agentCanvas": { "manifest": "./agent-canvas.extension.json" },
"files": ["agent-canvas.extension.json", "dist"]
}
40 changes: 40 additions & 0 deletions examples/extensions/hello-page/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Hello Page (example Canvas Extension)

Adds a **Hello** entry to the primary left navigation (after Automations) that opens a
full page rendering placeholder "Hello, world" content.

This demonstrates the `views` + `leftNavigation` surfaces (RFC §12.1) and the
trusted same-origin `browser.module` view runtime (RFC §12.4, §13): Canvas mounts the
extension's `mount({ root, context })` into a Canvas-owned route container at
`/canvas-extensions/example.hello-page/hello`.

## What it does

- Contributes a `views[]` entry with a `primarySidebar` / `afterAutomations` navigation
slot, an icon, and `order: 300`.
- Ships a browser-ready ESM module (`dist/index.js`) that renders into the provided
`root` element using vanilla DOM — no React, no bundler, no bare imports.
- Uses Canvas design tokens (`var(--oh-color-primary)`, `var(--cool-grey-*)`) so it
follows the active color theme, and reads `context.theme.colorScheme`.
- Calls the host API `context.ui.toast(...)` from a button to show a live host call.
- Returns a `dispose()` so Canvas can clean up on unmount/remount.

## Install

```sh
# packaged/restart-bounded:
agent-canvas install ./examples/extensions/hello-page --yes

# dev source stack (live), recommended for iterating:
npm run dev # in one terminal
agent-canvas install ./examples/extensions/hello-page --dev
```

After install (and enable), look for **Hello** in the left sidebar, just below
Automations.

## Notes

`dist/index.js` is hand-written browser ESM, so **no build step is required**. A real
extension would author in TS/JSX and bundle to a single browser-ready ESM file; the
emitted module must still avoid bare runtime imports (RFC §12.4, PoC §2.2).
35 changes: 35 additions & 0 deletions examples/extensions/hello-page/agent-canvas.extension.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"$schema": "https://schemas.openhands.dev/agent-canvas/extension.v1.json",
"schemaVersion": 1,
"id": "example.hello-page",
"displayName": "Hello Page",
"version": "0.1.0",
"description": "Adds a Hello page to the left navigation, rendering placeholder content.",
"publisher": { "name": "Agent Canvas Examples" },
"license": "MIT",
"compatibility": {
"agentCanvas": ">=1.0.0-rc.6 <2.0.0",
"extensionApi": 1
},
"permissions": {
"ui": ["views", "leftNavigation"]
},
"browser": {
"module": "./dist/index.js"
},
"contributes": {
"views": [
{
"id": "hello",
"title": "Hello",
"route": "/hello",
"navigation": {
"location": "primarySidebar",
"slot": "afterAutomations",
"order": 300,
"icon": "./dist/hello-page.svg"
}
}
]
}
}
9 changes: 9 additions & 0 deletions examples/extensions/hello-page/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@example/agent-canvas-hello-page",
"version": "0.1.0",
"type": "module",
"private": true,
"description": "Adds a 'Hello' page to the primary navigation with placeholder content.",
"agentCanvas": { "manifest": "./agent-canvas.extension.json" },
"files": ["agent-canvas.extension.json", "dist"]
}
53 changes: 53 additions & 0 deletions examples/extensions/shell-toolkit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Shell Toolkit (example Canvas Extension)

A **multi-surface** Canvas Extension: one package, one browser module, three
contribution surfaces. This is the case the single-surface examples don't show —
it demonstrates the unified module contract from RFC §13.1, where one module's
`activate()` registers route-less surfaces and `mount()` renders a view.

## Surfaces (all from `dist/index.js`)

| Surface | Contribution | Registered in |
|---|---|---|
| Tool visualizer | `toolVisualizers` — renders shell-like MCP tool calls as a terminal card | `activate()` |
| Conversation panel | `conversationRightPanels` — a **Commands** panel beside Files/Browser/Terminal | `activate()` |
| Navigation view | `views` — a **Shell** page in the left nav (after Automations) | `mount()` |

The visualizer and panel are route-less, so they register during the startup
`activate(context)` pass and exist regardless of whether the Shell view is open. The
returned disposable unregisters both on disable/remove — so on the dev source stack the
visualizer and panel can appear/disappear live without a restart (RFC §15.2/§13.1).

## Install

```sh
# packaged/restart-bounded:
agent-canvas install ./examples/extensions/shell-toolkit --yes

# dev source stack (live):
npm run dev
agent-canvas install ./examples/extensions/shell-toolkit --dev
```

## Verify

- A **Shell** entry appears in the left sidebar after Automations and opens the view.
- Open a conversation: a **Commands** tab appears beside Files / Browser / Terminal.
- When the agent runs a shell-like MCP tool (`tool_name` of `shell` / `bash` /
`execute_bash`), the event renders as a terminal card instead of the default body;
other tools and events fall through to the built-in renderers (RFC §12.9).

## Provisional contracts ⚠️

Two parts depend on contracts the RFC has not finalized; both are flagged inline in
`dist/index.js`:

1. **Visualizer body** — follows the not-yet-final agent-team API (PR #1277, RFC §12.9).
The real `Body` is a React component returning a React node from
`@openhands/agent-canvas/visualizers` primitives; this example returns a DOM node for
illustration. The relationship between the manifest `toolVisualizers[].module` field
and `activate()`-registration also needs to be pinned.
2. **Right-panel mount** — same assumed `registerRightPanel({ id, mount })` /
`mount({ root, conversation })` shape as [`app-panel`](../app-panel), pending §12.4/§13.1.

When those land, only `dist/index.js` should need to change; the manifest stays the same.
50 changes: 50 additions & 0 deletions examples/extensions/shell-toolkit/agent-canvas.extension.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"$schema": "https://schemas.openhands.dev/agent-canvas/extension.v1.json",
"schemaVersion": 1,
"id": "example.shell-toolkit",
"displayName": "Shell Toolkit",
"version": "0.1.0",
"description": "Multi-surface example: a tool visualizer for shell commands, a 'Commands' conversation panel, and a 'Shell' nav page — all from one extension.",
"publisher": { "name": "Agent Canvas Examples" },
"license": "MIT",
"compatibility": {
"agentCanvas": ">=1.0.0-rc.6 <2.0.0",
"extensionApi": 1
},
"permissions": {
"ui": ["toolVisualizers", "conversationPanels", "views", "leftNavigation"]
},
"browser": {
"module": "./dist/index.js"
},
"contributes": {
"toolVisualizers": [
{
"id": "example.shell-toolkit.shell",
"actionKinds": ["MCPToolAction"],
"observationKinds": ["MCPToolObservation"]
}
],
"conversationRightPanels": [
{
"id": "example.shell-toolkit.commands",
"title": "Commands",
"icon": "./dist/shell-toolkit.svg",
"order": 250
}
],
"views": [
{
"id": "shell",
"title": "Shell",
"route": "/shell",
"navigation": {
"location": "primarySidebar",
"slot": "afterAutomations",
"order": 350,
"icon": "./dist/shell-toolkit.svg"
}
}
]
}
}
9 changes: 9 additions & 0 deletions examples/extensions/shell-toolkit/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@example/agent-canvas-shell-toolkit",
"version": "0.1.0",
"type": "module",
"private": true,
"description": "Multi-surface Canvas Extension: a tool visualizer, a conversation panel, and a nav view from one module.",
"agentCanvas": { "manifest": "./agent-canvas.extension.json" },
"files": ["agent-canvas.extension.json", "dist"]
}
31 changes: 31 additions & 0 deletions examples/extensions/sunset-theme/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Sunset Theme (example Canvas Extension)

A **theme-only** Canvas Extension. It contributes one color theme — "Sunset" — and
nothing else: no view, settings panel, right panel, tool visualizer, SDK plugin, or
browser module.

This demonstrates the first-class theme-only case from RFC §12.2 / §13: a package that
ships **no `browser.module`** at all and still works, because color themes are projected
from manifest data (validated by Canvas), not from running extension code.

## What it does

Adds a "Sunset" option to **Settings > Application > Color Theme**. The theme overrides:

- `scale` — the `--cool-grey-*` semantic palette (warm dark tones).
- `heroui` — a few HeroUI HSL-channel variables (background/foreground/content/default).
- `tokens` — the `--oh-color-primary` / `--oh-accent` brand tokens (amber).

Canvas owns selection, persistence, application, and fallback. If the extension is
disabled or removed while "Sunset" is active, Canvas falls back to the default built-in
theme and records a diagnostic (RFC §12.2).

## Install

```sh
agent-canvas install ./examples/extensions/sunset-theme --yes
```

Then open Settings > Application > Color Theme and pick **Sunset**.

No build step: the extension is pure manifest data.
Loading
Loading