From 19a9e335577561f4ee32659eea08fb27a69c1975 Mon Sep 17 00:00:00 2001 From: Devin Date: Fri, 22 May 2026 22:33:55 -0400 Subject: [PATCH 01/14] docs: add extensions system RFC --- docs/ExtensionsSystemRFC.md | 899 ++++++++++++++++++++++++++++++++++++ 1 file changed, 899 insertions(+) create mode 100644 docs/ExtensionsSystemRFC.md diff --git a/docs/ExtensionsSystemRFC.md b/docs/ExtensionsSystemRFC.md new file mode 100644 index 000000000..fb43610ec --- /dev/null +++ b/docs/ExtensionsSystemRFC.md @@ -0,0 +1,899 @@ +# RFC: Agent Canvas Extensions + +Status: RFC, ready for review +Target: `OpenHands/agent-canvas` + + +## 1. Summary + +Agent Canvas should ship a first-class **Extensions** system: user-installable npm packages that extend the local Canvas experience and optionally contribute components to the agent runtime through SDK-supported surfaces. + +An Extension can contribute UI views, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces — Canvas does not patch or load code inside the Agent Server. + +The MVP delivers CLI install, a Packages management page, trusted same-origin extension views, dev-mode authoring with live reload, and SDK plugin / context merging on conversation launch. Marketplace, signing, sandboxed iframe views, parent-React component extensions, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. + +## 2. Motivation + +Today the Canvas surface area is fixed at build time. There is no supported path for users, partners, or our own product teams to ship a new launch template, a curated MCP setup, a UI view, or a packaged set of skills without modifying and rebuilding the application. This blocks three things we want: + +1. **Ecosystem.** Partners and the community cannot extend Canvas without forking. +2. **Vertical solutions.** We cannot ship product-specific launch flows (GitHub review, infra ops, data analysis) as composable add-ons that share a single distribution mechanism. +3. **Agent-built tools.** A future where an agent builds a Canvas extension during a conversation and proposes installing it is a meaningful product differentiator. Extensions are the substrate that makes that possible. + +The system must preserve the existing security and operational boundaries between Canvas (a local client + launcher) and the Agent Server (which may be local, remote, cloud, or ACP-backed). + +## 3. Product Vocabulary + +- **Extension** — an Agent Canvas npm package with an `agent-canvas.extension.json` manifest. +- **Extension Host** — local Node HTTP service launched by `agent-canvas`; installs, validates, and serves extension packages. +- **SDK Plugin** — the OpenHands SDK plugin format (`.plugin/plugin.json`), containing skills, hooks, MCP config, agents, and slash commands. Extensions may bundle or reference SDK plugins. +- **MCP Server** — external tool server config installed into agent settings. +- **Skill** — an OpenHands skill loaded by the SDK; usually delivered via an SDK plugin rather than copied into user skill directories. + +User-facing UI should use the word "Extension." Avoid "plugin" in the Canvas product surface because the SDK already owns that term. + +## 4. Goals + +- Install from npm package names, version ranges, tarballs, or local paths. +- Works when the user globally installs Agent Canvas and runs `agent-canvas`. +- Supports CLI install/manage commands, persisted enablement, and a process-level diagnostic kill switch. +- Keeps the Agent Server unmodified for MVP. +- Aligns with the OpenHands SDK plugin format rather than inventing a competing format. +- Lets one extension package ship UI plus agent-runtime descriptors. +- Third-party executable code is opt-in and auditable. +- Local development works without rebuilding Agent Canvas. +- Preserves current Skills, MCP, Launch, Automations, and Settings flows. + +## 5. Non-Goals + +- No extension may patch or monkeypatch the Agent Server. +- No extension may register server-side tools except through SDK-supported plugin/MCP mechanisms. +- No stable extension API may expose Canvas internals, parent React components, or DOM mutation hooks. MVP browser modules are trusted same-origin code, so DOM access is a trust concern rather than an enforceable sandbox boundary. +- No repository-local files may modify the running Canvas browser experience simply because a conversation is attached to that repository. +- No npm install scripts run by default during extension installation. +- No public marketplace, ratings, payments, reviews, or remote trust service in the first PRs. + +## 6. Prior Art + +Two existing systems informed this design. + +**Hermes (Nous Research).** We adopt: explicit opt-in enablement, three lifecycle states (enabled/disabled/installed), schema-validated manifests with capability declarations, multiple narrow contribution surfaces instead of one all-powerful hook API, good diagnostics (`list`, `doctor`, debug logs, skipped reasons), and explicit secret/env declarations. We do **not** adopt Hermes' direct `ctx.register_tool()` model or its automatic loading of user/project code — Canvas should not be an agent-server plugin loader, and tool override belongs in SDK governance. + +**Pi (pi.dev).** We adopt: a single generic `install` verb that detects artifact type, multiple authoring shapes (single directory, packaged module, package wrapping skills/SDK plugins/MCP templates), a developer reload story for explicitly registered local extensions, and clear language that local extensions are code requiring trust. We do **not** adopt Pi's repository-auto-discovery of extensions, its in-process tool registration, or its built-in tool overriding — those collapse the local/remote runtime boundary that Canvas must preserve. + +A prior internal prototype (`dv/addons-clean-v1`) contributed the typed host SDK shape, manifest validation, stable extension IDs, route host with error boundary, sidebar ordering, and path-normalization patterns. It is not merge-ready: it assumes a Vite build-time registry, which a globally-installed CLI cannot regenerate per install. An empty `src/addons/` directory remains on `main` from earlier exploration and will be removed as part of PR 0 (see §25). + +## 7. Architecture + +Three layers, each with a clear boundary: + +```mermaid +flowchart LR + User["User runs agent-canvas"] --> CLI["bin/agent-canvas.mjs"] + CLI --> Host["Extension Host (Node)"] + CLI --> Ingress["Ingress proxy"] + CLI --> AS["Agent Server"] + CLI --> UI["Static or Vite Canvas frontend"] + + Ingress -->|"/api/canvas/installations/*"| Host + Ingress -->|"/canvas-extensions/*"| Host + Ingress -->|"/api/automation/*"| Automation["Automation backend"] + Ingress -->|"/api/* and /sockets"| AS + Ingress -->|"/*"| UI + + UI -->|"registry, install, enable, settings"| Host + UI -->|"create conversation with PluginSpec[]"| AS + Host -->|"reads npm packages"| Store["~/.openhands/agent-canvas/installations"] +``` + +### 7.1 Extension Host (local Node service) + +The Extension Host runs in every launcher mode that supports extensions: the packaged `agent-canvas` CLI, `npm run dev`, `npm run dev:minimal`, and `npm run dev:static`. It owns: + +- npm package install/update/remove (always with `--ignore-scripts` by default). +- Manifest discovery and schema validation. +- Local package cache and `package-lock.json` for reproducibility. +- Enabled/disabled state. +- Extension settings and non-secret config. +- Serving extension browser assets. +- Serving a registry JSON document to the frontend. +- Producing launch contributions for the frontend. +- Diagnostics and logs. + +The Extension Host **must not** proxy arbitrary requests to the Agent Server. When it exposes Agent Server data through the supported extension API, it does so through narrow, typed, permission-declared capabilities. + +### 7.2 Frontend Runtime + +The Canvas frontend owns: + +- Extension management UI (the Packages page). +- Navigation for extension views. +- The extension view host that mounts trusted extension browser modules into Canvas-owned route containers (with a reserved future iframe runtime; see §12.1). +- Installation consent UX, permission display, secret setup prompts. +- Settings forms generated from extension JSON Schemas. +- Merging enabled extension launch contributions into conversation create payloads. +- Toasts, query invalidation, and error boundaries for extension actions. + +### 7.3 Agent Runtime (Agent Server) + +The Agent Server is unchanged for MVP. Canvas treats it as a black box and only uses documented request fields: + +- `Conversation(..., plugins=[PluginSource(...)])` via the create-conversation payload. +- `AgentContext.system_message_suffix`, already used by Canvas for the `` block ([src/api/agent-server-adapter.ts:91-225](src/api/agent-server-adapter.ts:91)). +- Existing MCP settings APIs. +- Existing skills APIs where supported. + +## 8. CLI UX + +### Global install and launch + +```sh +npm install -g @openhands/agent-canvas +agent-canvas +``` + +### Install + +`agent-canvas install` is a single generic verb that detects what is being installed and routes through the matching installer. + +```sh +agent-canvas install @acme/agent-canvas-github +agent-canvas install @acme/agent-canvas-github@1.2.3 +agent-canvas install ./local-extension +agent-canvas install ../my-extension --dev +``` + +Detection order: + +1. `package.json` has `agentCanvas.manifest` or an `agent-canvas.extension.json` file is present → Agent Canvas Extension. +2. `.plugin/plugin.json` is present → OpenHands SDK plugin reference. +3. `SKILL.md` is present → skill. +4. Future MCP template manifest → MCP template. +5. Multiple markers present → the explicit Agent Canvas Extension manifest wins (it may intentionally wrap SDK plugin, skill, or MCP contributions). + +For extensions, install validates the manifest, shows permissions, installs with scripts denied by default, and enables the extension after consent. Non-interactive flags: + +```sh +agent-canvas install @acme/agent-canvas-github --yes +agent-canvas install @acme/agent-canvas-github --no-enable +agent-canvas install @acme/agent-canvas-github --install-scripts=allow +``` + +Default is `--install-scripts=deny` (npm `--ignore-scripts`). + +### Management + +```sh +agent-canvas list +agent-canvas list extensions +agent-canvas enable acme.github +agent-canvas disable acme.github +agent-canvas remove acme.github +agent-canvas update acme.github +agent-canvas doctor +``` + +These commands operate without starting the full UI stack. Type filters keep the command useful once skills/MCP/plugins share the installer. + +### Process kill switch + +MVP enablement is persistent only: an artifact is active when it is installed and enabled in `config.json`. The one process-local exception is a support/debugging kill switch: + +```sh +agent-canvas --disable-extensions +``` + +This flag starts Canvas with all extension contributions ignored for that process. It does not create registry state, does not mutate `config.json`, and does not support selective run-only enablement. Per-run exact sets (`--extensions ...`), single-extension run overrides, and `install --run-only` are deferred until there is a concrete test/support workflow that needs them. + +## 9. Installable Artifact Store + +Canvas already stores client state at `~/.openhands/agent-canvas/` (session API keys, conversations, workspaces, automations DB, bash events). Installable artifacts live as a sibling so users have one obvious place to inspect and back up Canvas state: + +```text +~/.openhands/agent-canvas/installations/ + package.json + package-lock.json + config.json + artifacts.json + logs/ + dev/ + node_modules/ +``` + +`package.json` is a private package used only to install npm-backed artifacts. `artifacts.json` records detected artifact type, installed package/path, manifest location, state, and diagnostics. `config.json` records enable/disable state and non-secret settings: + +```json +{ + "schemaVersion": 1, + "enabled": { "extensions": ["acme.github"], "plugins": [], "skills": [], "mcpTemplates": [] }, + "disabled": { "extensions": [], "plugins": [], "skills": [], "mcpTemplates": [] }, + "settings": { "acme.github": { "defaultBranch": "main" } } +} +``` + +Secrets are never stored in this file. They are saved through the existing Agent Server secrets service. + +All installation is local to the Agent Canvas client. `agent-canvas install` never installs into a remote Agent Server, cloud sandbox, or team backend. + +## 10. Npm Package Contract + +Each extension package exposes its manifest through `package.json`: + +```json +{ + "name": "@acme/agent-canvas-github", + "version": "0.1.0", + "type": "module", + "agentCanvas": { "manifest": "./agent-canvas.extension.json" }, + "files": ["agent-canvas.extension.json", "dist", "agent"] +} +``` + +The manifest path must resolve inside the package root. Path traversal is rejected. + +## 11. Manifest Schema + +`agent-canvas.extension.json`: + +```json +{ + "$schema": "https://schemas.openhands.dev/agent-canvas/extension.v1.json", + "schemaVersion": 1, + "id": "acme.github", + "displayName": "GitHub Workspace Tools", + "version": "0.1.0", + "description": "GitHub launch helpers, MCP setup, and PR review workflows.", + "publisher": { "name": "Acme", "url": "https://example.com" }, + "license": "MIT", + "repository": "https://github.com/acme/agent-canvas-github", + "compatibility": { + "agentCanvas": ">=1.0.0-alpha.4 <2.0.0", + "extensionApi": 1 + }, + "activationEvents": ["onStartup"], + "permissions": { + "ui": ["views"], + "agent": ["sdkPlugins", "context"], + "mcp": ["templates"], + "network": ["https://api.github.com"] + }, + "browser": { + "module": "./dist/index.js" + }, + "contributes": { + "views": [ + { + "id": "dashboard", + "title": "GitHub", + "route": "/github", + "navigation": { "location": "extensions", "order": 300, "icon": "./dist/github.svg" } + } + ], + "agentPlugins": [ + { "id": "github-pr-review", "source": "./agent/github-pr-review", "autoInclude": "manual" } + ], + "mcpServers": [ + { + "id": "github", + "title": "GitHub", + "template": { "kind": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] }, + "requiredSecrets": ["GITHUB_TOKEN"] + } + ], + "launchTemplates": [ + { + "id": "review-pr", + "title": "Review a Pull Request", + "prompt": "Review the selected pull request and propose changes.", + "requiredAgentPlugins": ["github-pr-review"], + "requiredMcpServers": ["github"] + } + ], + "conversationContext": [ + { + "id": "github-guidance", + "title": "GitHub workflow guidance", + "autoInclude": "manual", + "content": "When working with GitHub, prefer the configured GitHub MCP server for issue and pull request metadata." + } + ] + }, + "configuration": { + "properties": { + "defaultBranch": { "type": "string", "default": "main", "title": "Default branch" } + } + }, + "secrets": [ + { + "name": "GITHUB_TOKEN", + "title": "GitHub token", + "description": "Used by the GitHub MCP server.", + "url": "https://github.com/settings/tokens", + "secret": true + } + ] +} +``` + +**Required fields:** `schemaVersion`, `id`, `displayName`, `version`, `compatibility.agentCanvas`, `compatibility.extensionApi`. + +**ID rules:** `^[a-z0-9][a-z0-9._-]*[a-z0-9]$`. Package names and extension IDs need not match. Duplicate enabled IDs are rejected. + +### 11.1 `browser` runtime modes + +The `browser` declaration accepts one of two shapes. The shape selects the runtime mode; the two are mutually exclusive and the validator rejects manifests that set both. + +| Field | Status | Runtime | +|---|---|---| +| `browser.module` | **Active (MVP).** | Browser-ready ESM module loaded into a Canvas-owned DOM container. The extension exports a `mount()` entry point and owns its rendering stack. | +| `browser.entry` | **Reserved, not yet supported.** | Future HTML document loaded into a sandboxed iframe (`sandbox="allow-scripts allow-forms allow-downloads"`, no `allow-same-origin`) with a postMessage RPC bridge mirroring the same context API. | + +The MVP implements only `browser.module`. The `browser.entry` field is reserved in the schema from day one so that a future iframe runtime can ship without a manifest schema bump, and so that extension authors can read the design intent today. + +**Why reserve `browser.entry` if the MVP only uses `browser.module`?** Same-origin browser modules are the smallest useful runtime for first-party and trusted partner extensions: they work in the globally installed CLI, avoid postMessage infrastructure, and let extension authors choose their own bundler/framework. They are **not** a sandbox. Once enabled, inline browser code should be treated as trusted local code with the same browser-origin privileges as Canvas itself (see §19). An iframe sandbox becomes valuable in two future scenarios we want to leave room for: + +1. A community extension marketplace where untrusted third-party code needs stronger isolation than install-time consent provides. +2. Extensions that intentionally ship their own framework or rendering stack and benefit from owning their own document. + +Designing the manifest, asset-serving routes, and runtime API around an async, host-mediated surface from the start means the iframe runtime is a future additive PR — not a redesign. Specifically: + +- The API in §13 is defined async-only, so the same interface is satisfiable by direct function calls (inline mode) or by a postMessage proxy (iframe mode). +- The `/canvas-extensions/:id/*` asset-serving route already exists in §17 and serves whichever bundle the extension declares. +- Manifest validation in PR 0 will accept `browser.entry` as a syntactically valid field but emit a validation error (`browser.entry is reserved and not yet supported`) until the iframe runtime ships, preventing accidental shipping of extensions that depend on an unimplemented mode. + +This is the minimum coverage needed today to keep the iframe option open later at low cost. + +## 12. Contribution Types + +### 12.1 `views` + +Routes rendered by Canvas at `/extensions/:extensionId/:viewId/*`. + +**MVP runtime: trusted same-origin browser module.** The extension's `browser.module` is loaded by the Canvas frontend with dynamic `import()` and mounted into a Canvas-owned DOM container at the view route. The extension exports a `mount()` function, receives the typed context (§13), and owns rendering inside that container. It may use React, Svelte, vanilla DOM, or another framework as long as the shipped module is browser-ready. It should use Canvas CSS variables/design tokens and host-provided APIs instead of importing Canvas internals. + +Inline extension code runs in the same browser origin as Canvas. That is intentional for MVP, and it means runtime capability declarations are a consent, audit, and UX contract rather than a hard browser sandbox. A trusted inline extension can technically call same-origin routes if it has access to the active session credentials, inspect browser state available to Canvas, and manipulate page DOM. The Packages UI must communicate that trust model clearly before enablement. Strong isolation is deferred to the future `browser.entry` iframe runtime. + +**Browser module packaging contract.** + +- `browser.module` must point to a built ESM file that a browser can import directly. +- The Extension Host serves static files only; it does not transpile TypeScript/JSX or run bundlers for installed packages. +- Runtime imports must be relative URLs inside the extension asset tree or bundled into the module. Bare runtime imports such as `react`, `@heroui/react`, or `@openhands/agent-canvas/extensions` are rejected for MVP because the packaged CLI cannot guarantee a Vite resolver or shared dependency graph. +- Type-only imports from `@openhands/agent-canvas/extensions` are allowed in author source and erased by the extension's build. +- The default export shape is: + +```ts +export default { + async mount({ root, context }: AgentCanvasExtensionMountParams) { + // Render into `root`. + return { + dispose() { + // Optional cleanup. + }, + }; + }, +}; +``` + +This DOM-island contract avoids React shared-instance problems in static builds while keeping the future iframe API close: the same async context can later cross a postMessage boundary. + +**Reserved future runtime: sandboxed iframe.** The manifest reserves `browser.entry` (§11.1) for a future iframe runtime intended for untrusted third-party extensions (e.g., a community marketplace). The iframe sandbox would be `sandbox="allow-scripts allow-forms allow-downloads"` with no `allow-same-origin`, communicating with the parent through a postMessage RPC bridge that exposes the same context API. This is not implemented in MVP; declaring it now keeps the option open without committing implementation cost today. + +### 12.2 `agentPlugins` + +Maps to OpenHands SDK plugin sources. Fields: `id`, `source` (package-relative path, GitHub shorthand, Git URL, or absolute path for dev mode), `ref` (optional branch/tag/commit), `repoPath` (optional subdirectory), `autoInclude` (`manual` | `enabled` | `always`; default `manual`). + +Resolution: + +- Package-relative paths are converted by the Extension Host to absolute paths inside the installed npm package. +- Remote sources pass through as SDK `PluginSource`. +- Local paths are allowed only when explicitly installed with `--dev` or through `agent-canvas install `. + +The current frontend `PluginSpec.parameters` field is collected by `/launch` but stripped by [agent-server-adapter.ts:707-711](src/api/agent-server-adapter.ts:707) before the create-conversation payload is sent. MVP extension behavior must not depend on arbitrary plugin parameters. + +### 12.3 `mcpServers` + +Extensions provide MCP templates, never silent installs. The UI shows required MCPs before launch and reuses the existing `InstallServerModal` pattern. + +**ACP constraint.** [src/routes/mcp.tsx:38-47](src/routes/mcp.tsx:38) currently disables MCP management while an ACP agent is active because the SDK rejects `mcp_config` on `ACPAgent` init. Extension MCP contributions inherit this guard. + +### 12.4 `launchTemplates` + +Reusable launch presets shown on home, extension pages, or automation setup. May reference required MCP IDs, required agent plugin IDs, optional context blocks, initial prompt text, and workspace requirements. + +### 12.5 `conversationContext` + +Context blocks may be appended to `AgentContext.system_message_suffix` only after explicit user enablement. Canvas renders all extension context inside a single block, appended after the existing `` block: + +```text + +The user enabled the following Agent Canvas extensions for this conversation. + +* acme.github/github-guidance + When working with GitHub, prefer the configured GitHub MCP server for issue and pull request metadata. + +``` + +Extension context **must not** be merged into `conversationInstructions`; that turns extension guidance into user-message content. An explicit `extensionSystemSuffix` option will be added to the conversation adapter so runtime services and extension context compose in one place. + +### 12.6 `configuration` and `secrets` + +`configuration` uses JSON Schema; values are stored in the Extension Host's `config.json`. `secrets` are declarations only; values are stored through the existing `SecretsService`. + +## 13. Extension Runtime API + +Extensions receive a typed context object exposing host-mediated operations. In MVP, the context is passed to the extension's `mount()` function along with a Canvas-owned DOM root. The API is defined async-only so that a future iframe runtime can satisfy the same interface via a postMessage RPC bridge without changing extension authoring. + +MVP module shape: + +```ts +interface AgentCanvasExtensionMountParams { + root: HTMLElement; + context: AgentCanvasExtensionContext; +} + +interface AgentCanvasExtensionDisposable { + dispose(): void | Promise; +} + +interface AgentCanvasExtensionModule { + mount( + params: AgentCanvasExtensionMountParams, + ): + | void + | AgentCanvasExtensionDisposable + | Promise; +} +``` + +MVP API surface: + +```ts +interface AgentCanvasExtensionContext { + extension: { id: string; version: string; settings: Record }; + theme: { colorScheme: "dark" | "light" }; + navigation: { + navigate(to: string, options?: { replace?: boolean }): Promise; + openExternal(url: string): Promise; + }; + ui: { + toast(message: string, options?: { tone?: "success" | "error" }): Promise; + }; + conversations: { + list(): Promise; + current(): Promise; + }; + launch: { + startConversation(options: unknown): Promise<{ conversationId: string }>; + }; + settings: { + readExtensionSettings(): Promise>; + patchExtensionSettings(value: Record): Promise; + }; +} +``` + +There is **no** supported generic `agentServer.request(path)`. The repo already routes Agent Server traffic through typed service wrappers; the extension surface follows the same pattern with named, permission-declared capabilities. For MVP inline browser modules, this is an API support boundary rather than a browser security boundary (see §19). + +## 14. Shared Type Contract + +Initial home: `src/extensions/types.ts`, exported as `@openhands/agent-canvas/extensions` once stable. (PR 0 will collapse generic installable-artifact types and extension-specific types into a single directory; splitting them across `src/installations/` and `src/extensions/` is premature before any consumer exists.) + +Initial types: + +```ts +export interface AgentCanvasExtensionManifest { + schemaVersion: 1; + id: string; + displayName: string; + version: string; + description?: string; + publisher?: { name: string; url?: string }; + compatibility: { agentCanvas: string; extensionApi: number }; + permissions?: ExtensionPermissions; + browser?: ExtensionBrowserContribution; + contributes?: ExtensionContributions; + configuration?: ExtensionConfigurationSchema; + secrets?: ExtensionSecretDeclaration[]; +} + +export interface AgentCanvasExtensionMountParams { + root: HTMLElement; + context: AgentCanvasExtensionContext; +} + +export interface AgentCanvasExtensionDisposable { + dispose(): void | Promise; +} + +export interface AgentCanvasExtensionModule { + mount( + params: AgentCanvasExtensionMountParams, + ): + | void + | AgentCanvasExtensionDisposable + | Promise; +} + +export type InstallableArtifactKind = + | "extension" + | "sdk-plugin" + | "skill" + | "mcp-template"; + +export interface InstallableArtifactRegistryEntry { + id: string; + kind: InstallableArtifactKind; + packageName: string; + version: string; + state: "enabled" | "disabled" | "installed" | "invalid"; + manifest: AgentCanvasExtensionManifest | Record; + assetBaseUrl?: string; + diagnostics: ExtensionDiagnostic[]; +} +``` + +`registerTool`, parent-React component registration, and Canvas-internal imports are intentionally absent from the MVP public contract. + +## 15. Permissions Model + +Permission groups: + +- `ui.views` — render trusted same-origin browser-module views. +- `ui.commands` — register commands. +- `agent.sdkPlugins` — include SDK plugins in conversation launches. +- `agent.context` — append extension context to system suffix. +- `mcp.templates` — offer MCP server templates. +- `agentServer.read` / `agentServer.write` — read/write Agent Server APIs through the Canvas broker. +- `secrets.declare` — request secret setup. +- `network` — list of external origins the extension says it may contact (advisory; not strictly enforced by CSP in MVP). + +User consent shows: package name + version, extension ID, publisher, repository, enabled contribution types, required secrets, whether trusted same-origin browser code is included, whether SDK plugins or context blocks can affect new conversations, whether the contribution works only with a local Agent Server, and any new or expanded permissions on update. + +### 15.1 Extension States + +Every artifact in the registry has exactly one persisted state: + +| State | Active by default? | Typical entry point | +|---|---|---| +| `enabled` | Yes | Consent flow at install, or `agent-canvas enable ` | +| `disabled` | No | `agent-canvas disable `, install with `--no-enable`, or a permission-expanding update that drops the artifact back to manual re-approval | +| `installed` | No | Installed but never enabled | +| `invalid` | No | Manifest failed validation; `diagnostics[]` on the entry explains why | + +**Conflict rule.** Disabled wins over enabled if a manual config edit creates both persisted entries for the same ID. The process-level `--disable-extensions` kill switch suppresses all extension loading for the current run but does not create a registry state. + +## 16. Conversation Runtime Awareness + +Agent Canvas is always local. The active Agent Server backend may be local, remote, OpenHands Cloud, or an ACP subprocess. The launch path computes a runtime compatibility report before applying extension contributions. + +Runtime classes: + +- `canvas-local` — browser UI and Extension Host only. Always available. +- `agent-server-local` — Agent Server process is local to this Canvas stack and can read local paths. +- `agent-server-remote` — remote Agent Server; local extension package paths are not visible. +- `cloud-runtime` — OpenHands Cloud; local paths are not visible to the sandbox. +- `acp-runtime` — agent is an ACP subprocess; MCP and context follow existing ACP rules. + +Compatibility: + +- Browser views and extension settings UI: `canvas-local`. +- Context blocks: any runtime that accepts `system_message_suffix`. +- Remote SDK plugin sources: any runtime that accepts `plugins`. +- Package-relative / local SDK plugin paths: `agent-server-local` only. +- MCP templates: where existing MCP settings work (ACP inherits the existing guard). +- Secrets: where the active backend's secrets service accepts them. + +Incompatible contributions are skipped with a disabled reason shown before launch. Local filesystem paths are never sent to remote/cloud runtimes. + +### 16.1 Local-filesystem detection + +`backend.kind === "local"` is **not sufficient** — a local-protocol backend can point at a remote host. The launcher will issue an explicit **`localInstallStoreReadable` capability flag** to the frontend on startup. The flag is true only when the Agent Server process was started by this same `agent-canvas` invocation and shares the local filesystem with `~/.openhands/agent-canvas/installations/`. Without that flag, package-relative SDK plugin paths are disabled and the UI shows a disabled-reason chip on the affected launch template. + +### 16.2 Runtime-location note + +When extension agent-side contributions are included, Canvas appends a short runtime-location note alongside `` and ``: + +```text + +Agent Canvas extensions are installed in the local Agent Canvas client. +Conversation runtime: agent-server-remote. +Do not assume local extension package paths are available inside the runtime. +Only use extension-provided capabilities explicitly listed in this prompt or exposed through runtime tools/settings. + +``` + +Contains no secrets. Local absolute paths are included only when the runtime is local and the path is intentionally part of an SDK plugin source. + +## 17. API Routes + +Mounted through ingress **before** `/api/*` is forwarded to the Agent Server, using the same precedence pattern already in place for `/api/automation/*` ([scripts/dev-with-automation.mjs](scripts/dev-with-automation.mjs) around the existing automation routes): + +```text +GET /api/canvas/installations/registry +GET /api/canvas/installations/diagnostics +POST /api/canvas/installations/install +POST /api/canvas/installations/:id/enable +POST /api/canvas/installations/:id/disable +DELETE /api/canvas/installations/:id +PATCH /api/canvas/installations/:id/settings +GET /api/canvas/installations/launch-contributions +GET /canvas-extensions/:id/*assetPath +``` + +Note the consistent `/api/canvas/installations/*` prefix for management — including `launch-contributions`, which was previously inconsistent. Asset serving keeps its own `/canvas-extensions/*` prefix because it serves untrusted third-party static files and benefits from being easy to identify in logs and CSP rules. + +The `install` route uses the same artifact detector as `agent-canvas install`. Mutating routes require the existing session API key. + +Frontend code must call these routes only through a dedicated `src/api/extensions-service.ts` wrapper. Because the route prefix begins with `/api/` but targets the local Extension Host rather than the Agent Server, PR 2 must update `src/api/no-direct-agent-server-calls.test.ts` with a narrow allowlist entry for that wrapper, mirroring the existing automation-service exception. This keeps the `/api/canvas/installations/*` ingress shape while preserving the repo rule that ordinary Agent Server traffic goes through `@openhands/typescript-client`. + +**Routing requirement:** the Extension Host route table must be implemented for every launch mode in the same PR that starts the host (Vite dev proxy, automation ingress, static serving path, and packaged CLI). + +Registry response: + +```json +{ + "schemaVersion": 1, + "extensionApi": 1, + "artifacts": [ + { + "id": "acme.github", + "kind": "extension", + "packageName": "@acme/agent-canvas-github", + "version": "0.1.0", + "state": "enabled", + "manifest": {}, + "assetBaseUrl": "/canvas-extensions/acme.github/", + "diagnostics": [] + } + ] +} +``` + +## 18. Conversation Launch Integration + +An extension contribution merge step runs before `buildStartConversationRequestWithEncryptedSettings()`: + +1. `useCreateConversation()` receives optional extension launch selections. +2. `AgentServerConversationService.createConversation()` asks `ExtensionsService.getLaunchContributions()` for globally enabled contributions (unless extensions are disabled). +3. Merge selected/auto-included SDK plugin specs with existing `plugins`. +4. Merge selected extension context into the new `extensionSystemSuffix` adapter option. +5. `agent-server-adapter.ts` appends extension context to `AgentContext.system_message_suffix`. +6. Existing payload creation sends `plugins` and no new server-specific fields. + +Merge rules: + +- Explicit `plugins` from `/launch` come first. +- Extension plugins follow in deterministic order: extension navigation order, then manifest order. +- Duplicate plugin sources are deduped by resolved `source/ref/repo_path` tuple. +- Extension context is deduped by `extensionId/contextId`. +- MCP templates are never auto-installed during conversation create. + +## 19. Security Posture + +**Threats.** Malicious browser JS in npm packages. Arbitrary code via npm install scripts. Rapidly changing unreviewed dev folders. Extension UI reading same-origin Canvas state or calling same-origin routes. Prompt injection via extension context. SDK plugin hooks/MCP servers in the agent runtime. Path traversal via manifests. Social engineering via agent-mediated install proposals (future). + +**Trust model for MVP browser code.** Inline `browser.module` code is same-origin trusted code. If enabled, it can exercise the same browser privileges as Canvas itself, including calling local Canvas/Agent Server routes when it has the required session credentials. The capability API is still valuable as the supported integration surface, but it is not a security boundary in MVP. The consent UI must say this plainly: enable browser-code extensions only from sources the user trusts. + +**MVP controls.** + +- `npm install --ignore-scripts` by default. +- Manifest path validation; all relative paths must stay inside package root. +- Explicit enablement; explicit permission display at install time. +- Explicit registration for dev source folders (never auto-discovered). +- Host-mediated extension API for supported integrations; permissions are consent/audit metadata, not hard same-origin browser enforcement. +- Path validation for extension asset serving. +- No automatic MCP installation. +- No repository-local extension discovery for Canvas UI extensions. +- Visible diagnostics and logs. + +**Future controls.** Sandboxed iframe runtime for untrusted/marketplace extensions (manifest support reserved as `browser.entry`; see §11.1 and §12.1). Signature/provenance verification. Lockfile integrity display. Enterprise allowlist/denylist policy. Per-extension CSP. Scanner integration before install. Trust labels for first-party extensions. + +## 20. Operational Semantics + +**Install defaults.** Interactive install validates, shows permissions, enables on confirmation. `--no-enable` installs disabled. `--yes` skips prompts but still fails closed on new permissions unless paired with an explicit non-interactive policy flag. Install scripts denied by default. + +**Update defaults.** Updates preserve enabled/disabled state only when the manifest ID is unchanged and permissions do not expand. If an update adds agent-affecting contributions, new secrets, broader network origins, or new write permissions, the artifact moves to `installed`/disabled until re-approval. Downgrades require explicit CLI input and are recorded in diagnostics. + +**Uninstall defaults.** `agent-canvas remove ` disables and removes the package; extension settings are preserved by default; secrets are never deleted by default; MCP servers / skill settings previously installed via an extension are not removed silently. `--purge` removes local settings and cached package data. `--purge-secrets` is separate and explicit. + +**Collision handling.** Artifact IDs must be unique. Installing a package whose manifest ID already exists updates it if the package identity matches, otherwise fails with a collision diagnostic. Two enabled artifacts cannot contribute the same view route, launch template ID, or context block ID under the same namespace. + +**Failure handling.** Invalid manifest → `invalid` state with diagnostics. Missing `browser.module` → keep installed, disable affected UI contribution. Manifest declares the reserved `browser.entry` field → `invalid` state with a "reserved, not yet supported" diagnostic. Extension view error → host-level error boundary and cleanup around the DOM container, rest of Canvas stays alive. Incompatible SDK plugin source → skip with preflight warning. Missing required secret → setup action surfaced, dependent contribution withheld. Extension Host unavailable → Canvas loads in degraded mode with a banner. + +**Package inspection rule.** The Extension Host never imports extension Node code to inspect an artifact. It reads only static files: `package.json`, `agent-canvas.extension.json`, `.plugin/plugin.json`, `SKILL.md`, and static asset paths. Extension-authored code runs only as the inline browser module loaded into the Canvas frontend, or as SDK/MCP/runtime code, in both cases only after explicit consent. + +## 21. Development Mode Authoring + +Dev mode lets contributors and agents iterate on extensions without publishing to npm or rebuilding Canvas. It is available only in development-capable stacks (`npm run dev`) and documented in contributor docs, not the end-user install path. + +### CLI + +```sh +agent-canvas install ../my-extension --dev +agent-canvas dev-extension register ../my-extension +agent-canvas dev-extension unregister local.my-extension +agent-canvas dev-extension list +``` + +### Registration store + +```text +~/.openhands/agent-canvas/installations/dev/dev-extensions.json +``` + +Each entry records absolute source path, manifest path, registration timestamp, and workspace-scoping. Auto-discovery of arbitrary repo folders is forbidden. + +### Behavior + +- Watch registered dev folders for changes (`fs.watch`; `chokidar` only if needed). +- Re-validate manifest on change. +- Serve assets directly from the source folder. +- Append a cache-busting version to the browser-module URL after changes so dynamic imports receive fresh code. +- Dispose and remount only the affected extension view when its module changes; avoid reloading the parent Canvas app. +- Surface manifest/build errors in the Packages page and extension view. +- The extension authoring project owns its build/watch command. Canvas watches declared output files and source manifests; it never runs package install scripts automatically and must ask before any package-manager command. +- Never hot-reload SDK plugin / tool state into an already-running conversation; a new conversation is required for agent-side contribution changes. + +### Safety rules + +Dev extensions run as trusted same-origin browser modules, just like installed MVP extensions. They are local code with full browser-origin access once enabled, so there is no automatic enablement from repository contents. Each new dev folder requires explicit user approval, and dev mode is visibly labeled in the UI with source path and warning copy. + +### Promotion + +```sh +agent-canvas install ../my-extension +``` + +or "Promote to Installed Extension" from the Packages page. Promotion installs into the normal store, freezes version/provenance, and disables dev watch for that extension. + +## 22. Agent-Mediated Installation (post-MVP) + +The future flow: an agent builds an extension in the workspace, proposes installing it, the local UI asks the user for approval, and the local Extension Host performs the install. The agent never installs directly. + +**Source types** the proposal mechanism will support: + +- `npm` (`{ packageName, version }`). +- `conversation-artifact` (`{ conversationId, path, sha256? }`) — Canvas downloads via existing conversation file APIs. Safe for any backend. +- `local-path` (`{ path }`) — only when `localInstallStoreReadable` is true. + +**New pieces required.** A Canvas-facing `canvas_extension.propose_install` tool module (proposal-only, no server-side install executor); a frontend handler for proposal ActionEvents with an approval modal; an Extension Host endpoint accepting an approved proposal and re-validating; conversation-artifact download path; permission-diff UI; registry refresh and extension-view remount after install. + +**Safety rules.** Proposal alone is not sufficient — user approval required. No write-capable Extension Host key in `` or the prompt. The agent cannot call `/api/canvas/installations/install` directly. Extension Host re-validates the downloaded artifact; proposal payload is advisory. Conversation artifacts are size-limited and checksum-verified when `sha256` is provided. Permission expansion follows the same re-approval rules as CLI updates. + +This is not part of PR 0 or PR 1. It becomes feasible after the local install store, artifact detector, Extension Host, and Packages UI exist. + +## 23. Upstream Dependencies + +MVP requires **zero broad Agent Server changes**. Three things to confirm against the current pinned SDK before PR 4: + +1. Agent Server create-conversation accepts `plugins` in the shape of SDK `PluginSource`. +2. `AgentContext.system_message_suffix` remains accepted for both `Agent` and `ACPAgent`. +3. SDK plugin local-path loading works from the host path Canvas passes. + +Nice-to-have, not blockers: resolved plugin refs in conversation info for audit; structured plugin load diagnostics as conversation events; `PluginSource.parameters` if the SDK decides to support it; a validation endpoint for plugin manifests; server-owned SDK plugin management APIs. + +Out of scope upstream: a generic server-side Canvas extension loader; server-side npm package installation; arbitrary frontend hook execution in the Agent Server; tool-override APIs for third-party Canvas packages. + +## 24. Frontend UX + +### Extensions hub + +Sections: + +- Skills +- MCP Servers +- Packages + +`/plugins` redirects to `/extensions/packages` (or becomes Packages) while retaining old bookmarks. + +Packages page sections: Enabled · Installed but disabled · Invalid · Install from npm/package spec · Developer extension path. + +Each extension card shows: display name, package name, version; state; contribution badges; required secrets status; enable/disable/remove actions; diagnostics. + +### Launch UX + +For launch templates: show required MCPs; show SDK plugins to be included; show context blocks to be appended; require confirmation on first use of any agent-affecting extension. + +### Extension view UX + +Extension views look like regular Canvas pages but are visibly extension-owned: title from manifest; small extension badge; browser-module loading/error states; diagnostics link. + +## 25. Implementation Plan + +### PR 0 — Contract and types + +Files: `docs/ExtensionsSystemRFC.md`, `src/extensions/types.ts`, `src/extensions/manifest-schema.ts`, `src/extensions/manifest-validation.ts`, `src/extensions/artifact-detection.ts`, `__tests__/extensions/manifest-validation.test.ts`. + +Deliverables: shared manifest and registry types; generic installable artifact kind definitions; artifact detection rules for extension packages, SDK plugins, skills, and placeholder MCP templates; schema-version constant; manifest validation helpers; storage-path helper for `~/.openhands/agent-canvas/installations`; fixtures for valid/invalid manifests; package export plan. No Extension Host, no frontend UI, no agent contribution merging. + +**Iframe-runtime forward compatibility (minimal, PR 0 scope):** the manifest schema and TypeScript contract accept both `browser.module` (active) and `browser.entry` (reserved). The validator recognizes `browser.entry` as syntactically valid but emits a `reserved-not-yet-supported` diagnostic so authors can't accidentally ship extensions that depend on an unimplemented runtime. The API contract in `src/extensions/types.ts` is defined async-only, which is the only change needed so that a future iframe runtime can satisfy the same interface over postMessage without an API redesign. No iframe host, no postMessage bridge, no asset-mode switching, and no parallel runtime code lands here — that work is deferred to a post-MVP PR triggered by one of the conditions in §28 Question 3. The total PR 0 cost of keeping the option open is a handful of schema fields, one validation rule, one fixture, and a short authoring note in the type comments. + +**Includes:** removing the empty `src/addons/` directory left over from the earlier prototype to avoid namespace confusion. + +### PR 1 — Runtime registry and CLI + +Files: `bin/agent-canvas.mjs`, `scripts/dev-safe.mjs`, `scripts/dev-with-automation.mjs`, `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, extension config helper, tests under `__tests__/extensions/`. + +Deliverables: generic install/manage CLI parsing; `install/list/enable/disable/remove/update/doctor` dispatched before stack startup; install store under `~/.openhands/agent-canvas/installations/`; artifact detection for all kinds; npm install with `--ignore-scripts`; manifest validation; Extension Host startup; ingress routes for `/api/canvas/installations/*` and `/canvas-extensions/*` across all four launch modes (Vite dev proxy, automation ingress, static serving, packaged CLI); dev extension registration store and `--dev` registration; launcher-issued `localInstallStoreReadable` capability flag; example package fixture using SDK plugin and context contributions. + +### PR 2 — Frontend management UI + +Files: `src/routes.ts`, `src/routes/extensions-packages.tsx`, `src/components/features/skills/extensions-navigation.tsx`, `src/api/extensions-service.ts`, `src/api/no-direct-agent-server-calls.test.ts`, `src/hooks/query/use-extensions.ts`, i18n. + +Deliverables: Packages page replaces "Plugins coming soon"; install/enable/disable/remove flows; dedicated ExtensionsService wrapper and narrow no-direct-Agent-Server test exception for `/api/canvas/installations/*`; dev extension badges, source paths, diagnostics; snapshot coverage for empty/installed/enabled/invalid states. + +### PR 3 — Trusted browser-module view host + +Files: `src/routes/extension-view-host.tsx`, `src/extensions/runtime/*`, sidebar components, `scripts/extension-host.mjs`. + +Deliverables: route `/extensions/:extensionId/:viewId/*`; DOM-container host for trusted `browser.module` extensions; dynamic import with cache-busting support; browser asset serving; `mount()`/`dispose()` lifecycle; minimal `navigation` and `ui.toast` APIs; example extension package with a bundled browser-ready module. + +### PR 3b — Dev extension watch mode + +Files: `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, `bin/agent-canvas.mjs`, `src/api/extensions-service.ts`, `src/routes/extensions-packages.tsx`, contributor docs. + +Deliverables: `--dev` install and `dev-extension` subcommands; dev registration store; source folder file watching; manifest revalidation on change; browser-module cache-bust/remount after asset changes; visible dev-mode labeling; example extension and contributor guide; tests for registration, traversal rejection, invalid manifest updates, and view remount signaling. + +### PR 4 — Agent contributions + +Files: `src/api/extensions-service.ts`, `src/api/conversation-service/agent-server-conversation-service.api.ts`, `src/api/agent-server-adapter.ts`, `src/hooks/mutation/use-create-conversation.ts`, launch/home components. + +Deliverables: enabled extension SDK plugin specs merge into new conversation payloads; selected extension context blocks append to system suffix via the new `extensionSystemSuffix` option; required MCP preflight UI; cloud/local disabled reasons gated on `localInstallStoreReadable`; tests for payload shape and merge order. + +### PR 5 — Hardening and polish + +Permission consent modal; secret setup flow; `doctor` command with actionable diagnostics; logs under the install store; first-party extension example; authoring docs; e2e snapshot coverage. + +## 26. Testing Strategy + +**Unit:** manifest schema validation; package path traversal rejection; duplicate ID handling; enable/disable precedence; registry sort order; asset route path validation; CLI arg parsing; dev extension registration and path validation; dev manifest revalidation after source changes; launch contribution merge and dedupe; extension context suffix rendering; runtime compatibility classification across all five runtime classes; permission drift / update re-approval; uninstall preserve vs purge. + +**Component:** Packages page states; permission display; browser-module view loading/error; MCP required preflight; incompatible runtime warning; dev badge. + +**E2E snapshots:** Packages page empty state; enabled extension with one view; invalid extension diagnostics; launch template requiring MCP; launch preflight with local-path SDK plugin disabled on remote backend; dev extension view remount after file change; future: agent-mediated install proposal flow. + +**Live E2E:** not required for MVP. If added later, follow existing live E2E rules under `tests/e2e/live/`. + +**Verification:** + +```sh +npm run typecheck && npm test && npm run build +``` + +## 27. Resolved Design Decisions + +These were open questions in the earlier draft. Decisions for the RFC: + +| # | Question | Decision | +|---|---|---| +| 1 | Web UI install in MVP, or CLI-only first? | **CLI-first.** Web UI install lands in PR 2 once CLI plumbing is proven; CLI remains the supported automation surface. | +| 2 | Iframe sandbox vs inline browser modules for extension views? | **Trusted same-origin browser modules as MVP default** (`browser.module`); reserve `browser.entry` in the manifest schema for a future sandboxed-iframe runtime aimed at untrusted/marketplace extensions. Trust comes from explicit install/enable consent, not browser-enforced isolation. See §11.1, §12.1, and §19. | +| 3 | What signals an Agent Server is filesystem-local? | **Launcher-issued `localInstallStoreReadable` capability flag.** `backend.kind === "local"` alone is insufficient. | +| 4 | `src/installations/` vs `src/extensions/` directory split? | **Single `src/extensions/` directory** until a real consumer requires the split. | +| 5 | API route prefix consistency? | **`/api/canvas/installations/*`** for all management routes; `/canvas-extensions/*` reserved for asset serving. | +| 6 | Empty `src/addons/` directory on `main`? | **Remove in PR 0.** | + +## 28. Remaining Open Questions + +1. Should agent-proposed dev extension registration ship with dev watch mode, or wait for the broader agent-mediated install proposal flow? +2. Should package-relative SDK plugin paths be MVP, or should MVP agent plugins require remote Git/GitHub sources first? (Leaning MVP, gated on the `localInstallStoreReadable` flag.) +3. What concrete trigger moves us to implement the reserved `browser.entry` iframe runtime? (Candidates: opening a community marketplace tier, onboarding the first untrusted third-party publisher, or needing browser-enforced isolation/CSP for an extension.) +4. Should extension context be global per extension, selected per launch template, or both? +5. Should the `@openhands/extensions` catalog eventually become one first-party Extension package, or remain a plain dependency for built-in catalogs? +6. Should repo-provided SDK plugins/skills ever be surfaced separately from Agent Canvas Extensions, or remain purely Agent Server/runtime concerns? + +## 29. Recommended MVP Cut + +Build the smallest powerful slice: + +1. CLI-managed npm extension install/enable/list. +2. Extension Host registry and asset serving. +3. Packages management page. +4. Trusted same-origin browser-module extension views (with the iframe runtime reserved in the manifest schema). +5. Dev-mode extension registration / watch / reload. +6. SDK plugin contribution merge for local conversations. +7. Context contribution merge behind explicit permission. + +Defer: marketplace, package signing, **sandboxed iframe runtime** (manifest field reserved as `browser.entry` but unimplemented), parent-React component extensions/shared dependency runtime, rich extension RPC APIs, agent-mediated installation, SDK/server install management, plugin parameters, and per-run extension enablement. + +This delivers a useful end-to-end extension system while keeping the Agent Server boundary clean. From 1a364657d6cd2ec7e67728b4746639a070b3b5a3 Mon Sep 17 00:00:00 2001 From: Devin Date: Fri, 22 May 2026 23:09:05 -0400 Subject: [PATCH 02/14] Ensure navigation story is covered, plan out building an mvp poc --- docs/ExtensionsSystemPoCPlan.md | 300 ++++++++++++++++++++++++++++++++ docs/ExtensionsSystemRFC.md | 37 +++- 2 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 docs/ExtensionsSystemPoCPlan.md diff --git a/docs/ExtensionsSystemPoCPlan.md b/docs/ExtensionsSystemPoCPlan.md new file mode 100644 index 000000000..3b1042b24 --- /dev/null +++ b/docs/ExtensionsSystemPoCPlan.md @@ -0,0 +1,300 @@ +# Agent Canvas Extensions MVP / PoC Build Plan + +Status: working build plan +Source RFC: [ExtensionsSystemRFC.md](./ExtensionsSystemRFC.md) +Target branch: `dv/extensions-poc-v1` + +## 1. Purpose + +This document turns the Extensions RFC into a practical MVP / PoC build path. The RFC should stay clean and reviewable as the product/architecture proposal. This plan is the working artifact for agents and developers implementing the proof, testing risky assumptions, and tightening the design based on real code. + +The MVP should be built as a vertical proof, not as isolated architecture layers. The first complete proof should demonstrate: + +1. A local or npm-packed extension installs into `~/.openhands/agent-canvas/installations`. +2. The Packages page shows the installed extension and its diagnostics. +3. A trusted `browser.module` view renders at `/extensions/:extensionId/:viewId/*`. +4. A view can contribute a primary Sidebar entry after Automations and before the conversation list. +5. A launch template can append extension context and include an SDK plugin when the runtime is filesystem-local. +6. Dev mode can register a local source folder, detect output changes, and remount the view without rebuilding Canvas. + +## 2. Risk-Burner Spikes + +Do these first, before investing in polished UI. + +### 2.1 SDK Plugin Source Smoke + +Add a tiny fixture SDK plugin under: + +```text +examples/extensions/hello-canvas/agent/hello-plugin +``` + +Start a local conversation with its resolved absolute path in `plugins`. Confirm the current pinned Agent Server accepts the `PluginSource` shape and can load a package-relative path when `localInstallStoreReadable` is true. + +If this fails, keep context contributions in MVP and move SDK plugin merge behind a feature flag until the SDK team confirms the loading contract. + +### 2.2 Static Dynamic-Import Smoke + +Serve a tiny browser-ready ESM file from a local test host and dynamically import it from the static production build. + +The extension module must use only bundled or relative imports. This proves the DOM-island `mount()` contract works without Vite, shared React, import maps, or module federation. + +### 2.3 Launcher Route Smoke + +Start a toy Extension Host and route `/api/canvas/installations/*` plus `/canvas-extensions/*` through the existing ingress/static-server/Vite proxy paths. + +Cover: + +- `scripts/dev-with-automation.mjs` ingress routes. +- `scripts/static-server.mjs` routes used by the packaged CLI/static mode. +- `vite.config.ts` proxy support for direct Vite access, especially `/canvas-extensions/*`. + +Exit criteria: a browser opened through either the ingress port or direct Vite/static port can fetch registry JSON and import a browser module from `/canvas-extensions/...`. + +## 3. Walking Skeleton + +Build the thinnest end-to-end slice next. + +### 3.1 Shared Contract + +Add: + +- `src/extensions/types.ts` +- manifest constants +- artifact detection +- storage-path helpers +- path normalization +- manifest validation + +Keep validation deliberately boring: + +- manifest v1 required fields +- ID rules +- relative-path containment +- mutually exclusive `browser.module` / `browser.entry` +- `reserved-not-yet-supported` diagnostic for `browser.entry` + +Avoid a large JSON Schema dependency unless configuration forms force it. + +### 3.2 Manager Library + +Add `scripts/extension-manager.mjs` as the single source of truth for install-store reads/writes. Both CLI and HTTP host call this module. + +It owns: + +- Store bootstrap: `package.json`, `package-lock.json`, `config.json`, `artifacts.json`, `logs/`. +- Artifact detection. +- Install from local path, tarball, npm spec. +- Enable/disable/remove/update. +- Registry and launch-contribution projection. +- Dev registration reads/writes. + +### 3.3 CLI Dispatch + +Update `bin/agent-canvas.mjs` so install/manage commands dispatch before stack startup: + +```sh +agent-canvas install ./examples/extensions/hello-canvas --yes +agent-canvas list extensions +agent-canvas enable hello.canvas +agent-canvas disable hello.canvas +agent-canvas doctor +agent-canvas --disable-extensions +``` + +MVP install can support local paths first, then npm specs. Npm installs must run in the private install store with install scripts denied by default. + +### 3.4 Extension Host + +Add `scripts/extension-host.mjs`, a local Node HTTP service backed by `extension-manager`. + +It should expose: + +- registry +- diagnostics +- launch contributions +- settings +- enable/disable/remove +- install +- static asset routes + +Mutating routes require the same session API key model the frontend already uses. + +### 3.5 Launcher Integration + +Add an `extensionHostPort` to launcher config, start the host before the frontend, and route it before `/api/*`: + +- `/api/canvas/installations/*` -> Extension Host. +- `/canvas-extensions/*` -> Extension Host. +- `/api/automation/*` -> automation. +- `/api/*`, `/sockets`, server metadata -> Agent Server. + +The launcher also emits frontend env/runtime metadata for: + +- `VITE_EXTENSIONS_ENABLED` or equivalent kill-switch state. +- `VITE_LOCAL_INSTALL_STORE_READABLE=true` only when this `agent-canvas` process started the local Agent Server. +- Extension Host URL for dev tooling if needed. + +Do not expose a write-capable Extension Host key in agent prompts. + +## 4. Frontend Proof + +### 4.1 API Wrapper + +Add `src/api/extensions-service.ts` for every `/api/canvas/installations/*` call. + +Update `src/api/no-direct-agent-server-calls.test.ts` with one narrow allowlist entry for this wrapper, mirroring automation. + +### 4.2 Packages Page + +Replace `/plugins` with a Packages view, while preserving redirects/bookmarks. Use the existing Extensions layout and navigation. + +The page should: + +- Add a Packages nav item in `ExtensionsNavigation`. +- Show Enabled, Installed, Disabled, Invalid, and Dev sections. +- Show install source, version, contribution badges, required secrets, diagnostics, enable/disable/remove actions. +- Stay dense and operational; no marketplace browsing in MVP. + +### 4.3 View Host + +Add `/extensions/:extensionId/:viewId/*` and a route component that: + +- Fetches registry data. +- Resolves `assetBaseUrl + browser.module`. +- Dynamically imports the module with a cache-busting version. +- Calls `mount({ root, context })`. +- Calls `dispose()` on unmount/remount. +- Provides minimal context: extension metadata/settings, `navigation.navigate`, `navigation.openExternal`, `ui.toast`, `settings.readExtensionSettings`, and `settings.patchExtensionSettings`. + +The extension view should be visibly extension-owned but render in a Canvas-owned route container. Errors stay local to the extension view. + +### 4.4 Primary Sidebar Entries + +Support view navigation contributions that render in the main Canvas Sidebar: + +```json +{ + "navigation": { + "location": "primarySidebar", + "slot": "afterAutomations", + "order": 300, + "icon": "./dist/cost-dashboard.svg" + } +} +``` + +MVP scope: + +- Only `primarySidebar` + `afterAutomations` is required. +- Entries render after Automations and before `SidebarConversationList`. +- Entries navigate to `/extensions/:extensionId/:viewId/*`. +- Disabled/invalid extensions do not render entries. +- Sorting is deterministic: `order`, extension display name, view ID. +- Expanded sidebar shows icon + title. +- Collapsed sidebar shows icon with a tooltip. +- Mobile drawer shows the same entry in the same relative position. + +This is the path that proves the original addons-style PoC works. Example: `hello.canvas` can include a Cost Dashboard view that appears immediately after Automations. + +## 5. Conversation Contributions + +Add contribution merge late enough that the install/store/view path already works. + +### 5.1 Launch Contributions Endpoint + +`ExtensionsService.getLaunchContributions()` fetches enabled extension contributions and returns a frontend-ready, compatibility-filtered shape. + +### 5.2 Runtime Compatibility + +Compute disabled reasons before launch: + +- Context blocks: allowed anywhere `system_message_suffix` is accepted. +- Remote SDK plugin sources: allowed where `plugins` are accepted. +- Package-relative/local SDK plugin paths: allowed only when `localInstallStoreReadable` is true. +- MCP templates: visible as setup requirements, never silently installed. + +### 5.3 Create-Conversation Merge + +Extend the create-conversation path with an `extensionSystemSuffix` option: + +- `useCreateConversation()` can receive extension launch selections. +- `AgentServerConversationService.createConversation()` asks the extensions service for enabled/selected contributions. +- Plugin specs merge with existing `/launch` plugin selections and dedupe by `source/ref/repo_path`. +- `agent-server-adapter.ts` appends `` and `` after ``. +- `conversationInstructions` remains user-message content and never receives extension context. + +For the first proof, a `hello.canvas` launch template should append a recognizable context block so the payload/unit test can prove the suffix path works without depending on LLM behavior. + +## 6. Dev Mode + +Dev mode should prove contributor ergonomics without turning Canvas into a build runner: + +```sh +agent-canvas install ../my-extension --dev +agent-canvas dev-extension register ../my-extension +agent-canvas dev-extension list +agent-canvas dev-extension unregister hello.canvas +``` + +Rules: + +- Dev registration stores absolute paths in `~/.openhands/agent-canvas/installations/dev/dev-extensions.json`. +- Canvas never auto-discovers repo folders. +- The extension project owns its build/watch command. +- Canvas watches the manifest and declared output files. +- Manifest changes revalidate and update diagnostics. +- Browser module changes bump a version token and remount only affected views. +- Agent-side contribution changes require a new conversation. + +The example extension should include a minimal `npm run build -- --watch` authoring flow in docs, but Canvas should not run that command automatically. + +## 7. Acceptance Demo + +The MVP is proven when this scriptable path works from a clean checkout: + +```sh +npm run build +node bin/agent-canvas.mjs install ./examples/extensions/hello-canvas --yes +node bin/agent-canvas.mjs list extensions +node bin/agent-canvas.mjs +``` + +Then in the browser: + +1. Open Packages and see `hello.canvas` enabled with no diagnostics. +2. See its primary Sidebar entry after Automations and before the conversation list. +3. Open its extension view from the Sidebar and see browser-module UI render. +4. Change a setting in the extension view and see it persist. +5. Start its launch template and verify the conversation payload includes the extension context suffix. +6. In `npm run dev`, register the same extension with `--dev`, rebuild the extension output, and see the view remount with the new code. + +## 8. Testing Strategy + +**Unit:** manifest validation; reserved `browser.entry` diagnostics; path traversal rejection; artifact detection; install-store bootstrap; enable/disable/remove/update state transitions; duplicate ID handling; asset route path validation; CLI arg parsing; no-install-scripts default; launch contribution projection; context suffix rendering; plugin merge/dedupe; runtime compatibility classification; dev registration and manifest revalidation. + +**Node integration:** run the Extension Host against a temp install store; install the `hello.canvas` fixture from local path and npm-packed tarball; fetch registry and asset URLs; verify mutating routes require the session API key; verify `doctor` reports invalid manifests and missing assets. + +**Component:** Packages page empty/installed/enabled/invalid/dev states; install/enable/disable/remove action wiring; primary Sidebar extension entries in expanded/collapsed/mobile states; extension view loading/error/remount; extension settings read/patch; launch template preflight for incompatible SDK plugin paths. + +**E2E snapshots:** Packages page with enabled extension; invalid extension diagnostics; primary Sidebar entry after Automations; extension view rendered from browser module; launch template showing context/plugin contribution; remote-backend disabled reason for local plugin path; dev extension view remount after output change. + +**Live E2E:** optional after the walking skeleton. If added, keep one cheap local live test that starts a conversation from `hello.canvas` and verifies the created payload/events show the context suffix or plugin load marker. Follow existing live E2E rules under `tests/e2e/live/`. + +**Verification:** + +```sh +npm run typecheck && npm test && npm run build +``` + +## 9. Defer Until After Proof + +- Marketplace/catalog discovery. +- Sandboxed iframe runtime. +- Package signing/provenance UI. +- Rich command palette integration. +- Agent-mediated installation. +- Automatic MCP installation. +- Per-run exact extension enablement. +- Shared React/design-system runtime imports for extensions. +- Remote/cloud delivery of local extension package contents. diff --git a/docs/ExtensionsSystemRFC.md b/docs/ExtensionsSystemRFC.md index fb43610ec..86549df88 100644 --- a/docs/ExtensionsSystemRFC.md +++ b/docs/ExtensionsSystemRFC.md @@ -8,7 +8,7 @@ Target: `OpenHands/agent-canvas` Agent Canvas should ship a first-class **Extensions** system: user-installable npm packages that extend the local Canvas experience and optionally contribute components to the agent runtime through SDK-supported surfaces. -An Extension can contribute UI views, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces — Canvas does not patch or load code inside the Agent Server. +An Extension can contribute UI views, primary navigation entries, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces — Canvas does not patch or load code inside the Agent Server. The MVP delivers CLI install, a Packages management page, trusted same-origin extension views, dev-mode authoring with live reload, and SDK plugin / context merging on conversation launch. Marketplace, signing, sandboxed iframe views, parent-React component extensions, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. @@ -266,7 +266,12 @@ The manifest path must resolve inside the package root. Path traversal is reject "id": "dashboard", "title": "GitHub", "route": "/github", - "navigation": { "location": "extensions", "order": 300, "icon": "./dist/github.svg" } + "navigation": { + "location": "primarySidebar", + "slot": "afterAutomations", + "order": 300, + "icon": "./dist/github.svg" + } } ], "agentPlugins": [ @@ -349,6 +354,26 @@ This is the minimum coverage needed today to keep the iframe option open later a Routes rendered by Canvas at `/extensions/:extensionId/:viewId/*`. +Views may optionally declare a navigation entry: + +```json +{ + "navigation": { + "location": "primarySidebar", + "slot": "afterAutomations", + "order": 300, + "icon": "./dist/cost-dashboard.svg" + } +} +``` + +MVP navigation locations: + +- `primarySidebar` — top-level Canvas sidebar entry. MVP supports the `afterAutomations` slot, rendered after Automations and before the conversation list. This covers dashboard-style extensions such as a Cost Dashboard. +- `extensions` — entry inside the Customize / Extensions hub. Use this for management/configuration views or lower-priority extension pages. + +Primary sidebar entries are for user-facing app surfaces, not transient launch templates. They must point at an extension view route, use a static icon asset from the extension package, and remain disabled when the extension is disabled or invalid. If multiple enabled extensions contribute to the same slot, Canvas sorts by `order`, then extension display name, then view ID for deterministic rendering. + **MVP runtime: trusted same-origin browser module.** The extension's `browser.module` is loaded by the Canvas frontend with dynamic `import()` and mounted into a Canvas-owned DOM container at the view route. The extension exports a `mount()` function, receives the typed context (§13), and owns rendering inside that container. It may use React, Svelte, vanilla DOM, or another framework as long as the shipped module is browser-ready. It should use Canvas CSS variables/design tokens and host-provided APIs instead of importing Canvas internals. Inline extension code runs in the same browser origin as Canvas. That is intentional for MVP, and it means runtime capability declarations are a consent, audit, and UX contract rather than a hard browser sandbox. A trusted inline extension can technically call same-origin routes if it has access to the active session credentials, inspect browser state available to Canvas, and manipulate page DOM. The Packages UI must communicate that trust model clearly before enablement. Strong isolation is deferred to the future `browser.entry` iframe runtime. @@ -538,7 +563,7 @@ export interface InstallableArtifactRegistryEntry { Permission groups: -- `ui.views` — render trusted same-origin browser-module views. +- `ui.views` — render trusted same-origin browser-module views and their declared navigation entries. - `ui.commands` — register commands. - `agent.sdkPlugins` — include SDK plugins in conversation launches. - `agent.context` — append extension context to system suffix. @@ -794,6 +819,10 @@ Each extension card shows: display name, package name, version; state; contribut For launch templates: show required MCPs; show SDK plugins to be included; show context blocks to be appended; require confirmation on first use of any agent-affecting extension. +### Primary navigation UX + +Enabled views with `navigation.location: "primarySidebar"` render as top-level Sidebar entries. For MVP, entries appear in the `afterAutomations` slot: after Automations and before the conversation list. Collapsed Sidebar mode shows only the icon with a tooltip. Mobile drawer mode shows the same entry in the same relative position. + ### Extension view UX Extension views look like regular Canvas pages but are visibly extension-owned: title from manifest; small extension badge; browser-module loading/error states; diagnostics link. @@ -826,7 +855,7 @@ Deliverables: Packages page replaces "Plugins coming soon"; install/enable/disab Files: `src/routes/extension-view-host.tsx`, `src/extensions/runtime/*`, sidebar components, `scripts/extension-host.mjs`. -Deliverables: route `/extensions/:extensionId/:viewId/*`; DOM-container host for trusted `browser.module` extensions; dynamic import with cache-busting support; browser asset serving; `mount()`/`dispose()` lifecycle; minimal `navigation` and `ui.toast` APIs; example extension package with a bundled browser-ready module. +Deliverables: route `/extensions/:extensionId/:viewId/*`; DOM-container host for trusted `browser.module` extensions; dynamic import with cache-busting support; browser asset serving; `mount()`/`dispose()` lifecycle; primary-sidebar navigation entries for views; minimal `navigation` and `ui.toast` APIs; example extension package with a bundled browser-ready module. ### PR 3b — Dev extension watch mode From 3f9d830cfaed73c04c8192b7e412f8c17e3c819c Mon Sep 17 00:00:00 2001 From: Devin Date: Fri, 22 May 2026 23:36:34 -0400 Subject: [PATCH 03/14] Updates after settings changes --- docs/ExtensionsSystemPoCPlan.md | 62 ++++++++++++++++++++++++++++----- docs/ExtensionsSystemRFC.md | 61 +++++++++++++++++++++++++------- 2 files changed, 102 insertions(+), 21 deletions(-) diff --git a/docs/ExtensionsSystemPoCPlan.md b/docs/ExtensionsSystemPoCPlan.md index 3b1042b24..70cf6f7c4 100644 --- a/docs/ExtensionsSystemPoCPlan.md +++ b/docs/ExtensionsSystemPoCPlan.md @@ -10,13 +10,23 @@ This document turns the Extensions RFC into a practical MVP / PoC build path. Th The MVP should be built as a vertical proof, not as isolated architecture layers. The first complete proof should demonstrate: -1. A local or npm-packed extension installs into `~/.openhands/agent-canvas/installations`. +1. A local or npm-packed Agent Canvas extension installs into `~/.openhands/agent-canvas/installations`. 2. The Packages page shows the installed extension and its diagnostics. 3. A trusted `browser.module` view renders at `/extensions/:extensionId/:viewId/*`. 4. A view can contribute a primary Sidebar entry after Automations and before the conversation list. 5. A launch template can append extension context and include an SDK plugin when the runtime is filesystem-local. 6. Dev mode can register a local source folder, detect output changes, and remount the view without rebuilding Canvas. +## 1.1 Scope Locks From The RFC + +These decisions should keep the build from spreading sideways: + +- First-class install/enable support is for Agent Canvas extension manifests only. Standalone SDK plugins, `SKILL.md` folders, and future MCP-template artifacts can be detected, but they return an explicit unsupported-in-MVP diagnostic unless wrapped by an extension. +- CLI management commands must dispatch before stack startup, before the static `build/` existence check, and before importing `scripts/dev-with-automation.mjs`. +- Extension author types need a real package subpath: `@openhands/agent-canvas/extensions`, emitted under `dist/extensions/*` and included in npm release checks. +- Conversation contribution work targets the current start payload: top-level `agent_settings` plus optional top-level `plugins`. Do not reintroduce the legacy `agent` payload shape. +- Package-relative/local SDK plugin sources are MVP only when the launcher says `localInstallStoreReadable=true`. ACP runtimes skip extension SDK plugins unless a pinned-version smoke proves the exact plugin shape is compatible. + ## 2. Risk-Burner Spikes Do these first, before investing in polished UI. @@ -29,9 +39,9 @@ Add a tiny fixture SDK plugin under: examples/extensions/hello-canvas/agent/hello-plugin ``` -Start a local conversation with its resolved absolute path in `plugins`. Confirm the current pinned Agent Server accepts the `PluginSource` shape and can load a package-relative path when `localInstallStoreReadable` is true. +Start a local conversation with its resolved absolute path in top-level `plugins`. Confirm the current pinned Agent Server (`config/defaults.json` pins `agentServer` to `1.23.0`) accepts the SDK `PluginSource` shape (`source`, optional `ref`, optional `repo_path`) and can load a package-relative path when `localInstallStoreReadable` is true. The plugin root should contain `.plugin/plugin.json`; if a fixture uses a local path, resolve `source` directly to the plugin root and omit `repo_path`. -If this fails, keep context contributions in MVP and move SDK plugin merge behind a feature flag until the SDK team confirms the loading contract. +Also run the same contribution preflight with an ACP agent configuration. If ACP fails or the plugin injects MCP/hooks/tool/agent-definition state, keep ACP on context-only extension support and show disabled reasons for SDK plugin contributions. If the local-path smoke fails for the pinned server, keep context contributions in MVP and move SDK plugin merge behind a feature flag until the SDK team confirms the loading contract. ### 2.2 Static Dynamic-Import Smoke @@ -39,6 +49,8 @@ Serve a tiny browser-ready ESM file from a local test host and dynamically impor The extension module must use only bundled or relative imports. This proves the DOM-island `mount()` contract works without Vite, shared React, import maps, or module federation. +Add a negative fixture with a bare runtime import such as `react` and verify validation rejects it with an actionable diagnostic. Type-only imports from `@openhands/agent-canvas/extensions` are fine because the extension build erases them; runtime imports from that package are not MVP-supported. + ### 2.3 Launcher Route Smoke Start a toy Extension Host and route `/api/canvas/installations/*` plus `/canvas-extensions/*` through the existing ingress/static-server/Vite proxy paths. @@ -46,11 +58,26 @@ Start a toy Extension Host and route `/api/canvas/installations/*` plus `/canvas Cover: - `scripts/dev-with-automation.mjs` ingress routes. +- `scripts/dev-safe.mjs` / `dev:minimal`, where direct Vite proxying must handle Extension Host routes without the automation ingress. - `scripts/static-server.mjs` routes used by the packaged CLI/static mode. - `vite.config.ts` proxy support for direct Vite access, especially `/canvas-extensions/*`. Exit criteria: a browser opened through either the ingress port or direct Vite/static port can fetch registry JSON and import a browser module from `/canvas-extensions/...`. +### 2.4 Npm Package / CLI Smoke + +Before any UI work, prove the upcoming release path: + +```sh +npm run build +npm run build:lib +npm pack --dry-run +node bin/agent-canvas.mjs list extensions +node bin/agent-canvas.mjs doctor +``` + +Then install from a packed tarball into a temp npm prefix and verify `agent-canvas list` and `agent-canvas doctor` work without a source checkout. This catches missing `scripts`, `build`, `dist/extensions`, schema files, and command-dispatch regressions before the feature depends on them. + ## 3. Walking Skeleton Build the thinnest end-to-end slice next. @@ -65,6 +92,7 @@ Add: - storage-path helpers - path normalization - manifest validation +- public package export wiring for `@openhands/agent-canvas/extensions` Keep validation deliberately boring: @@ -76,6 +104,8 @@ Keep validation deliberately boring: Avoid a large JSON Schema dependency unless configuration forms force it. +PR 0 should also update `package.json#exports` for `./extensions` and make sure the library build emits `dist/extensions/*`. Keep the actual Extension Host and CLI behavior out of PR 0. + ### 3.2 Manager Library Add `scripts/extension-manager.mjs` as the single source of truth for install-store reads/writes. Both CLI and HTTP host call this module. @@ -84,7 +114,8 @@ It owns: - Store bootstrap: `package.json`, `package-lock.json`, `config.json`, `artifacts.json`, `logs/`. - Artifact detection. -- Install from local path, tarball, npm spec. +- Install Agent Canvas extension manifests from local path, tarball, npm spec. +- Typed unsupported diagnostics for detected standalone SDK plugins, standalone skills, and placeholder MCP templates. - Enable/disable/remove/update. - Registry and launch-contribution projection. - Dev registration reads/writes. @@ -102,7 +133,7 @@ agent-canvas doctor agent-canvas --disable-extensions ``` -MVP install can support local paths first, then npm specs. Npm installs must run in the private install store with install scripts denied by default. +This dispatch must happen before checking for `build/` and before importing launcher scripts. MVP install can support local extension paths first, then npm specs. Npm installs must run in the private install store with install scripts denied by default. Standalone SDK plugin/skill/MCP-template inputs should return clear unsupported diagnostics rather than mutating user runtime state. ### 3.4 Extension Host @@ -133,17 +164,20 @@ The launcher also emits frontend env/runtime metadata for: - `VITE_EXTENSIONS_ENABLED` or equivalent kill-switch state. - `VITE_LOCAL_INSTALL_STORE_READABLE=true` only when this `agent-canvas` process started the local Agent Server. +- Extension Host route/base metadata if direct Vite/static access needs it. - Extension Host URL for dev tooling if needed. Do not expose a write-capable Extension Host key in agent prompts. +Route precedence is the failure-prone part: `/api/canvas/installations/*` and `/canvas-extensions/*` must match before `/api/*` and static SPA fallback in every launcher path. + ## 4. Frontend Proof ### 4.1 API Wrapper Add `src/api/extensions-service.ts` for every `/api/canvas/installations/*` call. -Update `src/api/no-direct-agent-server-calls.test.ts` with one narrow allowlist entry for this wrapper, mirroring automation. +Update `src/api/no-direct-agent-server-calls.test.ts` with one narrow allowlist entry for this wrapper, mirroring automation but covering the current blanket `fetch('/api/...')` rule as well as axios. The exception should allow only `/api/canvas/installations/*` from the wrapper; it must not weaken the Agent Server rule for arbitrary `/api/*` calls. ### 4.2 Packages Page @@ -155,6 +189,7 @@ The page should: - Show Enabled, Installed, Disabled, Invalid, and Dev sections. - Show install source, version, contribution badges, required secrets, diagnostics, enable/disable/remove actions. - Stay dense and operational; no marketplace browsing in MVP. +- Route all visible strings, tooltips, and action labels through i18n keys. ### 4.3 View Host @@ -212,7 +247,9 @@ Compute disabled reasons before launch: - Context blocks: allowed anywhere `system_message_suffix` is accepted. - Remote SDK plugin sources: allowed where `plugins` are accepted. - Package-relative/local SDK plugin paths: allowed only when `localInstallStoreReadable` is true. +- ACP runtimes: context-only unless the SDK plugin smoke proves the exact plugin shape is ACP-compatible. - MCP templates: visible as setup requirements, never silently installed. +- Public skills: do not assume public marketplace skills are loaded; current frontend defaults `VITE_LOAD_PUBLIC_SKILLS` to false unless explicitly set true. ### 5.3 Create-Conversation Merge @@ -222,6 +259,7 @@ Extend the create-conversation path with an `extensionSystemSuffix` option: - `AgentServerConversationService.createConversation()` asks the extensions service for enabled/selected contributions. - Plugin specs merge with existing `/launch` plugin selections and dedupe by `source/ref/repo_path`. - `agent-server-adapter.ts` appends `` and `` after ``. +- The adapter continues to emit top-level `agent_settings` and top-level `plugins`; no `agent` payload field is added. - `conversationInstructions` remains user-message content and never receives extension context. For the first proof, a `hello.canvas` launch template should append a recognizable context block so the payload/unit test can prove the suffix path works without depending on LLM behavior. @@ -255,8 +293,11 @@ The MVP is proven when this scriptable path works from a clean checkout: ```sh npm run build +npm run build:lib +npm pack --dry-run node bin/agent-canvas.mjs install ./examples/extensions/hello-canvas --yes node bin/agent-canvas.mjs list extensions +node bin/agent-canvas.mjs doctor node bin/agent-canvas.mjs ``` @@ -269,11 +310,15 @@ Then in the browser: 5. Start its launch template and verify the conversation payload includes the extension context suffix. 6. In `npm run dev`, register the same extension with `--dev`, rebuild the extension output, and see the view remount with the new code. +Release-path acceptance should also install the packed `@openhands/agent-canvas` tarball into a temp npm prefix and repeat `agent-canvas list`, `agent-canvas doctor`, and one packed/local extension install without relying on repo-local files outside the package. + ## 8. Testing Strategy -**Unit:** manifest validation; reserved `browser.entry` diagnostics; path traversal rejection; artifact detection; install-store bootstrap; enable/disable/remove/update state transitions; duplicate ID handling; asset route path validation; CLI arg parsing; no-install-scripts default; launch contribution projection; context suffix rendering; plugin merge/dedupe; runtime compatibility classification; dev registration and manifest revalidation. +**Unit:** manifest validation; reserved `browser.entry` diagnostics; path traversal rejection; artifact detection; unsupported standalone artifact diagnostics; install-store bootstrap; enable/disable/remove/update state transitions; duplicate ID handling; asset route path validation; CLI arg parsing before build checks; no-install-scripts default; launch contribution projection; context suffix rendering; plugin merge/dedupe; runtime compatibility classification; dev registration and manifest revalidation. + +**Node integration:** run the Extension Host against a temp install store; install the `hello.canvas` fixture from local path and npm-packed tarball; fetch registry and asset URLs; verify route precedence before `/api/*` in Vite, ingress, static server, and packaged CLI paths; verify mutating routes require the session API key; verify `doctor` reports invalid manifests, unsupported standalone artifacts, and missing assets. -**Node integration:** run the Extension Host against a temp install store; install the `hello.canvas` fixture from local path and npm-packed tarball; fetch registry and asset URLs; verify mutating routes require the session API key; verify `doctor` reports invalid manifests and missing assets. +**Release packaging:** `npm pack --dry-run`; packed-tarball install into a temp npm prefix; verify `agent-canvas list`, `agent-canvas doctor`, and an extension install work without a source checkout; verify `@openhands/agent-canvas/extensions` resolves for type consumers. **Component:** Packages page empty/installed/enabled/invalid/dev states; install/enable/disable/remove action wiring; primary Sidebar extension entries in expanded/collapsed/mobile states; extension view loading/error/remount; extension settings read/patch; launch template preflight for incompatible SDK plugin paths. @@ -291,6 +336,7 @@ npm run typecheck && npm test && npm run build - Marketplace/catalog discovery. - Sandboxed iframe runtime. +- Standalone SDK plugin / standalone skill / standalone MCP-template management. - Package signing/provenance UI. - Rich command palette integration. - Agent-mediated installation. diff --git a/docs/ExtensionsSystemRFC.md b/docs/ExtensionsSystemRFC.md index 86549df88..bf9e1a112 100644 --- a/docs/ExtensionsSystemRFC.md +++ b/docs/ExtensionsSystemRFC.md @@ -10,7 +10,7 @@ Agent Canvas should ship a first-class **Extensions** system: user-installable n An Extension can contribute UI views, primary navigation entries, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces — Canvas does not patch or load code inside the Agent Server. -The MVP delivers CLI install, a Packages management page, trusted same-origin extension views, dev-mode authoring with live reload, and SDK plugin / context merging on conversation launch. Marketplace, signing, sandboxed iframe views, parent-React component extensions, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. +The MVP delivers CLI install for Agent Canvas extension packages, a Packages management page, trusted same-origin extension views, dev-mode authoring with live reload, and SDK plugin / context merging on conversation launch. The CLI can detect adjacent artifact types such as standalone SDK plugins or `SKILL.md` folders, but the first executable slice should install/enable only packages with an Agent Canvas manifest. Marketplace, signing, sandboxed iframe views, parent-React component extensions, standalone skill/plugin management, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. ## 2. Motivation @@ -37,6 +37,7 @@ User-facing UI should use the word "Extension." Avoid "plugin" in the Canvas pro - Install from npm package names, version ranges, tarballs, or local paths. - Works when the user globally installs Agent Canvas and runs `agent-canvas`. - Supports CLI install/manage commands, persisted enablement, and a process-level diagnostic kill switch. +- Management commands work from a globally installed npm package without starting the full stack and without requiring the static `build/` directory. - Keeps the Agent Server unmodified for MVP. - Aligns with the OpenHands SDK plugin format rather than inventing a competing format. - Lets one extension package ship UI plus agent-runtime descriptors. @@ -51,6 +52,7 @@ User-facing UI should use the word "Extension." Avoid "plugin" in the Canvas pro - No stable extension API may expose Canvas internals, parent React components, or DOM mutation hooks. MVP browser modules are trusted same-origin code, so DOM access is a trust concern rather than an enforceable sandbox boundary. - No repository-local files may modify the running Canvas browser experience simply because a conversation is attached to that repository. - No npm install scripts run by default during extension installation. +- No standalone SDK plugin, standalone `SKILL.md`, or MCP template artifact becomes independently active in the first MVP slice; those artifacts must be wrapped by an Agent Canvas extension until their product UX is designed. - No public marketplace, ratings, payments, reviews, or remote trust service in the first PRs. ## 6. Prior Art @@ -125,6 +127,8 @@ The Agent Server is unchanged for MVP. Canvas treats it as a black box and only ## 8. CLI UX +Command dispatch is part of the release contract. `agent-canvas install`, `list`, `enable`, `disable`, `remove`, `update`, and `doctor` must parse and run before the launcher checks for a static `build/` directory or imports `scripts/dev-with-automation.mjs`. The full stack still requires packaged frontend assets, but management commands must work from a global npm install and from source checkouts that have not run `npm run build`. + ### Global install and launch ```sh @@ -151,6 +155,8 @@ Detection order: 4. Future MCP template manifest → MCP template. 5. Multiple markers present → the explicit Agent Canvas Extension manifest wins (it may intentionally wrap SDK plugin, skill, or MCP contributions). +MVP behavior: Agent Canvas Extension artifacts install and can be enabled. Standalone SDK plugin, skill, or MCP-template detection returns a typed "detected but unsupported in MVP" diagnostic unless the artifact is wrapped by an extension manifest. This keeps the single `install` verb and detector extensible without silently mutating SDK/user skill state before Canvas has the matching management UX. + For extensions, install validates the manifest, shows permissions, installs with scripts denied by default, and enables the extension after consent. Non-interactive flags: ```sh @@ -231,6 +237,16 @@ Each extension package exposes its manifest through `package.json`: The manifest path must resolve inside the package root. Path traversal is rejected. +### 10.1 Agent Canvas package release contract + +The host package must publish everything the global CLI and extension authors need: + +- `bin/agent-canvas.mjs`, launcher scripts, static `build/`, and any Extension Host scripts must be included by `package.json#files`. +- Extension author types must be exported as `@openhands/agent-canvas/extensions`. The release build must emit those files under `dist/extensions/*` and add a matching `./extensions` subpath export. +- If JSON Schemas are advertised by URL or package path, the schemas must either be served remotely or included in the npm package. Do not point authors at files that are excluded by `package.json#files`. +- Example extensions under `examples/` are source-repo fixtures unless `package.json#files` explicitly includes them. The acceptance path for a globally installed package should use either a packed fixture tarball or a separately published example package, not an unpublished repo-local path. +- Every release candidate should run `npm pack --dry-run` or an equivalent packed-tarball smoke so missing `build`, `scripts`, `dist/extensions`, or schema files are caught before publish. + ## 11. Manifest Schema `agent-canvas.extension.json`: @@ -405,15 +421,19 @@ This DOM-island contract avoids React shared-instance problems in static builds ### 12.2 `agentPlugins` -Maps to OpenHands SDK plugin sources. Fields: `id`, `source` (package-relative path, GitHub shorthand, Git URL, or absolute path for dev mode), `ref` (optional branch/tag/commit), `repoPath` (optional subdirectory), `autoInclude` (`manual` | `enabled` | `always`; default `manual`). +Maps to OpenHands SDK `PluginSource` values. Fields: `id`, `source` (package-relative path, GitHub shorthand, Git URL, or absolute path for dev mode), `ref` (optional branch/tag/commit), `repoPath` (optional subdirectory for remote git/GitHub sources), `autoInclude` (`manual` | `enabled` | `always`; default `manual`). Resolution: - Package-relative paths are converted by the Extension Host to absolute paths inside the installed npm package. - Remote sources pass through as SDK `PluginSource`. - Local paths are allowed only when explicitly installed with `--dev` or through `agent-canvas install `. +- `repoPath` maps to SDK `repo_path` and must be a relative path with no traversal. For package-relative/local sources, resolve `source` directly to the SDK plugin root and omit `repo_path`; local sources should not rely on `repo_path`. +- The resolved plugin root must contain the SDK plugin package shape (`.plugin/plugin.json` or `.claude-plugin/plugin.json`, plus optional `skills/`, `hooks/`, `.mcp.json`, `agents/`, and `commands/`). + +The current frontend `PluginSpec.parameters` field is collected by `/launch` but `buildConfiguredConversationSettings()` sends only `source`, `ref`, and `repo_path` in the create-conversation payload ([src/api/agent-server-adapter.ts](src/api/agent-server-adapter.ts)). MVP extension behavior must not depend on arbitrary plugin parameters. -The current frontend `PluginSpec.parameters` field is collected by `/launch` but stripped by [agent-server-adapter.ts:707-711](src/api/agent-server-adapter.ts:707) before the create-conversation payload is sent. MVP extension behavior must not depend on arbitrary plugin parameters. +**ACP constraint.** SDK plugins can merge skills, MCP config, hooks, and plugin agents before agent initialization. MCP config and other non-ACP-compatible fields can cause `ACPAgent` initialization to fail. MVP should skip `agentPlugins` for ACP runtimes unless the SDK smoke proves a prompt/skills-only plugin is accepted; extension context suffixes remain the compatible path for ACP. ### 12.3 `mcpServers` @@ -607,6 +627,7 @@ Compatibility: - Package-relative / local SDK plugin paths: `agent-server-local` only. - MCP templates: where existing MCP settings work (ACP inherits the existing guard). - Secrets: where the active backend's secrets service accepts them. +- ACP runtimes: extension context is allowed; extension MCP templates remain guarded by the existing MCP route; extension SDK plugins are disabled until a smoke test proves the specific plugin contribution does not inject ACP-incompatible MCP, hook, tool, or agent-definition state. Incompatible contributions are skipped with a disabled reason shown before launch. Local filesystem paths are never sent to remote/cloud runtimes. @@ -651,6 +672,8 @@ The `install` route uses the same artifact detector as `agent-canvas install`. M Frontend code must call these routes only through a dedicated `src/api/extensions-service.ts` wrapper. Because the route prefix begins with `/api/` but targets the local Extension Host rather than the Agent Server, PR 2 must update `src/api/no-direct-agent-server-calls.test.ts` with a narrow allowlist entry for that wrapper, mirroring the existing automation-service exception. This keeps the `/api/canvas/installations/*` ingress shape while preserving the repo rule that ordinary Agent Server traffic goes through `@openhands/typescript-client`. +Implementation note: the current guard has an axios allowlist and a separate blanket `fetch('/api/...')` check. Adding `src/api/extensions-service.ts` to the axios allowlist is not enough if the wrapper uses `fetch`; the test must explicitly allow only `/api/canvas/installations/*` calls from that wrapper, or the wrapper must use an approved local helper. Do not weaken the guard for arbitrary `/api/*` traffic. + **Routing requirement:** the Extension Host route table must be implemented for every launch mode in the same PR that starts the host (Vite dev proxy, automation ingress, static serving path, and packaged CLI). Registry response: @@ -685,6 +708,8 @@ An extension contribution merge step runs before `buildStartConversationRequestW 5. `agent-server-adapter.ts` appends extension context to `AgentContext.system_message_suffix`. 6. Existing payload creation sends `plugins` and no new server-specific fields. +Current-main payload constraint: Canvas now builds the SDK start request with top-level `agent_settings`, `workspace`, `initial_message`, and optional top-level `plugins`. Do not reintroduce the older legacy `agent` payload shape. Extension context belongs in `buildAgentContext()` / the resulting `agent_settings.agent_context.system_message_suffix`; extension SDK plugins belong in the same top-level `plugins` array already emitted by `buildConfiguredConversationSettings()`. + Merge rules: - Explicit `plugins` from `/launch` come first. @@ -789,12 +814,16 @@ This is not part of PR 0 or PR 1. It becomes feasible after the local install st ## 23. Upstream Dependencies -MVP requires **zero broad Agent Server changes**. Three things to confirm against the current pinned SDK before PR 4: +MVP requires **zero broad Agent Server changes**. Current `software-agent-sdk` exposes the needed request shape: `StartConversationRequest.plugins` is `list[PluginSource] | None`, and `PluginSource` carries `source`, optional `ref`, and optional `repo_path`. The SDK loads plugins lazily when a local conversation first runs, then merges plugin skills, MCP config, hooks, and plugin agents into the agent. This is enough for Canvas to forward descriptors; it is also why ACP and filesystem-local preflight matter. + +Three things to confirm against the current pinned Agent Server version (`config/defaults.json` currently pins `agentServer` to `1.23.0`) before PR 4: 1. Agent Server create-conversation accepts `plugins` in the shape of SDK `PluginSource`. 2. `AgentContext.system_message_suffix` remains accepted for both `Agent` and `ACPAgent`. 3. SDK plugin local-path loading works from the host path Canvas passes. +Current frontend behavior to preserve: `shouldLoadPublicSkills()` defaults to `false` unless `VITE_LOAD_PUBLIC_SKILLS=true`. Extension-contributed SDK plugins and context blocks must not rely on public marketplace skills being auto-loaded by default. + Nice-to-have, not blockers: resolved plugin refs in conversation info for audit; structured plugin load diagnostics as conversation events; `PluginSource.parameters` if the SDK decides to support it; a validation endpoint for plugin manifests; server-owned SDK plugin management APIs. Out of scope upstream: a generic server-side Canvas extension loader; server-side npm package installation; arbitrary frontend hook execution in the Agent Server; tool-override APIs for third-party Canvas packages. @@ -833,9 +862,9 @@ Extension views look like regular Canvas pages but are visibly extension-owned: Files: `docs/ExtensionsSystemRFC.md`, `src/extensions/types.ts`, `src/extensions/manifest-schema.ts`, `src/extensions/manifest-validation.ts`, `src/extensions/artifact-detection.ts`, `__tests__/extensions/manifest-validation.test.ts`. -Deliverables: shared manifest and registry types; generic installable artifact kind definitions; artifact detection rules for extension packages, SDK plugins, skills, and placeholder MCP templates; schema-version constant; manifest validation helpers; storage-path helper for `~/.openhands/agent-canvas/installations`; fixtures for valid/invalid manifests; package export plan. No Extension Host, no frontend UI, no agent contribution merging. +Deliverables: shared manifest and registry types; generic installable artifact kind definitions; artifact detection rules for extension packages, SDK plugins, skills, and placeholder MCP templates; schema-version constant; manifest validation helpers; storage-path helper for `~/.openhands/agent-canvas/installations`; fixtures for valid/invalid manifests; `@openhands/agent-canvas/extensions` subpath export plan wired to generated `dist/extensions/*`; release packaging checklist. No Extension Host, no frontend UI, no agent contribution merging. -**Iframe-runtime forward compatibility (minimal, PR 0 scope):** the manifest schema and TypeScript contract accept both `browser.module` (active) and `browser.entry` (reserved). The validator recognizes `browser.entry` as syntactically valid but emits a `reserved-not-yet-supported` diagnostic so authors can't accidentally ship extensions that depend on an unimplemented runtime. The API contract in `src/extensions/types.ts` is defined async-only, which is the only change needed so that a future iframe runtime can satisfy the same interface over postMessage without an API redesign. No iframe host, no postMessage bridge, no asset-mode switching, and no parallel runtime code lands here — that work is deferred to a post-MVP PR triggered by one of the conditions in §28 Question 3. The total PR 0 cost of keeping the option open is a handful of schema fields, one validation rule, one fixture, and a short authoring note in the type comments. +**Iframe-runtime forward compatibility (minimal, PR 0 scope):** the manifest schema and TypeScript contract accept both `browser.module` (active) and `browser.entry` (reserved). The validator recognizes `browser.entry` as syntactically valid but emits a `reserved-not-yet-supported` diagnostic so authors can't accidentally ship extensions that depend on an unimplemented runtime. The API contract in `src/extensions/types.ts` is defined async-only, which is the only change needed so that a future iframe runtime can satisfy the same interface over postMessage without an API redesign. No iframe host, no postMessage bridge, no asset-mode switching, and no parallel runtime code lands here — that work is deferred to a post-MVP PR triggered by one of the trust-boundary conditions in §27 Decision 11. The total PR 0 cost of keeping the option open is a handful of schema fields, one validation rule, one fixture, and a short authoring note in the type comments. **Includes:** removing the empty `src/addons/` directory left over from the earlier prototype to avoid namespace confusion. @@ -843,7 +872,7 @@ Deliverables: shared manifest and registry types; generic installable artifact k Files: `bin/agent-canvas.mjs`, `scripts/dev-safe.mjs`, `scripts/dev-with-automation.mjs`, `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, extension config helper, tests under `__tests__/extensions/`. -Deliverables: generic install/manage CLI parsing; `install/list/enable/disable/remove/update/doctor` dispatched before stack startup; install store under `~/.openhands/agent-canvas/installations/`; artifact detection for all kinds; npm install with `--ignore-scripts`; manifest validation; Extension Host startup; ingress routes for `/api/canvas/installations/*` and `/canvas-extensions/*` across all four launch modes (Vite dev proxy, automation ingress, static serving, packaged CLI); dev extension registration store and `--dev` registration; launcher-issued `localInstallStoreReadable` capability flag; example package fixture using SDK plugin and context contributions. +Deliverables: generic install/manage CLI parsing; `install/list/enable/disable/remove/update/doctor` dispatched before stack startup, before the static `build/` existence check, and before importing stack launcher scripts; install store under `~/.openhands/agent-canvas/installations/`; artifact detection for all kinds with independent install/enable support limited to Agent Canvas Extension manifests; typed unsupported diagnostics for standalone SDK plugins, skills, and MCP templates; npm install with `--ignore-scripts`; manifest validation; Extension Host startup; ingress routes for `/api/canvas/installations/*` and `/canvas-extensions/*` across all four launch modes (Vite dev proxy, automation ingress, static serving, packaged CLI); dev extension registration store and `--dev` registration; launcher-issued `localInstallStoreReadable` capability flag; example package fixture using SDK plugin and context contributions; `npm pack --dry-run` or packed-tarball smoke for the global CLI path. ### PR 2 — Frontend management UI @@ -877,6 +906,8 @@ Permission consent modal; secret setup flow; `doctor` command with actionable di **Unit:** manifest schema validation; package path traversal rejection; duplicate ID handling; enable/disable precedence; registry sort order; asset route path validation; CLI arg parsing; dev extension registration and path validation; dev manifest revalidation after source changes; launch contribution merge and dedupe; extension context suffix rendering; runtime compatibility classification across all five runtime classes; permission drift / update re-approval; uninstall preserve vs purge. +**Release packaging:** `npm pack --dry-run`; install from the packed tarball into a temp global/prefix; verify `agent-canvas list`, `agent-canvas doctor`, and `agent-canvas install --yes` work without a source checkout; verify full `agent-canvas` launch still finds `build/`, `scripts/`, and Extension Host routes. + **Component:** Packages page states; permission display; browser-module view loading/error; MCP required preflight; incompatible runtime warning; dev badge. **E2E snapshots:** Packages page empty state; enabled extension with one view; invalid extension diagnostics; launch template requiring MCP; launch preflight with local-path SDK plugin disabled on remote backend; dev extension view remount after file change; future: agent-mediated install proposal flow. @@ -901,15 +932,19 @@ These were open questions in the earlier draft. Decisions for the RFC: | 4 | `src/installations/` vs `src/extensions/` directory split? | **Single `src/extensions/` directory** until a real consumer requires the split. | | 5 | API route prefix consistency? | **`/api/canvas/installations/*`** for all management routes; `/canvas-extensions/*` reserved for asset serving. | | 6 | Empty `src/addons/` directory on `main`? | **Remove in PR 0.** | +| 7 | Should the first MVP independently install standalone SDK plugins, standalone skills, or MCP templates? | **No.** Detect them and return explicit unsupported diagnostics; require an Agent Canvas extension wrapper for MVP. | +| 8 | Should package-relative SDK plugin paths be MVP? | **Yes, but only when `localInstallStoreReadable` is true** and the pinned Agent Server smoke passes. Remote/cloud runtimes get disabled reasons rather than local paths. | +| 9 | Should extension context be global per extension, selected per launch template, or both? | **Both.** Context blocks use `autoInclude` for global/default behavior and launch templates can require/select specific context IDs. | +| 10 | Should repo-provided SDK plugins/skills ever be surfaced automatically as Canvas extensions? | **No automatic surfacing.** Repository content remains an Agent Server/runtime concern unless the user explicitly installs/registers an Agent Canvas extension path. | +| 11 | What triggers the reserved iframe runtime? | **A trust boundary requirement:** community marketplace distribution, an untrusted third-party publisher tier, or a concrete need for browser-enforced isolation/CSP. | +| 12 | Should agent-proposed dev extension registration ship with dev watch mode? | **No.** Defer to the broader post-MVP agent-mediated install proposal flow. | +| 13 | Which create-conversation payload shape should extensions target? | **Current SDK settings shape:** top-level `agent_settings` plus optional top-level `plugins`; do not reintroduce legacy `agent` payloads. | ## 28. Remaining Open Questions -1. Should agent-proposed dev extension registration ship with dev watch mode, or wait for the broader agent-mediated install proposal flow? -2. Should package-relative SDK plugin paths be MVP, or should MVP agent plugins require remote Git/GitHub sources first? (Leaning MVP, gated on the `localInstallStoreReadable` flag.) -3. What concrete trigger moves us to implement the reserved `browser.entry` iframe runtime? (Candidates: opening a community marketplace tier, onboarding the first untrusted third-party publisher, or needing browser-enforced isolation/CSP for an extension.) -4. Should extension context be global per extension, selected per launch template, or both? -5. Should the `@openhands/extensions` catalog eventually become one first-party Extension package, or remain a plain dependency for built-in catalogs? -6. Should repo-provided SDK plugins/skills ever be surfaced separately from Agent Canvas Extensions, or remain purely Agent Server/runtime concerns? +1. Should the `@openhands/extensions` catalog eventually become one first-party Extension package, or remain a plain dependency for built-in catalogs? +2. What is the graduation path for standalone SDK plugin, standalone skill, and MCP-template installers after the extension-package MVP proves out? +3. What provenance UI is required before enabling a public/community extension marketplace: npm integrity only, signatures, first-party allowlists, or enterprise policy? ## 29. Recommended MVP Cut From eff53f2629ce61067e490a929b7a63135e7efb31 Mon Sep 17 00:00:00 2001 From: Devin Date: Sat, 23 May 2026 07:15:21 -0400 Subject: [PATCH 04/14] Update with a plan --- docs/ExtensionsSystemAgentExecutionPlan.md | 652 +++++++++++++++++++++ docs/ExtensionsSystemPoCPlan.md | 3 + docs/ExtensionsSystemRFC.md | 2 + 3 files changed, 657 insertions(+) create mode 100644 docs/ExtensionsSystemAgentExecutionPlan.md diff --git a/docs/ExtensionsSystemAgentExecutionPlan.md b/docs/ExtensionsSystemAgentExecutionPlan.md new file mode 100644 index 000000000..bdf5c55a9 --- /dev/null +++ b/docs/ExtensionsSystemAgentExecutionPlan.md @@ -0,0 +1,652 @@ +# Agent Canvas Extensions PoC Agent Execution Plan + +Status: executable handoff plan +Source RFC: [ExtensionsSystemRFC.md](./ExtensionsSystemRFC.md) +Source PoC plan: [ExtensionsSystemPoCPlan.md](./ExtensionsSystemPoCPlan.md) +Target: a working proof of concept that can be built by multiple agents without one agent holding all context + +## 1. How To Use This Plan + +This is the checklist an implementation agent should follow. The RFC is the product and architecture contract; the PoC plan explains the shape of the proof. This file turns both into task-sized work. + +Rules for every agent: + +- Read this file, the RFC, the PoC plan, and `AGENTS.md` before editing. +- Run `git status --short` before starting and before handing off. +- Claim a task ID in the handoff notes or PR description before editing overlapping files. +- Keep each task scoped to its listed files unless implementation discovers a real dependency. +- End every task with: files changed, tests run, remaining risks, and the next task ID that is unblocked. +- Do not weaken repo rules: frontend Agent Server calls use typed clients; Extension Host routes use the dedicated wrapper exception; UI strings go through i18n; package dependencies stay exact-pinned. + +Suggested verification tiers: + +```sh +npm run typecheck +npm test +npm run build +npm run build:lib +npm pack --dry-run +``` + +Run the narrowest useful command while iterating. Before declaring a gate complete, run the gate's listed verification. + +## 2. Multi-Agent Work Model + +Use gates to coordinate merge order. Tasks inside a gate can be split, but the gate should not be considered complete until all acceptance checks pass. + +| Gate | Purpose | Parallelism | Blocks | +|---|---|---|---| +| G0 | Risk burners and contracts | Mostly serial | Everything else | +| G1 | Manager, CLI, install store, host routes | Manager/CLI and launcher work can split after shared contracts land | UI, view host, conversation merge | +| G2 | Example extension and package release path | Can run alongside G1 after contracts land | View host, acceptance demo | +| G3 | Frontend Packages page and ExtensionService | Can run once Extension Host registry shape is stable | View host UX, dev mode UX | +| G4 | Browser-module view host and sidebar entries | Can run after registry/assets exist | Acceptance demo | +| G5 | Conversation contribution merge | Can run after launch-contributions endpoint exists | Final proof | +| G6 | Dev mode watch/remount | Can run after manager, host, and view host exist | Final proof | +| G7 | Acceptance, release smoke, polish | Serial final pass | PoC complete | + +Avoid these overlapping edits unless one agent owns the integration: + +- `bin/agent-canvas.mjs`, `scripts/dev-safe.mjs`, `scripts/dev-with-automation.mjs`, `scripts/static-server.mjs`, and `vite.config.ts` should have one launcher/routing owner at a time. +- `src/api/agent-server-adapter.ts` and `src/api/conversation-service/agent-server-conversation-service.api.ts` should have one conversation-merge owner at a time. +- Sidebar route work should coordinate with Packages page route work before editing `src/routes.ts`. +- `package.json` exports/files changes should coordinate with release-smoke work. + +## 3. Gate G0: Risk Burners And Contract + +### G0.1 SDK Plugin Source Smoke + +- [ ] Owner: +- [ ] Depends on: none +- [ ] Files likely touched: + - `examples/extensions/hello-canvas/agent/hello-plugin/.plugin/plugin.json` + - `examples/extensions/hello-canvas/agent/hello-plugin/**` + - optional smoke helper under `scripts/` or `__tests__/extensions/` +- [ ] Implement: + - Create the smallest SDK plugin fixture with a visible skill/context marker. + - Start a local conversation with top-level `plugins: [{ source: absolutePluginPath }]`. + - Confirm pinned Agent Server `1.23.0` accepts `PluginSource` and loads from a local path. + - Run the same preflight against an ACP configuration or document that ACP remains context-only. +- [ ] Tests: + - Add a repeatable smoke or unit fixture where possible. + - If fully automated live smoke is too expensive, document exact manual command and observed payload/server response. +- [ ] Done when: + - Local-path plugin support is proven or SDK plugin merge is explicitly feature-flagged/deferred. + - ACP behavior is decided for MVP. +- [ ] Handoff notes: + +### G0.2 Static Dynamic Import Smoke + +- [ ] Owner: +- [ ] Depends on: none +- [ ] Files likely touched: + - `examples/extensions/hello-canvas/dist/index.js` + - `__tests__/extensions/` or a small script fixture +- [ ] Implement: + - Create a browser-ready ESM fixture exporting `mount({ root, context })`. + - Verify static production build can dynamically import it from `/canvas-extensions/...`. + - Add a negative fixture that uses a bare runtime import such as `react`. +- [ ] Tests: + - Validation rejects bare runtime imports with an actionable diagnostic. + - Valid module imports with only bundled/relative imports. +- [ ] Done when: + - DOM-island runtime works without Vite resolving shared dependencies. +- [ ] Handoff notes: + +### G0.3 Shared Types And Manifest Validation + +- [ ] Owner: +- [ ] Depends on: none +- [ ] Files likely touched: + - `src/extensions/types.ts` + - `src/extensions/manifest-schema.ts` + - `src/extensions/manifest-validation.ts` + - `src/extensions/artifact-detection.ts` + - `src/extensions/storage-paths.ts` + - `__tests__/extensions/manifest-validation.test.ts` + - `package.json` + - `tsconfig.lib.json` if needed for emitted type paths + - remove empty `src/addons/` +- [ ] Implement: + - Manifest v1 types, contribution types, registry entry types, diagnostics, installable artifact kinds. + - Validation for required fields, ID regex, duplicate/mutually exclusive `browser.module` and `browser.entry`, path containment, reserved `browser.entry`. + - Artifact detection order: Agent Canvas extension, SDK plugin, skill, MCP placeholder. + - Storage helpers for `~/.openhands/agent-canvas/installations`. + - Public package export `@openhands/agent-canvas/extensions` and emitted `dist/extensions/*`. +- [ ] Tests: + - Valid extension manifest. + - Missing required fields. + - Bad ID. + - Traversal in manifest/browser/icon/plugin paths. + - Both `browser.module` and `browser.entry`. + - Reserved `browser.entry` diagnostic. + - Standalone SDK plugin / `SKILL.md` detection returns unsupported-in-MVP kind. +- [ ] Verification: + +```sh +npm run typecheck +npm test -- __tests__/extensions/manifest-validation.test.ts +npm run build:lib +``` + +- [ ] Done when: + - Type consumers can import from `@openhands/agent-canvas/extensions`. + - No runtime host, CLI store, or UI behavior is introduced in this gate. +- [ ] Handoff notes: + +## 4. Gate G1: Manager, CLI, Host, And Routing + +### G1.1 Extension Manager Library + +- [ ] Owner: +- [ ] Depends on: G0.3 +- [ ] Files likely touched: + - `scripts/extension-manager.mjs` + - `__tests__/extensions/extension-manager.test.ts` + - `__tests__/extensions/fixtures/**` +- [ ] Implement: + - Store bootstrap: private `package.json`, `package-lock.json`, `artifacts.json`, `config.json`, `logs/`, `dev/`, `node_modules/`. + - Read/write registry state with atomic-ish writes where practical. + - Install Agent Canvas extension manifests from local paths first; tarball/npm spec can follow in the same gate if scoped. + - Run npm installs in the private store with `--ignore-scripts` by default. + - Enable/disable/remove/update state transitions. + - Disabled wins over enabled if config is manually inconsistent. + - Typed unsupported diagnostics for standalone SDK plugin, `SKILL.md`, and MCP-template artifacts. + - Registry projection with `assetBaseUrl`, diagnostics, state, version, package name. + - Launch contribution projection from enabled extensions. +- [ ] Tests: + - Store bootstrap. + - Local extension install and enable. + - `--no-enable` leaves disabled/installed. + - Unsupported standalone artifacts. + - Duplicate ID collision. + - Invalid manifest state. + - Remove preserves settings by default. + - Path traversal rejection. +- [ ] Verification: + +```sh +npm test -- __tests__/extensions/extension-manager.test.ts +``` + +- [ ] Done when: + - CLI and host can share the manager without duplicating store logic. +- [ ] Handoff notes: + +### G1.2 CLI Dispatch And Commands + +- [ ] Owner: +- [ ] Depends on: G1.1 +- [ ] Files likely touched: + - `bin/agent-canvas.mjs` + - optional `scripts/extension-cli.mjs` + - `__tests__/extensions/extension-cli.test.ts` +- [ ] Implement: + - Dispatch `install`, `list`, `enable`, `disable`, `remove`, `update`, `doctor`, and `dev-extension` before stack startup. + - Dispatch before checking for `build/`. + - Dispatch before importing `scripts/dev-with-automation.mjs`. + - `--disable-extensions` remains a process-local stack-start flag. + - Help text includes management commands and global install happy path. + - `doctor` reports invalid manifests, unsupported standalone artifacts, missing assets, package/export issues where detectable. +- [ ] Tests: + - `agent-canvas list extensions` works with no `build/`. + - `agent-canvas doctor` works with no `build/`. + - Unknown command produces useful help. + - `install --install-scripts=deny` is default. +- [ ] Verification: + +```sh +node bin/agent-canvas.mjs list extensions +node bin/agent-canvas.mjs doctor +npm test -- __tests__/extensions/extension-cli.test.ts +``` + +- [ ] Done when: + - Management commands are usable from source checkout and packed global install without launching services. +- [ ] Handoff notes: + +### G1.3 Extension Host HTTP Service + +- [ ] Owner: +- [ ] Depends on: G1.1 +- [ ] Files likely touched: + - `scripts/extension-host.mjs` + - `__tests__/extensions/extension-host.test.ts` +- [ ] Implement: + - `GET /api/canvas/installations/registry` + - `GET /api/canvas/installations/diagnostics` + - `POST /api/canvas/installations/install` + - `POST /api/canvas/installations/:id/enable` + - `POST /api/canvas/installations/:id/disable` + - `DELETE /api/canvas/installations/:id` + - `PATCH /api/canvas/installations/:id/settings` + - `GET /api/canvas/installations/launch-contributions` + - `GET /canvas-extensions/:id/*assetPath` + - Session API key guard for mutating routes. + - Static asset path containment and content types. +- [ ] Tests: + - Registry and diagnostics read. + - Mutating routes reject missing/bad API key. + - Asset serving works and traversal fails. + - Launch-contributions filters disabled/invalid extensions. +- [ ] Verification: + +```sh +npm test -- __tests__/extensions/extension-host.test.ts +``` + +- [ ] Done when: + - A toy registry and browser module can be fetched through the host directly. +- [ ] Handoff notes: + +### G1.4 Launcher And Proxy Integration + +- [ ] Owner: +- [ ] Depends on: G1.3 +- [ ] Files likely touched: + - `scripts/dev-safe.mjs` + - `scripts/dev-with-automation.mjs` + - `scripts/static-server.mjs` + - `vite.config.ts` + - `config/defaults.json` if adding default extension host port + - launcher/proxy tests under `__tests__/` +- [ ] Implement: + - Allocate/start Extension Host in `npm run dev`, `dev:minimal`, `dev:static`, and packaged `agent-canvas`. + - Route `/api/canvas/installations/*` and `/canvas-extensions/*` before `/api/*` and before SPA fallback. + - Add Vite proxy support for direct Vite access. + - Add static server route support for direct static-port access. + - Emit frontend env/runtime metadata: extension enabled flag, `localInstallStoreReadable`, route/base if needed. + - Do not expose a write-capable Extension Host key in ``. +- [ ] Tests: + - Route precedence unit tests for `scripts/static-server.mjs` and ingress route ordering. + - Launcher config test for `localInstallStoreReadable=true` only when this launcher starts the local Agent Server. + - Vite config contains extension routes before generic `/api` where applicable. +- [ ] Verification: + +```sh +npm test -- __tests__/vite-config.test.ts +npm test -- __tests__/extensions +``` + +- [ ] Done when: + - Registry JSON and browser module assets can be fetched through ingress and through the direct frontend/static port. +- [ ] Handoff notes: + +## 5. Gate G2: Example Extension And Release Path + +### G2.1 Hello Canvas Extension Fixture + +- [ ] Owner: +- [ ] Depends on: G0.3 +- [ ] Files likely touched: + - `examples/extensions/hello-canvas/package.json` + - `examples/extensions/hello-canvas/agent-canvas.extension.json` + - `examples/extensions/hello-canvas/dist/index.js` + - `examples/extensions/hello-canvas/dist/*.svg` + - `examples/extensions/hello-canvas/agent/hello-plugin/**` + - optional README under the fixture +- [ ] Implement: + - Manifest with `id: "hello.canvas"`. + - One `browser.module` view. + - One primary sidebar contribution after Automations. + - One launch template. + - One context block with a recognizable marker. + - One local SDK plugin contribution rooted at `agent/hello-plugin`. + - Minimal settings schema. + - No bare runtime imports in the shipped module. +- [ ] Tests: + - Manifest validation fixture. + - Asset route fixture. + - Launch contribution fixture. +- [ ] Done when: + - This one fixture can drive the full acceptance demo. +- [ ] Handoff notes: + +### G2.2 Release Packaging Smoke + +- [ ] Owner: +- [ ] Depends on: G0.3, G1.2 +- [ ] Files likely touched: + - `package.json` + - optional `scripts/package-smoke.mjs` + - `__tests__/extensions/package-smoke.test.ts` if automated +- [ ] Implement: + - Ensure `files` includes every runtime script/build artifact needed by the global CLI and Extension Host. + - Ensure `./extensions` export resolves from packed package. + - Add a repeatable smoke for packed tarball install into a temp npm prefix if practical. +- [ ] Verification: + +```sh +npm run build +npm run build:lib +npm pack --dry-run +``` + +- [ ] Done when: + - Packed package contains `bin`, `scripts`, `build`, `dist/extensions`, and any advertised schema files. + - `agent-canvas list extensions` and `agent-canvas doctor` work from packed/global install. +- [ ] Handoff notes: + +## 6. Gate G3: Frontend Packages Management + +### G3.1 ExtensionService API Wrapper + +- [ ] Owner: +- [ ] Depends on: G1.3 +- [ ] Files likely touched: + - `src/api/extensions-service.ts` + - `src/api/no-direct-agent-server-calls.test.ts` + - `src/hooks/query/use-extensions.ts` +- [ ] Implement: + - Dedicated wrapper for every `/api/canvas/installations/*` call. + - Narrow test exception for this wrapper, including the current `fetch('/api/...')` guard. + - React Query hooks for registry, diagnostics, install, enable, disable, remove, settings patch, launch contributions. +- [ ] Tests: + - No-direct-Agent-Server guard still fails arbitrary `/api/*` fetches outside the wrapper. + - Wrapper builds the expected routes and auth headers. +- [ ] Verification: + +```sh +npm test -- src/api/no-direct-agent-server-calls.test.ts +npm run typecheck +``` + +- [ ] Done when: + - Frontend consumers never hand-roll Extension Host fetches. +- [ ] Handoff notes: + +### G3.2 Packages Page + +- [ ] Owner: +- [ ] Depends on: G3.1 +- [ ] Files likely touched: + - `src/routes.ts` + - `src/routes/extensions-packages.tsx` + - `src/components/features/skills/extensions-navigation.tsx` + - `src/i18n/translation.json` + - generated i18n files via `npm run make-i18n` + - `tests/e2e/snapshots/**` +- [ ] Implement: + - Replace or redirect `/plugins` to Packages while preserving bookmarks. + - Packages nav item in the Extensions hub. + - Sections for Enabled, Installed/Disabled, Invalid, Dev. + - Cards show display name, package, version, state, contribution badges, required secrets, diagnostics, source path for dev entries. + - Actions for install, enable, disable, remove. + - User-facing copy goes through i18n keys. +- [ ] Tests: + - Component tests for empty/enabled/invalid/dev states. + - Snapshot tests for key states if scope allows in the PR. +- [ ] Verification: + +```sh +npm run make-i18n +npm run typecheck +npm test +``` + +- [ ] Done when: + - A user can see and manage `hello.canvas` from the UI. +- [ ] Handoff notes: + +## 7. Gate G4: Browser Module View Host And Sidebar + +### G4.1 Extension Runtime Host + +- [ ] Owner: +- [ ] Depends on: G1.3, G2.1, G3.1 +- [ ] Files likely touched: + - `src/routes.ts` + - `src/routes/extension-view-host.tsx` + - `src/extensions/runtime/*` + - `src/i18n/translation.json` +- [ ] Implement: + - Route `/extensions/:extensionId/:viewId/*`. + - Fetch registry, resolve enabled view, import `assetBaseUrl + browser.module`. + - Use `import(/* @vite-ignore */ url)` or equivalent for served extension modules. + - Add cache-busting version token. + - Call `mount({ root, context })`; call `dispose()` on unmount/remount. + - Provide minimal async context: metadata/settings, `navigation.navigate`, `navigation.openExternal`, `ui.toast`, settings read/patch. + - Local error boundary and diagnostics link. +- [ ] Tests: + - Mount success. + - Mount failure shows local error and does not crash Canvas. + - Dispose called on unmount/remount. + - Settings read/patch calls wrapper. +- [ ] Verification: + +```sh +npm run typecheck +npm test +``` + +- [ ] Done when: + - `hello.canvas` view renders from served browser module. +- [ ] Handoff notes: + +### G4.2 Primary Sidebar Entries + +- [ ] Owner: +- [ ] Depends on: G4.1 +- [ ] Files likely touched: + - `src/components/features/sidebar/sidebar.tsx` + - `src/components/features/sidebar/sidebar-rail-body.tsx` + - related mobile/sidebar components + - `src/i18n/translation.json` + - snapshot tests +- [ ] Implement: + - Render enabled `primarySidebar` / `afterAutomations` view contributions after Automations and before conversation list. + - Deterministic sort: `order`, extension display name, view ID. + - Expanded sidebar shows icon + title. + - Collapsed sidebar shows icon with tooltip. + - Mobile drawer shows same relative position. + - Disabled/invalid extensions do not render entries. +- [ ] Tests: + - Expanded/collapsed/mobile render positions. + - Disabled/invalid entries absent. + - Sorting deterministic. +- [ ] Verification: + +```sh +npm run typecheck +npm run test:e2e:snapshots -- --grep "sidebar" +``` + +- [ ] Done when: + - `hello.canvas` sidebar item appears immediately after Automations. +- [ ] Handoff notes: + +## 8. Gate G5: Conversation Contributions + +### G5.1 Launch Contributions And Runtime Compatibility + +- [ ] Owner: +- [ ] Depends on: G1.3, G3.1 +- [ ] Files likely touched: + - `src/api/extensions-service.ts` + - `src/extensions/runtime-compatibility.ts` + - `__tests__/extensions/runtime-compatibility.test.ts` +- [ ] Implement: + - `ExtensionsService.getLaunchContributions()`. + - Runtime classes: `canvas-local`, `agent-server-local`, `agent-server-remote`, `cloud-runtime`, `acp-runtime`. + - Use launcher-issued `localInstallStoreReadable`. + - Disable package-relative/local SDK plugin paths unless filesystem-local. + - Disable extension SDK plugins for ACP unless the G0 smoke proves allowed. + - Do not assume public marketplace skills are loaded. +- [ ] Tests: + - Compatibility matrix across all runtime classes. + - Disabled reasons are stable and UI-ready. + - Remote/cloud never receive local absolute paths. +- [ ] Done when: + - Launch templates can show why each contribution will or will not be included. +- [ ] Handoff notes: + +### G5.2 Create Conversation Merge + +- [ ] Owner: +- [ ] Depends on: G5.1 +- [ ] Files likely touched: + - `src/api/conversation-service/agent-server-conversation-service.api.ts` + - `src/api/agent-server-adapter.ts` + - `src/hooks/mutation/use-create-conversation.ts` + - launch/home components + - `__tests__/api/agent-server-adapter.test.ts` + - `__tests__/extensions/conversation-contributions.test.ts` +- [ ] Implement: + - `extensionSystemSuffix` adapter option. + - Append `` and `` after ``. + - Merge extension plugin specs with existing `/launch` plugin selections. + - Dedupe by `source/ref/repo_path`. + - Preserve current top-level `agent_settings` and top-level `plugins` payload shape. + - Keep `conversationInstructions` as user-message content only. +- [ ] Tests: + - Payload includes extension suffix when selected/enabled. + - Payload omits extension context when disabled. + - Existing runtime services suffix still appears first. + - Plugin merge order and dedupe. + - Local-path plugin skipped on remote/cloud. +- [ ] Verification: + +```sh +npm test -- __tests__/api/agent-server-adapter.test.ts +npm run typecheck +``` + +- [ ] Done when: + - Starting `hello.canvas` launch template produces the expected payload without depending on LLM behavior. +- [ ] Handoff notes: + +## 9. Gate G6: Dev Mode + +### G6.1 Dev Registration And Watch + +- [ ] Owner: +- [ ] Depends on: G1.1, G1.3, G4.1 +- [ ] Files likely touched: + - `scripts/extension-manager.mjs` + - `scripts/extension-host.mjs` + - `bin/agent-canvas.mjs` + - `src/api/extensions-service.ts` + - `src/routes/extensions-packages.tsx` + - tests under `__tests__/extensions/` +- [ ] Implement: + - `agent-canvas install --dev`. + - `agent-canvas dev-extension register/list/unregister`. + - Store registrations in `~/.openhands/agent-canvas/installations/dev/dev-extensions.json`. + - No repo auto-discovery. + - Serve assets directly from registered source folder. + - Watch manifest and declared output files. + - Manifest changes revalidate and update diagnostics. + - Browser module changes bump version token and remount affected views. + - Agent-side changes require new conversation. +- [ ] Tests: + - Registration/unregistration. + - Absolute path storage. + - Traversal rejection. + - Invalid manifest update transitions to invalid diagnostics. + - Asset change emits remount/cache-bust signal. +- [ ] Done when: + - A developer can rebuild `hello.canvas` and see the view remount without rebuilding Canvas. +- [ ] Handoff notes: + +## 10. Gate G7: Acceptance And Polish + +### G7.1 End-To-End Acceptance Demo + +- [ ] Owner: +- [ ] Depends on: G1 through G6 +- [ ] Run: + +```sh +npm run build +npm run build:lib +npm pack --dry-run +node bin/agent-canvas.mjs install ./examples/extensions/hello-canvas --yes +node bin/agent-canvas.mjs list extensions +node bin/agent-canvas.mjs doctor +node bin/agent-canvas.mjs +``` + +- [ ] Browser checks: + - Packages shows `hello.canvas` enabled with no diagnostics. + - Sidebar entry appears after Automations and before conversations. + - Extension view renders. + - Extension settings patch persists. + - Launch template shows context/plugin contribution preflight. + - Conversation payload includes extension suffix. + - Remote/cloud compatibility path shows disabled reason for local plugin path. + - Dev registration and remount path works in `npm run dev`. +- [ ] Done when: + - The PoC works from a clean checkout. +- [ ] Handoff notes: + +### G7.2 Packed Global Install Acceptance + +- [ ] Owner: +- [ ] Depends on: G7.1 +- [ ] Run from a temp directory/prefix: + - Install the packed `@openhands/agent-canvas` tarball. + - Verify `agent-canvas list extensions`. + - Verify `agent-canvas doctor`. + - Install a packed or local `hello.canvas` extension. + - Launch `agent-canvas` and repeat the browser acceptance checks. +- [ ] Done when: + - The happy path matches the upcoming npm release story: global install, then `agent-canvas`. +- [ ] Handoff notes: + +### G7.3 Final Verification + +- [ ] Owner: +- [ ] Depends on: G7.1, G7.2 +- [ ] Run: + +```sh +npm run typecheck && npm test && npm run build +``` + +- [ ] Also run targeted snapshot/live checks if the PR touched those surfaces: + +```sh +npm run test:e2e:snapshots +npm run test:e2e:live -- --check +``` + +- [ ] Done when: + - Full repo verification passes or failures are documented as unrelated/pre-existing. + - The final handoff lists exact commands, output summary, known risks, and follow-up tasks. +- [ ] Handoff notes: + +## 11. Cross-Cutting Acceptance Criteria + +The PoC is not complete unless all of these are true: + +- [ ] Management CLI commands work without starting services. +- [ ] Management CLI commands work when no `build/` directory exists. +- [ ] Global npm package contains every runtime artifact it needs. +- [ ] Extension Host routes work through Vite, ingress, static server, and packaged CLI. +- [ ] `/api/canvas/installations/*` traffic is isolated behind `src/api/extensions-service.ts`. +- [ ] Ordinary Agent Server API calls still use `@openhands/typescript-client`. +- [ ] Browser modules are trusted same-origin DOM islands and documented/consented as such. +- [ ] Bare runtime imports in extension browser modules are rejected for MVP. +- [ ] Local SDK plugin paths are sent only when `localInstallStoreReadable=true`. +- [ ] ACP runtimes do not receive incompatible extension plugin/MCP contributions. +- [ ] `conversationInstructions` never receives extension system context. +- [ ] Extension context composes with existing `` suffix. +- [ ] `hello.canvas` proves install, registry, UI, sidebar, view, settings, launch context, SDK plugin preflight, and dev remount. +- [ ] Tests cover the risk surfaces listed in the PoC plan. + +## 12. Handoff Template + +Use this at the end of every agent task: + +```md +Task ID: +Status: complete | blocked | partial +Files changed: +Tests run: +Result: +Known risks: +Next unblocked tasks: +Notes for next agent: +``` + +Blocked tasks should include the exact command, error, and the smallest proposed next step. Avoid broad "needs investigation" handoffs; name the file, route, command, or test that failed. diff --git a/docs/ExtensionsSystemPoCPlan.md b/docs/ExtensionsSystemPoCPlan.md index 70cf6f7c4..865b894d4 100644 --- a/docs/ExtensionsSystemPoCPlan.md +++ b/docs/ExtensionsSystemPoCPlan.md @@ -2,12 +2,15 @@ Status: working build plan Source RFC: [ExtensionsSystemRFC.md](./ExtensionsSystemRFC.md) +Agent execution checklist: [ExtensionsSystemAgentExecutionPlan.md](./ExtensionsSystemAgentExecutionPlan.md) Target branch: `dv/extensions-poc-v1` ## 1. Purpose This document turns the Extensions RFC into a practical MVP / PoC build path. The RFC should stay clean and reviewable as the product/architecture proposal. This plan is the working artifact for agents and developers implementing the proof, testing risky assumptions, and tightening the design based on real code. +For multi-agent implementation, use [ExtensionsSystemAgentExecutionPlan.md](./ExtensionsSystemAgentExecutionPlan.md) as the task checklist. It breaks this plan into gates, parallel workstreams, task IDs, file ownership, verification commands, and handoff notes. + The MVP should be built as a vertical proof, not as isolated architecture layers. The first complete proof should demonstrate: 1. A local or npm-packed Agent Canvas extension installs into `~/.openhands/agent-canvas/installations`. diff --git a/docs/ExtensionsSystemRFC.md b/docs/ExtensionsSystemRFC.md index bf9e1a112..12264a083 100644 --- a/docs/ExtensionsSystemRFC.md +++ b/docs/ExtensionsSystemRFC.md @@ -858,6 +858,8 @@ Extension views look like regular Canvas pages but are visibly extension-owned: ## 25. Implementation Plan +The multi-agent, checkable execution checklist for this plan lives in [ExtensionsSystemAgentExecutionPlan.md](./ExtensionsSystemAgentExecutionPlan.md). Use this section for PR boundaries and the execution plan for task ownership, dependencies, verification commands, and handoff notes. + ### PR 0 — Contract and types Files: `docs/ExtensionsSystemRFC.md`, `src/extensions/types.ts`, `src/extensions/manifest-schema.ts`, `src/extensions/manifest-validation.ts`, `src/extensions/artifact-detection.ts`, `__tests__/extensions/manifest-validation.test.ts`. From ef2b98af748b1709f19b85d0a949ad154fb1de4c Mon Sep 17 00:00:00 2001 From: Devin Date: Wed, 3 Jun 2026 12:24:52 -0400 Subject: [PATCH 05/14] docs: refresh extensions poc plan --- docs/ExtensionsSystemAgentExecutionPlan.md | 14 +++++++----- docs/ExtensionsSystemPoCPlan.md | 17 +++++++++----- docs/ExtensionsSystemRFC.md | 26 +++++++++++++--------- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/docs/ExtensionsSystemAgentExecutionPlan.md b/docs/ExtensionsSystemAgentExecutionPlan.md index bdf5c55a9..ac4ebc72b 100644 --- a/docs/ExtensionsSystemAgentExecutionPlan.md +++ b/docs/ExtensionsSystemAgentExecutionPlan.md @@ -4,6 +4,7 @@ Status: executable handoff plan Source RFC: [ExtensionsSystemRFC.md](./ExtensionsSystemRFC.md) Source PoC plan: [ExtensionsSystemPoCPlan.md](./ExtensionsSystemPoCPlan.md) Target: a working proof of concept that can be built by multiple agents without one agent holding all context +Current repo baseline: merged through `upstream/main` at `929e5afc` / `origin/main` at `62f5eae7`. This checklist assumes `agentServer` `1.24.0`, the current local/public auth modes, frontend-only/backend-only partial stacks, MCP catalog data from `@openhands/extensions/integrations`, and public skills defaulting on unless `VITE_LOAD_PUBLIC_SKILLS=false`. ## 1. How To Use This Plan @@ -65,7 +66,7 @@ Avoid these overlapping edits unless one agent owns the integration: - [ ] Implement: - Create the smallest SDK plugin fixture with a visible skill/context marker. - Start a local conversation with top-level `plugins: [{ source: absolutePluginPath }]`. - - Confirm pinned Agent Server `1.23.0` accepts `PluginSource` and loads from a local path. + - Confirm pinned Agent Server `1.24.0` accepts `PluginSource` and loads from a local path. - Run the same preflight against an ACP configuration or document that ACP remains context-only. - [ ] Tests: - Add a repeatable smoke or unit fixture where possible. @@ -106,7 +107,7 @@ Avoid these overlapping edits unless one agent owns the integration: - `__tests__/extensions/manifest-validation.test.ts` - `package.json` - `tsconfig.lib.json` if needed for emitted type paths - - remove empty `src/addons/` + - keep new public types under `src/extensions/`; do not recreate the old `src/addons/` namespace - [ ] Implement: - Manifest v1 types, contribution types, registry entry types, diagnostics, installable artifact kinds. - Validation for required fields, ID regex, duplicate/mutually exclusive `browser.module` and `browser.entry`, path containment, reserved `browser.entry`. @@ -183,6 +184,7 @@ npm test -- __tests__/extensions/extension-manager.test.ts - `__tests__/extensions/extension-cli.test.ts` - [ ] Implement: - Dispatch `install`, `list`, `enable`, `disable`, `remove`, `update`, `doctor`, and `dev-extension` before stack startup. + - Dispatch before `--public`, `--frontend-only`, and `--backend-only` stack validation. - Dispatch before checking for `build/`. - Dispatch before importing `scripts/dev-with-automation.mjs`. - `--disable-extensions` remains a process-local stack-start flag. @@ -255,11 +257,13 @@ npm test -- __tests__/extensions/extension-host.test.ts - Route `/api/canvas/installations/*` and `/canvas-extensions/*` before `/api/*` and before SPA fallback. - Add Vite proxy support for direct Vite access. - Add static server route support for direct static-port access. + - Cover `--frontend-only` and `--backend-only`: frontend-only may expose local Packages/browser assets with agent-runtime diagnostics; backend-only should skip browser asset hosting unless a future CLI-only host mode explicitly needs it. - Emit frontend env/runtime metadata: extension enabled flag, `localInstallStoreReadable`, route/base if needed. - Do not expose a write-capable Extension Host key in ``. - [ ] Tests: - Route precedence unit tests for `scripts/static-server.mjs` and ingress route ordering. - Launcher config test for `localInstallStoreReadable=true` only when this launcher starts the local Agent Server. + - Partial-stack route tests for frontend-only/backend-only behavior. - Vite config contains extension routes before generic `/api` where applicable. - [ ] Verification: @@ -368,7 +372,7 @@ npm run typecheck - `tests/e2e/snapshots/**` - [ ] Implement: - Replace or redirect `/plugins` to Packages while preserving bookmarks. - - Packages nav item in the Extensions hub. + - Packages nav item in the existing Extensions hub (`/customize` primary entry, desktop redirect to `/skills`, mobile `ExtensionsMobileHub`). - Sections for Enabled, Installed/Disabled, Invalid, Dev. - Cards show display name, package, version, state, contribution badges, required secrets, diagnostics, source path for dev entries. - Actions for install, enable, disable, remove. @@ -471,7 +475,7 @@ npm run test:e2e:snapshots -- --grep "sidebar" - Use launcher-issued `localInstallStoreReadable`. - Disable package-relative/local SDK plugin paths unless filesystem-local. - Disable extension SDK plugins for ACP unless the G0 smoke proves allowed. - - Do not assume public marketplace skills are loaded. + - Do not assume public marketplace skills are loaded; they now default on but remain opt-out via `VITE_LOAD_PUBLIC_SKILLS=false`. - [ ] Tests: - Compatibility matrix across all runtime classes. - Disabled reasons are stable and UI-ready. @@ -496,7 +500,7 @@ npm run test:e2e:snapshots -- --grep "sidebar" - Append `` and `` after ``. - Merge extension plugin specs with existing `/launch` plugin selections. - Dedupe by `source/ref/repo_path`. - - Preserve current top-level `agent_settings` and top-level `plugins` payload shape. + - Preserve current top-level start payload shape: `agent_settings`, `workspace`, `confirmation_policy`, `tool_module_qualnames`, optional `initial_message`, optional `plugins`, and runtime fields such as `hook_config`, `agent_definitions`, `security_analyzer`, `tags`, and `secrets`. - Keep `conversationInstructions` as user-message content only. - [ ] Tests: - Payload includes extension suffix when selected/enabled. diff --git a/docs/ExtensionsSystemPoCPlan.md b/docs/ExtensionsSystemPoCPlan.md index 865b894d4..d84a3c669 100644 --- a/docs/ExtensionsSystemPoCPlan.md +++ b/docs/ExtensionsSystemPoCPlan.md @@ -4,6 +4,7 @@ Status: working build plan Source RFC: [ExtensionsSystemRFC.md](./ExtensionsSystemRFC.md) Agent execution checklist: [ExtensionsSystemAgentExecutionPlan.md](./ExtensionsSystemAgentExecutionPlan.md) Target branch: `dv/extensions-poc-v1` +Current repo baseline: merged through `upstream/main` at `929e5afc` / `origin/main` at `62f5eae7`. The plan below has been refreshed for the current launcher/auth split, partial-stack modes, MCP integrations catalog, `agentServer` pin `1.24.0`, and public skills defaulting on. ## 1. Purpose @@ -16,7 +17,7 @@ The MVP should be built as a vertical proof, not as isolated architecture layers 1. A local or npm-packed Agent Canvas extension installs into `~/.openhands/agent-canvas/installations`. 2. The Packages page shows the installed extension and its diagnostics. 3. A trusted `browser.module` view renders at `/extensions/:extensionId/:viewId/*`. -4. A view can contribute a primary Sidebar entry after Automations and before the conversation list. +4. A view can contribute a primary Sidebar entry after Automations and before the conversation list, matching the current `SidebarRailBody` order (`New Chat`, `Customize`, `Automations`, then conversations). 5. A launch template can append extension context and include an SDK plugin when the runtime is filesystem-local. 6. Dev mode can register a local source folder, detect output changes, and remount the view without rebuilding Canvas. @@ -26,6 +27,7 @@ These decisions should keep the build from spreading sideways: - First-class install/enable support is for Agent Canvas extension manifests only. Standalone SDK plugins, `SKILL.md` folders, and future MCP-template artifacts can be detected, but they return an explicit unsupported-in-MVP diagnostic unless wrapped by an extension. - CLI management commands must dispatch before stack startup, before the static `build/` existence check, and before importing `scripts/dev-with-automation.mjs`. +- CLI management commands must also dispatch before `--public`, `--frontend-only`, and `--backend-only` stack validation, because `install/list/doctor` should work even when no frontend/backend is being launched. - Extension author types need a real package subpath: `@openhands/agent-canvas/extensions`, emitted under `dist/extensions/*` and included in npm release checks. - Conversation contribution work targets the current start payload: top-level `agent_settings` plus optional top-level `plugins`. Do not reintroduce the legacy `agent` payload shape. - Package-relative/local SDK plugin sources are MVP only when the launcher says `localInstallStoreReadable=true`. ACP runtimes skip extension SDK plugins unless a pinned-version smoke proves the exact plugin shape is compatible. @@ -42,7 +44,7 @@ Add a tiny fixture SDK plugin under: examples/extensions/hello-canvas/agent/hello-plugin ``` -Start a local conversation with its resolved absolute path in top-level `plugins`. Confirm the current pinned Agent Server (`config/defaults.json` pins `agentServer` to `1.23.0`) accepts the SDK `PluginSource` shape (`source`, optional `ref`, optional `repo_path`) and can load a package-relative path when `localInstallStoreReadable` is true. The plugin root should contain `.plugin/plugin.json`; if a fixture uses a local path, resolve `source` directly to the plugin root and omit `repo_path`. +Start a local conversation with its resolved absolute path in top-level `plugins`. Confirm the current pinned Agent Server (`config/defaults.json` pins `agentServer` to `1.24.0`) accepts the SDK `PluginSource` shape (`source`, optional `ref`, optional `repo_path`) and can load a package-relative path when `localInstallStoreReadable` is true. The plugin root should contain `.plugin/plugin.json`; if a fixture uses a local path, resolve `source` directly to the plugin root and omit `repo_path`. Also run the same contribution preflight with an ACP agent configuration. If ACP fails or the plugin injects MCP/hooks/tool/agent-definition state, keep ACP on context-only extension support and show disabled reasons for SDK plugin contributions. If the local-path smoke fails for the pinned server, keep context contributions in MVP and move SDK plugin merge behind a feature flag until the SDK team confirms the loading contract. @@ -64,6 +66,7 @@ Cover: - `scripts/dev-safe.mjs` / `dev:minimal`, where direct Vite proxying must handle Extension Host routes without the automation ingress. - `scripts/static-server.mjs` routes used by the packaged CLI/static mode. - `vite.config.ts` proxy support for direct Vite access, especially `/canvas-extensions/*`. +- `--frontend-only` and `--backend-only` partial-stack behavior: frontend-only can expose Packages/extension views but should show backend-dependent diagnostics, while backend-only should not start the Extension Host view/assets path unless a future CLI-only host mode explicitly needs it. Exit criteria: a browser opened through either the ingress port or direct Vite/static port can fetch registry JSON and import a browser module from `/canvas-extensions/...`. @@ -136,7 +139,7 @@ agent-canvas doctor agent-canvas --disable-extensions ``` -This dispatch must happen before checking for `build/` and before importing launcher scripts. MVP install can support local extension paths first, then npm specs. Npm installs must run in the private install store with install scripts denied by default. Standalone SDK plugin/skill/MCP-template inputs should return clear unsupported diagnostics rather than mutating user runtime state. +This dispatch must happen before public/partial-stack validation, before checking for `build/`, and before importing launcher scripts. MVP install can support local extension paths first, then npm specs. Npm installs must run in the private install store with install scripts denied by default. Standalone SDK plugin/skill/MCP-template inputs should return clear unsupported diagnostics rather than mutating user runtime state. ### 3.4 Extension Host @@ -174,6 +177,8 @@ Do not expose a write-capable Extension Host key in agent prompts. Route precedence is the failure-prone part: `/api/canvas/installations/*` and `/canvas-extensions/*` must match before `/api/*` and static SPA fallback in every launcher path. +Partial-stack mode rule: frontend-only may start enough Extension Host surface to render local Packages and extension browser assets, but must mark agent-runtime contributions unavailable because no Agent Server is running. Backend-only should skip frontend asset hosting and extension browser routes by default; CLI management still works because it dispatches before stack startup. + ## 4. Frontend Proof ### 4.1 API Wrapper @@ -184,7 +189,7 @@ Update `src/api/no-direct-agent-server-calls.test.ts` with one narrow allowlist ### 4.2 Packages Page -Replace `/plugins` with a Packages view, while preserving redirects/bookmarks. Use the existing Extensions layout and navigation. +Replace `/plugins` with a Packages view, while preserving redirects/bookmarks. Use the existing Extensions layout and navigation: `/customize` is the primary-sidebar hub entry, desktop redirects to `/skills`, mobile renders `ExtensionsMobileHub`, and `ExtensionsNavigation` currently contains Skills, MCP Servers, and a coming-soon Plugins item. The page should: @@ -252,7 +257,7 @@ Compute disabled reasons before launch: - Package-relative/local SDK plugin paths: allowed only when `localInstallStoreReadable` is true. - ACP runtimes: context-only unless the SDK plugin smoke proves the exact plugin shape is ACP-compatible. - MCP templates: visible as setup requirements, never silently installed. -- Public skills: do not assume public marketplace skills are loaded; current frontend defaults `VITE_LOAD_PUBLIC_SKILLS` to false unless explicitly set true. +- Public skills: do not assume public marketplace skills are loaded; current frontend defaults to loading them unless `VITE_LOAD_PUBLIC_SKILLS=false`, but users and deployments can opt out. ### 5.3 Create-Conversation Merge @@ -262,7 +267,7 @@ Extend the create-conversation path with an `extensionSystemSuffix` option: - `AgentServerConversationService.createConversation()` asks the extensions service for enabled/selected contributions. - Plugin specs merge with existing `/launch` plugin selections and dedupe by `source/ref/repo_path`. - `agent-server-adapter.ts` appends `` and `` after ``. -- The adapter continues to emit top-level `agent_settings` and top-level `plugins`; no `agent` payload field is added. +- The adapter continues to preserve the current top-level payload shape: `agent_settings`, `workspace`, `confirmation_policy`, `tool_module_qualnames`, optional `initial_message`, optional `plugins`, and runtime fields such as `hook_config`, `agent_definitions`, `security_analyzer`, `tags`, and `secrets`. No `agent` payload field is added. - `conversationInstructions` remains user-message content and never receives extension context. For the first proof, a `hello.canvas` launch template should append a recognizable context block so the payload/unit test can prove the suffix path works without depending on LLM behavior. diff --git a/docs/ExtensionsSystemRFC.md b/docs/ExtensionsSystemRFC.md index 12264a083..281db63b8 100644 --- a/docs/ExtensionsSystemRFC.md +++ b/docs/ExtensionsSystemRFC.md @@ -3,6 +3,8 @@ Status: RFC, ready for review Target: `OpenHands/agent-canvas` +Current repo baseline: merged through `upstream/main` at `929e5afc` / `origin/main` at `62f5eae7` while updating this draft. The current package version is `1.0.0-alpha.10`, `config/defaults.json` pins `agentServer` to `1.24.0`, and `@openhands/typescript-client` is `1.24.3`. + ## 1. Summary @@ -63,7 +65,7 @@ Two existing systems informed this design. **Pi (pi.dev).** We adopt: a single generic `install` verb that detects artifact type, multiple authoring shapes (single directory, packaged module, package wrapping skills/SDK plugins/MCP templates), a developer reload story for explicitly registered local extensions, and clear language that local extensions are code requiring trust. We do **not** adopt Pi's repository-auto-discovery of extensions, its in-process tool registration, or its built-in tool overriding — those collapse the local/remote runtime boundary that Canvas must preserve. -A prior internal prototype (`dv/addons-clean-v1`) contributed the typed host SDK shape, manifest validation, stable extension IDs, route host with error boundary, sidebar ordering, and path-normalization patterns. It is not merge-ready: it assumes a Vite build-time registry, which a globally-installed CLI cannot regenerate per install. An empty `src/addons/` directory remains on `main` from earlier exploration and will be removed as part of PR 0 (see §25). +A prior internal prototype (`dv/addons-clean-v1`) contributed the typed host SDK shape, manifest validation, stable extension IDs, route host with error boundary, sidebar ordering, and path-normalization patterns. It is not merge-ready: it assumes a Vite build-time registry, which a globally-installed CLI cannot regenerate per install. The current tree no longer has a `src/addons/` implementation to remove; PR 0 should simply keep the public extension namespace under `src/extensions/` so the old prototype vocabulary does not leak back in. ## 7. Architecture @@ -263,7 +265,7 @@ The host package must publish everything the global CLI and extension authors ne "license": "MIT", "repository": "https://github.com/acme/agent-canvas-github", "compatibility": { - "agentCanvas": ">=1.0.0-alpha.4 <2.0.0", + "agentCanvas": ">=1.0.0-alpha.10 <2.0.0", "extensionApi": 1 }, "activationEvents": ["onStartup"], @@ -439,7 +441,9 @@ The current frontend `PluginSpec.parameters` field is collected by `/launch` but Extensions provide MCP templates, never silent installs. The UI shows required MCPs before launch and reuses the existing `InstallServerModal` pattern. -**ACP constraint.** [src/routes/mcp.tsx:38-47](src/routes/mcp.tsx:38) currently disables MCP management while an ACP agent is active because the SDK rejects `mcp_config` on `ACPAgent` init. Extension MCP contributions inherit this guard. +**ACP constraint.** [src/routes/mcp.tsx](src/routes/mcp.tsx) currently disables MCP management while an ACP agent is active because the SDK rejects `mcp_config` on `ACPAgent` init. Extension MCP contributions inherit this guard. + +**Current catalog shape.** The MCP marketplace now imports `IntegrationCatalogEntry` from `@openhands/extensions/integrations`, not the old `mcps` export. Extension MCP templates should either use the same `IntegrationTransport` vocabulary or provide an adapter that can project templates into the existing MCP install modal without reintroducing a parallel catalog model. The current install flow can save required secrets through the existing secrets-aware MCP form; extension templates should reuse that path rather than inventing separate secret persistence. ### 12.4 `launchTemplates` @@ -708,7 +712,7 @@ An extension contribution merge step runs before `buildStartConversationRequestW 5. `agent-server-adapter.ts` appends extension context to `AgentContext.system_message_suffix`. 6. Existing payload creation sends `plugins` and no new server-specific fields. -Current-main payload constraint: Canvas now builds the SDK start request with top-level `agent_settings`, `workspace`, `initial_message`, and optional top-level `plugins`. Do not reintroduce the older legacy `agent` payload shape. Extension context belongs in `buildAgentContext()` / the resulting `agent_settings.agent_context.system_message_suffix`; extension SDK plugins belong in the same top-level `plugins` array already emitted by `buildConfiguredConversationSettings()`. +Current-main payload constraint: Canvas now builds the SDK start request with top-level `agent_settings`, `workspace`, `confirmation_policy`, `initial_message`, `tool_module_qualnames`, optional `plugins`, and optional runtime fields such as `hook_config`, `agent_definitions`, `security_analyzer`, `tags`, and `secrets`. Do not reintroduce the older legacy `agent` payload shape and do not drop those existing top-level fields while merging extensions. Extension context belongs in `buildAgentContext()` / the resulting `agent_settings.agent_context.system_message_suffix`; extension SDK plugins belong in the same top-level `plugins` array already emitted by `buildConfiguredConversationSettings()`. Merge rules: @@ -816,13 +820,13 @@ This is not part of PR 0 or PR 1. It becomes feasible after the local install st MVP requires **zero broad Agent Server changes**. Current `software-agent-sdk` exposes the needed request shape: `StartConversationRequest.plugins` is `list[PluginSource] | None`, and `PluginSource` carries `source`, optional `ref`, and optional `repo_path`. The SDK loads plugins lazily when a local conversation first runs, then merges plugin skills, MCP config, hooks, and plugin agents into the agent. This is enough for Canvas to forward descriptors; it is also why ACP and filesystem-local preflight matter. -Three things to confirm against the current pinned Agent Server version (`config/defaults.json` currently pins `agentServer` to `1.23.0`) before PR 4: +Three things to confirm against the current pinned Agent Server version (`config/defaults.json` currently pins `agentServer` to `1.24.0`) before PR 4: 1. Agent Server create-conversation accepts `plugins` in the shape of SDK `PluginSource`. 2. `AgentContext.system_message_suffix` remains accepted for both `Agent` and `ACPAgent`. 3. SDK plugin local-path loading works from the host path Canvas passes. -Current frontend behavior to preserve: `shouldLoadPublicSkills()` defaults to `false` unless `VITE_LOAD_PUBLIC_SKILLS=true`. Extension-contributed SDK plugins and context blocks must not rely on public marketplace skills being auto-loaded by default. +Current frontend behavior to preserve: `shouldLoadPublicSkills()` now defaults to `true`; set `VITE_LOAD_PUBLIC_SKILLS=false` to opt out. Extension-contributed SDK plugins and context blocks still must not depend on public marketplace skills being present, because deployments and users can disable them. Nice-to-have, not blockers: resolved plugin refs in conversation info for audit; structured plugin load diagnostics as conversation events; `PluginSource.parameters` if the SDK decides to support it; a validation endpoint for plugin manifests; server-owned SDK plugin management APIs. @@ -838,7 +842,7 @@ Sections: - MCP Servers - Packages -`/plugins` redirects to `/extensions/packages` (or becomes Packages) while retaining old bookmarks. +Current navigation state: `/customize` is the Extensions hub entry in the primary sidebar; desktop redirects to `/skills`, mobile renders the hub; `/skills` and `/mcp` are live; `/plugins` is still a coming-soon page inside `ExtensionsNavigation`. MVP should replace `/plugins` with Packages or redirect it to the new Packages route while retaining old bookmarks. Packages page sections: Enabled · Installed but disabled · Invalid · Install from npm/package spec · Developer extension path. @@ -850,7 +854,7 @@ For launch templates: show required MCPs; show SDK plugins to be included; show ### Primary navigation UX -Enabled views with `navigation.location: "primarySidebar"` render as top-level Sidebar entries. For MVP, entries appear in the `afterAutomations` slot: after Automations and before the conversation list. Collapsed Sidebar mode shows only the icon with a tooltip. Mobile drawer mode shows the same entry in the same relative position. +Enabled views with `navigation.location: "primarySidebar"` render as top-level Sidebar entries. For MVP, entries appear in the `afterAutomations` slot: after Automations and before the conversation list, which is the current static order in `SidebarRailBody` (`New Chat`, `Customize`, `Automations`, then `SidebarConversationList`). Collapsed Sidebar mode shows only the icon with a tooltip. Mobile drawer mode shows the same entry in the same relative position. ### Extension view UX @@ -868,13 +872,13 @@ Deliverables: shared manifest and registry types; generic installable artifact k **Iframe-runtime forward compatibility (minimal, PR 0 scope):** the manifest schema and TypeScript contract accept both `browser.module` (active) and `browser.entry` (reserved). The validator recognizes `browser.entry` as syntactically valid but emits a `reserved-not-yet-supported` diagnostic so authors can't accidentally ship extensions that depend on an unimplemented runtime. The API contract in `src/extensions/types.ts` is defined async-only, which is the only change needed so that a future iframe runtime can satisfy the same interface over postMessage without an API redesign. No iframe host, no postMessage bridge, no asset-mode switching, and no parallel runtime code lands here — that work is deferred to a post-MVP PR triggered by one of the trust-boundary conditions in §27 Decision 11. The total PR 0 cost of keeping the option open is a handful of schema fields, one validation rule, one fixture, and a short authoring note in the type comments. -**Includes:** removing the empty `src/addons/` directory left over from the earlier prototype to avoid namespace confusion. +**Namespace guard:** keep new types and runtime helpers under `src/extensions/`; do not recreate the older `src/addons/` namespace. ### PR 1 — Runtime registry and CLI Files: `bin/agent-canvas.mjs`, `scripts/dev-safe.mjs`, `scripts/dev-with-automation.mjs`, `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, extension config helper, tests under `__tests__/extensions/`. -Deliverables: generic install/manage CLI parsing; `install/list/enable/disable/remove/update/doctor` dispatched before stack startup, before the static `build/` existence check, and before importing stack launcher scripts; install store under `~/.openhands/agent-canvas/installations/`; artifact detection for all kinds with independent install/enable support limited to Agent Canvas Extension manifests; typed unsupported diagnostics for standalone SDK plugins, skills, and MCP templates; npm install with `--ignore-scripts`; manifest validation; Extension Host startup; ingress routes for `/api/canvas/installations/*` and `/canvas-extensions/*` across all four launch modes (Vite dev proxy, automation ingress, static serving, packaged CLI); dev extension registration store and `--dev` registration; launcher-issued `localInstallStoreReadable` capability flag; example package fixture using SDK plugin and context contributions; `npm pack --dry-run` or packed-tarball smoke for the global CLI path. +Deliverables: generic install/manage CLI parsing; `install/list/enable/disable/remove/update/doctor` dispatched before stack startup, before `--public` / `--frontend-only` / `--backend-only` stack validation, before the static `build/` existence check, and before importing stack launcher scripts; install store under `~/.openhands/agent-canvas/installations/`; artifact detection for all kinds with independent install/enable support limited to Agent Canvas Extension manifests; typed unsupported diagnostics for standalone SDK plugins, skills, and MCP templates; npm install with `--ignore-scripts`; manifest validation; Extension Host startup; ingress routes for `/api/canvas/installations/*` and `/canvas-extensions/*` across all launch modes (Vite dev proxy, automation ingress, static serving, packaged CLI, plus the current frontend-only/backend-only partial-stack modes); dev extension registration store and `--dev` registration; launcher-issued `localInstallStoreReadable` capability flag; example package fixture using SDK plugin and context contributions; `npm pack --dry-run` or packed-tarball smoke for the global CLI path. ### PR 2 — Frontend management UI @@ -933,7 +937,7 @@ These were open questions in the earlier draft. Decisions for the RFC: | 3 | What signals an Agent Server is filesystem-local? | **Launcher-issued `localInstallStoreReadable` capability flag.** `backend.kind === "local"` alone is insufficient. | | 4 | `src/installations/` vs `src/extensions/` directory split? | **Single `src/extensions/` directory** until a real consumer requires the split. | | 5 | API route prefix consistency? | **`/api/canvas/installations/*`** for all management routes; `/canvas-extensions/*` reserved for asset serving. | -| 6 | Empty `src/addons/` directory on `main`? | **Remove in PR 0.** | +| 6 | Empty `src/addons/` directory on `main`? | **No longer present.** PR 0 should avoid reviving the old `addons` namespace and use `src/extensions/`. | | 7 | Should the first MVP independently install standalone SDK plugins, standalone skills, or MCP templates? | **No.** Detect them and return explicit unsupported diagnostics; require an Agent Canvas extension wrapper for MVP. | | 8 | Should package-relative SDK plugin paths be MVP? | **Yes, but only when `localInstallStoreReadable` is true** and the pinned Agent Server smoke passes. Remote/cloud runtimes get disabled reasons rather than local paths. | | 9 | Should extension context be global per extension, selected per launch template, or both? | **Both.** Context blocks use `autoInclude` for global/default behavior and launch templates can require/select specific context IDs. | From c8203eb477edbc3aa9389db8b1a1cf50ffee8974 Mon Sep 17 00:00:00 2001 From: Devin Date: Thu, 11 Jun 2026 14:33:38 -0400 Subject: [PATCH 06/14] Update extension planning for visualizer work --- docs/ExtensionsSystemAgentExecutionPlan.md | 77 ++++++++++- docs/ExtensionsSystemPoCPlan.md | 65 ++++++++-- docs/ExtensionsSystemRFC.md | 143 ++++++++++++++++++--- 3 files changed, 248 insertions(+), 37 deletions(-) diff --git a/docs/ExtensionsSystemAgentExecutionPlan.md b/docs/ExtensionsSystemAgentExecutionPlan.md index ac4ebc72b..7ec77bb8e 100644 --- a/docs/ExtensionsSystemAgentExecutionPlan.md +++ b/docs/ExtensionsSystemAgentExecutionPlan.md @@ -4,7 +4,7 @@ Status: executable handoff plan Source RFC: [ExtensionsSystemRFC.md](./ExtensionsSystemRFC.md) Source PoC plan: [ExtensionsSystemPoCPlan.md](./ExtensionsSystemPoCPlan.md) Target: a working proof of concept that can be built by multiple agents without one agent holding all context -Current repo baseline: merged through `upstream/main` at `929e5afc` / `origin/main` at `62f5eae7`. This checklist assumes `agentServer` `1.24.0`, the current local/public auth modes, frontend-only/backend-only partial stacks, MCP catalog data from `@openhands/extensions/integrations`, and public skills defaulting on unless `VITE_LOAD_PUBLIC_SKILLS=false`. +Current repo baseline: merged through `upstream/main` at `40844533`. This checklist assumes `agentServer` `1.27.0`, the current local/public auth modes, frontend-only/backend-only partial stacks, MCP catalog data from `@openhands/extensions/integrations`, the merged PR #1246 tool visualizer registry, and public skills defaulting on unless `VITE_LOAD_PUBLIC_SKILLS=false`. ## 1. How To Use This Plan @@ -41,7 +41,7 @@ Use gates to coordinate merge order. Tasks inside a gate can be split, but the g | G1 | Manager, CLI, install store, host routes | Manager/CLI and launcher work can split after shared contracts land | UI, view host, conversation merge | | G2 | Example extension and package release path | Can run alongside G1 after contracts land | View host, acceptance demo | | G3 | Frontend Packages page and ExtensionService | Can run once Extension Host registry shape is stable | View host UX, dev mode UX | -| G4 | Browser-module view host and sidebar entries | Can run after registry/assets exist | Acceptance demo | +| G4 | Browser-module view host, sidebar entries, visualizers, and slots | Can run after registry/assets exist; visualizers can split after shared types land | Acceptance demo | | G5 | Conversation contribution merge | Can run after launch-contributions endpoint exists | Final proof | | G6 | Dev mode watch/remount | Can run after manager, host, and view host exist | Final proof | | G7 | Acceptance, release smoke, polish | Serial final pass | PoC complete | @@ -66,7 +66,7 @@ Avoid these overlapping edits unless one agent owns the integration: - [ ] Implement: - Create the smallest SDK plugin fixture with a visible skill/context marker. - Start a local conversation with top-level `plugins: [{ source: absolutePluginPath }]`. - - Confirm pinned Agent Server `1.24.0` accepts `PluginSource` and loads from a local path. + - Confirm pinned Agent Server `1.27.0` accepts `PluginSource` and loads from a local path. - Run the same preflight against an ACP configuration or document that ACP remains context-only. - [ ] Tests: - Add a repeatable smoke or unit fixture where possible. @@ -110,10 +110,11 @@ Avoid these overlapping edits unless one agent owns the integration: - keep new public types under `src/extensions/`; do not recreate the old `src/addons/` namespace - [ ] Implement: - Manifest v1 types, contribution types, registry entry types, diagnostics, installable artifact kinds. + - Contribution types for `toolVisualizers` and `conversationSlots`. - Validation for required fields, ID regex, duplicate/mutually exclusive `browser.module` and `browser.entry`, path containment, reserved `browser.entry`. - Artifact detection order: Agent Canvas extension, SDK plugin, skill, MCP placeholder. - Storage helpers for `~/.openhands/agent-canvas/installations`. - - Public package export `@openhands/agent-canvas/extensions` and emitted `dist/extensions/*`. + - Public package exports `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers` with emitted `dist/extensions/*` and `dist/visualizers/*` outputs. - [ ] Tests: - Valid extension manifest. - Missing required fields. @@ -131,7 +132,7 @@ npm run build:lib ``` - [ ] Done when: - - Type consumers can import from `@openhands/agent-canvas/extensions`. + - Type consumers can import from `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers`. - No runtime host, CLI store, or UI behavior is introduced in this gate. - [ ] Handoff notes: @@ -327,7 +328,7 @@ npm pack --dry-run ``` - [ ] Done when: - - Packed package contains `bin`, `scripts`, `build`, `dist/extensions`, and any advertised schema files. + - Packed package contains `bin`, `scripts`, `build`, `dist/extensions`, `dist/visualizers`, and any advertised schema files. - `agent-canvas list extensions` and `agent-canvas doctor` work from packed/global install. - [ ] Handoff notes: @@ -459,6 +460,70 @@ npm run test:e2e:snapshots -- --grep "sidebar" - `hello.canvas` sidebar item appears immediately after Automations. - [ ] Handoff notes: +### G4.3 Extension Tool Visualizers + +- [ ] Owner: +- [ ] Depends on: G0.3, G1.3, G3.1 +- [ ] Files likely touched: + - `src/components/features/chat/tool-visualizers/*` + - `src/extensions/visualizers/*` + - `src/api/extensions-service.ts` + - `package.json` + - `tsconfig.lib.json` + - visualizer tests under `__tests__/components/features/chat/tool-visualizers/` + - extension tests under `__tests__/extensions/` +- [ ] Implement: + - Public `@openhands/agent-canvas/visualizers` subpath with stable authoring types and primitives. + - Extension visualizer registry layer that composes before built-in visualizers and markdown fallback. + - `priority` plus `matches()` support. + - Manifest projection for `contributes.toolVisualizers`. + - Error boundary/fallback behavior: thrown extension renderer records a diagnostic and falls through to the next renderer/default. + - Fixture extension visualizer for one MCP/tool variant. +- [ ] Tests: + - Extension visualizer wins over built-in when matching. + - Higher priority wins among extension visualizers. + - `matches()` narrows one action/observation variant without replacing the whole kind. + - Built-in visualizer still handles unmatched events. + - Markdown fallback still handles unregistered events. + - Throwing extension renderer falls through. +- [ ] Verification: + +```sh +npm test -- __tests__/components/features/chat/tool-visualizers +npm run build:lib +npm run typecheck +``` + +- [ ] Done when: + - A local extension can change how one event/tool renders without forking Agent Canvas. +- [ ] Handoff notes: + +### G4.4 Conversation Slots And Badges + +- [ ] Owner: +- [ ] Depends on: G4.3 +- [ ] Files likely touched: + - conversation header/sidebar/footer/badge components + - `src/extensions/slots/*` + - `src/i18n/translation.json` + - tests under `__tests__/extensions/` + - focused component tests near each slot host +- [ ] Implement: + - Slot registry for `conversation.header.actions`, `conversation.sidebar`, `conversation.footer`, and `conversation.badges`. + - Optional `conversation.event.visualizers` manifest alias that maps to tool visualizer registration. + - Stable slot props: conversation ID/status, active backend summary, workspace summary, extension settings helpers, navigation/toast helpers. + - Deterministic ordering by priority, extension display name, and slot contribution ID. + - Per-slot error boundaries. + - No raw store, React Query client, or Canvas-internal component API in the public contract. +- [ ] Tests: + - Slots render in the intended hosts. + - Ordering is deterministic. + - Disabled/invalid extensions do not render slot content. + - Slot error is localized and does not break the conversation page. +- [ ] Done when: + - A local extension can add a backend/workspace/conversation badge or header action without a route. +- [ ] Handoff notes: + ## 8. Gate G5: Conversation Contributions ### G5.1 Launch Contributions And Runtime Compatibility diff --git a/docs/ExtensionsSystemPoCPlan.md b/docs/ExtensionsSystemPoCPlan.md index d84a3c669..13f841bf8 100644 --- a/docs/ExtensionsSystemPoCPlan.md +++ b/docs/ExtensionsSystemPoCPlan.md @@ -4,7 +4,7 @@ Status: working build plan Source RFC: [ExtensionsSystemRFC.md](./ExtensionsSystemRFC.md) Agent execution checklist: [ExtensionsSystemAgentExecutionPlan.md](./ExtensionsSystemAgentExecutionPlan.md) Target branch: `dv/extensions-poc-v1` -Current repo baseline: merged through `upstream/main` at `929e5afc` / `origin/main` at `62f5eae7`. The plan below has been refreshed for the current launcher/auth split, partial-stack modes, MCP integrations catalog, `agentServer` pin `1.24.0`, and public skills defaulting on. +Current repo baseline: merged through `upstream/main` at `40844533`. The plan below has been refreshed for the current launcher/auth split, partial-stack modes, MCP integrations catalog, `agentServer` pin `1.27.0`, the merged PR #1246 tool visualizer registry, and public skills defaulting on. ## 1. Purpose @@ -18,8 +18,9 @@ The MVP should be built as a vertical proof, not as isolated architecture layers 2. The Packages page shows the installed extension and its diagnostics. 3. A trusted `browser.module` view renders at `/extensions/:extensionId/:viewId/*`. 4. A view can contribute a primary Sidebar entry after Automations and before the conversation list, matching the current `SidebarRailBody` order (`New Chat`, `Customize`, `Automations`, then conversations). -5. A launch template can append extension context and include an SDK plugin when the runtime is filesystem-local. -6. Dev mode can register a local source folder, detect output changes, and remount the view without rebuilding Canvas. +5. A local extension can register a custom tool visualizer for one MCP/action variant and safely fall back to built-in rendering when it does not match or throws. +6. A launch template can append extension context and include an SDK plugin when the runtime is filesystem-local. +7. Dev mode can register a local source folder, detect output changes, and remount the view without rebuilding Canvas. ## 1.1 Scope Locks From The RFC @@ -28,9 +29,19 @@ These decisions should keep the build from spreading sideways: - First-class install/enable support is for Agent Canvas extension manifests only. Standalone SDK plugins, `SKILL.md` folders, and future MCP-template artifacts can be detected, but they return an explicit unsupported-in-MVP diagnostic unless wrapped by an extension. - CLI management commands must dispatch before stack startup, before the static `build/` existence check, and before importing `scripts/dev-with-automation.mjs`. - CLI management commands must also dispatch before `--public`, `--frontend-only`, and `--backend-only` stack validation, because `install/list/doctor` should work even when no frontend/backend is being launched. -- Extension author types need a real package subpath: `@openhands/agent-canvas/extensions`, emitted under `dist/extensions/*` and included in npm release checks. +- Extension author types need real package subpaths: `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers`, emitted under `dist/extensions/*` and `dist/visualizers/*` and included in npm release checks. - Conversation contribution work targets the current start payload: top-level `agent_settings` plus optional top-level `plugins`. Do not reintroduce the legacy `agent` payload shape. - Package-relative/local SDK plugin sources are MVP only when the launcher says `localInstallStoreReadable=true`. ACP runtimes skip extension SDK plugins unless a pinned-version smoke proves the exact plugin shape is compatible. +- The first route-less JavaScript workstream is custom tool visualizers, not arbitrary root-mounted components. Conversation slots/badges follow after visualizers prove the registration/fallback model. + +## 1.2 Focused Workstream From Issue #481 + +The issue feedback points to two concrete places where users want to add content without forking Agent Canvas: + +1. **Custom tool/event visualizers.** PR #1246 added an internal visualizer dispatcher that already asks a registry for a React body and falls back to markdown. Extensions should expose that seam first through `registerToolVisualizer()` or a manifest-projected equivalent. +2. **Conversation UI slots and badges.** Users want content near the active conversation: header actions, sidebar widgets, footer/status indicators, event renderer hooks, and badges for backend/workspace/conversation state. + +The implementation order should be visualizers first, then slots. Visualizers are narrower, already have a merged internal seam, and have a clear failure mode: try extension renderer, then built-in renderer, then markdown fallback. Slots are still explicit product surface area and should not become a generic "mount anything at app root" API. ## 2. Risk-Burner Spikes @@ -44,7 +55,7 @@ Add a tiny fixture SDK plugin under: examples/extensions/hello-canvas/agent/hello-plugin ``` -Start a local conversation with its resolved absolute path in top-level `plugins`. Confirm the current pinned Agent Server (`config/defaults.json` pins `agentServer` to `1.24.0`) accepts the SDK `PluginSource` shape (`source`, optional `ref`, optional `repo_path`) and can load a package-relative path when `localInstallStoreReadable` is true. The plugin root should contain `.plugin/plugin.json`; if a fixture uses a local path, resolve `source` directly to the plugin root and omit `repo_path`. +Start a local conversation with its resolved absolute path in top-level `plugins`. Confirm the current pinned Agent Server (`config/defaults.json` pins `agentServer` to `1.27.0`) accepts the SDK `PluginSource` shape (`source`, optional `ref`, optional `repo_path`) and can load a package-relative path when `localInstallStoreReadable` is true. The plugin root should contain `.plugin/plugin.json`; if a fixture uses a local path, resolve `source` directly to the plugin root and omit `repo_path`. Also run the same contribution preflight with an ACP agent configuration. If ACP fails or the plugin injects MCP/hooks/tool/agent-definition state, keep ACP on context-only extension support and show disabled reasons for SDK plugin contributions. If the local-path smoke fails for the pinned server, keep context contributions in MVP and move SDK plugin merge behind a feature flag until the SDK team confirms the loading contract. @@ -54,7 +65,7 @@ Serve a tiny browser-ready ESM file from a local test host and dynamically impor The extension module must use only bundled or relative imports. This proves the DOM-island `mount()` contract works without Vite, shared React, import maps, or module federation. -Add a negative fixture with a bare runtime import such as `react` and verify validation rejects it with an actionable diagnostic. Type-only imports from `@openhands/agent-canvas/extensions` are fine because the extension build erases them; runtime imports from that package are not MVP-supported. +Add a negative fixture with a bare runtime import such as `react` and verify validation rejects it with an actionable diagnostic. Type-only imports from `@openhands/agent-canvas/extensions` are fine because the extension build erases them. Author source may import from `@openhands/agent-canvas/visualizers`, but emitted browser modules must bundle those helpers; raw runtime imports from Agent Canvas package subpaths are not MVP-supported. ### 2.3 Launcher Route Smoke @@ -82,7 +93,7 @@ node bin/agent-canvas.mjs list extensions node bin/agent-canvas.mjs doctor ``` -Then install from a packed tarball into a temp npm prefix and verify `agent-canvas list` and `agent-canvas doctor` work without a source checkout. This catches missing `scripts`, `build`, `dist/extensions`, schema files, and command-dispatch regressions before the feature depends on them. +Then install from a packed tarball into a temp npm prefix and verify `agent-canvas list` and `agent-canvas doctor` work without a source checkout. This catches missing `scripts`, `build`, `dist/extensions`, `dist/visualizers`, schema files, and command-dispatch regressions before the feature depends on them. ## 3. Walking Skeleton @@ -98,7 +109,7 @@ Add: - storage-path helpers - path normalization - manifest validation -- public package export wiring for `@openhands/agent-canvas/extensions` +- public package export wiring for `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers` Keep validation deliberately boring: @@ -110,7 +121,7 @@ Keep validation deliberately boring: Avoid a large JSON Schema dependency unless configuration forms force it. -PR 0 should also update `package.json#exports` for `./extensions` and make sure the library build emits `dist/extensions/*`. Keep the actual Extension Host and CLI behavior out of PR 0. +PR 0 should also update `package.json#exports` for `./extensions` and `./visualizers` and make sure the library build emits `dist/extensions/*` and `dist/visualizers/*`. Keep the actual Extension Host and CLI behavior out of PR 0. ### 3.2 Manager Library @@ -240,6 +251,35 @@ MVP scope: This is the path that proves the original addons-style PoC works. Example: `hello.canvas` can include a Cost Dashboard view that appears immediately after Automations. +### 4.5 Tool Visualizer Extension Surface + +Use the merged built-in visualizer implementation as the first route-less JS proof. + +MVP scope: + +- Add public authoring types under `@openhands/agent-canvas/visualizers`. +- Project enabled extension visualizers into a registry that is consulted before built-in visualizers. +- Support `priority` and `matches()` so an extension can override one specific MCP tool or event variant without replacing an entire action/observation kind. +- Preserve the existing fallback chain: extension visualizer, built-in visualizer, markdown fallback. +- Wrap extension visualizer bodies in error boundaries; a thrown renderer should record a diagnostic and fall through to the next renderer/default. +- Keep visualizer modules in the trusted local browser-code model. No marketplace or sandbox claim yet. + +Example fixture: `hello.canvas` registers a visualizer for one synthetic or MCP-style tool call and renders a compact card. Tests should prove extension-first order, `matches()` narrowing, built-in fallback, markdown fallback, and throw-to-next-renderer behavior. + +### 4.6 Conversation Slots And Badges + +After visualizers, add explicit conversation insertion points rather than a generic app-root component API. + +Initial slot list: + +- `conversation.header.actions` +- `conversation.sidebar` +- `conversation.footer` +- `conversation.badges` +- `conversation.event.visualizers` as alias/sugar for tool visualizer registration + +Slot props should be narrow and stable: active conversation ID/status, active backend summary, workspace summary, and host APIs for navigation, toasts, and extension settings. Do not expose raw stores, React Query clients, or Canvas component internals as the stable extension contract. + ## 5. Conversation Contributions Add contribution merge late enough that the install/store/view path already works. @@ -322,13 +362,13 @@ Release-path acceptance should also install the packed `@openhands/agent-canvas` ## 8. Testing Strategy -**Unit:** manifest validation; reserved `browser.entry` diagnostics; path traversal rejection; artifact detection; unsupported standalone artifact diagnostics; install-store bootstrap; enable/disable/remove/update state transitions; duplicate ID handling; asset route path validation; CLI arg parsing before build checks; no-install-scripts default; launch contribution projection; context suffix rendering; plugin merge/dedupe; runtime compatibility classification; dev registration and manifest revalidation. +**Unit:** manifest validation; reserved `browser.entry` diagnostics; path traversal rejection; artifact detection; unsupported standalone artifact diagnostics; install-store bootstrap; enable/disable/remove/update state transitions; duplicate ID handling; asset route path validation; CLI arg parsing before build checks; no-install-scripts default; visualizer contribution projection; visualizer ordering/fallback/error-boundary behavior; slot registration ordering; launch contribution projection; context suffix rendering; plugin merge/dedupe; runtime compatibility classification; dev registration and manifest revalidation. **Node integration:** run the Extension Host against a temp install store; install the `hello.canvas` fixture from local path and npm-packed tarball; fetch registry and asset URLs; verify route precedence before `/api/*` in Vite, ingress, static server, and packaged CLI paths; verify mutating routes require the session API key; verify `doctor` reports invalid manifests, unsupported standalone artifacts, and missing assets. -**Release packaging:** `npm pack --dry-run`; packed-tarball install into a temp npm prefix; verify `agent-canvas list`, `agent-canvas doctor`, and an extension install work without a source checkout; verify `@openhands/agent-canvas/extensions` resolves for type consumers. +**Release packaging:** `npm pack --dry-run`; packed-tarball install into a temp npm prefix; verify `agent-canvas list`, `agent-canvas doctor`, and an extension install work without a source checkout; verify `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers` resolve for type consumers. -**Component:** Packages page empty/installed/enabled/invalid/dev states; install/enable/disable/remove action wiring; primary Sidebar extension entries in expanded/collapsed/mobile states; extension view loading/error/remount; extension settings read/patch; launch template preflight for incompatible SDK plugin paths. +**Component:** Packages page empty/installed/enabled/invalid/dev states; install/enable/disable/remove action wiring; primary Sidebar extension entries in expanded/collapsed/mobile states; extension view loading/error/remount; extension settings read/patch; extension visualizer renders/falls back inside the existing event wrapper; conversation badge/header/footer slots render in order; launch template preflight for incompatible SDK plugin paths. **E2E snapshots:** Packages page with enabled extension; invalid extension diagnostics; primary Sidebar entry after Automations; extension view rendered from browser module; launch template showing context/plugin contribution; remote-backend disabled reason for local plugin path; dev extension view remount after output change. @@ -352,3 +392,4 @@ npm run typecheck && npm test && npm run build - Per-run exact extension enablement. - Shared React/design-system runtime imports for extensions. - Remote/cloud delivery of local extension package contents. +- Arbitrary root-mounted route-less components. diff --git a/docs/ExtensionsSystemRFC.md b/docs/ExtensionsSystemRFC.md index 281db63b8..3f1334e0c 100644 --- a/docs/ExtensionsSystemRFC.md +++ b/docs/ExtensionsSystemRFC.md @@ -3,16 +3,16 @@ Status: RFC, ready for review Target: `OpenHands/agent-canvas` -Current repo baseline: merged through `upstream/main` at `929e5afc` / `origin/main` at `62f5eae7` while updating this draft. The current package version is `1.0.0-alpha.10`, `config/defaults.json` pins `agentServer` to `1.24.0`, and `@openhands/typescript-client` is `1.24.3`. +Current repo baseline: merged through `upstream/main` at `40844533`. The current package version is `1.0.0-rc.6`, `config/defaults.json` pins `agentServer` to `1.27.0`, `@openhands/extensions` is `0.4.0`, and `@openhands/typescript-client` is `1.24.3`. ## 1. Summary Agent Canvas should ship a first-class **Extensions** system: user-installable npm packages that extend the local Canvas experience and optionally contribute components to the agent runtime through SDK-supported surfaces. -An Extension can contribute UI views, primary navigation entries, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces — Canvas does not patch or load code inside the Agent Server. +An Extension can contribute UI views, primary navigation entries, custom tool/event visualizers, conversation UI slots, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces; Canvas does not patch or load code inside the Agent Server. -The MVP delivers CLI install for Agent Canvas extension packages, a Packages management page, trusted same-origin extension views, dev-mode authoring with live reload, and SDK plugin / context merging on conversation launch. The CLI can detect adjacent artifact types such as standalone SDK plugins or `SKILL.md` folders, but the first executable slice should install/enable only packages with an Agent Canvas manifest. Marketplace, signing, sandboxed iframe views, parent-React component extensions, standalone skill/plugin management, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. +The MVP delivers CLI install for Agent Canvas extension packages, a Packages management page, trusted same-origin extension views, dev-mode authoring with live reload, and a narrow first route-less JavaScript surface: extension-provided tool visualizers that compose with the built-in visualizer registry introduced by PR #1246. Conversation slots and badges are the next focused surface after visualizers; broad root-mounted components remain deferred. The CLI can detect adjacent artifact types such as standalone SDK plugins or `SKILL.md` folders, but the first executable slice should install/enable only packages with an Agent Canvas manifest. Marketplace, signing, sandboxed iframe views, arbitrary app-root component/shared dependency runtimes, standalone skill/plugin management, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. ## 2. Motivation @@ -31,6 +31,8 @@ The system must preserve the existing security and operational boundaries betwee - **SDK Plugin** — the OpenHands SDK plugin format (`.plugin/plugin.json`), containing skills, hooks, MCP config, agents, and slash commands. Extensions may bundle or reference SDK plugins. - **MCP Server** — external tool server config installed into agent settings. - **Skill** — an OpenHands skill loaded by the SDK; usually delivered via an SDK plugin rather than copied into user skill directories. +- **Tool Visualizer** — a React renderer for one action/observation kind or one more specific tool variant, selected before the built-in markdown fallback. +- **Conversation Slot** — a named insertion point in the conversation UI such as header actions, sidebar widgets, footer indicators, badges, or event renderer hooks. User-facing UI should use the word "Extension." Avoid "plugin" in the Canvas product surface because the SDK already owns that term. @@ -52,6 +54,7 @@ User-facing UI should use the word "Extension." Avoid "plugin" in the Canvas pro - No extension may patch or monkeypatch the Agent Server. - No extension may register server-side tools except through SDK-supported plugin/MCP mechanisms. - No stable extension API may expose Canvas internals, parent React components, or DOM mutation hooks. MVP browser modules are trusted same-origin code, so DOM access is a trust concern rather than an enforceable sandbox boundary. +- No broad root-mounted route-less JavaScript surface lands before narrower slot APIs prove the need. Tool visualizers are the first route-less JS surface because the registry seam already exists and the fallback behavior is well defined. - No repository-local files may modify the running Canvas browser experience simply because a conversation is attached to that repository. - No npm install scripts run by default during extension installation. - No standalone SDK plugin, standalone `SKILL.md`, or MCP template artifact becomes independently active in the first MVP slice; those artifacts must be wrapped by an Agent Canvas extension until their product UX is designed. @@ -244,10 +247,10 @@ The manifest path must resolve inside the package root. Path traversal is reject The host package must publish everything the global CLI and extension authors need: - `bin/agent-canvas.mjs`, launcher scripts, static `build/`, and any Extension Host scripts must be included by `package.json#files`. -- Extension author types must be exported as `@openhands/agent-canvas/extensions`. The release build must emit those files under `dist/extensions/*` and add a matching `./extensions` subpath export. +- Extension author types must be exported as `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers`. The release build must emit those files under `dist/extensions/*` and `dist/visualizers/*` and add matching subpath exports. - If JSON Schemas are advertised by URL or package path, the schemas must either be served remotely or included in the npm package. Do not point authors at files that are excluded by `package.json#files`. - Example extensions under `examples/` are source-repo fixtures unless `package.json#files` explicitly includes them. The acceptance path for a globally installed package should use either a packed fixture tarball or a separately published example package, not an unpublished repo-local path. -- Every release candidate should run `npm pack --dry-run` or an equivalent packed-tarball smoke so missing `build`, `scripts`, `dist/extensions`, or schema files are caught before publish. +- Every release candidate should run `npm pack --dry-run` or an equivalent packed-tarball smoke so missing `build`, `scripts`, `dist/extensions`, `dist/visualizers`, or schema files are caught before publish. ## 11. Manifest Schema @@ -265,12 +268,12 @@ The host package must publish everything the global CLI and extension authors ne "license": "MIT", "repository": "https://github.com/acme/agent-canvas-github", "compatibility": { - "agentCanvas": ">=1.0.0-alpha.10 <2.0.0", + "agentCanvas": ">=1.0.0-rc.6 <2.0.0", "extensionApi": 1 }, "activationEvents": ["onStartup"], "permissions": { - "ui": ["views"], + "ui": ["views", "toolVisualizers"], "agent": ["sdkPlugins", "context"], "mcp": ["templates"], "network": ["https://api.github.com"] @@ -292,6 +295,15 @@ The host package must publish everything the global CLI and extension authors ne } } ], + "toolVisualizers": [ + { + "id": "github-pr-check", + "module": "./dist/github-pr-check-visualizer.js", + "actionKinds": ["MCPToolAction"], + "observationKinds": ["MCPToolObservation"], + "priority": 25 + } + ], "agentPlugins": [ { "id": "github-pr-review", "source": "./agent/github-pr-review", "autoInclude": "manual" } ], @@ -400,8 +412,8 @@ Inline extension code runs in the same browser origin as Canvas. That is intenti - `browser.module` must point to a built ESM file that a browser can import directly. - The Extension Host serves static files only; it does not transpile TypeScript/JSX or run bundlers for installed packages. -- Runtime imports must be relative URLs inside the extension asset tree or bundled into the module. Bare runtime imports such as `react`, `@heroui/react`, or `@openhands/agent-canvas/extensions` are rejected for MVP because the packaged CLI cannot guarantee a Vite resolver or shared dependency graph. -- Type-only imports from `@openhands/agent-canvas/extensions` are allowed in author source and erased by the extension's build. +- Runtime imports must be relative URLs inside the extension asset tree or bundled into the module. Bare runtime imports such as `react`, `@heroui/react`, `@openhands/agent-canvas/extensions`, or `@openhands/agent-canvas/visualizers` are rejected for MVP because the packaged CLI cannot guarantee a Vite resolver or shared dependency graph. +- Type-only imports from `@openhands/agent-canvas/extensions` are allowed in author source and erased by the extension's build. Author source may import `@openhands/agent-canvas/visualizers`, but emitted browser modules must bundle those helpers rather than leaving a bare package import. - The default export shape is: ```ts @@ -464,7 +476,73 @@ The user enabled the following Agent Canvas extensions for this conversation. Extension context **must not** be merged into `conversationInstructions`; that turns extension guidance into user-message content. An explicit `extensionSystemSuffix` option will be added to the conversation adapter so runtime services and extension context compose in one place. -### 12.6 `configuration` and `secrets` +### 12.6 `toolVisualizers` + +Custom event rendering is the first concrete route-less JavaScript extension surface. PR #1246 added the internal `src/components/features/chat/tool-visualizers` registry and dispatcher: `getEventContent()` asks the registry for a React body by action/observation kind, and falls back to the existing markdown renderer when no visualizer matches. Extensions should build on that seam instead of introducing an unrelated event-rendering model. + +Manifest contribution shape: + +```json +{ + "contributes": { + "toolVisualizers": [ + { + "id": "acme.kubernetes.apply", + "module": "./dist/kubernetes-apply-visualizer.js", + "actionKinds": ["MCPToolAction"], + "observationKinds": ["MCPToolObservation"], + "priority": 50 + } + ] + } +} +``` + +Runtime registration shape: + +```ts +export default function register(api: AgentCanvasExtensionApi) { + api.registerToolVisualizer({ + id: "acme.kubernetes.apply", + actionKinds: ["MCPToolAction"], + observationKinds: ["MCPToolObservation"], + priority: 50, + matches({ action }) { + return ( + action?.action.kind === "MCPToolAction" && + action.action.tool_name === "kubectl_apply" + ); + }, + Body({ action, observation }) { + return ; + }, + }); +} +``` + +Selection rules: + +- Extension visualizers are considered before built-in visualizers. +- Higher `priority` wins within extension visualizers; ties sort by extension ID then visualizer ID. +- `matches()` narrows within a kind so one extension can override a specific MCP tool without replacing all `MCPToolAction` rendering. +- If an extension visualizer throws, Canvas catches the error, reports a local diagnostic, and tries the next matching visualizer before falling back to the built-in renderer or markdown fallback. +- Visualizer modules use the same trusted local browser-code model as `browser.module`; no sandbox claim is made in MVP. + +Public authoring types and primitives should be exported from `@openhands/agent-canvas/visualizers`, separate from the broader `@openhands/agent-canvas/extensions` management contract. The public surface should wrap or re-export stable versions of `defineVisualizer`, `VisualizerProps`, shared primitives such as code blocks/diff/output panes, and action/observation kind types. Do not expose internal file paths under `src/components/features/chat/tool-visualizers/*` as the authoring API. + +### 12.7 `conversationSlots` + +Conversation slots are the next focused extension surface after tool visualizers. They answer the UI-slot feedback in issue #481 without opening arbitrary app-root mutation. Initial slot names should be explicit and few: + +- `conversation.header.actions` — icon/button actions near the active conversation header. +- `conversation.sidebar` — compact widgets in the conversation side area when available. +- `conversation.footer` — status indicators near the chat input/footer region. +- `conversation.badges` — small status chips for backend, workspace, or extension-provided state. +- `conversation.event.visualizers` — alias or manifest sugar for tool visualizer contributions. + +Slot renderers receive a narrow context object: active conversation ID, execution status, active backend summary, workspace summary, and a host API for navigation/toasts/settings. They should not receive raw React Query clients or arbitrary store access as the stable contract. That restriction keeps the API supportable even while the MVP runtime is trusted same-origin code. + +### 12.8 `configuration` and `secrets` `configuration` uses JSON Schema; values are stored in the Extension Host's `config.json`. `secrets` are declarations only; values are stored through the existing `SecretsService`. @@ -507,6 +585,16 @@ interface AgentCanvasExtensionContext { ui: { toast(message: string, options?: { tone?: "success" | "error" }): Promise; }; + visualizers: { + registerToolVisualizer( + visualizer: AgentCanvasToolVisualizer, + ): Promise; + }; + conversationSlots: { + registerSlot( + slot: AgentCanvasConversationSlot, + ): Promise; + }; conversations: { list(): Promise; current(): Promise; @@ -521,7 +609,7 @@ interface AgentCanvasExtensionContext { } ``` -There is **no** supported generic `agentServer.request(path)`. The repo already routes Agent Server traffic through typed service wrappers; the extension surface follows the same pattern with named, permission-declared capabilities. For MVP inline browser modules, this is an API support boundary rather than a browser security boundary (see §19). +For the first implementation slice, `visualizers.registerToolVisualizer()` is the only required route-less registration API. `conversationSlots.registerSlot()` can remain typed/reserved until the slot insertion points are implemented. There is **no** supported generic `agentServer.request(path)`. The repo already routes Agent Server traffic through typed service wrappers; the extension surface follows the same pattern with named, permission-declared capabilities. For MVP inline browser modules, this is an API support boundary rather than a browser security boundary (see §19). ## 14. Shared Type Contract @@ -581,13 +669,15 @@ export interface InstallableArtifactRegistryEntry { } ``` -`registerTool`, parent-React component registration, and Canvas-internal imports are intentionally absent from the MVP public contract. +`registerTool`, arbitrary app-root components, raw store/query-client access, and Canvas-internal imports are intentionally absent from the MVP public contract. ## 15. Permissions Model Permission groups: - `ui.views` — render trusted same-origin browser-module views and their declared navigation entries. +- `ui.toolVisualizers` — register custom action/observation renderers in the conversation event pipeline. +- `ui.conversationSlots` — register named conversation UI slot content when slot support lands. - `ui.commands` — register commands. - `agent.sdkPlugins` — include SDK plugins in conversation launches. - `agent.context` — append extension context to system suffix. @@ -820,7 +910,7 @@ This is not part of PR 0 or PR 1. It becomes feasible after the local install st MVP requires **zero broad Agent Server changes**. Current `software-agent-sdk` exposes the needed request shape: `StartConversationRequest.plugins` is `list[PluginSource] | None`, and `PluginSource` carries `source`, optional `ref`, and optional `repo_path`. The SDK loads plugins lazily when a local conversation first runs, then merges plugin skills, MCP config, hooks, and plugin agents into the agent. This is enough for Canvas to forward descriptors; it is also why ACP and filesystem-local preflight matter. -Three things to confirm against the current pinned Agent Server version (`config/defaults.json` currently pins `agentServer` to `1.24.0`) before PR 4: +Three things to confirm against the current pinned Agent Server version (`config/defaults.json` currently pins `agentServer` to `1.27.0`) before PR 4: 1. Agent Server create-conversation accepts `plugins` in the shape of SDK `PluginSource`. 2. `AgentContext.system_message_suffix` remains accepted for both `Agent` and `ACPAgent`. @@ -868,7 +958,7 @@ The multi-agent, checkable execution checklist for this plan lives in [Extension Files: `docs/ExtensionsSystemRFC.md`, `src/extensions/types.ts`, `src/extensions/manifest-schema.ts`, `src/extensions/manifest-validation.ts`, `src/extensions/artifact-detection.ts`, `__tests__/extensions/manifest-validation.test.ts`. -Deliverables: shared manifest and registry types; generic installable artifact kind definitions; artifact detection rules for extension packages, SDK plugins, skills, and placeholder MCP templates; schema-version constant; manifest validation helpers; storage-path helper for `~/.openhands/agent-canvas/installations`; fixtures for valid/invalid manifests; `@openhands/agent-canvas/extensions` subpath export plan wired to generated `dist/extensions/*`; release packaging checklist. No Extension Host, no frontend UI, no agent contribution merging. +Deliverables: shared manifest and registry types; generic installable artifact kind definitions; contribution types for views, visualizers, slots, launch templates, SDK plugins, MCP templates, and context; artifact detection rules for extension packages, SDK plugins, skills, and placeholder MCP templates; schema-version constant; manifest validation helpers; storage-path helper for `~/.openhands/agent-canvas/installations`; fixtures for valid/invalid manifests; `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers` subpath export plan wired to generated `dist/extensions/*` and `dist/visualizers/*`; release packaging checklist. No Extension Host, no frontend UI, no agent contribution merging. **Iframe-runtime forward compatibility (minimal, PR 0 scope):** the manifest schema and TypeScript contract accept both `browser.module` (active) and `browser.entry` (reserved). The validator recognizes `browser.entry` as syntactically valid but emits a `reserved-not-yet-supported` diagnostic so authors can't accidentally ship extensions that depend on an unimplemented runtime. The API contract in `src/extensions/types.ts` is defined async-only, which is the only change needed so that a future iframe runtime can satisfy the same interface over postMessage without an API redesign. No iframe host, no postMessage bridge, no asset-mode switching, and no parallel runtime code lands here — that work is deferred to a post-MVP PR triggered by one of the trust-boundary conditions in §27 Decision 11. The total PR 0 cost of keeping the option open is a handful of schema fields, one validation rule, one fixture, and a short authoring note in the type comments. @@ -892,7 +982,19 @@ Files: `src/routes/extension-view-host.tsx`, `src/extensions/runtime/*`, sidebar Deliverables: route `/extensions/:extensionId/:viewId/*`; DOM-container host for trusted `browser.module` extensions; dynamic import with cache-busting support; browser asset serving; `mount()`/`dispose()` lifecycle; primary-sidebar navigation entries for views; minimal `navigation` and `ui.toast` APIs; example extension package with a bundled browser-ready module. -### PR 3b — Dev extension watch mode +### PR 3a — Extension tool visualizers + +Files: `src/components/features/chat/tool-visualizers/*`, `src/extensions/visualizers/*`, `src/api/extensions-service.ts`, `src/routes/extension-view-host.tsx` or equivalent runtime loader, `package.json`, `tsconfig.lib.json`, tests under `__tests__/extensions/` and existing visualizer tests. + +Deliverables: public `@openhands/agent-canvas/visualizers` subpath; extension visualizer registry layer that composes before built-ins and then markdown fallback; `priority` and `matches()` support; error boundary/fallback behavior for extension renderers; manifest projection for `contributes.toolVisualizers`; `hello.canvas` or a focused fixture overriding one MCP tool visualizer; unit tests proving extension-first order, built-in fallback, markdown fallback, and throw-to-next-renderer behavior. + +### PR 3b — Conversation slots and badges + +Files: conversation header/sidebar/footer/badge components, `src/extensions/slots/*`, `src/i18n/translation.json`, tests under `__tests__/extensions/` and focused component tests. + +Deliverables: narrow slot registry for `conversation.header.actions`, `conversation.sidebar`, `conversation.footer`, and `conversation.badges`; stable slot props with conversation/backend/workspace summaries; no raw store or React Query client in the public API; per-slot error boundaries; deterministic ordering by priority and extension ID; visible examples for a backend badge or workspace status chip. + +### PR 3c — Dev extension watch mode Files: `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, `bin/agent-canvas.mjs`, `src/api/extensions-service.ts`, `src/routes/extensions-packages.tsx`, contributor docs. @@ -945,6 +1047,8 @@ These were open questions in the earlier draft. Decisions for the RFC: | 11 | What triggers the reserved iframe runtime? | **A trust boundary requirement:** community marketplace distribution, an untrusted third-party publisher tier, or a concrete need for browser-enforced isolation/CSP. | | 12 | Should agent-proposed dev extension registration ship with dev watch mode? | **No.** Defer to the broader post-MVP agent-mediated install proposal flow. | | 13 | Which create-conversation payload shape should extensions target? | **Current SDK settings shape:** top-level `agent_settings` plus optional top-level `plugins`; do not reintroduce legacy `agent` payloads. | +| 14 | What is the first route-less JavaScript surface? | **Tool visualizers.** They build on the merged PR #1246 registry seam and have a clear fallback path. | +| 15 | Should conversation slots ship before visualizers? | **No.** Keep slots explicit and follow visualizers with header/sidebar/footer/badge slots once the registry/runtime shape is proven. | ## 28. Remaining Open Questions @@ -960,10 +1064,11 @@ Build the smallest powerful slice: 2. Extension Host registry and asset serving. 3. Packages management page. 4. Trusted same-origin browser-module extension views (with the iframe runtime reserved in the manifest schema). -5. Dev-mode extension registration / watch / reload. -6. SDK plugin contribution merge for local conversations. -7. Context contribution merge behind explicit permission. +5. Extension tool visualizers as the first route-less JavaScript surface. +6. Dev-mode extension registration / watch / reload. +7. SDK plugin contribution merge for local conversations. +8. Context contribution merge behind explicit permission. -Defer: marketplace, package signing, **sandboxed iframe runtime** (manifest field reserved as `browser.entry` but unimplemented), parent-React component extensions/shared dependency runtime, rich extension RPC APIs, agent-mediated installation, SDK/server install management, plugin parameters, and per-run extension enablement. +Defer: marketplace, package signing, **sandboxed iframe runtime** (manifest field reserved as `browser.entry` but unimplemented), arbitrary root-mounted route-less components/shared dependency runtime, rich extension RPC APIs beyond the visualizer/slot surfaces, agent-mediated installation, SDK/server install management, plugin parameters, and per-run extension enablement. This delivers a useful end-to-end extension system while keeping the Agent Server boundary clean. From dad50a419b1a0e9d2b8a29c176a957f9e7b8e4fa Mon Sep 17 00:00:00 2001 From: Devin Date: Thu, 11 Jun 2026 14:52:22 -0400 Subject: [PATCH 07/14] Clarify Canvas Extensions surfaces --- docs/ExtensionsSystemAgentExecutionPlan.md | 172 ++++++------ docs/ExtensionsSystemPoCPlan.md | 130 +++++---- docs/ExtensionsSystemRFC.md | 290 ++++++++++++++------- 3 files changed, 353 insertions(+), 239 deletions(-) diff --git a/docs/ExtensionsSystemAgentExecutionPlan.md b/docs/ExtensionsSystemAgentExecutionPlan.md index 7ec77bb8e..68644e99f 100644 --- a/docs/ExtensionsSystemAgentExecutionPlan.md +++ b/docs/ExtensionsSystemAgentExecutionPlan.md @@ -1,4 +1,4 @@ -# Agent Canvas Extensions PoC Agent Execution Plan +# Canvas Extensions PoC Agent Execution Plan Status: executable handoff plan Source RFC: [ExtensionsSystemRFC.md](./ExtensionsSystemRFC.md) @@ -40,8 +40,8 @@ Use gates to coordinate merge order. Tasks inside a gate can be split, but the g | G0 | Risk burners and contracts | Mostly serial | Everything else | | G1 | Manager, CLI, install store, host routes | Manager/CLI and launcher work can split after shared contracts land | UI, view host, conversation merge | | G2 | Example extension and package release path | Can run alongside G1 after contracts land | View host, acceptance demo | -| G3 | Frontend Packages page and ExtensionService | Can run once Extension Host registry shape is stable | View host UX, dev mode UX | -| G4 | Browser-module view host, sidebar entries, visualizers, and slots | Can run after registry/assets exist; visualizers can split after shared types land | Acceptance demo | +| G3 | Frontend Packages page and CanvasExtensionService | Can run once Extension Host registry shape is stable | View host UX, dev mode UX | +| G4 | Browser-module view host, left navigation, settings panels, right panels, and visualizers | Can run after registry/assets exist; visualizers can split after shared types land | Acceptance demo | | G5 | Conversation contribution merge | Can run after launch-contributions endpoint exists | Final proof | | G6 | Dev mode watch/remount | Can run after manager, host, and view host exist | Final proof | | G7 | Acceptance, release smoke, polish | Serial final pass | PoC complete | @@ -62,7 +62,7 @@ Avoid these overlapping edits unless one agent owns the integration: - [ ] Files likely touched: - `examples/extensions/hello-canvas/agent/hello-plugin/.plugin/plugin.json` - `examples/extensions/hello-canvas/agent/hello-plugin/**` - - optional smoke helper under `scripts/` or `__tests__/extensions/` + - optional smoke helper under `scripts/` or `__tests__/canvas-extensions/` - [ ] Implement: - Create the smallest SDK plugin fixture with a visible skill/context marker. - Start a local conversation with top-level `plugins: [{ source: absolutePluginPath }]`. @@ -82,10 +82,10 @@ Avoid these overlapping edits unless one agent owns the integration: - [ ] Depends on: none - [ ] Files likely touched: - `examples/extensions/hello-canvas/dist/index.js` - - `__tests__/extensions/` or a small script fixture + - `__tests__/canvas-extensions/` or a small script fixture - [ ] Implement: - Create a browser-ready ESM fixture exporting `mount({ root, context })`. - - Verify static production build can dynamically import it from `/canvas-extensions/...`. + - Verify static production build can dynamically import it from `/canvas-extension-assets/...`. - Add a negative fixture that uses a bare runtime import such as `react`. - [ ] Tests: - Validation rejects bare runtime imports with an actionable diagnostic. @@ -99,22 +99,22 @@ Avoid these overlapping edits unless one agent owns the integration: - [ ] Owner: - [ ] Depends on: none - [ ] Files likely touched: - - `src/extensions/types.ts` - - `src/extensions/manifest-schema.ts` - - `src/extensions/manifest-validation.ts` - - `src/extensions/artifact-detection.ts` - - `src/extensions/storage-paths.ts` - - `__tests__/extensions/manifest-validation.test.ts` + - `src/canvas-extensions/types.ts` + - `src/canvas-extensions/manifest-schema.ts` + - `src/canvas-extensions/manifest-validation.ts` + - `src/canvas-extensions/artifact-detection.ts` + - `src/canvas-extensions/storage-paths.ts` + - `__tests__/canvas-extensions/manifest-validation.test.ts` - `package.json` - `tsconfig.lib.json` if needed for emitted type paths - - keep new public types under `src/extensions/`; do not recreate the old `src/addons/` namespace + - keep new public types under `src/canvas-extensions/`; do not recreate the old `src/addons/` namespace - [ ] Implement: - Manifest v1 types, contribution types, registry entry types, diagnostics, installable artifact kinds. - - Contribution types for `toolVisualizers` and `conversationSlots`. + - Contribution types for left navigation, `settingsPanels`, `conversationRightPanels`, and `toolVisualizers`. - Validation for required fields, ID regex, duplicate/mutually exclusive `browser.module` and `browser.entry`, path containment, reserved `browser.entry`. - - Artifact detection order: Agent Canvas extension, SDK plugin, skill, MCP placeholder. + - Artifact detection order: Canvas Extension, SDK plugin, skill, MCP placeholder. - Storage helpers for `~/.openhands/agent-canvas/installations`. - - Public package exports `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers` with emitted `dist/extensions/*` and `dist/visualizers/*` outputs. + - Public package exports `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` with emitted `dist/extensions/*` and `dist/visualizers/*` outputs. - [ ] Tests: - Valid extension manifest. - Missing required fields. @@ -127,12 +127,12 @@ Avoid these overlapping edits unless one agent owns the integration: ```sh npm run typecheck -npm test -- __tests__/extensions/manifest-validation.test.ts +npm test -- __tests__/canvas-extensions/manifest-validation.test.ts npm run build:lib ``` - [ ] Done when: - - Type consumers can import from `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers`. + - Type consumers can import from `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers`. - No runtime host, CLI store, or UI behavior is introduced in this gate. - [ ] Handoff notes: @@ -144,12 +144,12 @@ npm run build:lib - [ ] Depends on: G0.3 - [ ] Files likely touched: - `scripts/extension-manager.mjs` - - `__tests__/extensions/extension-manager.test.ts` - - `__tests__/extensions/fixtures/**` + - `__tests__/canvas-extensions/extension-manager.test.ts` + - `__tests__/canvas-extensions/fixtures/**` - [ ] Implement: - Store bootstrap: private `package.json`, `package-lock.json`, `artifacts.json`, `config.json`, `logs/`, `dev/`, `node_modules/`. - Read/write registry state with atomic-ish writes where practical. - - Install Agent Canvas extension manifests from local paths first; tarball/npm spec can follow in the same gate if scoped. + - Install Canvas Extension manifests from local paths first; tarball/npm spec can follow in the same gate if scoped. - Run npm installs in the private store with `--ignore-scripts` by default. - Enable/disable/remove/update state transitions. - Disabled wins over enabled if config is manually inconsistent. @@ -168,7 +168,7 @@ npm run build:lib - [ ] Verification: ```sh -npm test -- __tests__/extensions/extension-manager.test.ts +npm test -- __tests__/canvas-extensions/extension-manager.test.ts ``` - [ ] Done when: @@ -182,7 +182,7 @@ npm test -- __tests__/extensions/extension-manager.test.ts - [ ] Files likely touched: - `bin/agent-canvas.mjs` - optional `scripts/extension-cli.mjs` - - `__tests__/extensions/extension-cli.test.ts` + - `__tests__/canvas-extensions/extension-cli.test.ts` - [ ] Implement: - Dispatch `install`, `list`, `enable`, `disable`, `remove`, `update`, `doctor`, and `dev-extension` before stack startup. - Dispatch before `--public`, `--frontend-only`, and `--backend-only` stack validation. @@ -192,16 +192,16 @@ npm test -- __tests__/extensions/extension-manager.test.ts - Help text includes management commands and global install happy path. - `doctor` reports invalid manifests, unsupported standalone artifacts, missing assets, package/export issues where detectable. - [ ] Tests: - - `agent-canvas list extensions` works with no `build/`. + - `agent-canvas list canvas-extensions` works with no `build/`. - `agent-canvas doctor` works with no `build/`. - Unknown command produces useful help. - `install --install-scripts=deny` is default. - [ ] Verification: ```sh -node bin/agent-canvas.mjs list extensions +node bin/agent-canvas.mjs list canvas-extensions node bin/agent-canvas.mjs doctor -npm test -- __tests__/extensions/extension-cli.test.ts +npm test -- __tests__/canvas-extensions/extension-cli.test.ts ``` - [ ] Done when: @@ -214,17 +214,17 @@ npm test -- __tests__/extensions/extension-cli.test.ts - [ ] Depends on: G1.1 - [ ] Files likely touched: - `scripts/extension-host.mjs` - - `__tests__/extensions/extension-host.test.ts` + - `__tests__/canvas-extensions/extension-host.test.ts` - [ ] Implement: - - `GET /api/canvas/installations/registry` - - `GET /api/canvas/installations/diagnostics` - - `POST /api/canvas/installations/install` - - `POST /api/canvas/installations/:id/enable` - - `POST /api/canvas/installations/:id/disable` - - `DELETE /api/canvas/installations/:id` - - `PATCH /api/canvas/installations/:id/settings` - - `GET /api/canvas/installations/launch-contributions` - - `GET /canvas-extensions/:id/*assetPath` + - `GET /api/canvas/canvas-extensions/registry` + - `GET /api/canvas/canvas-extensions/diagnostics` + - `POST /api/canvas/canvas-extensions/install` + - `POST /api/canvas/canvas-extensions/:id/enable` + - `POST /api/canvas/canvas-extensions/:id/disable` + - `DELETE /api/canvas/canvas-extensions/:id` + - `PATCH /api/canvas/canvas-extensions/:id/settings` + - `GET /api/canvas/canvas-extensions/launch-contributions` + - `GET /canvas-extension-assets/:id/*assetPath` - Session API key guard for mutating routes. - Static asset path containment and content types. - [ ] Tests: @@ -235,7 +235,7 @@ npm test -- __tests__/extensions/extension-cli.test.ts - [ ] Verification: ```sh -npm test -- __tests__/extensions/extension-host.test.ts +npm test -- __tests__/canvas-extensions/extension-host.test.ts ``` - [ ] Done when: @@ -255,7 +255,7 @@ npm test -- __tests__/extensions/extension-host.test.ts - launcher/proxy tests under `__tests__/` - [ ] Implement: - Allocate/start Extension Host in `npm run dev`, `dev:minimal`, `dev:static`, and packaged `agent-canvas`. - - Route `/api/canvas/installations/*` and `/canvas-extensions/*` before `/api/*` and before SPA fallback. + - Route `/api/canvas/canvas-extensions/*` and `/canvas-extension-assets/*` before `/api/*` and before SPA fallback. - Add Vite proxy support for direct Vite access. - Add static server route support for direct static-port access. - Cover `--frontend-only` and `--backend-only`: frontend-only may expose local Packages/browser assets with agent-runtime diagnostics; backend-only should skip browser asset hosting unless a future CLI-only host mode explicitly needs it. @@ -270,7 +270,7 @@ npm test -- __tests__/extensions/extension-host.test.ts ```sh npm test -- __tests__/vite-config.test.ts -npm test -- __tests__/extensions +npm test -- __tests__/canvas-extensions ``` - [ ] Done when: @@ -314,7 +314,7 @@ npm test -- __tests__/extensions - [ ] Files likely touched: - `package.json` - optional `scripts/package-smoke.mjs` - - `__tests__/extensions/package-smoke.test.ts` if automated + - `__tests__/canvas-extensions/package-smoke.test.ts` if automated - [ ] Implement: - Ensure `files` includes every runtime script/build artifact needed by the global CLI and Extension Host. - Ensure `./extensions` export resolves from packed package. @@ -329,21 +329,21 @@ npm pack --dry-run - [ ] Done when: - Packed package contains `bin`, `scripts`, `build`, `dist/extensions`, `dist/visualizers`, and any advertised schema files. - - `agent-canvas list extensions` and `agent-canvas doctor` work from packed/global install. + - `agent-canvas list canvas-extensions` and `agent-canvas doctor` work from packed/global install. - [ ] Handoff notes: ## 6. Gate G3: Frontend Packages Management -### G3.1 ExtensionService API Wrapper +### G3.1 CanvasExtensionService API Wrapper - [ ] Owner: - [ ] Depends on: G1.3 - [ ] Files likely touched: - - `src/api/extensions-service.ts` + - `src/api/canvas-extensions-service.ts` - `src/api/no-direct-agent-server-calls.test.ts` - `src/hooks/query/use-extensions.ts` - [ ] Implement: - - Dedicated wrapper for every `/api/canvas/installations/*` call. + - Dedicated wrapper for every `/api/canvas/canvas-extensions/*` call. - Narrow test exception for this wrapper, including the current `fetch('/api/...')` guard. - React Query hooks for registry, diagnostics, install, enable, disable, remove, settings patch, launch contributions. - [ ] Tests: @@ -366,14 +366,16 @@ npm run typecheck - [ ] Depends on: G3.1 - [ ] Files likely touched: - `src/routes.ts` - - `src/routes/extensions-packages.tsx` + - `src/routes/canvas-extensions-packages.tsx` - `src/components/features/skills/extensions-navigation.tsx` + - `src/routes/extensions-hub.tsx` if the existing Customize hub needs route/link updates - `src/i18n/translation.json` - generated i18n files via `npm run make-i18n` - `tests/e2e/snapshots/**` - [ ] Implement: - Replace or redirect `/plugins` to Packages while preserving bookmarks. - - Packages nav item in the existing Extensions hub (`/customize` primary entry, desktop redirect to `/skills`, mobile `ExtensionsMobileHub`). + - Packages nav item in the existing Customize area (`/customize` primary entry, desktop redirect to `/skills`, mobile Customize hub). + - Treat legacy "extensions" file/component names as existing Customize implementation details; do not use them as new product vocabulary. - Sections for Enabled, Installed/Disabled, Invalid, Dev. - Cards show display name, package, version, state, contribution badges, required secrets, diagnostics, source path for dev entries. - Actions for install, enable, disable, remove. @@ -393,7 +395,7 @@ npm test - A user can see and manage `hello.canvas` from the UI. - [ ] Handoff notes: -## 7. Gate G4: Browser Module View Host And Sidebar +## 7. Gate G4: Browser Module View Host And UI Surfaces ### G4.1 Extension Runtime Host @@ -402,10 +404,10 @@ npm test - [ ] Files likely touched: - `src/routes.ts` - `src/routes/extension-view-host.tsx` - - `src/extensions/runtime/*` + - `src/canvas-extensions/runtime/*` - `src/i18n/translation.json` - [ ] Implement: - - Route `/extensions/:extensionId/:viewId/*`. + - Route `/canvas-extensions/:extensionId/:viewId/*`. - Fetch registry, resolve enabled view, import `assetBaseUrl + browser.module`. - Use `import(/* @vite-ignore */ url)` or equivalent for served extension modules. - Add cache-busting version token. @@ -428,7 +430,7 @@ npm test - `hello.canvas` view renders from served browser module. - [ ] Handoff notes: -### G4.2 Primary Sidebar Entries +### G4.2 Left Navigation Entries - [ ] Owner: - [ ] Depends on: G4.1 @@ -466,14 +468,14 @@ npm run test:e2e:snapshots -- --grep "sidebar" - [ ] Depends on: G0.3, G1.3, G3.1 - [ ] Files likely touched: - `src/components/features/chat/tool-visualizers/*` - - `src/extensions/visualizers/*` - - `src/api/extensions-service.ts` + - `src/canvas-extensions/visualizers/*` + - `src/api/canvas-extensions-service.ts` - `package.json` - `tsconfig.lib.json` - visualizer tests under `__tests__/components/features/chat/tool-visualizers/` - - extension tests under `__tests__/extensions/` + - extension tests under `__tests__/canvas-extensions/` - [ ] Implement: - - Public `@openhands/agent-canvas/visualizers` subpath with stable authoring types and primitives. + - Public `@openhands/agent-canvas/canvas-visualizers` subpath with stable authoring types and primitives. - Extension visualizer registry layer that composes before built-in visualizers and markdown fallback. - `priority` plus `matches()` support. - Manifest projection for `contributes.toolVisualizers`. @@ -498,30 +500,34 @@ npm run typecheck - A local extension can change how one event/tool renders without forking Agent Canvas. - [ ] Handoff notes: -### G4.4 Conversation Slots And Badges +### G4.4 Settings Panels And Conversation Right Panels - [ ] Owner: -- [ ] Depends on: G4.3 +- [ ] Depends on: G4.1 - [ ] Files likely touched: - - conversation header/sidebar/footer/badge components - - `src/extensions/slots/*` + - settings route/components + - conversation right-panel/tab components + - `src/canvas-extensions/panels/*` - `src/i18n/translation.json` - - tests under `__tests__/extensions/` - - focused component tests near each slot host + - tests under `__tests__/canvas-extensions/` + - focused component tests near settings and conversation panel hosts - [ ] Implement: - - Slot registry for `conversation.header.actions`, `conversation.sidebar`, `conversation.footer`, and `conversation.badges`. - - Optional `conversation.event.visualizers` manifest alias that maps to tool visualizer registration. - - Stable slot props: conversation ID/status, active backend summary, workspace summary, extension settings helpers, navigation/toast helpers. - - Deterministic ordering by priority, extension display name, and slot contribution ID. - - Per-slot error boundaries. + - Settings panel registry for `contributes.settingsPanels`. + - Render settings panels only under a visible Extensions header after built-in settings sections. + - Conversation right-panel registry for `contributes.conversationRightPanels`. + - Render conversation right panels alongside Files, Browser, and Terminal. + - Stable panel props: conversation ID/status where relevant, active backend summary, workspace summary, extension settings helpers, navigation/toast helpers. + - Deterministic ordering by `order`, extension display name, and panel contribution ID. + - Per-panel error boundaries. - No raw store, React Query client, or Canvas-internal component API in the public contract. - [ ] Tests: - - Slots render in the intended hosts. + - Settings panels render only under the Extensions header after built-in settings. + - Conversation right panels render beside Files, Browser, and Terminal. - Ordering is deterministic. - - Disabled/invalid extensions do not render slot content. - - Slot error is localized and does not break the conversation page. + - Disabled/invalid extensions do not render panel content. + - Panel errors are localized and do not break settings or conversation pages. - [ ] Done when: - - A local extension can add a backend/workspace/conversation badge or header action without a route. + - A local Canvas Extension can add a settings panel and a conversation right panel without a full app route. - [ ] Handoff notes: ## 8. Gate G5: Conversation Contributions @@ -531,11 +537,11 @@ npm run typecheck - [ ] Owner: - [ ] Depends on: G1.3, G3.1 - [ ] Files likely touched: - - `src/api/extensions-service.ts` - - `src/extensions/runtime-compatibility.ts` - - `__tests__/extensions/runtime-compatibility.test.ts` + - `src/api/canvas-extensions-service.ts` + - `src/canvas-extensions/runtime-compatibility.ts` + - `__tests__/canvas-extensions/runtime-compatibility.test.ts` - [ ] Implement: - - `ExtensionsService.getLaunchContributions()`. + - `CanvasExtensionsService.getLaunchContributions()`. - Runtime classes: `canvas-local`, `agent-server-local`, `agent-server-remote`, `cloud-runtime`, `acp-runtime`. - Use launcher-issued `localInstallStoreReadable`. - Disable package-relative/local SDK plugin paths unless filesystem-local. @@ -559,7 +565,7 @@ npm run typecheck - `src/hooks/mutation/use-create-conversation.ts` - launch/home components - `__tests__/api/agent-server-adapter.test.ts` - - `__tests__/extensions/conversation-contributions.test.ts` + - `__tests__/canvas-extensions/conversation-contributions.test.ts` - [ ] Implement: - `extensionSystemSuffix` adapter option. - Append `` and `` after ``. @@ -594,9 +600,9 @@ npm run typecheck - `scripts/extension-manager.mjs` - `scripts/extension-host.mjs` - `bin/agent-canvas.mjs` - - `src/api/extensions-service.ts` - - `src/routes/extensions-packages.tsx` - - tests under `__tests__/extensions/` + - `src/api/canvas-extensions-service.ts` + - `src/routes/canvas-extensions-packages.tsx` + - tests under `__tests__/canvas-extensions/` - [ ] Implement: - `agent-canvas install --dev`. - `agent-canvas dev-extension register/list/unregister`. @@ -630,15 +636,17 @@ npm run build npm run build:lib npm pack --dry-run node bin/agent-canvas.mjs install ./examples/extensions/hello-canvas --yes -node bin/agent-canvas.mjs list extensions +node bin/agent-canvas.mjs list canvas-extensions node bin/agent-canvas.mjs doctor node bin/agent-canvas.mjs ``` - [ ] Browser checks: - Packages shows `hello.canvas` enabled with no diagnostics. - - Sidebar entry appears after Automations and before conversations. + - Left navigation entry appears after Automations and before conversations. - Extension view renders. + - Settings panel appears under a visible Extensions header after built-in settings. + - Conversation right panel appears beside Files, Browser, and Terminal. - Extension settings patch persists. - Launch template shows context/plugin contribution preflight. - Conversation payload includes extension suffix. @@ -654,7 +662,7 @@ node bin/agent-canvas.mjs - [ ] Depends on: G7.1 - [ ] Run from a temp directory/prefix: - Install the packed `@openhands/agent-canvas` tarball. - - Verify `agent-canvas list extensions`. + - Verify `agent-canvas list canvas-extensions`. - Verify `agent-canvas doctor`. - Install a packed or local `hello.canvas` extension. - Launch `agent-canvas` and repeat the browser acceptance checks. @@ -692,7 +700,7 @@ The PoC is not complete unless all of these are true: - [ ] Management CLI commands work when no `build/` directory exists. - [ ] Global npm package contains every runtime artifact it needs. - [ ] Extension Host routes work through Vite, ingress, static server, and packaged CLI. -- [ ] `/api/canvas/installations/*` traffic is isolated behind `src/api/extensions-service.ts`. +- [ ] `/api/canvas/canvas-extensions/*` traffic is isolated behind `src/api/canvas-extensions-service.ts`. - [ ] Ordinary Agent Server API calls still use `@openhands/typescript-client`. - [ ] Browser modules are trusted same-origin DOM islands and documented/consented as such. - [ ] Bare runtime imports in extension browser modules are rejected for MVP. @@ -700,7 +708,7 @@ The PoC is not complete unless all of these are true: - [ ] ACP runtimes do not receive incompatible extension plugin/MCP contributions. - [ ] `conversationInstructions` never receives extension system context. - [ ] Extension context composes with existing `` suffix. -- [ ] `hello.canvas` proves install, registry, UI, sidebar, view, settings, launch context, SDK plugin preflight, and dev remount. +- [ ] `hello.canvas` proves install, registry, UI, left navigation, view, settings panel, conversation right panel, tool visualizer, launch context, SDK plugin preflight, and dev remount. - [ ] Tests cover the risk surfaces listed in the PoC plan. ## 12. Handoff Template diff --git a/docs/ExtensionsSystemPoCPlan.md b/docs/ExtensionsSystemPoCPlan.md index 13f841bf8..656025885 100644 --- a/docs/ExtensionsSystemPoCPlan.md +++ b/docs/ExtensionsSystemPoCPlan.md @@ -1,4 +1,4 @@ -# Agent Canvas Extensions MVP / PoC Build Plan +# Canvas Extensions MVP / PoC Build Plan Status: working build plan Source RFC: [ExtensionsSystemRFC.md](./ExtensionsSystemRFC.md) @@ -8,40 +8,44 @@ Current repo baseline: merged through `upstream/main` at `40844533`. The plan be ## 1. Purpose -This document turns the Extensions RFC into a practical MVP / PoC build path. The RFC should stay clean and reviewable as the product/architecture proposal. This plan is the working artifact for agents and developers implementing the proof, testing risky assumptions, and tightening the design based on real code. +This document turns the Canvas Extensions RFC into a practical MVP / PoC build path. The RFC should stay clean and reviewable as the product/architecture proposal. This plan is the working artifact for agents and developers implementing the proof, testing risky assumptions, and tightening the design based on real code. For multi-agent implementation, use [ExtensionsSystemAgentExecutionPlan.md](./ExtensionsSystemAgentExecutionPlan.md) as the task checklist. It breaks this plan into gates, parallel workstreams, task IDs, file ownership, verification commands, and handoff notes. The MVP should be built as a vertical proof, not as isolated architecture layers. The first complete proof should demonstrate: -1. A local or npm-packed Agent Canvas extension installs into `~/.openhands/agent-canvas/installations`. -2. The Packages page shows the installed extension and its diagnostics. -3. A trusted `browser.module` view renders at `/extensions/:extensionId/:viewId/*`. -4. A view can contribute a primary Sidebar entry after Automations and before the conversation list, matching the current `SidebarRailBody` order (`New Chat`, `Customize`, `Automations`, then conversations). -5. A local extension can register a custom tool visualizer for one MCP/action variant and safely fall back to built-in rendering when it does not match or throws. -6. A launch template can append extension context and include an SDK plugin when the runtime is filesystem-local. -7. Dev mode can register a local source folder, detect output changes, and remount the view without rebuilding Canvas. +1. A local or npm-packed Canvas Extension installs into `~/.openhands/agent-canvas/installations`. +2. The Packages page under Customize shows the installed Canvas Extension and its diagnostics. +3. A trusted `browser.module` view renders at `/canvas-extensions/:extensionId/:viewId/*`. +4. A view can contribute a left navigation entry after Automations and before the conversation list, matching the current `SidebarRailBody` order (`New Chat`, `Customize`, `Automations`, then conversations). +5. A Canvas Extension can add a settings panel only under a visible Extensions header after built-in settings. +6. A Canvas Extension can add a conversation right panel alongside Files, Browser, and Terminal. +7. A local Canvas Extension can register a custom tool visualizer for one MCP/action variant and safely fall back to built-in rendering when it does not match or throws. +8. A launch template can append extension context and include an SDK plugin when the runtime is filesystem-local. +9. Dev mode can register a local source folder, detect output changes, and remount the view without rebuilding Canvas. ## 1.1 Scope Locks From The RFC These decisions should keep the build from spreading sideways: -- First-class install/enable support is for Agent Canvas extension manifests only. Standalone SDK plugins, `SKILL.md` folders, and future MCP-template artifacts can be detected, but they return an explicit unsupported-in-MVP diagnostic unless wrapped by an extension. +- First-class install/enable support is for Canvas Extension manifests only. Standalone SDK plugins, `SKILL.md` folders, and future MCP-template artifacts can be detected, but they return an explicit unsupported-in-MVP diagnostic unless wrapped by a Canvas Extension. - CLI management commands must dispatch before stack startup, before the static `build/` existence check, and before importing `scripts/dev-with-automation.mjs`. - CLI management commands must also dispatch before `--public`, `--frontend-only`, and `--backend-only` stack validation, because `install/list/doctor` should work even when no frontend/backend is being launched. -- Extension author types need real package subpaths: `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers`, emitted under `dist/extensions/*` and `dist/visualizers/*` and included in npm release checks. +- Extension author types need real package subpaths: `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers`, emitted under `dist/extensions/*` and `dist/visualizers/*` and included in npm release checks. - Conversation contribution work targets the current start payload: top-level `agent_settings` plus optional top-level `plugins`. Do not reintroduce the legacy `agent` payload shape. - Package-relative/local SDK plugin sources are MVP only when the launcher says `localInstallStoreReadable=true`. ACP runtimes skip extension SDK plugins unless a pinned-version smoke proves the exact plugin shape is compatible. -- The first route-less JavaScript workstream is custom tool visualizers, not arbitrary root-mounted components. Conversation slots/badges follow after visualizers prove the registration/fallback model. +- The first route-less JavaScript workstreams are custom tool visualizers, settings panels, and conversation right panels, not arbitrary root-mounted components. Generic conversation slots remain deferred until a concrete workflow cannot be handled by a right panel or visualizer. ## 1.2 Focused Workstream From Issue #481 -The issue feedback points to two concrete places where users want to add content without forking Agent Canvas: +The issue feedback and product feedback point to four concrete places where users should be able to add content without forking Agent Canvas: 1. **Custom tool/event visualizers.** PR #1246 added an internal visualizer dispatcher that already asks a registry for a React body and falls back to markdown. Extensions should expose that seam first through `registerToolVisualizer()` or a manifest-projected equivalent. -2. **Conversation UI slots and badges.** Users want content near the active conversation: header actions, sidebar widgets, footer/status indicators, event renderer hooks, and badges for backend/workspace/conversation state. +2. **Left navigation entries.** A Canvas Extension can add a top-level navigation item after Automations and before the conversation list. +3. **Settings panels.** A Canvas Extension can add configuration UI only under a visible Extensions header after built-in settings. +4. **Conversation right panels.** A Canvas Extension can add a panel beside Files, Browser, and Terminal for conversation-adjacent tools and context. -The implementation order should be visualizers first, then slots. Visualizers are narrower, already have a merged internal seam, and have a clear failure mode: try extension renderer, then built-in renderer, then markdown fallback. Slots are still explicit product surface area and should not become a generic "mount anything at app root" API. +The implementation order should be visualizers first where possible because they are narrow, already have a merged internal seam, and have a clear failure mode: try extension renderer, then built-in renderer, then markdown fallback. Settings panels and conversation right panels should follow as explicit product surfaces. Generic "mount anything at app root" slots remain deferred. ## 2. Risk-Burner Spikes @@ -65,21 +69,21 @@ Serve a tiny browser-ready ESM file from a local test host and dynamically impor The extension module must use only bundled or relative imports. This proves the DOM-island `mount()` contract works without Vite, shared React, import maps, or module federation. -Add a negative fixture with a bare runtime import such as `react` and verify validation rejects it with an actionable diagnostic. Type-only imports from `@openhands/agent-canvas/extensions` are fine because the extension build erases them. Author source may import from `@openhands/agent-canvas/visualizers`, but emitted browser modules must bundle those helpers; raw runtime imports from Agent Canvas package subpaths are not MVP-supported. +Add a negative fixture with a bare runtime import such as `react` and verify validation rejects it with an actionable diagnostic. Type-only imports from `@openhands/agent-canvas/canvas-extensions` are fine because the extension build erases them. Author source may import from `@openhands/agent-canvas/canvas-visualizers`, but emitted browser modules must bundle those helpers; raw runtime imports from Agent Canvas package subpaths are not MVP-supported. ### 2.3 Launcher Route Smoke -Start a toy Extension Host and route `/api/canvas/installations/*` plus `/canvas-extensions/*` through the existing ingress/static-server/Vite proxy paths. +Start a toy Extension Host and route `/api/canvas/canvas-extensions/*` plus `/canvas-extension-assets/*` through the existing ingress/static-server/Vite proxy paths. Cover: - `scripts/dev-with-automation.mjs` ingress routes. - `scripts/dev-safe.mjs` / `dev:minimal`, where direct Vite proxying must handle Extension Host routes without the automation ingress. - `scripts/static-server.mjs` routes used by the packaged CLI/static mode. -- `vite.config.ts` proxy support for direct Vite access, especially `/canvas-extensions/*`. +- `vite.config.ts` proxy support for direct Vite access, especially `/canvas-extension-assets/*`. - `--frontend-only` and `--backend-only` partial-stack behavior: frontend-only can expose Packages/extension views but should show backend-dependent diagnostics, while backend-only should not start the Extension Host view/assets path unless a future CLI-only host mode explicitly needs it. -Exit criteria: a browser opened through either the ingress port or direct Vite/static port can fetch registry JSON and import a browser module from `/canvas-extensions/...`. +Exit criteria: a browser opened through either the ingress port or direct Vite/static port can fetch registry JSON and import a browser module from `/canvas-extension-assets/...`. ### 2.4 Npm Package / CLI Smoke @@ -89,7 +93,7 @@ Before any UI work, prove the upcoming release path: npm run build npm run build:lib npm pack --dry-run -node bin/agent-canvas.mjs list extensions +node bin/agent-canvas.mjs list canvas-extensions node bin/agent-canvas.mjs doctor ``` @@ -103,13 +107,13 @@ Build the thinnest end-to-end slice next. Add: -- `src/extensions/types.ts` +- `src/canvas-extensions/types.ts` - manifest constants - artifact detection - storage-path helpers - path normalization - manifest validation -- public package export wiring for `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers` +- public package export wiring for `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` Keep validation deliberately boring: @@ -121,7 +125,7 @@ Keep validation deliberately boring: Avoid a large JSON Schema dependency unless configuration forms force it. -PR 0 should also update `package.json#exports` for `./extensions` and `./visualizers` and make sure the library build emits `dist/extensions/*` and `dist/visualizers/*`. Keep the actual Extension Host and CLI behavior out of PR 0. +PR 0 should also update `package.json#exports` for `./canvas-extensions` and `./canvas-visualizers` and make sure the library build emits `dist/extensions/*` and `dist/visualizers/*`. Keep the actual Extension Host and CLI behavior out of PR 0. ### 3.2 Manager Library @@ -131,7 +135,7 @@ It owns: - Store bootstrap: `package.json`, `package-lock.json`, `config.json`, `artifacts.json`, `logs/`. - Artifact detection. -- Install Agent Canvas extension manifests from local path, tarball, npm spec. +- Install Canvas Extension manifests from local path, tarball, npm spec. - Typed unsupported diagnostics for detected standalone SDK plugins, standalone skills, and placeholder MCP templates. - Enable/disable/remove/update. - Registry and launch-contribution projection. @@ -143,7 +147,7 @@ Update `bin/agent-canvas.mjs` so install/manage commands dispatch before stack s ```sh agent-canvas install ./examples/extensions/hello-canvas --yes -agent-canvas list extensions +agent-canvas list canvas-extensions agent-canvas enable hello.canvas agent-canvas disable hello.canvas agent-canvas doctor @@ -172,8 +176,8 @@ Mutating routes require the same session API key model the frontend already uses Add an `extensionHostPort` to launcher config, start the host before the frontend, and route it before `/api/*`: -- `/api/canvas/installations/*` -> Extension Host. -- `/canvas-extensions/*` -> Extension Host. +- `/api/canvas/canvas-extensions/*` -> Extension Host. +- `/canvas-extension-assets/*` -> Extension Host. - `/api/automation/*` -> automation. - `/api/*`, `/sockets`, server metadata -> Agent Server. @@ -186,7 +190,7 @@ The launcher also emits frontend env/runtime metadata for: Do not expose a write-capable Extension Host key in agent prompts. -Route precedence is the failure-prone part: `/api/canvas/installations/*` and `/canvas-extensions/*` must match before `/api/*` and static SPA fallback in every launcher path. +Route precedence is the failure-prone part: `/api/canvas/canvas-extensions/*` and `/canvas-extension-assets/*` must match before `/api/*` and static SPA fallback in every launcher path. Partial-stack mode rule: frontend-only may start enough Extension Host surface to render local Packages and extension browser assets, but must mark agent-runtime contributions unavailable because no Agent Server is running. Backend-only should skip frontend asset hosting and extension browser routes by default; CLI management still works because it dispatches before stack startup. @@ -194,17 +198,18 @@ Partial-stack mode rule: frontend-only may start enough Extension Host surface t ### 4.1 API Wrapper -Add `src/api/extensions-service.ts` for every `/api/canvas/installations/*` call. +Add `src/api/canvas-extensions-service.ts` for every `/api/canvas/canvas-extensions/*` call. -Update `src/api/no-direct-agent-server-calls.test.ts` with one narrow allowlist entry for this wrapper, mirroring automation but covering the current blanket `fetch('/api/...')` rule as well as axios. The exception should allow only `/api/canvas/installations/*` from the wrapper; it must not weaken the Agent Server rule for arbitrary `/api/*` calls. +Update `src/api/no-direct-agent-server-calls.test.ts` with one narrow allowlist entry for this wrapper, mirroring automation but covering the current blanket `fetch('/api/...')` rule as well as axios. The exception should allow only `/api/canvas/canvas-extensions/*` from the wrapper; it must not weaken the Agent Server rule for arbitrary `/api/*` calls. ### 4.2 Packages Page -Replace `/plugins` with a Packages view, while preserving redirects/bookmarks. Use the existing Extensions layout and navigation: `/customize` is the primary-sidebar hub entry, desktop redirects to `/skills`, mobile renders `ExtensionsMobileHub`, and `ExtensionsNavigation` currently contains Skills, MCP Servers, and a coming-soon Plugins item. +Replace `/plugins` with a Packages view, while preserving redirects/bookmarks. Use the existing Customize layout and navigation: `/customize` is the primary-sidebar hub entry, desktop redirects to `/skills`, mobile renders the Customize hub, and the current Customize navigation contains Skills, MCP Servers, and a coming-soon Plugins item. The page should: -- Add a Packages nav item in `ExtensionsNavigation`. +- Add a Packages nav item in the Customize navigation. +- Treat legacy file names such as `extensions-hub.tsx` and `extensions-navigation.tsx` as existing Customize implementation details, not product vocabulary for the new Canvas Extensions system. - Show Enabled, Installed, Disabled, Invalid, and Dev sections. - Show install source, version, contribution badges, required secrets, diagnostics, enable/disable/remove actions. - Stay dense and operational; no marketplace browsing in MVP. @@ -212,7 +217,7 @@ The page should: ### 4.3 View Host -Add `/extensions/:extensionId/:viewId/*` and a route component that: +Add `/canvas-extensions/:extensionId/:viewId/*` and a route component that: - Fetches registry data. - Resolves `assetBaseUrl + browser.module`. @@ -223,7 +228,7 @@ Add `/extensions/:extensionId/:viewId/*` and a route component that: The extension view should be visibly extension-owned but render in a Canvas-owned route container. Errors stay local to the extension view. -### 4.4 Primary Sidebar Entries +### 4.4 Left Navigation Entries Support view navigation contributions that render in the main Canvas Sidebar: @@ -242,7 +247,7 @@ MVP scope: - Only `primarySidebar` + `afterAutomations` is required. - Entries render after Automations and before `SidebarConversationList`. -- Entries navigate to `/extensions/:extensionId/:viewId/*`. +- Entries navigate to `/canvas-extensions/:extensionId/:viewId/*`. - Disabled/invalid extensions do not render entries. - Sorting is deterministic: `order`, extension display name, view ID. - Expanded sidebar shows icon + title. @@ -257,7 +262,7 @@ Use the merged built-in visualizer implementation as the first route-less JS pro MVP scope: -- Add public authoring types under `@openhands/agent-canvas/visualizers`. +- Add public authoring types under `@openhands/agent-canvas/canvas-visualizers`. - Project enabled extension visualizers into a registry that is consulted before built-in visualizers. - Support `priority` and `matches()` so an extension can override one specific MCP tool or event variant without replacing an entire action/observation kind. - Preserve the existing fallback chain: extension visualizer, built-in visualizer, markdown fallback. @@ -266,19 +271,29 @@ MVP scope: Example fixture: `hello.canvas` registers a visualizer for one synthetic or MCP-style tool call and renders a compact card. Tests should prove extension-first order, `matches()` narrowing, built-in fallback, markdown fallback, and throw-to-next-renderer behavior. -### 4.6 Conversation Slots And Badges +### 4.6 Settings Panels -After visualizers, add explicit conversation insertion points rather than a generic app-root component API. +Add Canvas Extension settings panels under one visible Extensions header after all built-in settings sections. -Initial slot list: +MVP scope: + +- Panels render only in the settings experience, not in the old top-level Extensions section. +- Built-in settings keep their current order; Canvas Extension panels are grouped after them. +- Disabled/invalid extensions do not render settings panels. +- Canvas owns the panel chrome, save/cancel affordances, loading states, and error boundaries. +- Panel renderers receive extension settings helpers only; no raw settings store or React Query client access. + +### 4.7 Conversation Right Panels -- `conversation.header.actions` -- `conversation.sidebar` -- `conversation.footer` -- `conversation.badges` -- `conversation.event.visualizers` as alias/sugar for tool visualizer registration +Add Canvas Extension panels to the existing conversation right-panel/tab system beside Files, Browser, and Terminal. + +MVP scope: -Slot props should be narrow and stable: active conversation ID/status, active backend summary, workspace summary, and host APIs for navigation, toasts, and extension settings. Do not expose raw stores, React Query clients, or Canvas component internals as the stable extension contract. +- Panels declare `conversationRightPanels` in the manifest. +- Tabs sort deterministically by `order`, extension display name, and panel ID. +- Disabled/invalid extensions do not render panels. +- Panels receive active conversation ID/status, workspace summary, backend summary, and host APIs for navigation, toasts, and extension settings. +- Canvas owns panel placement, collapsed/expanded behavior, loading state, and error boundaries. ## 5. Conversation Contributions @@ -286,7 +301,7 @@ Add contribution merge late enough that the install/store/view path already work ### 5.1 Launch Contributions Endpoint -`ExtensionsService.getLaunchContributions()` fetches enabled extension contributions and returns a frontend-ready, compatibility-filtered shape. +`CanvasExtensionsService.getLaunchContributions()` fetches enabled extension contributions and returns a frontend-ready, compatibility-filtered shape. ### 5.2 Runtime Compatibility @@ -344,7 +359,7 @@ npm run build npm run build:lib npm pack --dry-run node bin/agent-canvas.mjs install ./examples/extensions/hello-canvas --yes -node bin/agent-canvas.mjs list extensions +node bin/agent-canvas.mjs list canvas-extensions node bin/agent-canvas.mjs doctor node bin/agent-canvas.mjs ``` @@ -352,25 +367,28 @@ node bin/agent-canvas.mjs Then in the browser: 1. Open Packages and see `hello.canvas` enabled with no diagnostics. -2. See its primary Sidebar entry after Automations and before the conversation list. -3. Open its extension view from the Sidebar and see browser-module UI render. -4. Change a setting in the extension view and see it persist. -5. Start its launch template and verify the conversation payload includes the extension context suffix. -6. In `npm run dev`, register the same extension with `--dev`, rebuild the extension output, and see the view remount with the new code. +2. See its left navigation entry after Automations and before the conversation list. +3. Open its extension view from the left navigation and see browser-module UI render. +4. Open settings and confirm its panel appears under the visible Extensions header after built-in settings. +5. Change a setting in the extension panel and see it persist. +6. Open a conversation and confirm its right panel appears beside Files, Browser, and Terminal. +7. Trigger its fixture tool/event and verify the Canvas Extension visualizer renders with fallback still intact. +8. Start its launch template and verify the conversation payload includes the extension context suffix. +9. In `npm run dev`, register the same extension with `--dev`, rebuild the extension output, and see the view remount with the new code. Release-path acceptance should also install the packed `@openhands/agent-canvas` tarball into a temp npm prefix and repeat `agent-canvas list`, `agent-canvas doctor`, and one packed/local extension install without relying on repo-local files outside the package. ## 8. Testing Strategy -**Unit:** manifest validation; reserved `browser.entry` diagnostics; path traversal rejection; artifact detection; unsupported standalone artifact diagnostics; install-store bootstrap; enable/disable/remove/update state transitions; duplicate ID handling; asset route path validation; CLI arg parsing before build checks; no-install-scripts default; visualizer contribution projection; visualizer ordering/fallback/error-boundary behavior; slot registration ordering; launch contribution projection; context suffix rendering; plugin merge/dedupe; runtime compatibility classification; dev registration and manifest revalidation. +**Unit:** manifest validation; reserved `browser.entry` diagnostics; path traversal rejection; artifact detection; unsupported standalone artifact diagnostics; install-store bootstrap; enable/disable/remove/update state transitions; duplicate ID handling; asset route path validation; CLI arg parsing before build checks; no-install-scripts default; visualizer contribution projection; visualizer ordering/fallback/error-boundary behavior; settings panel projection/ordering; conversation right-panel projection/ordering; launch contribution projection; context suffix rendering; plugin merge/dedupe; runtime compatibility classification; dev registration and manifest revalidation. **Node integration:** run the Extension Host against a temp install store; install the `hello.canvas` fixture from local path and npm-packed tarball; fetch registry and asset URLs; verify route precedence before `/api/*` in Vite, ingress, static server, and packaged CLI paths; verify mutating routes require the session API key; verify `doctor` reports invalid manifests, unsupported standalone artifacts, and missing assets. -**Release packaging:** `npm pack --dry-run`; packed-tarball install into a temp npm prefix; verify `agent-canvas list`, `agent-canvas doctor`, and an extension install work without a source checkout; verify `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers` resolve for type consumers. +**Release packaging:** `npm pack --dry-run`; packed-tarball install into a temp npm prefix; verify `agent-canvas list`, `agent-canvas doctor`, and an extension install work without a source checkout; verify `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` resolve for type consumers. -**Component:** Packages page empty/installed/enabled/invalid/dev states; install/enable/disable/remove action wiring; primary Sidebar extension entries in expanded/collapsed/mobile states; extension view loading/error/remount; extension settings read/patch; extension visualizer renders/falls back inside the existing event wrapper; conversation badge/header/footer slots render in order; launch template preflight for incompatible SDK plugin paths. +**Component:** Packages page empty/installed/enabled/invalid/dev states under Customize; install/enable/disable/remove action wiring; left navigation entries in expanded/collapsed/mobile states; extension view loading/error/remount; settings panel placement under the visible Extensions header; extension settings read/patch; extension visualizer renders/falls back inside the existing event wrapper; conversation right panels render beside Files/Browser/Terminal; launch template preflight for incompatible SDK plugin paths. -**E2E snapshots:** Packages page with enabled extension; invalid extension diagnostics; primary Sidebar entry after Automations; extension view rendered from browser module; launch template showing context/plugin contribution; remote-backend disabled reason for local plugin path; dev extension view remount after output change. +**E2E snapshots:** Packages page with enabled extension under Customize; invalid extension diagnostics; left navigation entry after Automations; settings panel under Extensions header; conversation right panel beside Files/Browser/Terminal; extension view rendered from browser module; launch template showing context/plugin contribution; remote-backend disabled reason for local plugin path; dev extension view remount after output change. **Live E2E:** optional after the walking skeleton. If added, keep one cheap local live test that starts a conversation from `hello.canvas` and verifies the created payload/events show the context suffix or plugin load marker. Follow existing live E2E rules under `tests/e2e/live/`. diff --git a/docs/ExtensionsSystemRFC.md b/docs/ExtensionsSystemRFC.md index 3f1334e0c..02d0fa6e0 100644 --- a/docs/ExtensionsSystemRFC.md +++ b/docs/ExtensionsSystemRFC.md @@ -1,4 +1,4 @@ -# RFC: Agent Canvas Extensions +# RFC: Canvas Extensions Status: RFC, ready for review Target: `OpenHands/agent-canvas` @@ -8,11 +8,11 @@ Current repo baseline: merged through `upstream/main` at `40844533`. The current ## 1. Summary -Agent Canvas should ship a first-class **Extensions** system: user-installable npm packages that extend the local Canvas experience and optionally contribute components to the agent runtime through SDK-supported surfaces. +Agent Canvas should ship a first-class **Canvas Extensions** system: user-installable npm packages that extend the local Canvas experience and optionally contribute components to the agent runtime through SDK-supported surfaces. -An Extension can contribute UI views, primary navigation entries, custom tool/event visualizers, conversation UI slots, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces; Canvas does not patch or load code inside the Agent Server. +A Canvas Extension can contribute UI views, left navigation entries, settings panels, conversation right panels, custom tool/event visualizers, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Canvas Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces; Canvas does not patch or load code inside the Agent Server. -The MVP delivers CLI install for Agent Canvas extension packages, a Packages management page, trusted same-origin extension views, dev-mode authoring with live reload, and a narrow first route-less JavaScript surface: extension-provided tool visualizers that compose with the built-in visualizer registry introduced by PR #1246. Conversation slots and badges are the next focused surface after visualizers; broad root-mounted components remain deferred. The CLI can detect adjacent artifact types such as standalone SDK plugins or `SKILL.md` folders, but the first executable slice should install/enable only packages with an Agent Canvas manifest. Marketplace, signing, sandboxed iframe views, arbitrary app-root component/shared dependency runtimes, standalone skill/plugin management, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. +The MVP delivers CLI install for Canvas Extension packages, a Packages management page under Customize, trusted same-origin extension views, dev-mode authoring with live reload, and focused UI contribution surfaces: left navigation entries, settings panels under a visible Extensions header, conversation right panels beside Files/Browser/Terminal, and extension-provided tool visualizers that compose with the built-in visualizer registry introduced by PR #1246. Broad root-mounted components remain deferred. The CLI can detect adjacent artifact types such as standalone SDK plugins or `SKILL.md` folders, but the first executable slice should install/enable only packages with a Canvas Extension manifest. Marketplace, signing, sandboxed iframe views, arbitrary app-root component/shared dependency runtimes, standalone skill/plugin management, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. ## 2. Motivation @@ -26,15 +26,17 @@ The system must preserve the existing security and operational boundaries betwee ## 3. Product Vocabulary -- **Extension** — an Agent Canvas npm package with an `agent-canvas.extension.json` manifest. -- **Extension Host** — local Node HTTP service launched by `agent-canvas`; installs, validates, and serves extension packages. -- **SDK Plugin** — the OpenHands SDK plugin format (`.plugin/plugin.json`), containing skills, hooks, MCP config, agents, and slash commands. Extensions may bundle or reference SDK plugins. +- **Canvas Extension** — an Agent Canvas-specific npm package with an `agent-canvas.extension.json` manifest. It extends the Agent Canvas UI, UX, and local product behavior. +- **Canvas Extension Host** — local Node HTTP service launched by `agent-canvas`; installs, validates, and serves Canvas Extension packages. +- **OpenHands Extensions Catalog** — the existing `@openhands/extensions` package/repository that provides shared OpenHands assets such as integrations/catalog data. It is not the Canvas Extension format and should not be treated as an installable Canvas Extension package by default. +- **SDK Plugin** — the OpenHands SDK plugin format (`.plugin/plugin.json`), containing skills, hooks, MCP config, agents, and slash commands. Canvas Extensions may bundle or reference SDK plugins. - **MCP Server** — external tool server config installed into agent settings. - **Skill** — an OpenHands skill loaded by the SDK; usually delivered via an SDK plugin rather than copied into user skill directories. - **Tool Visualizer** — a React renderer for one action/observation kind or one more specific tool variant, selected before the built-in markdown fallback. -- **Conversation Slot** — a named insertion point in the conversation UI such as header actions, sidebar widgets, footer indicators, badges, or event renderer hooks. +- **Settings Panel** — a Canvas Extension-owned settings surface shown only under a visible Extensions header after the built-in settings sections. +- **Conversation Right Panel** — a Canvas Extension-owned panel in the conversation work area alongside built-in Files, Browser, and Terminal panels. -User-facing UI should use the word "Extension." Avoid "plugin" in the Canvas product surface because the SDK already owns that term. +User-facing UI should use "Canvas Extension" when the distinction matters, and may shorten to "Extension" only inside an already-labeled Canvas Extensions area. Avoid "plugin" in the Canvas product surface because the SDK already owns that term. Avoid naming top-level app routes or hubs "Extensions" because the product already renamed that old section to Customize. ## 4. Goals @@ -44,7 +46,7 @@ User-facing UI should use the word "Extension." Avoid "plugin" in the Canvas pro - Management commands work from a globally installed npm package without starting the full stack and without requiring the static `build/` directory. - Keeps the Agent Server unmodified for MVP. - Aligns with the OpenHands SDK plugin format rather than inventing a competing format. -- Lets one extension package ship UI plus agent-runtime descriptors. +- Lets one Canvas Extension package ship UI plus agent-runtime descriptors. - Third-party executable code is opt-in and auditable. - Local development works without rebuilding Agent Canvas. - Preserves current Skills, MCP, Launch, Automations, and Settings flows. @@ -57,7 +59,7 @@ User-facing UI should use the word "Extension." Avoid "plugin" in the Canvas pro - No broad root-mounted route-less JavaScript surface lands before narrower slot APIs prove the need. Tool visualizers are the first route-less JS surface because the registry seam already exists and the fallback behavior is well defined. - No repository-local files may modify the running Canvas browser experience simply because a conversation is attached to that repository. - No npm install scripts run by default during extension installation. -- No standalone SDK plugin, standalone `SKILL.md`, or MCP template artifact becomes independently active in the first MVP slice; those artifacts must be wrapped by an Agent Canvas extension until their product UX is designed. +- No standalone SDK plugin, standalone `SKILL.md`, or MCP template artifact becomes independently active in the first MVP slice; those artifacts must be wrapped by a Canvas Extension until their product UX is designed. - No public marketplace, ratings, payments, reviews, or remote trust service in the first PRs. ## 6. Prior Art @@ -68,7 +70,7 @@ Two existing systems informed this design. **Pi (pi.dev).** We adopt: a single generic `install` verb that detects artifact type, multiple authoring shapes (single directory, packaged module, package wrapping skills/SDK plugins/MCP templates), a developer reload story for explicitly registered local extensions, and clear language that local extensions are code requiring trust. We do **not** adopt Pi's repository-auto-discovery of extensions, its in-process tool registration, or its built-in tool overriding — those collapse the local/remote runtime boundary that Canvas must preserve. -A prior internal prototype (`dv/addons-clean-v1`) contributed the typed host SDK shape, manifest validation, stable extension IDs, route host with error boundary, sidebar ordering, and path-normalization patterns. It is not merge-ready: it assumes a Vite build-time registry, which a globally-installed CLI cannot regenerate per install. The current tree no longer has a `src/addons/` implementation to remove; PR 0 should simply keep the public extension namespace under `src/extensions/` so the old prototype vocabulary does not leak back in. +A prior internal prototype (`dv/addons-clean-v1`) contributed the typed host SDK shape, manifest validation, stable extension IDs, route host with error boundary, sidebar ordering, and path-normalization patterns. It is not merge-ready: it assumes a Vite build-time registry, which a globally-installed CLI cannot regenerate per install. The current tree no longer has a `src/addons/` implementation to remove; PR 0 should use the `src/canvas-extensions/` namespace so neither the old `addons` vocabulary nor the old app-level "Extensions" section leaks back in. ## 7. Architecture @@ -82,8 +84,8 @@ flowchart LR CLI --> AS["Agent Server"] CLI --> UI["Static or Vite Canvas frontend"] - Ingress -->|"/api/canvas/installations/*"| Host - Ingress -->|"/canvas-extensions/*"| Host + Ingress -->|"/api/canvas/canvas-extensions/*"| Host + Ingress -->|"/canvas-extension-assets/*"| Host Ingress -->|"/api/automation/*"| Automation["Automation backend"] Ingress -->|"/api/* and /sockets"| AS Ingress -->|"/*"| UI @@ -154,15 +156,15 @@ agent-canvas install ../my-extension --dev Detection order: -1. `package.json` has `agentCanvas.manifest` or an `agent-canvas.extension.json` file is present → Agent Canvas Extension. +1. `package.json` has `agentCanvas.manifest` or an `agent-canvas.extension.json` file is present → Canvas Extension. 2. `.plugin/plugin.json` is present → OpenHands SDK plugin reference. 3. `SKILL.md` is present → skill. 4. Future MCP template manifest → MCP template. -5. Multiple markers present → the explicit Agent Canvas Extension manifest wins (it may intentionally wrap SDK plugin, skill, or MCP contributions). +5. Multiple markers present → the explicit Canvas Extension manifest wins (it may intentionally wrap SDK plugin, skill, or MCP contributions). -MVP behavior: Agent Canvas Extension artifacts install and can be enabled. Standalone SDK plugin, skill, or MCP-template detection returns a typed "detected but unsupported in MVP" diagnostic unless the artifact is wrapped by an extension manifest. This keeps the single `install` verb and detector extensible without silently mutating SDK/user skill state before Canvas has the matching management UX. +MVP behavior: Canvas Extension artifacts install and can be enabled. Standalone SDK plugin, skill, or MCP-template detection returns a typed "detected but unsupported in MVP" diagnostic unless the artifact is wrapped by a Canvas Extension manifest. This keeps the single `install` verb and detector extensible without silently mutating SDK/user skill state before Canvas has the matching management UX. -For extensions, install validates the manifest, shows permissions, installs with scripts denied by default, and enables the extension after consent. Non-interactive flags: +For Canvas Extensions, install validates the manifest, shows permissions, installs with scripts denied by default, and enables the extension after consent. Non-interactive flags: ```sh agent-canvas install @acme/agent-canvas-github --yes @@ -176,7 +178,7 @@ Default is `--install-scripts=deny` (npm `--ignore-scripts`). ```sh agent-canvas list -agent-canvas list extensions +agent-canvas list canvas-extensions agent-canvas enable acme.github agent-canvas disable acme.github agent-canvas remove acme.github @@ -247,7 +249,7 @@ The manifest path must resolve inside the package root. Path traversal is reject The host package must publish everything the global CLI and extension authors need: - `bin/agent-canvas.mjs`, launcher scripts, static `build/`, and any Extension Host scripts must be included by `package.json#files`. -- Extension author types must be exported as `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers`. The release build must emit those files under `dist/extensions/*` and `dist/visualizers/*` and add matching subpath exports. +- Extension author types must be exported as `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers`. The release build must emit those files under `dist/extensions/*` and `dist/visualizers/*` and add matching subpath exports. - If JSON Schemas are advertised by URL or package path, the schemas must either be served remotely or included in the npm package. Do not point authors at files that are excluded by `package.json#files`. - Example extensions under `examples/` are source-repo fixtures unless `package.json#files` explicitly includes them. The acceptance path for a globally installed package should use either a packed fixture tarball or a separately published example package, not an unpublished repo-local path. - Every release candidate should run `npm pack --dry-run` or an equivalent packed-tarball smoke so missing `build`, `scripts`, `dist/extensions`, `dist/visualizers`, or schema files are caught before publish. @@ -273,7 +275,7 @@ The host package must publish everything the global CLI and extension authors ne }, "activationEvents": ["onStartup"], "permissions": { - "ui": ["views", "toolVisualizers"], + "ui": ["views", "leftNavigation", "settingsPanels", "conversationPanels", "toolVisualizers"], "agent": ["sdkPlugins", "context"], "mcp": ["templates"], "network": ["https://api.github.com"] @@ -295,6 +297,23 @@ The host package must publish everything the global CLI and extension authors ne } } ], + "settingsPanels": [ + { + "id": "github-settings", + "title": "GitHub", + "viewId": "dashboard", + "order": 200 + } + ], + "conversationRightPanels": [ + { + "id": "github-pr-context", + "title": "PR Context", + "viewId": "dashboard", + "icon": "./dist/github.svg", + "order": 200 + } + ], "toolVisualizers": [ { "id": "github-pr-check", @@ -373,7 +392,7 @@ The MVP implements only `browser.module`. The `browser.entry` field is reserved Designing the manifest, asset-serving routes, and runtime API around an async, host-mediated surface from the start means the iframe runtime is a future additive PR — not a redesign. Specifically: - The API in §13 is defined async-only, so the same interface is satisfiable by direct function calls (inline mode) or by a postMessage proxy (iframe mode). -- The `/canvas-extensions/:id/*` asset-serving route already exists in §17 and serves whichever bundle the extension declares. +- The `/canvas-extension-assets/:id/*` asset-serving route already exists in §17 and serves whichever bundle the extension declares. - Manifest validation in PR 0 will accept `browser.entry` as a syntactically valid field but emit a validation error (`browser.entry is reserved and not yet supported`) until the iframe runtime ships, preventing accidental shipping of extensions that depend on an unimplemented mode. This is the minimum coverage needed today to keep the iframe option open later at low cost. @@ -382,7 +401,7 @@ This is the minimum coverage needed today to keep the iframe option open later a ### 12.1 `views` -Routes rendered by Canvas at `/extensions/:extensionId/:viewId/*`. +Routes rendered by Canvas at `/canvas-extensions/:extensionId/:viewId/*`. Views may optionally declare a navigation entry: @@ -399,10 +418,67 @@ Views may optionally declare a navigation entry: MVP navigation locations: -- `primarySidebar` — top-level Canvas sidebar entry. MVP supports the `afterAutomations` slot, rendered after Automations and before the conversation list. This covers dashboard-style extensions such as a Cost Dashboard. -- `extensions` — entry inside the Customize / Extensions hub. Use this for management/configuration views or lower-priority extension pages. +- `primarySidebar` — top-level left navigation entry. MVP supports the `afterAutomations` slot, rendered after Automations and before the conversation list. This covers dashboard-style Canvas Extensions such as a Cost Dashboard. +- `customize` — entry inside the existing Customize area. Use this for management/configuration views or lower-priority extension pages. Do not reintroduce a top-level "Extensions" hub; the old Extensions section has been renamed to Customize. + +Primary sidebar entries are for user-facing app surfaces, not transient launch templates. They must point at a Canvas Extension view route, use a static icon asset from the extension package, and remain disabled when the extension is disabled or invalid. If multiple enabled Canvas Extensions contribute to the same slot, Canvas sorts by `order`, then extension display name, then view ID for deterministic rendering. + +### 12.2 `settingsPanels` + +Settings panels let a Canvas Extension add configuration UI without owning a full route. They are intentionally constrained to one visible location: after all built-in settings sections, under an explicit **Extensions** header inside the current settings experience. + +Manifest contribution shape: + +```json +{ + "contributes": { + "settingsPanels": [ + { + "id": "acme.github.settings", + "title": "GitHub", + "viewId": "dashboard", + "order": 200 + } + ] + } +} +``` + +Rules: + +- Canvas Extension settings panels never appear interleaved with built-in settings. They render only under the visible Extensions header after other settings. +- The panel renderer may reuse a declared `viewId` or a dedicated browser module entry, but Canvas owns the settings chrome, heading, save/cancel affordances, loading state, and error boundary. +- Settings panels receive only extension metadata/settings, schema-derived values, and host APIs for `readExtensionSettings()` / `patchExtensionSettings()`. They do not receive raw global settings stores. +- If an extension is disabled or invalid, its settings panel is omitted and its persisted settings remain in the install store until removal/purge. -Primary sidebar entries are for user-facing app surfaces, not transient launch templates. They must point at an extension view route, use a static icon asset from the extension package, and remain disabled when the extension is disabled or invalid. If multiple enabled extensions contribute to the same slot, Canvas sorts by `order`, then extension display name, then view ID for deterministic rendering. +### 12.3 `conversationRightPanels` + +Conversation right panels let a Canvas Extension add a work surface beside the built-in Files, Browser, and Terminal panels. They are for conversation-adjacent tools such as PR context, cost dashboards, deployment status, or domain-specific inspectors. + +Manifest contribution shape: + +```json +{ + "contributes": { + "conversationRightPanels": [ + { + "id": "acme.github.pr-context", + "title": "PR Context", + "viewId": "dashboard", + "icon": "./dist/github.svg", + "order": 200 + } + ] + } +} +``` + +Rules: + +- Panels appear in the existing conversation right-panel/tab system alongside Files, Browser, and Terminal, not as arbitrary overlays or app-root components. +- Panels receive active conversation ID/status, selected workspace summary, backend summary, and narrow host APIs for navigation/toasts/settings. +- Canvas owns panel tab placement, collapsed/expanded behavior, focus management, loading state, and error boundaries. +- Panels must degrade cleanly when no conversation is active, when the extension is disabled, or when the current runtime does not support an agent-side contribution. **MVP runtime: trusted same-origin browser module.** The extension's `browser.module` is loaded by the Canvas frontend with dynamic `import()` and mounted into a Canvas-owned DOM container at the view route. The extension exports a `mount()` function, receives the typed context (§13), and owns rendering inside that container. It may use React, Svelte, vanilla DOM, or another framework as long as the shipped module is browser-ready. It should use Canvas CSS variables/design tokens and host-provided APIs instead of importing Canvas internals. @@ -412,8 +488,8 @@ Inline extension code runs in the same browser origin as Canvas. That is intenti - `browser.module` must point to a built ESM file that a browser can import directly. - The Extension Host serves static files only; it does not transpile TypeScript/JSX or run bundlers for installed packages. -- Runtime imports must be relative URLs inside the extension asset tree or bundled into the module. Bare runtime imports such as `react`, `@heroui/react`, `@openhands/agent-canvas/extensions`, or `@openhands/agent-canvas/visualizers` are rejected for MVP because the packaged CLI cannot guarantee a Vite resolver or shared dependency graph. -- Type-only imports from `@openhands/agent-canvas/extensions` are allowed in author source and erased by the extension's build. Author source may import `@openhands/agent-canvas/visualizers`, but emitted browser modules must bundle those helpers rather than leaving a bare package import. +- Runtime imports must be relative URLs inside the extension asset tree or bundled into the module. Bare runtime imports such as `react`, `@heroui/react`, `@openhands/agent-canvas/canvas-extensions`, or `@openhands/agent-canvas/canvas-visualizers` are rejected for MVP because the packaged CLI cannot guarantee a Vite resolver or shared dependency graph. +- Type-only imports from `@openhands/agent-canvas/canvas-extensions` are allowed in author source and erased by the extension's build. Author source may import `@openhands/agent-canvas/canvas-visualizers`, but emitted browser modules must bundle those helpers rather than leaving a bare package import. - The default export shape is: ```ts @@ -433,7 +509,7 @@ This DOM-island contract avoids React shared-instance problems in static builds **Reserved future runtime: sandboxed iframe.** The manifest reserves `browser.entry` (§11.1) for a future iframe runtime intended for untrusted third-party extensions (e.g., a community marketplace). The iframe sandbox would be `sandbox="allow-scripts allow-forms allow-downloads"` with no `allow-same-origin`, communicating with the parent through a postMessage RPC bridge that exposes the same context API. This is not implemented in MVP; declaring it now keeps the option open without committing implementation cost today. -### 12.2 `agentPlugins` +### 12.4 `agentPlugins` Maps to OpenHands SDK `PluginSource` values. Fields: `id`, `source` (package-relative path, GitHub shorthand, Git URL, or absolute path for dev mode), `ref` (optional branch/tag/commit), `repoPath` (optional subdirectory for remote git/GitHub sources), `autoInclude` (`manual` | `enabled` | `always`; default `manual`). @@ -449,7 +525,7 @@ The current frontend `PluginSpec.parameters` field is collected by `/launch` but **ACP constraint.** SDK plugins can merge skills, MCP config, hooks, and plugin agents before agent initialization. MCP config and other non-ACP-compatible fields can cause `ACPAgent` initialization to fail. MVP should skip `agentPlugins` for ACP runtimes unless the SDK smoke proves a prompt/skills-only plugin is accepted; extension context suffixes remain the compatible path for ACP. -### 12.3 `mcpServers` +### 12.5 `mcpServers` Extensions provide MCP templates, never silent installs. The UI shows required MCPs before launch and reuses the existing `InstallServerModal` pattern. @@ -457,17 +533,17 @@ Extensions provide MCP templates, never silent installs. The UI shows required M **Current catalog shape.** The MCP marketplace now imports `IntegrationCatalogEntry` from `@openhands/extensions/integrations`, not the old `mcps` export. Extension MCP templates should either use the same `IntegrationTransport` vocabulary or provide an adapter that can project templates into the existing MCP install modal without reintroducing a parallel catalog model. The current install flow can save required secrets through the existing secrets-aware MCP form; extension templates should reuse that path rather than inventing separate secret persistence. -### 12.4 `launchTemplates` +### 12.6 `launchTemplates` Reusable launch presets shown on home, extension pages, or automation setup. May reference required MCP IDs, required agent plugin IDs, optional context blocks, initial prompt text, and workspace requirements. -### 12.5 `conversationContext` +### 12.7 `conversationContext` Context blocks may be appended to `AgentContext.system_message_suffix` only after explicit user enablement. Canvas renders all extension context inside a single block, appended after the existing `` block: ```text -The user enabled the following Agent Canvas extensions for this conversation. +The user enabled the following Canvas Extensions for this conversation. * acme.github/github-guidance When working with GitHub, prefer the configured GitHub MCP server for issue and pull request metadata. @@ -476,7 +552,7 @@ The user enabled the following Agent Canvas extensions for this conversation. Extension context **must not** be merged into `conversationInstructions`; that turns extension guidance into user-message content. An explicit `extensionSystemSuffix` option will be added to the conversation adapter so runtime services and extension context compose in one place. -### 12.6 `toolVisualizers` +### 12.8 `toolVisualizers` Custom event rendering is the first concrete route-less JavaScript extension surface. PR #1246 added the internal `src/components/features/chat/tool-visualizers` registry and dispatcher: `getEventContent()` asks the registry for a React body by action/observation kind, and falls back to the existing markdown renderer when no visualizer matches. Extensions should build on that seam instead of introducing an unrelated event-rendering model. @@ -528,21 +604,13 @@ Selection rules: - If an extension visualizer throws, Canvas catches the error, reports a local diagnostic, and tries the next matching visualizer before falling back to the built-in renderer or markdown fallback. - Visualizer modules use the same trusted local browser-code model as `browser.module`; no sandbox claim is made in MVP. -Public authoring types and primitives should be exported from `@openhands/agent-canvas/visualizers`, separate from the broader `@openhands/agent-canvas/extensions` management contract. The public surface should wrap or re-export stable versions of `defineVisualizer`, `VisualizerProps`, shared primitives such as code blocks/diff/output panes, and action/observation kind types. Do not expose internal file paths under `src/components/features/chat/tool-visualizers/*` as the authoring API. +Public authoring types and primitives should be exported from `@openhands/agent-canvas/canvas-visualizers`, separate from the broader `@openhands/agent-canvas/canvas-extensions` management contract. The public surface should wrap or re-export stable versions of `defineVisualizer`, `VisualizerProps`, shared primitives such as code blocks/diff/output panes, and action/observation kind types. Do not expose internal file paths under `src/components/features/chat/tool-visualizers/*` as the authoring API. -### 12.7 `conversationSlots` +### 12.9 Deferred Generic Conversation Slots -Conversation slots are the next focused extension surface after tool visualizers. They answer the UI-slot feedback in issue #481 without opening arbitrary app-root mutation. Initial slot names should be explicit and few: +Generic conversation slots remain deferred. The concrete MVP surfaces are `conversationRightPanels` and `toolVisualizers`, because they map to existing product regions and existing renderer architecture. Header actions, footer widgets, badges, or arbitrary conversation sidebar content should not ship until there is a specific user workflow that cannot be handled by a right panel or a tool visualizer. -- `conversation.header.actions` — icon/button actions near the active conversation header. -- `conversation.sidebar` — compact widgets in the conversation side area when available. -- `conversation.footer` — status indicators near the chat input/footer region. -- `conversation.badges` — small status chips for backend, workspace, or extension-provided state. -- `conversation.event.visualizers` — alias or manifest sugar for tool visualizer contributions. - -Slot renderers receive a narrow context object: active conversation ID, execution status, active backend summary, workspace summary, and a host API for navigation/toasts/settings. They should not receive raw React Query clients or arbitrary store access as the stable contract. That restriction keeps the API supportable even while the MVP runtime is trusted same-origin code. - -### 12.8 `configuration` and `secrets` +### 12.10 `configuration` and `secrets` `configuration` uses JSON Schema; values are stored in the Extension Host's `config.json`. `secrets` are declarations only; values are stored through the existing `SecretsService`. @@ -590,9 +658,14 @@ interface AgentCanvasExtensionContext { visualizer: AgentCanvasToolVisualizer, ): Promise; }; - conversationSlots: { - registerSlot( - slot: AgentCanvasConversationSlot, + settingsPanels: { + registerSettingsPanel( + panel: AgentCanvasSettingsPanel, + ): Promise; + }; + conversationPanels: { + registerRightPanel( + panel: AgentCanvasConversationRightPanel, ): Promise; }; conversations: { @@ -609,11 +682,11 @@ interface AgentCanvasExtensionContext { } ``` -For the first implementation slice, `visualizers.registerToolVisualizer()` is the only required route-less registration API. `conversationSlots.registerSlot()` can remain typed/reserved until the slot insertion points are implemented. There is **no** supported generic `agentServer.request(path)`. The repo already routes Agent Server traffic through typed service wrappers; the extension surface follows the same pattern with named, permission-declared capabilities. For MVP inline browser modules, this is an API support boundary rather than a browser security boundary (see §19). +For the first implementation slice, the route-less registration APIs are limited to `visualizers.registerToolVisualizer()`, `settingsPanels.registerSettingsPanel()`, and `conversationPanels.registerRightPanel()`. There is **no** supported generic `agentServer.request(path)`. The repo already routes Agent Server traffic through typed service wrappers; the extension surface follows the same pattern with named, permission-declared capabilities. For MVP inline browser modules, this is an API support boundary rather than a browser security boundary (see §19). ## 14. Shared Type Contract -Initial home: `src/extensions/types.ts`, exported as `@openhands/agent-canvas/extensions` once stable. (PR 0 will collapse generic installable-artifact types and extension-specific types into a single directory; splitting them across `src/installations/` and `src/extensions/` is premature before any consumer exists.) +Initial home: `src/canvas-extensions/types.ts`, exported as `@openhands/agent-canvas/canvas-extensions` once stable. (PR 0 will collapse generic installable-artifact types and extension-specific types into a single directory; splitting them across `src/installations/` and `src/canvas-extensions/` is premature before any consumer exists.) Initial types: @@ -675,9 +748,11 @@ export interface InstallableArtifactRegistryEntry { Permission groups: -- `ui.views` — render trusted same-origin browser-module views and their declared navigation entries. +- `ui.views` — render trusted same-origin browser-module views. +- `ui.leftNavigation` — add declared left navigation entries that point at Canvas Extension views. +- `ui.settingsPanels` — add settings panels only under the visible Extensions header after built-in settings. +- `ui.conversationPanels` — add right-side conversation panels alongside Files, Browser, and Terminal. - `ui.toolVisualizers` — register custom action/observation renderers in the conversation event pipeline. -- `ui.conversationSlots` — register named conversation UI slot content when slot support lands. - `ui.commands` — register commands. - `agent.sdkPlugins` — include SDK plugins in conversation launches. - `agent.context` — append extension context to system suffix. @@ -735,7 +810,7 @@ When extension agent-side contributions are included, Canvas appends a short run ```text -Agent Canvas extensions are installed in the local Agent Canvas client. +Canvas Extensions are installed in the local Agent Canvas client. Conversation runtime: agent-server-remote. Do not assume local extension package paths are available inside the runtime. Only use extension-provided capabilities explicitly listed in this prompt or exposed through runtime tools/settings. @@ -749,24 +824,24 @@ Contains no secrets. Local absolute paths are included only when the runtime is Mounted through ingress **before** `/api/*` is forwarded to the Agent Server, using the same precedence pattern already in place for `/api/automation/*` ([scripts/dev-with-automation.mjs](scripts/dev-with-automation.mjs) around the existing automation routes): ```text -GET /api/canvas/installations/registry -GET /api/canvas/installations/diagnostics -POST /api/canvas/installations/install -POST /api/canvas/installations/:id/enable -POST /api/canvas/installations/:id/disable -DELETE /api/canvas/installations/:id -PATCH /api/canvas/installations/:id/settings -GET /api/canvas/installations/launch-contributions -GET /canvas-extensions/:id/*assetPath +GET /api/canvas/canvas-extensions/registry +GET /api/canvas/canvas-extensions/diagnostics +POST /api/canvas/canvas-extensions/install +POST /api/canvas/canvas-extensions/:id/enable +POST /api/canvas/canvas-extensions/:id/disable +DELETE /api/canvas/canvas-extensions/:id +PATCH /api/canvas/canvas-extensions/:id/settings +GET /api/canvas/canvas-extensions/launch-contributions +GET /canvas-extension-assets/:id/*assetPath ``` -Note the consistent `/api/canvas/installations/*` prefix for management — including `launch-contributions`, which was previously inconsistent. Asset serving keeps its own `/canvas-extensions/*` prefix because it serves untrusted third-party static files and benefits from being easy to identify in logs and CSP rules. +Note the consistent `/api/canvas/canvas-extensions/*` prefix for management — including `launch-contributions`, which was previously inconsistent. Asset serving keeps its own `/canvas-extension-assets/*` prefix because it serves untrusted third-party static files and benefits from being easy to identify in logs and CSP rules. The `install` route uses the same artifact detector as `agent-canvas install`. Mutating routes require the existing session API key. -Frontend code must call these routes only through a dedicated `src/api/extensions-service.ts` wrapper. Because the route prefix begins with `/api/` but targets the local Extension Host rather than the Agent Server, PR 2 must update `src/api/no-direct-agent-server-calls.test.ts` with a narrow allowlist entry for that wrapper, mirroring the existing automation-service exception. This keeps the `/api/canvas/installations/*` ingress shape while preserving the repo rule that ordinary Agent Server traffic goes through `@openhands/typescript-client`. +Frontend code must call these routes only through a dedicated `src/api/canvas-extensions-service.ts` wrapper. Because the route prefix begins with `/api/` but targets the local Extension Host rather than the Agent Server, PR 2 must update `src/api/no-direct-agent-server-calls.test.ts` with a narrow allowlist entry for that wrapper, mirroring the existing automation-service exception. This keeps the `/api/canvas/canvas-extensions/*` ingress shape while preserving the repo rule that ordinary Agent Server traffic goes through `@openhands/typescript-client`. -Implementation note: the current guard has an axios allowlist and a separate blanket `fetch('/api/...')` check. Adding `src/api/extensions-service.ts` to the axios allowlist is not enough if the wrapper uses `fetch`; the test must explicitly allow only `/api/canvas/installations/*` calls from that wrapper, or the wrapper must use an approved local helper. Do not weaken the guard for arbitrary `/api/*` traffic. +Implementation note: the current guard has an axios allowlist and a separate blanket `fetch('/api/...')` check. Adding `src/api/canvas-extensions-service.ts` to the axios allowlist is not enough if the wrapper uses `fetch`; the test must explicitly allow only `/api/canvas/canvas-extensions/*` calls from that wrapper, or the wrapper must use an approved local helper. Do not weaken the guard for arbitrary `/api/*` traffic. **Routing requirement:** the Extension Host route table must be implemented for every launch mode in the same PR that starts the host (Vite dev proxy, automation ingress, static serving path, and packaged CLI). @@ -784,7 +859,7 @@ Registry response: "version": "0.1.0", "state": "enabled", "manifest": {}, - "assetBaseUrl": "/canvas-extensions/acme.github/", + "assetBaseUrl": "/canvas-extension-assets/acme.github/", "diagnostics": [] } ] @@ -796,7 +871,7 @@ Registry response: An extension contribution merge step runs before `buildStartConversationRequestWithEncryptedSettings()`: 1. `useCreateConversation()` receives optional extension launch selections. -2. `AgentServerConversationService.createConversation()` asks `ExtensionsService.getLaunchContributions()` for globally enabled contributions (unless extensions are disabled). +2. `AgentServerConversationService.createConversation()` asks `CanvasExtensionsService.getLaunchContributions()` for globally enabled contributions (unless extensions are disabled). 3. Merge selected/auto-included SDK plugin specs with existing `plugins`. 4. Merge selected extension context into the new `extensionSystemSuffix` adapter option. 5. `agent-server-adapter.ts` appends extension context to `AgentContext.system_message_suffix`. @@ -902,7 +977,7 @@ The future flow: an agent builds an extension in the workspace, proposes install **New pieces required.** A Canvas-facing `canvas_extension.propose_install` tool module (proposal-only, no server-side install executor); a frontend handler for proposal ActionEvents with an approval modal; an Extension Host endpoint accepting an approved proposal and re-validating; conversation-artifact download path; permission-diff UI; registry refresh and extension-view remount after install. -**Safety rules.** Proposal alone is not sufficient — user approval required. No write-capable Extension Host key in `` or the prompt. The agent cannot call `/api/canvas/installations/install` directly. Extension Host re-validates the downloaded artifact; proposal payload is advisory. Conversation artifacts are size-limited and checksum-verified when `sha256` is provided. Permission expansion follows the same re-approval rules as CLI updates. +**Safety rules.** Proposal alone is not sufficient — user approval required. No write-capable Extension Host key in `` or the prompt. The agent cannot call `/api/canvas/canvas-extensions/install` directly. Extension Host re-validates the downloaded artifact; proposal payload is advisory. Conversation artifacts are size-limited and checksum-verified when `sha256` is provided. Permission expansion follows the same re-approval rules as CLI updates. This is not part of PR 0 or PR 1. It becomes feasible after the local install store, artifact detector, Extension Host, and Packages UI exist. @@ -924,7 +999,7 @@ Out of scope upstream: a generic server-side Canvas extension loader; server-sid ## 24. Frontend UX -### Extensions hub +### Customize And Canvas Extensions Sections: @@ -932,7 +1007,9 @@ Sections: - MCP Servers - Packages -Current navigation state: `/customize` is the Extensions hub entry in the primary sidebar; desktop redirects to `/skills`, mobile renders the hub; `/skills` and `/mcp` are live; `/plugins` is still a coming-soon page inside `ExtensionsNavigation`. MVP should replace `/plugins` with Packages or redirect it to the new Packages route while retaining old bookmarks. +Current navigation state: `/customize` is the Customize entry in the primary sidebar; desktop redirects to `/skills`, mobile renders the Customize hub; `/skills` and `/mcp` are live; `/plugins` is still a coming-soon page inside the current Customize navigation. MVP should replace `/plugins` with Packages or redirect it to the new Packages route while retaining old bookmarks. Do not rename the Customize section back to Extensions. + +Implementation note: some current files still use legacy "extensions" names, such as `src/routes/extensions-hub.tsx` and `src/components/features/skills/extensions-navigation.tsx`. Treat those as existing Customize implementation details. New Canvas Extension code should use `canvas-extensions` names, and user-facing copy should say Customize or Canvas Extensions as appropriate. Packages page sections: Enabled · Installed but disabled · Invalid · Install from npm/package spec · Developer extension path. @@ -946,6 +1023,14 @@ For launch templates: show required MCPs; show SDK plugins to be included; show Enabled views with `navigation.location: "primarySidebar"` render as top-level Sidebar entries. For MVP, entries appear in the `afterAutomations` slot: after Automations and before the conversation list, which is the current static order in `SidebarRailBody` (`New Chat`, `Customize`, `Automations`, then `SidebarConversationList`). Collapsed Sidebar mode shows only the icon with a tooltip. Mobile drawer mode shows the same entry in the same relative position. +### Settings panel UX + +Canvas Extension settings panels appear only in the settings experience, grouped under a visible Extensions header after built-in settings sections. Canvas owns the settings page chrome and persistence affordances; extension renderers fill only their declared panel body. + +### Conversation right-panel UX + +Enabled `conversationRightPanels` render in the existing conversation right-panel/tab area beside Files, Browser, and Terminal. Canvas owns tab placement, keyboard/focus behavior, collapsed state, loading state, and error boundaries. + ### Extension view UX Extension views look like regular Canvas pages but are visibly extension-owned: title from manifest; small extension badge; browser-module loading/error states; diagnostics link. @@ -956,53 +1041,53 @@ The multi-agent, checkable execution checklist for this plan lives in [Extension ### PR 0 — Contract and types -Files: `docs/ExtensionsSystemRFC.md`, `src/extensions/types.ts`, `src/extensions/manifest-schema.ts`, `src/extensions/manifest-validation.ts`, `src/extensions/artifact-detection.ts`, `__tests__/extensions/manifest-validation.test.ts`. +Files: `docs/ExtensionsSystemRFC.md`, `src/canvas-extensions/types.ts`, `src/canvas-extensions/manifest-schema.ts`, `src/canvas-extensions/manifest-validation.ts`, `src/canvas-extensions/artifact-detection.ts`, `__tests__/canvas-extensions/manifest-validation.test.ts`. -Deliverables: shared manifest and registry types; generic installable artifact kind definitions; contribution types for views, visualizers, slots, launch templates, SDK plugins, MCP templates, and context; artifact detection rules for extension packages, SDK plugins, skills, and placeholder MCP templates; schema-version constant; manifest validation helpers; storage-path helper for `~/.openhands/agent-canvas/installations`; fixtures for valid/invalid manifests; `@openhands/agent-canvas/extensions` and `@openhands/agent-canvas/visualizers` subpath export plan wired to generated `dist/extensions/*` and `dist/visualizers/*`; release packaging checklist. No Extension Host, no frontend UI, no agent contribution merging. +Deliverables: shared manifest and registry types; generic installable artifact kind definitions; contribution types for views, left navigation, settings panels, conversation right panels, tool visualizers, launch templates, SDK plugins, MCP templates, and context; artifact detection rules for extension packages, SDK plugins, skills, and placeholder MCP templates; schema-version constant; manifest validation helpers; storage-path helper for `~/.openhands/agent-canvas/installations`; fixtures for valid/invalid manifests; `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` subpath export plan wired to generated `dist/extensions/*` and `dist/visualizers/*`; release packaging checklist. No Extension Host, no frontend UI, no agent contribution merging. -**Iframe-runtime forward compatibility (minimal, PR 0 scope):** the manifest schema and TypeScript contract accept both `browser.module` (active) and `browser.entry` (reserved). The validator recognizes `browser.entry` as syntactically valid but emits a `reserved-not-yet-supported` diagnostic so authors can't accidentally ship extensions that depend on an unimplemented runtime. The API contract in `src/extensions/types.ts` is defined async-only, which is the only change needed so that a future iframe runtime can satisfy the same interface over postMessage without an API redesign. No iframe host, no postMessage bridge, no asset-mode switching, and no parallel runtime code lands here — that work is deferred to a post-MVP PR triggered by one of the trust-boundary conditions in §27 Decision 11. The total PR 0 cost of keeping the option open is a handful of schema fields, one validation rule, one fixture, and a short authoring note in the type comments. +**Iframe-runtime forward compatibility (minimal, PR 0 scope):** the manifest schema and TypeScript contract accept both `browser.module` (active) and `browser.entry` (reserved). The validator recognizes `browser.entry` as syntactically valid but emits a `reserved-not-yet-supported` diagnostic so authors can't accidentally ship extensions that depend on an unimplemented runtime. The API contract in `src/canvas-extensions/types.ts` is defined async-only, which is the only change needed so that a future iframe runtime can satisfy the same interface over postMessage without an API redesign. No iframe host, no postMessage bridge, no asset-mode switching, and no parallel runtime code lands here — that work is deferred to a post-MVP PR triggered by one of the trust-boundary conditions in §27 Decision 11. The total PR 0 cost of keeping the option open is a handful of schema fields, one validation rule, one fixture, and a short authoring note in the type comments. -**Namespace guard:** keep new types and runtime helpers under `src/extensions/`; do not recreate the older `src/addons/` namespace. +**Namespace guard:** keep new types and runtime helpers under `src/canvas-extensions/`; do not recreate the older `src/addons/` namespace. ### PR 1 — Runtime registry and CLI -Files: `bin/agent-canvas.mjs`, `scripts/dev-safe.mjs`, `scripts/dev-with-automation.mjs`, `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, extension config helper, tests under `__tests__/extensions/`. +Files: `bin/agent-canvas.mjs`, `scripts/dev-safe.mjs`, `scripts/dev-with-automation.mjs`, `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, extension config helper, tests under `__tests__/canvas-extensions/`. -Deliverables: generic install/manage CLI parsing; `install/list/enable/disable/remove/update/doctor` dispatched before stack startup, before `--public` / `--frontend-only` / `--backend-only` stack validation, before the static `build/` existence check, and before importing stack launcher scripts; install store under `~/.openhands/agent-canvas/installations/`; artifact detection for all kinds with independent install/enable support limited to Agent Canvas Extension manifests; typed unsupported diagnostics for standalone SDK plugins, skills, and MCP templates; npm install with `--ignore-scripts`; manifest validation; Extension Host startup; ingress routes for `/api/canvas/installations/*` and `/canvas-extensions/*` across all launch modes (Vite dev proxy, automation ingress, static serving, packaged CLI, plus the current frontend-only/backend-only partial-stack modes); dev extension registration store and `--dev` registration; launcher-issued `localInstallStoreReadable` capability flag; example package fixture using SDK plugin and context contributions; `npm pack --dry-run` or packed-tarball smoke for the global CLI path. +Deliverables: generic install/manage CLI parsing; `install/list/enable/disable/remove/update/doctor` dispatched before stack startup, before `--public` / `--frontend-only` / `--backend-only` stack validation, before the static `build/` existence check, and before importing stack launcher scripts; install store under `~/.openhands/agent-canvas/installations/`; artifact detection for all kinds with independent install/enable support limited to Canvas Extension manifests; typed unsupported diagnostics for standalone SDK plugins, skills, and MCP templates; npm install with `--ignore-scripts`; manifest validation; Extension Host startup; ingress routes for `/api/canvas/canvas-extensions/*` and `/canvas-extension-assets/*` across all launch modes (Vite dev proxy, automation ingress, static serving, packaged CLI, plus the current frontend-only/backend-only partial-stack modes); dev extension registration store and `--dev` registration; launcher-issued `localInstallStoreReadable` capability flag; example package fixture using SDK plugin and context contributions; `npm pack --dry-run` or packed-tarball smoke for the global CLI path. ### PR 2 — Frontend management UI -Files: `src/routes.ts`, `src/routes/extensions-packages.tsx`, `src/components/features/skills/extensions-navigation.tsx`, `src/api/extensions-service.ts`, `src/api/no-direct-agent-server-calls.test.ts`, `src/hooks/query/use-extensions.ts`, i18n. +Files: `src/routes.ts`, `src/routes/canvas-extensions-packages.tsx`, legacy Customize navigation files such as `src/components/features/skills/extensions-navigation.tsx`, `src/api/canvas-extensions-service.ts`, `src/api/no-direct-agent-server-calls.test.ts`, `src/hooks/query/use-extensions.ts`, i18n. -Deliverables: Packages page replaces "Plugins coming soon"; install/enable/disable/remove flows; dedicated ExtensionsService wrapper and narrow no-direct-Agent-Server test exception for `/api/canvas/installations/*`; dev extension badges, source paths, diagnostics; snapshot coverage for empty/installed/enabled/invalid states. +Deliverables: Packages page under Customize replaces "Plugins coming soon"; install/enable/disable/remove flows; dedicated CanvasExtensionsService wrapper and narrow no-direct-Agent-Server test exception for `/api/canvas/canvas-extensions/*`; dev extension badges, source paths, diagnostics; snapshot coverage for empty/installed/enabled/invalid states. Do not reintroduce a top-level Extensions section. ### PR 3 — Trusted browser-module view host -Files: `src/routes/extension-view-host.tsx`, `src/extensions/runtime/*`, sidebar components, `scripts/extension-host.mjs`. +Files: `src/routes/extension-view-host.tsx`, `src/canvas-extensions/runtime/*`, sidebar components, `scripts/extension-host.mjs`. -Deliverables: route `/extensions/:extensionId/:viewId/*`; DOM-container host for trusted `browser.module` extensions; dynamic import with cache-busting support; browser asset serving; `mount()`/`dispose()` lifecycle; primary-sidebar navigation entries for views; minimal `navigation` and `ui.toast` APIs; example extension package with a bundled browser-ready module. +Deliverables: route `/canvas-extensions/:extensionId/:viewId/*`; DOM-container host for trusted `browser.module` extensions; dynamic import with cache-busting support; browser asset serving; `mount()`/`dispose()` lifecycle; left navigation entries for views; minimal `navigation` and `ui.toast` APIs; example extension package with a bundled browser-ready module. ### PR 3a — Extension tool visualizers -Files: `src/components/features/chat/tool-visualizers/*`, `src/extensions/visualizers/*`, `src/api/extensions-service.ts`, `src/routes/extension-view-host.tsx` or equivalent runtime loader, `package.json`, `tsconfig.lib.json`, tests under `__tests__/extensions/` and existing visualizer tests. +Files: `src/components/features/chat/tool-visualizers/*`, `src/canvas-extensions/visualizers/*`, `src/api/canvas-extensions-service.ts`, `src/routes/extension-view-host.tsx` or equivalent runtime loader, `package.json`, `tsconfig.lib.json`, tests under `__tests__/canvas-extensions/` and existing visualizer tests. -Deliverables: public `@openhands/agent-canvas/visualizers` subpath; extension visualizer registry layer that composes before built-ins and then markdown fallback; `priority` and `matches()` support; error boundary/fallback behavior for extension renderers; manifest projection for `contributes.toolVisualizers`; `hello.canvas` or a focused fixture overriding one MCP tool visualizer; unit tests proving extension-first order, built-in fallback, markdown fallback, and throw-to-next-renderer behavior. +Deliverables: public `@openhands/agent-canvas/canvas-visualizers` subpath; extension visualizer registry layer that composes before built-ins and then markdown fallback; `priority` and `matches()` support; error boundary/fallback behavior for extension renderers; manifest projection for `contributes.toolVisualizers`; `hello.canvas` or a focused fixture overriding one MCP tool visualizer; unit tests proving extension-first order, built-in fallback, markdown fallback, and throw-to-next-renderer behavior. -### PR 3b — Conversation slots and badges +### PR 3b — Settings panels and conversation right panels -Files: conversation header/sidebar/footer/badge components, `src/extensions/slots/*`, `src/i18n/translation.json`, tests under `__tests__/extensions/` and focused component tests. +Files: settings route/components, conversation right-panel/tab components, `src/canvas-extensions/panels/*`, `src/i18n/translation.json`, tests under `__tests__/canvas-extensions/` and focused component tests. -Deliverables: narrow slot registry for `conversation.header.actions`, `conversation.sidebar`, `conversation.footer`, and `conversation.badges`; stable slot props with conversation/backend/workspace summaries; no raw store or React Query client in the public API; per-slot error boundaries; deterministic ordering by priority and extension ID; visible examples for a backend badge or workspace status chip. +Deliverables: settings panels under a visible Extensions header after built-in settings; conversation right panels beside Files, Browser, and Terminal; stable props with conversation/backend/workspace summaries where relevant; no raw store or React Query client in the public API; per-panel error boundaries; deterministic ordering by priority/order and extension ID; visible examples for a settings panel and PR/context right panel. ### PR 3c — Dev extension watch mode -Files: `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, `bin/agent-canvas.mjs`, `src/api/extensions-service.ts`, `src/routes/extensions-packages.tsx`, contributor docs. +Files: `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, `bin/agent-canvas.mjs`, `src/api/canvas-extensions-service.ts`, `src/routes/canvas-extensions-packages.tsx`, contributor docs. Deliverables: `--dev` install and `dev-extension` subcommands; dev registration store; source folder file watching; manifest revalidation on change; browser-module cache-bust/remount after asset changes; visible dev-mode labeling; example extension and contributor guide; tests for registration, traversal rejection, invalid manifest updates, and view remount signaling. ### PR 4 — Agent contributions -Files: `src/api/extensions-service.ts`, `src/api/conversation-service/agent-server-conversation-service.api.ts`, `src/api/agent-server-adapter.ts`, `src/hooks/mutation/use-create-conversation.ts`, launch/home components. +Files: `src/api/canvas-extensions-service.ts`, `src/api/conversation-service/agent-server-conversation-service.api.ts`, `src/api/agent-server-adapter.ts`, `src/hooks/mutation/use-create-conversation.ts`, launch/home components. Deliverables: enabled extension SDK plugin specs merge into new conversation payloads; selected extension context blocks append to system suffix via the new `extensionSystemSuffix` option; required MCP preflight UI; cloud/local disabled reasons gated on `localInstallStoreReadable`; tests for payload shape and merge order. @@ -1016,9 +1101,9 @@ Permission consent modal; secret setup flow; `doctor` command with actionable di **Release packaging:** `npm pack --dry-run`; install from the packed tarball into a temp global/prefix; verify `agent-canvas list`, `agent-canvas doctor`, and `agent-canvas install --yes` work without a source checkout; verify full `agent-canvas` launch still finds `build/`, `scripts/`, and Extension Host routes. -**Component:** Packages page states; permission display; browser-module view loading/error; MCP required preflight; incompatible runtime warning; dev badge. +**Component:** Packages page states under Customize; permission display; browser-module view loading/error; settings panel placement under the visible Extensions header; conversation right-panel tab rendering/error boundaries; MCP required preflight; incompatible runtime warning; dev badge. -**E2E snapshots:** Packages page empty state; enabled extension with one view; invalid extension diagnostics; launch template requiring MCP; launch preflight with local-path SDK plugin disabled on remote backend; dev extension view remount after file change; future: agent-mediated install proposal flow. +**E2E snapshots:** Packages page empty state under Customize; enabled extension with one view; invalid extension diagnostics; settings panel under Extensions header; conversation right panel beside Files/Browser/Terminal; launch template requiring MCP; launch preflight with local-path SDK plugin disabled on remote backend; dev extension view remount after file change; future: agent-mediated install proposal flow. **Live E2E:** not required for MVP. If added later, follow existing live E2E rules under `tests/e2e/live/`. @@ -1037,18 +1122,18 @@ These were open questions in the earlier draft. Decisions for the RFC: | 1 | Web UI install in MVP, or CLI-only first? | **CLI-first.** Web UI install lands in PR 2 once CLI plumbing is proven; CLI remains the supported automation surface. | | 2 | Iframe sandbox vs inline browser modules for extension views? | **Trusted same-origin browser modules as MVP default** (`browser.module`); reserve `browser.entry` in the manifest schema for a future sandboxed-iframe runtime aimed at untrusted/marketplace extensions. Trust comes from explicit install/enable consent, not browser-enforced isolation. See §11.1, §12.1, and §19. | | 3 | What signals an Agent Server is filesystem-local? | **Launcher-issued `localInstallStoreReadable` capability flag.** `backend.kind === "local"` alone is insufficient. | -| 4 | `src/installations/` vs `src/extensions/` directory split? | **Single `src/extensions/` directory** until a real consumer requires the split. | -| 5 | API route prefix consistency? | **`/api/canvas/installations/*`** for all management routes; `/canvas-extensions/*` reserved for asset serving. | -| 6 | Empty `src/addons/` directory on `main`? | **No longer present.** PR 0 should avoid reviving the old `addons` namespace and use `src/extensions/`. | -| 7 | Should the first MVP independently install standalone SDK plugins, standalone skills, or MCP templates? | **No.** Detect them and return explicit unsupported diagnostics; require an Agent Canvas extension wrapper for MVP. | +| 4 | `src/installations/` vs `src/canvas-extensions/` directory split? | **Single `src/canvas-extensions/` directory** until a real consumer requires the split. | +| 5 | API route prefix consistency? | **`/api/canvas/canvas-extensions/*`** for all management routes; `/canvas-extension-assets/*` reserved for asset serving. | +| 6 | Empty `src/addons/` directory on `main`? | **No longer present.** PR 0 should avoid reviving the old `addons` namespace and use `src/canvas-extensions/`. | +| 7 | Should the first MVP independently install standalone SDK plugins, standalone skills, or MCP templates? | **No.** Detect them and return explicit unsupported diagnostics; require an Canvas Extension wrapper for MVP. | | 8 | Should package-relative SDK plugin paths be MVP? | **Yes, but only when `localInstallStoreReadable` is true** and the pinned Agent Server smoke passes. Remote/cloud runtimes get disabled reasons rather than local paths. | | 9 | Should extension context be global per extension, selected per launch template, or both? | **Both.** Context blocks use `autoInclude` for global/default behavior and launch templates can require/select specific context IDs. | -| 10 | Should repo-provided SDK plugins/skills ever be surfaced automatically as Canvas extensions? | **No automatic surfacing.** Repository content remains an Agent Server/runtime concern unless the user explicitly installs/registers an Agent Canvas extension path. | +| 10 | Should repo-provided SDK plugins/skills ever be surfaced automatically as Canvas extensions? | **No automatic surfacing.** Repository content remains an Agent Server/runtime concern unless the user explicitly installs/registers an Canvas Extension path. | | 11 | What triggers the reserved iframe runtime? | **A trust boundary requirement:** community marketplace distribution, an untrusted third-party publisher tier, or a concrete need for browser-enforced isolation/CSP. | | 12 | Should agent-proposed dev extension registration ship with dev watch mode? | **No.** Defer to the broader post-MVP agent-mediated install proposal flow. | | 13 | Which create-conversation payload shape should extensions target? | **Current SDK settings shape:** top-level `agent_settings` plus optional top-level `plugins`; do not reintroduce legacy `agent` payloads. | -| 14 | What is the first route-less JavaScript surface? | **Tool visualizers.** They build on the merged PR #1246 registry seam and have a clear fallback path. | -| 15 | Should conversation slots ship before visualizers? | **No.** Keep slots explicit and follow visualizers with header/sidebar/footer/badge slots once the registry/runtime shape is proven. | +| 14 | What route-less JavaScript surfaces are in scope? | **Tool visualizers, settings panels, and conversation right panels.** They map to concrete existing product regions and avoid arbitrary app-root mounting. | +| 15 | Should generic conversation slots ship before these surfaces? | **No.** Header/footer/badge/sidebar slots remain deferred until a workflow cannot be handled by a right panel or tool visualizer. | ## 28. Remaining Open Questions @@ -1064,10 +1149,13 @@ Build the smallest powerful slice: 2. Extension Host registry and asset serving. 3. Packages management page. 4. Trusted same-origin browser-module extension views (with the iframe runtime reserved in the manifest schema). -5. Extension tool visualizers as the first route-less JavaScript surface. -6. Dev-mode extension registration / watch / reload. -7. SDK plugin contribution merge for local conversations. -8. Context contribution merge behind explicit permission. +5. Left navigation entries for Canvas Extension views. +6. Settings panels under a visible Extensions header after built-in settings. +7. Conversation right panels beside Files, Browser, and Terminal. +8. Extension tool visualizers. +9. Dev-mode extension registration / watch / reload. +10. SDK plugin contribution merge for local conversations. +11. Context contribution merge behind explicit permission. Defer: marketplace, package signing, **sandboxed iframe runtime** (manifest field reserved as `browser.entry` but unimplemented), arbitrary root-mounted route-less components/shared dependency runtime, rich extension RPC APIs beyond the visualizer/slot surfaces, agent-mediated installation, SDK/server install management, plugin parameters, and per-run extension enablement. From 2b2530f9a5c1ca3842121d70ceacab1b0a192d2b Mon Sep 17 00:00:00 2001 From: Devin Date: Thu, 11 Jun 2026 15:18:20 -0400 Subject: [PATCH 08/14] Rename management view to Extensions --- docs/ExtensionsSystemAgentExecutionPlan.md | 25 +++++++++-------- docs/ExtensionsSystemPoCPlan.md | 21 +++++++------- docs/ExtensionsSystemRFC.md | 32 +++++++++++----------- 3 files changed, 40 insertions(+), 38 deletions(-) diff --git a/docs/ExtensionsSystemAgentExecutionPlan.md b/docs/ExtensionsSystemAgentExecutionPlan.md index 68644e99f..7f68c03e3 100644 --- a/docs/ExtensionsSystemAgentExecutionPlan.md +++ b/docs/ExtensionsSystemAgentExecutionPlan.md @@ -40,7 +40,7 @@ Use gates to coordinate merge order. Tasks inside a gate can be split, but the g | G0 | Risk burners and contracts | Mostly serial | Everything else | | G1 | Manager, CLI, install store, host routes | Manager/CLI and launcher work can split after shared contracts land | UI, view host, conversation merge | | G2 | Example extension and package release path | Can run alongside G1 after contracts land | View host, acceptance demo | -| G3 | Frontend Packages page and CanvasExtensionService | Can run once Extension Host registry shape is stable | View host UX, dev mode UX | +| G3 | Frontend Extensions page and CanvasExtensionService | Can run once Extension Host registry shape is stable | View host UX, dev mode UX | | G4 | Browser-module view host, left navigation, settings panels, right panels, and visualizers | Can run after registry/assets exist; visualizers can split after shared types land | Acceptance demo | | G5 | Conversation contribution merge | Can run after launch-contributions endpoint exists | Final proof | | G6 | Dev mode watch/remount | Can run after manager, host, and view host exist | Final proof | @@ -50,7 +50,7 @@ Avoid these overlapping edits unless one agent owns the integration: - `bin/agent-canvas.mjs`, `scripts/dev-safe.mjs`, `scripts/dev-with-automation.mjs`, `scripts/static-server.mjs`, and `vite.config.ts` should have one launcher/routing owner at a time. - `src/api/agent-server-adapter.ts` and `src/api/conversation-service/agent-server-conversation-service.api.ts` should have one conversation-merge owner at a time. -- Sidebar route work should coordinate with Packages page route work before editing `src/routes.ts`. +- Sidebar route work should coordinate with Extensions page route work before editing `src/routes.ts`. - `package.json` exports/files changes should coordinate with release-smoke work. ## 3. Gate G0: Risk Burners And Contract @@ -258,7 +258,7 @@ npm test -- __tests__/canvas-extensions/extension-host.test.ts - Route `/api/canvas/canvas-extensions/*` and `/canvas-extension-assets/*` before `/api/*` and before SPA fallback. - Add Vite proxy support for direct Vite access. - Add static server route support for direct static-port access. - - Cover `--frontend-only` and `--backend-only`: frontend-only may expose local Packages/browser assets with agent-runtime diagnostics; backend-only should skip browser asset hosting unless a future CLI-only host mode explicitly needs it. + - Cover `--frontend-only` and `--backend-only`: frontend-only may expose local Extensions/browser assets with agent-runtime diagnostics; backend-only should skip browser asset hosting unless a future CLI-only host mode explicitly needs it. - Emit frontend env/runtime metadata: extension enabled flag, `localInstallStoreReadable`, route/base if needed. - Do not expose a write-capable Extension Host key in ``. - [ ] Tests: @@ -332,7 +332,7 @@ npm pack --dry-run - `agent-canvas list canvas-extensions` and `agent-canvas doctor` work from packed/global install. - [ ] Handoff notes: -## 6. Gate G3: Frontend Packages Management +## 6. Gate G3: Frontend Extensions Management ### G3.1 CanvasExtensionService API Wrapper @@ -360,24 +360,25 @@ npm run typecheck - Frontend consumers never hand-roll Extension Host fetches. - [ ] Handoff notes: -### G3.2 Packages Page +### G3.2 Extensions Page - [ ] Owner: - [ ] Depends on: G3.1 - [ ] Files likely touched: - `src/routes.ts` - - `src/routes/canvas-extensions-packages.tsx` + - `src/routes/canvas-extensions-page.tsx` - `src/components/features/skills/extensions-navigation.tsx` - `src/routes/extensions-hub.tsx` if the existing Customize hub needs route/link updates - `src/i18n/translation.json` - generated i18n files via `npm run make-i18n` - `tests/e2e/snapshots/**` - [ ] Implement: - - Replace or redirect `/plugins` to Packages while preserving bookmarks. - - Packages nav item in the existing Customize area (`/customize` primary entry, desktop redirect to `/skills`, mobile Customize hub). + - Replace or redirect `/plugins` to Extensions while preserving bookmarks. + - Extensions nav item in the existing Customize area (`/customize` primary entry, desktop redirect to `/skills`, mobile Customize hub). - Treat legacy "extensions" file/component names as existing Customize implementation details; do not use them as new product vocabulary. - - Sections for Enabled, Installed/Disabled, Invalid, Dev. - - Cards show display name, package, version, state, contribution badges, required secrets, diagnostics, source path for dev entries. + - Simple stacked view of installed Canvas Extensions and their status. + - Status grouping/filtering only as needed for scanability: Enabled, Disabled, Invalid, Dev. + - Rows/cards show display name, package, version, state, contribution badges, required secrets, diagnostics, source path for dev entries. - Actions for install, enable, disable, remove. - User-facing copy goes through i18n keys. - [ ] Tests: @@ -601,7 +602,7 @@ npm run typecheck - `scripts/extension-host.mjs` - `bin/agent-canvas.mjs` - `src/api/canvas-extensions-service.ts` - - `src/routes/canvas-extensions-packages.tsx` + - `src/routes/canvas-extensions-page.tsx` - tests under `__tests__/canvas-extensions/` - [ ] Implement: - `agent-canvas install --dev`. @@ -642,7 +643,7 @@ node bin/agent-canvas.mjs ``` - [ ] Browser checks: - - Packages shows `hello.canvas` enabled with no diagnostics. + - Extensions shows `hello.canvas` enabled with no diagnostics. - Left navigation entry appears after Automations and before conversations. - Extension view renders. - Settings panel appears under a visible Extensions header after built-in settings. diff --git a/docs/ExtensionsSystemPoCPlan.md b/docs/ExtensionsSystemPoCPlan.md index 656025885..6bbb8b304 100644 --- a/docs/ExtensionsSystemPoCPlan.md +++ b/docs/ExtensionsSystemPoCPlan.md @@ -15,7 +15,7 @@ For multi-agent implementation, use [ExtensionsSystemAgentExecutionPlan.md](./Ex The MVP should be built as a vertical proof, not as isolated architecture layers. The first complete proof should demonstrate: 1. A local or npm-packed Canvas Extension installs into `~/.openhands/agent-canvas/installations`. -2. The Packages page under Customize shows the installed Canvas Extension and its diagnostics. +2. The Extensions page under Customize shows the installed Canvas Extension and its diagnostics. 3. A trusted `browser.module` view renders at `/canvas-extensions/:extensionId/:viewId/*`. 4. A view can contribute a left navigation entry after Automations and before the conversation list, matching the current `SidebarRailBody` order (`New Chat`, `Customize`, `Automations`, then conversations). 5. A Canvas Extension can add a settings panel only under a visible Extensions header after built-in settings. @@ -81,7 +81,7 @@ Cover: - `scripts/dev-safe.mjs` / `dev:minimal`, where direct Vite proxying must handle Extension Host routes without the automation ingress. - `scripts/static-server.mjs` routes used by the packaged CLI/static mode. - `vite.config.ts` proxy support for direct Vite access, especially `/canvas-extension-assets/*`. -- `--frontend-only` and `--backend-only` partial-stack behavior: frontend-only can expose Packages/extension views but should show backend-dependent diagnostics, while backend-only should not start the Extension Host view/assets path unless a future CLI-only host mode explicitly needs it. +- `--frontend-only` and `--backend-only` partial-stack behavior: frontend-only can expose Extensions/extension views but should show backend-dependent diagnostics, while backend-only should not start the Extension Host view/assets path unless a future CLI-only host mode explicitly needs it. Exit criteria: a browser opened through either the ingress port or direct Vite/static port can fetch registry JSON and import a browser module from `/canvas-extension-assets/...`. @@ -192,7 +192,7 @@ Do not expose a write-capable Extension Host key in agent prompts. Route precedence is the failure-prone part: `/api/canvas/canvas-extensions/*` and `/canvas-extension-assets/*` must match before `/api/*` and static SPA fallback in every launcher path. -Partial-stack mode rule: frontend-only may start enough Extension Host surface to render local Packages and extension browser assets, but must mark agent-runtime contributions unavailable because no Agent Server is running. Backend-only should skip frontend asset hosting and extension browser routes by default; CLI management still works because it dispatches before stack startup. +Partial-stack mode rule: frontend-only may start enough Extension Host surface to render local Extensions and extension browser assets, but must mark agent-runtime contributions unavailable because no Agent Server is running. Backend-only should skip frontend asset hosting and extension browser routes by default; CLI management still works because it dispatches before stack startup. ## 4. Frontend Proof @@ -202,15 +202,16 @@ Add `src/api/canvas-extensions-service.ts` for every `/api/canvas/canvas-extensi Update `src/api/no-direct-agent-server-calls.test.ts` with one narrow allowlist entry for this wrapper, mirroring automation but covering the current blanket `fetch('/api/...')` rule as well as axios. The exception should allow only `/api/canvas/canvas-extensions/*` from the wrapper; it must not weaken the Agent Server rule for arbitrary `/api/*` calls. -### 4.2 Packages Page +### 4.2 Extensions Page -Replace `/plugins` with a Packages view, while preserving redirects/bookmarks. Use the existing Customize layout and navigation: `/customize` is the primary-sidebar hub entry, desktop redirects to `/skills`, mobile renders the Customize hub, and the current Customize navigation contains Skills, MCP Servers, and a coming-soon Plugins item. +Replace `/plugins` with an Extensions view, while preserving redirects/bookmarks. Use the existing Customize layout and navigation: `/customize` is the primary-sidebar hub entry, desktop redirects to `/skills`, mobile renders the Customize hub, and the current Customize navigation contains Skills, MCP Servers, and a coming-soon Plugins item. The page should: -- Add a Packages nav item in the Customize navigation. +- Add an Extensions nav item in the Customize navigation. - Treat legacy file names such as `extensions-hub.tsx` and `extensions-navigation.tsx` as existing Customize implementation details, not product vocabulary for the new Canvas Extensions system. -- Show Enabled, Installed, Disabled, Invalid, and Dev sections. +- Show a simple stacked view of installed Canvas Extensions and their status. +- Use status grouping/filtering only as needed for scanability: Enabled, Disabled, Invalid, and Dev. - Show install source, version, contribution badges, required secrets, diagnostics, enable/disable/remove actions. - Stay dense and operational; no marketplace browsing in MVP. - Route all visible strings, tooltips, and action labels through i18n keys. @@ -366,7 +367,7 @@ node bin/agent-canvas.mjs Then in the browser: -1. Open Packages and see `hello.canvas` enabled with no diagnostics. +1. Open Extensions and see `hello.canvas` enabled with no diagnostics. 2. See its left navigation entry after Automations and before the conversation list. 3. Open its extension view from the left navigation and see browser-module UI render. 4. Open settings and confirm its panel appears under the visible Extensions header after built-in settings. @@ -386,9 +387,9 @@ Release-path acceptance should also install the packed `@openhands/agent-canvas` **Release packaging:** `npm pack --dry-run`; packed-tarball install into a temp npm prefix; verify `agent-canvas list`, `agent-canvas doctor`, and an extension install work without a source checkout; verify `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` resolve for type consumers. -**Component:** Packages page empty/installed/enabled/invalid/dev states under Customize; install/enable/disable/remove action wiring; left navigation entries in expanded/collapsed/mobile states; extension view loading/error/remount; settings panel placement under the visible Extensions header; extension settings read/patch; extension visualizer renders/falls back inside the existing event wrapper; conversation right panels render beside Files/Browser/Terminal; launch template preflight for incompatible SDK plugin paths. +**Component:** Extensions page empty/installed/enabled/invalid/dev states under Customize; install/enable/disable/remove action wiring; left navigation entries in expanded/collapsed/mobile states; extension view loading/error/remount; settings panel placement under the visible Extensions header; extension settings read/patch; extension visualizer renders/falls back inside the existing event wrapper; conversation right panels render beside Files/Browser/Terminal; launch template preflight for incompatible SDK plugin paths. -**E2E snapshots:** Packages page with enabled extension under Customize; invalid extension diagnostics; left navigation entry after Automations; settings panel under Extensions header; conversation right panel beside Files/Browser/Terminal; extension view rendered from browser module; launch template showing context/plugin contribution; remote-backend disabled reason for local plugin path; dev extension view remount after output change. +**E2E snapshots:** Extensions page with enabled extension under Customize; invalid extension diagnostics; left navigation entry after Automations; settings panel under Extensions header; conversation right panel beside Files/Browser/Terminal; extension view rendered from browser module; launch template showing context/plugin contribution; remote-backend disabled reason for local plugin path; dev extension view remount after output change. **Live E2E:** optional after the walking skeleton. If added, keep one cheap local live test that starts a conversation from `hello.canvas` and verifies the created payload/events show the context suffix or plugin load marker. Follow existing live E2E rules under `tests/e2e/live/`. diff --git a/docs/ExtensionsSystemRFC.md b/docs/ExtensionsSystemRFC.md index 02d0fa6e0..24e1b1267 100644 --- a/docs/ExtensionsSystemRFC.md +++ b/docs/ExtensionsSystemRFC.md @@ -12,7 +12,7 @@ Agent Canvas should ship a first-class **Canvas Extensions** system: user-instal A Canvas Extension can contribute UI views, left navigation entries, settings panels, conversation right panels, custom tool/event visualizers, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Canvas Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces; Canvas does not patch or load code inside the Agent Server. -The MVP delivers CLI install for Canvas Extension packages, a Packages management page under Customize, trusted same-origin extension views, dev-mode authoring with live reload, and focused UI contribution surfaces: left navigation entries, settings panels under a visible Extensions header, conversation right panels beside Files/Browser/Terminal, and extension-provided tool visualizers that compose with the built-in visualizer registry introduced by PR #1246. Broad root-mounted components remain deferred. The CLI can detect adjacent artifact types such as standalone SDK plugins or `SKILL.md` folders, but the first executable slice should install/enable only packages with a Canvas Extension manifest. Marketplace, signing, sandboxed iframe views, arbitrary app-root component/shared dependency runtimes, standalone skill/plugin management, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. +The MVP delivers CLI install for Canvas Extension packages, an Extensions page under Customize, trusted same-origin extension views, dev-mode authoring with live reload, and focused UI contribution surfaces: left navigation entries, settings panels under a visible Extensions header, conversation right panels beside Files/Browser/Terminal, and extension-provided tool visualizers that compose with the built-in visualizer registry introduced by PR #1246. Broad root-mounted components remain deferred. The CLI can detect adjacent artifact types such as standalone SDK plugins or `SKILL.md` folders, but the first executable slice should install/enable only packages with a Canvas Extension manifest. Marketplace, signing, sandboxed iframe views, arbitrary app-root component/shared dependency runtimes, standalone skill/plugin management, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. ## 2. Motivation @@ -115,7 +115,7 @@ The Extension Host **must not** proxy arbitrary requests to the Agent Server. Wh The Canvas frontend owns: -- Extension management UI (the Packages page). +- Extension management UI (the Extensions page). - Navigation for extension views. - The extension view host that mounts trusted extension browser modules into Canvas-owned route containers (with a reserved future iframe runtime; see §12.1). - Installation consent UX, permission display, secret setup prompts. @@ -482,7 +482,7 @@ Rules: **MVP runtime: trusted same-origin browser module.** The extension's `browser.module` is loaded by the Canvas frontend with dynamic `import()` and mounted into a Canvas-owned DOM container at the view route. The extension exports a `mount()` function, receives the typed context (§13), and owns rendering inside that container. It may use React, Svelte, vanilla DOM, or another framework as long as the shipped module is browser-ready. It should use Canvas CSS variables/design tokens and host-provided APIs instead of importing Canvas internals. -Inline extension code runs in the same browser origin as Canvas. That is intentional for MVP, and it means runtime capability declarations are a consent, audit, and UX contract rather than a hard browser sandbox. A trusted inline extension can technically call same-origin routes if it has access to the active session credentials, inspect browser state available to Canvas, and manipulate page DOM. The Packages UI must communicate that trust model clearly before enablement. Strong isolation is deferred to the future `browser.entry` iframe runtime. +Inline extension code runs in the same browser origin as Canvas. That is intentional for MVP, and it means runtime capability declarations are a consent, audit, and UX contract rather than a hard browser sandbox. A trusted inline extension can technically call same-origin routes if it has access to the active session credentials, inspect browser state available to Canvas, and manipulate page DOM. The Extensions UI must communicate that trust model clearly before enablement. Strong isolation is deferred to the future `browser.entry` iframe runtime. **Browser module packaging contract.** @@ -949,7 +949,7 @@ Each entry records absolute source path, manifest path, registration timestamp, - Serve assets directly from the source folder. - Append a cache-busting version to the browser-module URL after changes so dynamic imports receive fresh code. - Dispose and remount only the affected extension view when its module changes; avoid reloading the parent Canvas app. -- Surface manifest/build errors in the Packages page and extension view. +- Surface manifest/build errors in the Extensions page and extension view. - The extension authoring project owns its build/watch command. Canvas watches declared output files and source manifests; it never runs package install scripts automatically and must ask before any package-manager command. - Never hot-reload SDK plugin / tool state into an already-running conversation; a new conversation is required for agent-side contribution changes. @@ -963,7 +963,7 @@ Dev extensions run as trusted same-origin browser modules, just like installed M agent-canvas install ../my-extension ``` -or "Promote to Installed Extension" from the Packages page. Promotion installs into the normal store, freezes version/provenance, and disables dev watch for that extension. +or "Promote to Installed Extension" from the Extensions page. Promotion installs into the normal store, freezes version/provenance, and disables dev watch for that extension. ## 22. Agent-Mediated Installation (post-MVP) @@ -979,7 +979,7 @@ The future flow: an agent builds an extension in the workspace, proposes install **Safety rules.** Proposal alone is not sufficient — user approval required. No write-capable Extension Host key in `` or the prompt. The agent cannot call `/api/canvas/canvas-extensions/install` directly. Extension Host re-validates the downloaded artifact; proposal payload is advisory. Conversation artifacts are size-limited and checksum-verified when `sha256` is provided. Permission expansion follows the same re-approval rules as CLI updates. -This is not part of PR 0 or PR 1. It becomes feasible after the local install store, artifact detector, Extension Host, and Packages UI exist. +This is not part of PR 0 or PR 1. It becomes feasible after the local install store, artifact detector, Extension Host, and Extensions UI exist. ## 23. Upstream Dependencies @@ -1005,15 +1005,15 @@ Sections: - Skills - MCP Servers -- Packages +- Extensions -Current navigation state: `/customize` is the Customize entry in the primary sidebar; desktop redirects to `/skills`, mobile renders the Customize hub; `/skills` and `/mcp` are live; `/plugins` is still a coming-soon page inside the current Customize navigation. MVP should replace `/plugins` with Packages or redirect it to the new Packages route while retaining old bookmarks. Do not rename the Customize section back to Extensions. +Current navigation state: `/customize` is the Customize entry in the primary sidebar; desktop redirects to `/skills`, mobile renders the Customize hub; `/skills` and `/mcp` are live; `/plugins` is still a coming-soon page inside the current Customize navigation. MVP should replace `/plugins` with Extensions or redirect it to the new Extensions route while retaining old bookmarks. Do not rename the Customize section back to Extensions. Implementation note: some current files still use legacy "extensions" names, such as `src/routes/extensions-hub.tsx` and `src/components/features/skills/extensions-navigation.tsx`. Treat those as existing Customize implementation details. New Canvas Extension code should use `canvas-extensions` names, and user-facing copy should say Customize or Canvas Extensions as appropriate. -Packages page sections: Enabled · Installed but disabled · Invalid · Install from npm/package spec · Developer extension path. +The Extensions page is a simple stacked view of installed Canvas Extensions and their status. MVP rows should be grouped or filtered by status only as needed for scanability: Enabled, Disabled, Invalid, and Dev. Install-from-package/dev-path controls can sit above or below the stack, but marketplace-style browsing is out of scope. -Each extension card shows: display name, package name, version; state; contribution badges; required secrets status; enable/disable/remove actions; diagnostics. +Each extension row/card shows: display name, package name, version; state; contribution badges; required secrets status; enable/disable/remove actions; diagnostics. ### Launch UX @@ -1057,9 +1057,9 @@ Deliverables: generic install/manage CLI parsing; `install/list/enable/disable/r ### PR 2 — Frontend management UI -Files: `src/routes.ts`, `src/routes/canvas-extensions-packages.tsx`, legacy Customize navigation files such as `src/components/features/skills/extensions-navigation.tsx`, `src/api/canvas-extensions-service.ts`, `src/api/no-direct-agent-server-calls.test.ts`, `src/hooks/query/use-extensions.ts`, i18n. +Files: `src/routes.ts`, `src/routes/canvas-extensions-page.tsx`, legacy Customize navigation files such as `src/components/features/skills/extensions-navigation.tsx`, `src/api/canvas-extensions-service.ts`, `src/api/no-direct-agent-server-calls.test.ts`, `src/hooks/query/use-extensions.ts`, i18n. -Deliverables: Packages page under Customize replaces "Plugins coming soon"; install/enable/disable/remove flows; dedicated CanvasExtensionsService wrapper and narrow no-direct-Agent-Server test exception for `/api/canvas/canvas-extensions/*`; dev extension badges, source paths, diagnostics; snapshot coverage for empty/installed/enabled/invalid states. Do not reintroduce a top-level Extensions section. +Deliverables: Extensions page under Customize replaces "Plugins coming soon"; a simple stacked installed-extensions/status view; install/enable/disable/remove flows; dedicated CanvasExtensionsService wrapper and narrow no-direct-Agent-Server test exception for `/api/canvas/canvas-extensions/*`; dev extension badges, source paths, diagnostics; snapshot coverage for empty/installed/enabled/invalid states. Do not reintroduce a top-level Extensions section. ### PR 3 — Trusted browser-module view host @@ -1081,7 +1081,7 @@ Deliverables: settings panels under a visible Extensions header after built-in s ### PR 3c — Dev extension watch mode -Files: `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, `bin/agent-canvas.mjs`, `src/api/canvas-extensions-service.ts`, `src/routes/canvas-extensions-packages.tsx`, contributor docs. +Files: `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, `bin/agent-canvas.mjs`, `src/api/canvas-extensions-service.ts`, `src/routes/canvas-extensions-page.tsx`, contributor docs. Deliverables: `--dev` install and `dev-extension` subcommands; dev registration store; source folder file watching; manifest revalidation on change; browser-module cache-bust/remount after asset changes; visible dev-mode labeling; example extension and contributor guide; tests for registration, traversal rejection, invalid manifest updates, and view remount signaling. @@ -1101,9 +1101,9 @@ Permission consent modal; secret setup flow; `doctor` command with actionable di **Release packaging:** `npm pack --dry-run`; install from the packed tarball into a temp global/prefix; verify `agent-canvas list`, `agent-canvas doctor`, and `agent-canvas install --yes` work without a source checkout; verify full `agent-canvas` launch still finds `build/`, `scripts/`, and Extension Host routes. -**Component:** Packages page states under Customize; permission display; browser-module view loading/error; settings panel placement under the visible Extensions header; conversation right-panel tab rendering/error boundaries; MCP required preflight; incompatible runtime warning; dev badge. +**Component:** Extensions page states under Customize; permission display; browser-module view loading/error; settings panel placement under the visible Extensions header; conversation right-panel tab rendering/error boundaries; MCP required preflight; incompatible runtime warning; dev badge. -**E2E snapshots:** Packages page empty state under Customize; enabled extension with one view; invalid extension diagnostics; settings panel under Extensions header; conversation right panel beside Files/Browser/Terminal; launch template requiring MCP; launch preflight with local-path SDK plugin disabled on remote backend; dev extension view remount after file change; future: agent-mediated install proposal flow. +**E2E snapshots:** Extensions page empty state under Customize; enabled extension with one view; invalid extension diagnostics; settings panel under Extensions header; conversation right panel beside Files/Browser/Terminal; launch template requiring MCP; launch preflight with local-path SDK plugin disabled on remote backend; dev extension view remount after file change; future: agent-mediated install proposal flow. **Live E2E:** not required for MVP. If added later, follow existing live E2E rules under `tests/e2e/live/`. @@ -1147,7 +1147,7 @@ Build the smallest powerful slice: 1. CLI-managed npm extension install/enable/list. 2. Extension Host registry and asset serving. -3. Packages management page. +3. Extensions management page. 4. Trusted same-origin browser-module extension views (with the iframe runtime reserved in the manifest schema). 5. Left navigation entries for Canvas Extension views. 6. Settings panels under a visible Extensions header after built-in settings. From f45959a67b07ed1252bf50dd26bd8585a00ea463 Mon Sep 17 00:00:00 2001 From: Devin Date: Thu, 11 Jun 2026 16:04:05 -0400 Subject: [PATCH 09/14] Clarify extension FAQ and theme contributions --- docs/ExtensionsSystemAgentExecutionPlan.md | 23 +- docs/ExtensionsSystemPoCPlan.md | 60 ++-- docs/ExtensionsSystemRFC.md | 346 +++++++++++++++++++-- 3 files changed, 372 insertions(+), 57 deletions(-) diff --git a/docs/ExtensionsSystemAgentExecutionPlan.md b/docs/ExtensionsSystemAgentExecutionPlan.md index 7f68c03e3..d031f6952 100644 --- a/docs/ExtensionsSystemAgentExecutionPlan.md +++ b/docs/ExtensionsSystemAgentExecutionPlan.md @@ -104,14 +104,17 @@ Avoid these overlapping edits unless one agent owns the integration: - `src/canvas-extensions/manifest-validation.ts` - `src/canvas-extensions/artifact-detection.ts` - `src/canvas-extensions/storage-paths.ts` + - `src/themes/color-themes.ts` - `__tests__/canvas-extensions/manifest-validation.test.ts` - `package.json` - `tsconfig.lib.json` if needed for emitted type paths - keep new public types under `src/canvas-extensions/`; do not recreate the old `src/addons/` namespace - [ ] Implement: - Manifest v1 types, contribution types, registry entry types, diagnostics, installable artifact kinds. - - Contribution types for left navigation, `settingsPanels`, `conversationRightPanels`, and `toolVisualizers`. + - Contribution types for left navigation, `colorThemes`, `settingsPanels`, `conversationRightPanels`, and `toolVisualizers`. + - `AgentCanvasColorThemeDefinition` aligned with the current app theme model: `label`, `scale`, `heroui`, and optional semantic `tokens`. - Validation for required fields, ID regex, duplicate/mutually exclusive `browser.module` and `browser.entry`, path containment, reserved `browser.entry`. + - Validation for color theme IDs and allowed theme token keys. - Artifact detection order: Canvas Extension, SDK plugin, skill, MCP placeholder. - Storage helpers for `~/.openhands/agent-canvas/installations`. - Public package exports `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` with emitted `dist/extensions/*` and `dist/visualizers/*` outputs. @@ -122,6 +125,8 @@ Avoid these overlapping edits unless one agent owns the integration: - Traversal in manifest/browser/icon/plugin paths. - Both `browser.module` and `browser.entry`. - Reserved `browser.entry` diagnostic. + - Theme-only extension manifest is valid. + - Invalid color theme token key is rejected. - Standalone SDK plugin / `SKILL.md` detection returns unsupported-in-MVP kind. - [ ] Verification: @@ -501,11 +506,13 @@ npm run typecheck - A local extension can change how one event/tool renders without forking Agent Canvas. - [ ] Handoff notes: -### G4.4 Settings Panels And Conversation Right Panels +### G4.4 Color Themes, Settings Panels, And Conversation Right Panels - [ ] Owner: - [ ] Depends on: G4.1 - [ ] Files likely touched: + - `src/themes/color-themes.ts` + - `src/components/features/settings/app-settings/theme-input.tsx` - settings route/components - conversation right-panel/tab components - `src/canvas-extensions/panels/*` @@ -513,6 +520,10 @@ npm run typecheck - tests under `__tests__/canvas-extensions/` - focused component tests near settings and conversation panel hosts - [ ] Implement: + - Color theme registry for `contributes.colorThemes`. + - Project enabled extension color themes into Settings > Application > Color Theme. + - Support theme-only extensions with no view/panel/visualizer contribution. + - Fall back to the default built-in theme if the selected extension theme is disabled, removed, or invalid. - Settings panel registry for `contributes.settingsPanels`. - Render settings panels only under a visible Extensions header after built-in settings sections. - Conversation right-panel registry for `contributes.conversationRightPanels`. @@ -522,13 +533,16 @@ npm run typecheck - Per-panel error boundaries. - No raw store, React Query client, or Canvas-internal component API in the public contract. - [ ] Tests: + - Extension color theme appears in the Color Theme dropdown. + - Theme-only extension contributes no other UI and remains valid. + - Selected extension theme falls back when disabled/invalid. - Settings panels render only under the Extensions header after built-in settings. - Conversation right panels render beside Files, Browser, and Terminal. - Ordering is deterministic. - Disabled/invalid extensions do not render panel content. - Panel errors are localized and do not break settings or conversation pages. - [ ] Done when: - - A local Canvas Extension can add a settings panel and a conversation right panel without a full app route. + - A local Canvas Extension can add a color theme, a settings panel, and a conversation right panel without a full app route. - [ ] Handoff notes: ## 8. Gate G5: Conversation Contributions @@ -646,6 +660,7 @@ node bin/agent-canvas.mjs - Extensions shows `hello.canvas` enabled with no diagnostics. - Left navigation entry appears after Automations and before conversations. - Extension view renders. + - Extension color theme appears in Settings > Application > Color Theme and can be selected. - Settings panel appears under a visible Extensions header after built-in settings. - Conversation right panel appears beside Files, Browser, and Terminal. - Extension settings patch persists. @@ -709,7 +724,7 @@ The PoC is not complete unless all of these are true: - [ ] ACP runtimes do not receive incompatible extension plugin/MCP contributions. - [ ] `conversationInstructions` never receives extension system context. - [ ] Extension context composes with existing `` suffix. -- [ ] `hello.canvas` proves install, registry, UI, left navigation, view, settings panel, conversation right panel, tool visualizer, launch context, SDK plugin preflight, and dev remount. +- [ ] `hello.canvas` proves install, registry, UI, left navigation, view, color theme, settings panel, conversation right panel, tool visualizer, launch context, SDK plugin preflight, and dev remount. - [ ] Tests cover the risk surfaces listed in the PoC plan. ## 12. Handoff Template diff --git a/docs/ExtensionsSystemPoCPlan.md b/docs/ExtensionsSystemPoCPlan.md index 6bbb8b304..f97929a8d 100644 --- a/docs/ExtensionsSystemPoCPlan.md +++ b/docs/ExtensionsSystemPoCPlan.md @@ -18,11 +18,12 @@ The MVP should be built as a vertical proof, not as isolated architecture layers 2. The Extensions page under Customize shows the installed Canvas Extension and its diagnostics. 3. A trusted `browser.module` view renders at `/canvas-extensions/:extensionId/:viewId/*`. 4. A view can contribute a left navigation entry after Automations and before the conversation list, matching the current `SidebarRailBody` order (`New Chat`, `Customize`, `Automations`, then conversations). -5. A Canvas Extension can add a settings panel only under a visible Extensions header after built-in settings. -6. A Canvas Extension can add a conversation right panel alongside Files, Browser, and Terminal. -7. A local Canvas Extension can register a custom tool visualizer for one MCP/action variant and safely fall back to built-in rendering when it does not match or throws. -8. A launch template can append extension context and include an SDK plugin when the runtime is filesystem-local. -9. Dev mode can register a local source folder, detect output changes, and remount the view without rebuilding Canvas. +5. A Canvas Extension can register a color theme that appears in Settings > Application > Color Theme; a theme-only extension is valid. +6. A Canvas Extension can add a settings panel only under a visible Extensions header after built-in settings. +7. A Canvas Extension can add a conversation right panel alongside Files, Browser, and Terminal. +8. A local Canvas Extension can register a custom tool visualizer for one MCP/action variant and safely fall back to built-in rendering when it does not match or throws. +9. A launch template can append extension context and include an SDK plugin when the runtime is filesystem-local. +10. Dev mode can register a local source folder, detect output changes, and remount the view without rebuilding Canvas. ## 1.1 Scope Locks From The RFC @@ -34,18 +35,19 @@ These decisions should keep the build from spreading sideways: - Extension author types need real package subpaths: `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers`, emitted under `dist/extensions/*` and `dist/visualizers/*` and included in npm release checks. - Conversation contribution work targets the current start payload: top-level `agent_settings` plus optional top-level `plugins`. Do not reintroduce the legacy `agent` payload shape. - Package-relative/local SDK plugin sources are MVP only when the launcher says `localInstallStoreReadable=true`. ACP runtimes skip extension SDK plugins unless a pinned-version smoke proves the exact plugin shape is compatible. -- The first route-less JavaScript workstreams are custom tool visualizers, settings panels, and conversation right panels, not arbitrary root-mounted components. Generic conversation slots remain deferred until a concrete workflow cannot be handled by a right panel or visualizer. +- The first route-less/registry-backed workstreams are color themes, custom tool visualizers, settings panels, and conversation right panels, not arbitrary root-mounted components. Generic conversation slots remain deferred until a concrete workflow cannot be handled by a right panel or visualizer. ## 1.2 Focused Workstream From Issue #481 -The issue feedback and product feedback point to four concrete places where users should be able to add content without forking Agent Canvas: +The issue feedback and product feedback point to five concrete places where users should be able to add content without forking Agent Canvas: 1. **Custom tool/event visualizers.** PR #1246 added an internal visualizer dispatcher that already asks a registry for a React body and falls back to markdown. Extensions should expose that seam first through `registerToolVisualizer()` or a manifest-projected equivalent. 2. **Left navigation entries.** A Canvas Extension can add a top-level navigation item after Automations and before the conversation list. -3. **Settings panels.** A Canvas Extension can add configuration UI only under a visible Extensions header after built-in settings. -4. **Conversation right panels.** A Canvas Extension can add a panel beside Files, Browser, and Terminal for conversation-adjacent tools and context. +3. **Color themes.** A Canvas Extension can add one or more color themes to Settings > Application > Color Theme, including a theme-only package with no view or panel. +4. **Settings panels.** A Canvas Extension can add configuration UI only under a visible Extensions header after built-in settings. +5. **Conversation right panels.** A Canvas Extension can add a panel beside Files, Browser, and Terminal for conversation-adjacent tools and context. -The implementation order should be visualizers first where possible because they are narrow, already have a merged internal seam, and have a clear failure mode: try extension renderer, then built-in renderer, then markdown fallback. Settings panels and conversation right panels should follow as explicit product surfaces. Generic "mount anything at app root" slots remain deferred. +The implementation order should be visualizers first where possible because they are narrow, already have a merged internal seam, and have a clear failure mode: try extension renderer, then built-in renderer, then markdown fallback. Color themes are also a narrow early surface because the app already centralizes built-in themes in `src/themes/color-themes.ts` and exposes them through Settings > Application > Color Theme. Settings panels and conversation right panels should follow as explicit product surfaces. Generic "mount anything at app root" slots remain deferred. ## 2. Risk-Burner Spikes @@ -272,7 +274,22 @@ MVP scope: Example fixture: `hello.canvas` registers a visualizer for one synthetic or MCP-style tool call and renders a compact card. Tests should prove extension-first order, `matches()` narrowing, built-in fallback, markdown fallback, and throw-to-next-renderer behavior. -### 4.6 Settings Panels +### 4.6 Color Theme Extension Surface + +Add Canvas Extension color themes to the existing application theme selector. + +MVP scope: + +- Project enabled `contributes.colorThemes` entries into the same data source used by Settings > Application > Color Theme. +- Support theme-only extensions with no view, panel, visualizer, or agent contribution. +- Validate theme IDs and allowed token keys before exposing them to the dropdown. +- Disabled/invalid extensions do not expose theme options. +- If a selected extension theme becomes unavailable, fall back to the default built-in theme and show a diagnostic. +- Keep this as theme definition data, not arbitrary CSS or a custom extension-owned settings control. + +Example fixture: `hello.canvas` contributes one recognizable color theme so Settings > Application > Color Theme can select it without opening any extension-owned UI. + +### 4.7 Settings Panels Add Canvas Extension settings panels under one visible Extensions header after all built-in settings sections. @@ -284,7 +301,7 @@ MVP scope: - Canvas owns the panel chrome, save/cancel affordances, loading states, and error boundaries. - Panel renderers receive extension settings helpers only; no raw settings store or React Query client access. -### 4.7 Conversation Right Panels +### 4.8 Conversation Right Panels Add Canvas Extension panels to the existing conversation right-panel/tab system beside Files, Browser, and Terminal. @@ -370,26 +387,27 @@ Then in the browser: 1. Open Extensions and see `hello.canvas` enabled with no diagnostics. 2. See its left navigation entry after Automations and before the conversation list. 3. Open its extension view from the left navigation and see browser-module UI render. -4. Open settings and confirm its panel appears under the visible Extensions header after built-in settings. -5. Change a setting in the extension panel and see it persist. -6. Open a conversation and confirm its right panel appears beside Files, Browser, and Terminal. -7. Trigger its fixture tool/event and verify the Canvas Extension visualizer renders with fallback still intact. -8. Start its launch template and verify the conversation payload includes the extension context suffix. -9. In `npm run dev`, register the same extension with `--dev`, rebuild the extension output, and see the view remount with the new code. +4. Open Settings > Application > Color Theme and select the extension-provided color theme. +5. Open settings and confirm its panel appears under the visible Extensions header after built-in settings. +6. Change a setting in the extension panel and see it persist. +7. Open a conversation and confirm its right panel appears beside Files, Browser, and Terminal. +8. Trigger its fixture tool/event and verify the Canvas Extension visualizer renders with fallback still intact. +9. Start its launch template and verify the conversation payload includes the extension context suffix. +10. In `npm run dev`, register the same extension with `--dev`, rebuild the extension output, and see the view remount with the new code. Release-path acceptance should also install the packed `@openhands/agent-canvas` tarball into a temp npm prefix and repeat `agent-canvas list`, `agent-canvas doctor`, and one packed/local extension install without relying on repo-local files outside the package. ## 8. Testing Strategy -**Unit:** manifest validation; reserved `browser.entry` diagnostics; path traversal rejection; artifact detection; unsupported standalone artifact diagnostics; install-store bootstrap; enable/disable/remove/update state transitions; duplicate ID handling; asset route path validation; CLI arg parsing before build checks; no-install-scripts default; visualizer contribution projection; visualizer ordering/fallback/error-boundary behavior; settings panel projection/ordering; conversation right-panel projection/ordering; launch contribution projection; context suffix rendering; plugin merge/dedupe; runtime compatibility classification; dev registration and manifest revalidation. +**Unit:** manifest validation; reserved `browser.entry` diagnostics; path traversal rejection; artifact detection; unsupported standalone artifact diagnostics; install-store bootstrap; enable/disable/remove/update state transitions; duplicate ID handling; color theme projection/validation/fallback; asset route path validation; CLI arg parsing before build checks; no-install-scripts default; visualizer contribution projection; visualizer ordering/fallback/error-boundary behavior; settings panel projection/ordering; conversation right-panel projection/ordering; launch contribution projection; context suffix rendering; plugin merge/dedupe; runtime compatibility classification; dev registration and manifest revalidation. **Node integration:** run the Extension Host against a temp install store; install the `hello.canvas` fixture from local path and npm-packed tarball; fetch registry and asset URLs; verify route precedence before `/api/*` in Vite, ingress, static server, and packaged CLI paths; verify mutating routes require the session API key; verify `doctor` reports invalid manifests, unsupported standalone artifacts, and missing assets. **Release packaging:** `npm pack --dry-run`; packed-tarball install into a temp npm prefix; verify `agent-canvas list`, `agent-canvas doctor`, and an extension install work without a source checkout; verify `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` resolve for type consumers. -**Component:** Extensions page empty/installed/enabled/invalid/dev states under Customize; install/enable/disable/remove action wiring; left navigation entries in expanded/collapsed/mobile states; extension view loading/error/remount; settings panel placement under the visible Extensions header; extension settings read/patch; extension visualizer renders/falls back inside the existing event wrapper; conversation right panels render beside Files/Browser/Terminal; launch template preflight for incompatible SDK plugin paths. +**Component:** Extensions page empty/installed/enabled/invalid/dev states under Customize; install/enable/disable/remove action wiring; left navigation entries in expanded/collapsed/mobile states; extension view loading/error/remount; extension color theme appears in Settings > Application > Color Theme and applies through the existing theme input; settings panel placement under the visible Extensions header; extension settings read/patch; extension visualizer renders/falls back inside the existing event wrapper; conversation right panels render beside Files/Browser/Terminal; launch template preflight for incompatible SDK plugin paths. -**E2E snapshots:** Extensions page with enabled extension under Customize; invalid extension diagnostics; left navigation entry after Automations; settings panel under Extensions header; conversation right panel beside Files/Browser/Terminal; extension view rendered from browser module; launch template showing context/plugin contribution; remote-backend disabled reason for local plugin path; dev extension view remount after output change. +**E2E snapshots:** Extensions page with enabled extension under Customize; invalid extension diagnostics; left navigation entry after Automations; extension color theme visible in Settings > Application; settings panel under Extensions header; conversation right panel beside Files/Browser/Terminal; extension view rendered from browser module; launch template showing context/plugin contribution; remote-backend disabled reason for local plugin path; dev extension view remount after output change. **Live E2E:** optional after the walking skeleton. If added, keep one cheap local live test that starts a conversation from `hello.canvas` and verifies the created payload/events show the context suffix or plugin load marker. Follow existing live E2E rules under `tests/e2e/live/`. diff --git a/docs/ExtensionsSystemRFC.md b/docs/ExtensionsSystemRFC.md index 24e1b1267..7e8d03572 100644 --- a/docs/ExtensionsSystemRFC.md +++ b/docs/ExtensionsSystemRFC.md @@ -10,9 +10,9 @@ Current repo baseline: merged through `upstream/main` at `40844533`. The current Agent Canvas should ship a first-class **Canvas Extensions** system: user-installable npm packages that extend the local Canvas experience and optionally contribute components to the agent runtime through SDK-supported surfaces. -A Canvas Extension can contribute UI views, left navigation entries, settings panels, conversation right panels, custom tool/event visualizers, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Canvas Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces; Canvas does not patch or load code inside the Agent Server. +A Canvas Extension can contribute UI views, left navigation entries, color themes, settings panels, conversation right panels, custom tool/event visualizers, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Canvas Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces; Canvas does not patch or load code inside the Agent Server. -The MVP delivers CLI install for Canvas Extension packages, an Extensions page under Customize, trusted same-origin extension views, dev-mode authoring with live reload, and focused UI contribution surfaces: left navigation entries, settings panels under a visible Extensions header, conversation right panels beside Files/Browser/Terminal, and extension-provided tool visualizers that compose with the built-in visualizer registry introduced by PR #1246. Broad root-mounted components remain deferred. The CLI can detect adjacent artifact types such as standalone SDK plugins or `SKILL.md` folders, but the first executable slice should install/enable only packages with a Canvas Extension manifest. Marketplace, signing, sandboxed iframe views, arbitrary app-root component/shared dependency runtimes, standalone skill/plugin management, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. +The MVP delivers CLI install for Canvas Extension packages, an Extensions page under Customize, trusted same-origin extension views, dev-mode authoring with live reload, and focused UI contribution surfaces: left navigation entries, color themes that appear in Settings > Application > Color Theme, settings panels under a visible Extensions header, conversation right panels beside Files/Browser/Terminal, and extension-provided tool visualizers that compose with the built-in visualizer registry introduced by PR #1246. Broad root-mounted components remain deferred. The CLI can detect adjacent artifact types such as standalone SDK plugins or `SKILL.md` folders, but the first executable slice should install/enable only packages with a Canvas Extension manifest. Marketplace, signing, sandboxed iframe views, arbitrary app-root component/shared dependency runtimes, standalone skill/plugin management, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. ## 2. Motivation @@ -33,6 +33,7 @@ The system must preserve the existing security and operational boundaries betwee - **MCP Server** — external tool server config installed into agent settings. - **Skill** — an OpenHands skill loaded by the SDK; usually delivered via an SDK plugin rather than copied into user skill directories. - **Tool Visualizer** — a React renderer for one action/observation kind or one more specific tool variant, selected before the built-in markdown fallback. +- **Color Theme** — a Canvas Extension-provided theme definition that appears as an option in Settings > Application > Color Theme. Theme-only Canvas Extensions are valid and should not need to ship a view or settings panel. - **Settings Panel** — a Canvas Extension-owned settings surface shown only under a visible Extensions header after the built-in settings sections. - **Conversation Right Panel** — a Canvas Extension-owned panel in the conversation work area alongside built-in Files, Browser, and Terminal panels. @@ -275,7 +276,7 @@ The host package must publish everything the global CLI and extension authors ne }, "activationEvents": ["onStartup"], "permissions": { - "ui": ["views", "leftNavigation", "settingsPanels", "conversationPanels", "toolVisualizers"], + "ui": ["views", "leftNavigation", "colorThemes", "settingsPanels", "conversationPanels", "toolVisualizers"], "agent": ["sdkPlugins", "context"], "mcp": ["templates"], "network": ["https://api.github.com"] @@ -297,6 +298,23 @@ The host package must publish everything the global CLI and extension authors ne } } ], + "colorThemes": [ + { + "id": "acme.github.midnight", + "label": "GitHub Midnight", + "scale": { + "--cool-grey-950": "#080B12", + "--cool-grey-900": "#111827" + }, + "heroui": { + "--heroui-background": "222 40% 5%", + "--heroui-foreground": "216 26% 82%" + }, + "tokens": { + "--oh-color-primary": "#7DD3FC" + } + } + ], "settingsPanels": [ { "id": "github-settings", @@ -423,7 +441,46 @@ MVP navigation locations: Primary sidebar entries are for user-facing app surfaces, not transient launch templates. They must point at a Canvas Extension view route, use a static icon asset from the extension package, and remain disabled when the extension is disabled or invalid. If multiple enabled Canvas Extensions contribute to the same slot, Canvas sorts by `order`, then extension display name, then view ID for deterministic rendering. -### 12.2 `settingsPanels` +### 12.2 `colorThemes` + +Color themes let a Canvas Extension add one or more theme options to Settings > Application > Color Theme. A theme-only Canvas Extension is valid: it may contribute `colorThemes` and no views, settings panels, right panels, tool visualizers, or agent-runtime behavior. + +Manifest contribution shape: + +```json +{ + "contributes": { + "colorThemes": [ + { + "id": "acme.github.midnight", + "label": "GitHub Midnight", + "scale": { + "--cool-grey-950": "#080B12", + "--cool-grey-900": "#111827" + }, + "heroui": { + "--heroui-background": "222 40% 5%", + "--heroui-foreground": "216 26% 82%" + }, + "tokens": { + "--oh-color-primary": "#7DD3FC" + } + } + ] + } +} +``` + +Rules: + +- Extension color themes feed the same Settings > Application > Color Theme dropdown used by built-in themes. They should not create a separate settings panel or custom picker. +- Canvas owns theme selection, persistence, application, fallback, and validation. Extensions contribute theme definitions only. +- MVP theme definitions should align with the current `ColorThemeDefinition` shape in [src/themes/color-themes.ts](../src/themes/color-themes.ts): `label`, `scale` CSS custom property overrides, HeroUI HSL-channel overrides, and optional semantic `tokens`. +- Canvas should validate allowed CSS custom property keys rather than allowing arbitrary global CSS. This keeps theme contributions narrower than same-origin browser modules even though the extension package itself remains trusted local code. +- If the active theme belongs to a disabled, removed, or invalid extension, Canvas falls back to the default built-in color theme and records a diagnostic. +- Duplicate theme IDs are rejected. Theme IDs should be namespaced by extension ID or package domain for predictable persistence. + +### 12.3 `settingsPanels` Settings panels let a Canvas Extension add configuration UI without owning a full route. They are intentionally constrained to one visible location: after all built-in settings sections, under an explicit **Extensions** header inside the current settings experience. @@ -446,12 +503,12 @@ Manifest contribution shape: Rules: -- Canvas Extension settings panels never appear interleaved with built-in settings. They render only under the visible Extensions header after other settings. +- Canvas Extension settings panels never appear intertwined with built-in settings. They render only under the visible Extensions header after other settings. - The panel renderer may reuse a declared `viewId` or a dedicated browser module entry, but Canvas owns the settings chrome, heading, save/cancel affordances, loading state, and error boundary. - Settings panels receive only extension metadata/settings, schema-derived values, and host APIs for `readExtensionSettings()` / `patchExtensionSettings()`. They do not receive raw global settings stores. - If an extension is disabled or invalid, its settings panel is omitted and its persisted settings remain in the install store until removal/purge. -### 12.3 `conversationRightPanels` +### 12.4 `conversationRightPanels` Conversation right panels let a Canvas Extension add a work surface beside the built-in Files, Browser, and Terminal panels. They are for conversation-adjacent tools such as PR context, cost dashboards, deployment status, or domain-specific inspectors. @@ -509,7 +566,7 @@ This DOM-island contract avoids React shared-instance problems in static builds **Reserved future runtime: sandboxed iframe.** The manifest reserves `browser.entry` (§11.1) for a future iframe runtime intended for untrusted third-party extensions (e.g., a community marketplace). The iframe sandbox would be `sandbox="allow-scripts allow-forms allow-downloads"` with no `allow-same-origin`, communicating with the parent through a postMessage RPC bridge that exposes the same context API. This is not implemented in MVP; declaring it now keeps the option open without committing implementation cost today. -### 12.4 `agentPlugins` +### 12.5 `agentPlugins` Maps to OpenHands SDK `PluginSource` values. Fields: `id`, `source` (package-relative path, GitHub shorthand, Git URL, or absolute path for dev mode), `ref` (optional branch/tag/commit), `repoPath` (optional subdirectory for remote git/GitHub sources), `autoInclude` (`manual` | `enabled` | `always`; default `manual`). @@ -525,7 +582,7 @@ The current frontend `PluginSpec.parameters` field is collected by `/launch` but **ACP constraint.** SDK plugins can merge skills, MCP config, hooks, and plugin agents before agent initialization. MCP config and other non-ACP-compatible fields can cause `ACPAgent` initialization to fail. MVP should skip `agentPlugins` for ACP runtimes unless the SDK smoke proves a prompt/skills-only plugin is accepted; extension context suffixes remain the compatible path for ACP. -### 12.5 `mcpServers` +### 12.6 `mcpServers` Extensions provide MCP templates, never silent installs. The UI shows required MCPs before launch and reuses the existing `InstallServerModal` pattern. @@ -533,11 +590,11 @@ Extensions provide MCP templates, never silent installs. The UI shows required M **Current catalog shape.** The MCP marketplace now imports `IntegrationCatalogEntry` from `@openhands/extensions/integrations`, not the old `mcps` export. Extension MCP templates should either use the same `IntegrationTransport` vocabulary or provide an adapter that can project templates into the existing MCP install modal without reintroducing a parallel catalog model. The current install flow can save required secrets through the existing secrets-aware MCP form; extension templates should reuse that path rather than inventing separate secret persistence. -### 12.6 `launchTemplates` +### 12.7 `launchTemplates` Reusable launch presets shown on home, extension pages, or automation setup. May reference required MCP IDs, required agent plugin IDs, optional context blocks, initial prompt text, and workspace requirements. -### 12.7 `conversationContext` +### 12.8 `conversationContext` Context blocks may be appended to `AgentContext.system_message_suffix` only after explicit user enablement. Canvas renders all extension context inside a single block, appended after the existing `` block: @@ -552,7 +609,7 @@ The user enabled the following Canvas Extensions for this conversation. Extension context **must not** be merged into `conversationInstructions`; that turns extension guidance into user-message content. An explicit `extensionSystemSuffix` option will be added to the conversation adapter so runtime services and extension context compose in one place. -### 12.8 `toolVisualizers` +### 12.9 `toolVisualizers` Custom event rendering is the first concrete route-less JavaScript extension surface. PR #1246 added the internal `src/components/features/chat/tool-visualizers` registry and dispatcher: `getEventContent()` asks the registry for a React body by action/observation kind, and falls back to the existing markdown renderer when no visualizer matches. Extensions should build on that seam instead of introducing an unrelated event-rendering model. @@ -606,11 +663,11 @@ Selection rules: Public authoring types and primitives should be exported from `@openhands/agent-canvas/canvas-visualizers`, separate from the broader `@openhands/agent-canvas/canvas-extensions` management contract. The public surface should wrap or re-export stable versions of `defineVisualizer`, `VisualizerProps`, shared primitives such as code blocks/diff/output panes, and action/observation kind types. Do not expose internal file paths under `src/components/features/chat/tool-visualizers/*` as the authoring API. -### 12.9 Deferred Generic Conversation Slots +### 12.10 Deferred Generic Conversation Slots Generic conversation slots remain deferred. The concrete MVP surfaces are `conversationRightPanels` and `toolVisualizers`, because they map to existing product regions and existing renderer architecture. Header actions, footer widgets, badges, or arbitrary conversation sidebar content should not ship until there is a specific user workflow that cannot be handled by a right panel or a tool visualizer. -### 12.10 `configuration` and `secrets` +### 12.11 `configuration` and `secrets` `configuration` uses JSON Schema; values are stored in the Extension Host's `config.json`. `secrets` are declarations only; values are stored through the existing `SecretsService`. @@ -658,6 +715,11 @@ interface AgentCanvasExtensionContext { visualizer: AgentCanvasToolVisualizer, ): Promise; }; + colorThemes: { + registerColorTheme( + theme: AgentCanvasColorThemeDefinition, + ): Promise; + }; settingsPanels: { registerSettingsPanel( panel: AgentCanvasSettingsPanel, @@ -682,7 +744,7 @@ interface AgentCanvasExtensionContext { } ``` -For the first implementation slice, the route-less registration APIs are limited to `visualizers.registerToolVisualizer()`, `settingsPanels.registerSettingsPanel()`, and `conversationPanels.registerRightPanel()`. There is **no** supported generic `agentServer.request(path)`. The repo already routes Agent Server traffic through typed service wrappers; the extension surface follows the same pattern with named, permission-declared capabilities. For MVP inline browser modules, this is an API support boundary rather than a browser security boundary (see §19). +For the first implementation slice, the route-less registration APIs are limited to `visualizers.registerToolVisualizer()`, `colorThemes.registerColorTheme()`, `settingsPanels.registerSettingsPanel()`, and `conversationPanels.registerRightPanel()`. Manifest-projected `contributes.colorThemes` should be enough for most theme-only packages; the runtime API exists for the same lifecycle/disposal model as other UI registries. There is **no** supported generic `agentServer.request(path)`. The repo already routes Agent Server traffic through typed service wrappers; the extension surface follows the same pattern with named, permission-declared capabilities. For MVP inline browser modules, this is an API support boundary rather than a browser security boundary (see §19). ## 14. Shared Type Contract @@ -724,6 +786,14 @@ export interface AgentCanvasExtensionModule { | Promise; } +export interface AgentCanvasColorThemeDefinition { + id: string; + label: string; + scale: Record; + heroui: Record; + tokens?: Record; +} + export type InstallableArtifactKind = | "extension" | "sdk-plugin" @@ -750,6 +820,7 @@ Permission groups: - `ui.views` — render trusted same-origin browser-module views. - `ui.leftNavigation` — add declared left navigation entries that point at Canvas Extension views. +- `ui.colorThemes` — add validated color theme definitions to Settings > Application > Color Theme. - `ui.settingsPanels` — add settings panels only under the visible Extensions header after built-in settings. - `ui.conversationPanels` — add right-side conversation panels alongside Files, Browser, and Terminal. - `ui.toolVisualizers` — register custom action/observation renderers in the conversation event pipeline. @@ -1023,6 +1094,10 @@ For launch templates: show required MCPs; show SDK plugins to be included; show Enabled views with `navigation.location: "primarySidebar"` render as top-level Sidebar entries. For MVP, entries appear in the `afterAutomations` slot: after Automations and before the conversation list, which is the current static order in `SidebarRailBody` (`New Chat`, `Customize`, `Automations`, then `SidebarConversationList`). Collapsed Sidebar mode shows only the icon with a tooltip. Mobile drawer mode shows the same entry in the same relative position. +### Color theme UX + +Enabled `colorThemes` contributions appear in Settings > Application > Color Theme alongside built-in themes. Theme-only Canvas Extensions should require no extra UI surface: installing and enabling the extension makes its themes selectable, and disabling/removing the extension removes those options. If the current selected theme is no longer available, Canvas falls back to the default built-in theme and records a local diagnostic. + ### Settings panel UX Canvas Extension settings panels appear only in the settings experience, grouped under a visible Extensions header after built-in settings sections. Canvas owns the settings page chrome and persistence affordances; extension renderers fill only their declared panel body. @@ -1041,9 +1116,9 @@ The multi-agent, checkable execution checklist for this plan lives in [Extension ### PR 0 — Contract and types -Files: `docs/ExtensionsSystemRFC.md`, `src/canvas-extensions/types.ts`, `src/canvas-extensions/manifest-schema.ts`, `src/canvas-extensions/manifest-validation.ts`, `src/canvas-extensions/artifact-detection.ts`, `__tests__/canvas-extensions/manifest-validation.test.ts`. +Files: `docs/ExtensionsSystemRFC.md`, `src/canvas-extensions/types.ts`, `src/canvas-extensions/manifest-schema.ts`, `src/canvas-extensions/manifest-validation.ts`, `src/canvas-extensions/artifact-detection.ts`, `src/themes/color-themes.ts`, `__tests__/canvas-extensions/manifest-validation.test.ts`. -Deliverables: shared manifest and registry types; generic installable artifact kind definitions; contribution types for views, left navigation, settings panels, conversation right panels, tool visualizers, launch templates, SDK plugins, MCP templates, and context; artifact detection rules for extension packages, SDK plugins, skills, and placeholder MCP templates; schema-version constant; manifest validation helpers; storage-path helper for `~/.openhands/agent-canvas/installations`; fixtures for valid/invalid manifests; `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` subpath export plan wired to generated `dist/extensions/*` and `dist/visualizers/*`; release packaging checklist. No Extension Host, no frontend UI, no agent contribution merging. +Deliverables: shared manifest and registry types; generic installable artifact kind definitions; contribution types for views, left navigation, color themes, settings panels, conversation right panels, tool visualizers, launch templates, SDK plugins, MCP templates, and context; artifact detection rules for extension packages, SDK plugins, skills, and placeholder MCP templates; schema-version constant; manifest validation helpers; storage-path helper for `~/.openhands/agent-canvas/installations`; fixtures for valid/invalid manifests, including a theme-only extension; `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` subpath export plan wired to generated `dist/extensions/*` and `dist/visualizers/*`; release packaging checklist. No Extension Host, no frontend UI, no agent contribution merging. **Iframe-runtime forward compatibility (minimal, PR 0 scope):** the manifest schema and TypeScript contract accept both `browser.module` (active) and `browser.entry` (reserved). The validator recognizes `browser.entry` as syntactically valid but emits a `reserved-not-yet-supported` diagnostic so authors can't accidentally ship extensions that depend on an unimplemented runtime. The API contract in `src/canvas-extensions/types.ts` is defined async-only, which is the only change needed so that a future iframe runtime can satisfy the same interface over postMessage without an API redesign. No iframe host, no postMessage bridge, no asset-mode switching, and no parallel runtime code lands here — that work is deferred to a post-MVP PR triggered by one of the trust-boundary conditions in §27 Decision 11. The total PR 0 cost of keeping the option open is a handful of schema fields, one validation rule, one fixture, and a short authoring note in the type comments. @@ -1073,11 +1148,11 @@ Files: `src/components/features/chat/tool-visualizers/*`, `src/canvas-extensions Deliverables: public `@openhands/agent-canvas/canvas-visualizers` subpath; extension visualizer registry layer that composes before built-ins and then markdown fallback; `priority` and `matches()` support; error boundary/fallback behavior for extension renderers; manifest projection for `contributes.toolVisualizers`; `hello.canvas` or a focused fixture overriding one MCP tool visualizer; unit tests proving extension-first order, built-in fallback, markdown fallback, and throw-to-next-renderer behavior. -### PR 3b — Settings panels and conversation right panels +### PR 3b — Color themes, settings panels, and conversation right panels -Files: settings route/components, conversation right-panel/tab components, `src/canvas-extensions/panels/*`, `src/i18n/translation.json`, tests under `__tests__/canvas-extensions/` and focused component tests. +Files: [src/themes/color-themes.ts](../src/themes/color-themes.ts), [src/components/features/settings/app-settings/theme-input.tsx](../src/components/features/settings/app-settings/theme-input.tsx), settings route/components, conversation right-panel/tab components, `src/canvas-extensions/panels/*`, `src/i18n/translation.json`, tests under `__tests__/canvas-extensions/` and focused component tests. -Deliverables: settings panels under a visible Extensions header after built-in settings; conversation right panels beside Files, Browser, and Terminal; stable props with conversation/backend/workspace summaries where relevant; no raw store or React Query client in the public API; per-panel error boundaries; deterministic ordering by priority/order and extension ID; visible examples for a settings panel and PR/context right panel. +Deliverables: extension color themes appear in Settings > Application > Color Theme; theme-only extensions install and work without views or panels; unavailable extension themes fall back to the default built-in theme; settings panels under a visible Extensions header after built-in settings; conversation right panels beside Files, Browser, and Terminal; stable props with conversation/backend/workspace summaries where relevant; no raw store or React Query client in the public API; per-panel error boundaries; deterministic ordering by priority/order and extension ID; visible examples for a color theme, settings panel, and PR/context right panel. ### PR 3c — Dev extension watch mode @@ -1097,13 +1172,13 @@ Permission consent modal; secret setup flow; `doctor` command with actionable di ## 26. Testing Strategy -**Unit:** manifest schema validation; package path traversal rejection; duplicate ID handling; enable/disable precedence; registry sort order; asset route path validation; CLI arg parsing; dev extension registration and path validation; dev manifest revalidation after source changes; launch contribution merge and dedupe; extension context suffix rendering; runtime compatibility classification across all five runtime classes; permission drift / update re-approval; uninstall preserve vs purge. +**Unit:** manifest schema validation; package path traversal rejection; duplicate ID handling; color theme contribution validation/fallback; enable/disable precedence; registry sort order; asset route path validation; CLI arg parsing; dev extension registration and path validation; dev manifest revalidation after source changes; launch contribution merge and dedupe; extension context suffix rendering; runtime compatibility classification across all five runtime classes; permission drift / update re-approval; uninstall preserve vs purge. **Release packaging:** `npm pack --dry-run`; install from the packed tarball into a temp global/prefix; verify `agent-canvas list`, `agent-canvas doctor`, and `agent-canvas install --yes` work without a source checkout; verify full `agent-canvas` launch still finds `build/`, `scripts/`, and Extension Host routes. -**Component:** Extensions page states under Customize; permission display; browser-module view loading/error; settings panel placement under the visible Extensions header; conversation right-panel tab rendering/error boundaries; MCP required preflight; incompatible runtime warning; dev badge. +**Component:** Extensions page states under Customize; permission display; browser-module view loading/error; extension color theme appears in Settings > Application > Color Theme and can be selected; settings panel placement under the visible Extensions header; conversation right-panel tab rendering/error boundaries; MCP required preflight; incompatible runtime warning; dev badge. -**E2E snapshots:** Extensions page empty state under Customize; enabled extension with one view; invalid extension diagnostics; settings panel under Extensions header; conversation right panel beside Files/Browser/Terminal; launch template requiring MCP; launch preflight with local-path SDK plugin disabled on remote backend; dev extension view remount after file change; future: agent-mediated install proposal flow. +**E2E snapshots:** Extensions page empty state under Customize; enabled extension with one view; invalid extension diagnostics; extension color theme option in Settings > Application; settings panel under Extensions header; conversation right panel beside Files/Browser/Terminal; launch template requiring MCP; launch preflight with local-path SDK plugin disabled on remote backend; dev extension view remount after file change; future: agent-mediated install proposal flow. **Live E2E:** not required for MVP. If added later, follow existing live E2E rules under `tests/e2e/live/`. @@ -1125,19 +1200,19 @@ These were open questions in the earlier draft. Decisions for the RFC: | 4 | `src/installations/` vs `src/canvas-extensions/` directory split? | **Single `src/canvas-extensions/` directory** until a real consumer requires the split. | | 5 | API route prefix consistency? | **`/api/canvas/canvas-extensions/*`** for all management routes; `/canvas-extension-assets/*` reserved for asset serving. | | 6 | Empty `src/addons/` directory on `main`? | **No longer present.** PR 0 should avoid reviving the old `addons` namespace and use `src/canvas-extensions/`. | -| 7 | Should the first MVP independently install standalone SDK plugins, standalone skills, or MCP templates? | **No.** Detect them and return explicit unsupported diagnostics; require an Canvas Extension wrapper for MVP. | +| 7 | Should the first MVP independently install standalone SDK plugins, standalone skills, or MCP templates? | **No.** Detect them and return explicit unsupported diagnostics; require a Canvas Extension wrapper for MVP. | | 8 | Should package-relative SDK plugin paths be MVP? | **Yes, but only when `localInstallStoreReadable` is true** and the pinned Agent Server smoke passes. Remote/cloud runtimes get disabled reasons rather than local paths. | | 9 | Should extension context be global per extension, selected per launch template, or both? | **Both.** Context blocks use `autoInclude` for global/default behavior and launch templates can require/select specific context IDs. | -| 10 | Should repo-provided SDK plugins/skills ever be surfaced automatically as Canvas extensions? | **No automatic surfacing.** Repository content remains an Agent Server/runtime concern unless the user explicitly installs/registers an Canvas Extension path. | +| 10 | Should repo-provided SDK plugins/skills ever be surfaced automatically as Canvas extensions? | **No automatic surfacing.** Repository content remains an Agent Server/runtime concern unless the user explicitly installs/registers a Canvas Extension path. | | 11 | What triggers the reserved iframe runtime? | **A trust boundary requirement:** community marketplace distribution, an untrusted third-party publisher tier, or a concrete need for browser-enforced isolation/CSP. | | 12 | Should agent-proposed dev extension registration ship with dev watch mode? | **No.** Defer to the broader post-MVP agent-mediated install proposal flow. | | 13 | Which create-conversation payload shape should extensions target? | **Current SDK settings shape:** top-level `agent_settings` plus optional top-level `plugins`; do not reintroduce legacy `agent` payloads. | -| 14 | What route-less JavaScript surfaces are in scope? | **Tool visualizers, settings panels, and conversation right panels.** They map to concrete existing product regions and avoid arbitrary app-root mounting. | +| 14 | What route-less and registry-backed UI surfaces are in scope? | **Color themes, tool visualizers, settings panels, and conversation right panels.** They map to concrete existing product regions and avoid arbitrary app-root mounting. Color themes are registry data, not arbitrary JavaScript placement. | | 15 | Should generic conversation slots ship before these surfaces? | **No.** Header/footer/badge/sidebar slots remain deferred until a workflow cannot be handled by a right panel or tool visualizer. | ## 28. Remaining Open Questions -1. Should the `@openhands/extensions` catalog eventually become one first-party Extension package, or remain a plain dependency for built-in catalogs? +1. Should the `@openhands/extensions` catalog eventually become one first-party Canvas Extension package, or remain a plain dependency for built-in catalogs? 2. What is the graduation path for standalone SDK plugin, standalone skill, and MCP-template installers after the extension-package MVP proves out? 3. What provenance UI is required before enabling a public/community extension marketplace: npm integrity only, signatures, first-party allowlists, or enterprise policy? @@ -1150,13 +1225,220 @@ Build the smallest powerful slice: 3. Extensions management page. 4. Trusted same-origin browser-module extension views (with the iframe runtime reserved in the manifest schema). 5. Left navigation entries for Canvas Extension views. -6. Settings panels under a visible Extensions header after built-in settings. -7. Conversation right panels beside Files, Browser, and Terminal. -8. Extension tool visualizers. -9. Dev-mode extension registration / watch / reload. -10. SDK plugin contribution merge for local conversations. -11. Context contribution merge behind explicit permission. +6. Color themes in Settings > Application > Color Theme. +7. Settings panels under a visible Extensions header after built-in settings. +8. Conversation right panels beside Files, Browser, and Terminal. +9. Extension tool visualizers. +10. Dev-mode extension registration / watch / reload. +11. SDK plugin contribution merge for local conversations. +12. Context contribution merge behind explicit permission. Defer: marketplace, package signing, **sandboxed iframe runtime** (manifest field reserved as `browser.entry` but unimplemented), arbitrary root-mounted route-less components/shared dependency runtime, rich extension RPC APIs beyond the visualizer/slot surfaces, agent-mediated installation, SDK/server install management, plugin parameters, and per-run extension enablement. This delivers a useful end-to-end extension system while keeping the Agent Server boundary clean. + +## 30. Engineering FAQ + +### 30.1 Naming And Product Scope + +**Q: What is a Canvas Extension, exactly?** + +A Canvas Extension is an Agent Canvas-specific package that extends the Canvas UI, UX, or local product behavior. It can add left navigation entries, settings panels, conversation right panels, custom tool visualizers, launch templates, SDK plugin descriptors, MCP templates, and system-context blocks. It is not the same thing as an OpenHands SDK plugin, a skill, an MCP server, or the existing `@openhands/extensions` catalog. + +**Q: How is this different from the existing `@openhands/extensions` repo/package?** + +`@openhands/extensions` is an OpenHands catalog/dependency for shared assets such as integrations metadata. It is not an installable Canvas Extension format. Canvas Extensions may reuse data from that catalog, and a future first-party Canvas Extension could wrap some of it, but the catalog should remain a plain dependency until there is a product reason to expose it as a user-installed Canvas Extension. + +**Q: Why is the product name "Canvas Extensions" if the app section was renamed from Extensions to Customize?** + +The app-level section remains Customize. Inside Customize, the Canvas Extension management item can be named Extensions because it is specifically about installed Canvas Extensions. The RFC avoids reintroducing a top-level Extensions hub and explicitly treats legacy file names like `extensions-navigation.tsx` as Customize implementation details. + +**Q: Why not call the management page "Packages"?** + +"Packages" is too broad and makes this feature sound like a generic npm/package manager. The user-facing surface should be an Extensions page under Customize. The page should be a simple stacked view of installed Canvas Extensions and their status. Package terminology stays in CLI/install mechanics where npm package behavior is actually being discussed. + +**Q: Should the manifest still be named `agent-canvas.extension.json`?** + +Current recommendation: keep it for MVP because it is explicit to Agent Canvas, short, and already used throughout the plan. Renaming it to something like `agent-canvas.canvas-extension.json` would reduce ambiguity but adds churn. Clarification requested: do we want to pay that naming churn before PR 0, or keep the shorter manifest name and rely on docs/product vocabulary? + +**Q: Are `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` the right public subpaths?** + +They are intentionally verbose to avoid confusion with `@openhands/extensions`. They also distinguish Canvas Extension authoring types from visualizer authoring helpers. Clarification requested: should we keep these explicit subpaths, or prefer shorter `./extensions` and `./visualizers` exports with stronger documentation? + +### 30.2 UI Contribution Surfaces + +**Q: What UI surfaces are in MVP scope?** + +Five concrete surfaces are in scope: + +- Left navigation entries that point to Canvas Extension views. +- Color themes that appear in Settings > Application > Color Theme. +- Settings panels shown only under a visible Extensions header after built-in settings. +- Conversation right panels shown beside Files, Browser, and Terminal. +- Tool visualizers that render matched action/observation events. + +Generic header/footer/sidebar/badge slots are deferred. + +**Q: Can a Canvas Extension just register a color theme?** + +Yes. Theme-only Canvas Extensions should be a first-class supported case because color themes are likely to be an early low-risk extension type. A theme-only package can contribute `colorThemes` and no view, settings panel, right panel, tool visualizer, SDK plugin, MCP template, or launch template. Once enabled, its themes appear in Settings > Application > Color Theme with built-in themes. + +**Q: Are extension color themes arbitrary CSS?** + +No. The proposed MVP contract is validated theme data aligned with the current `ColorThemeDefinition` model: `scale` CSS custom properties, HeroUI HSL-channel values, and optional semantic `tokens`. Canvas owns validation, persistence, application, and fallback. This should not become a general-purpose global stylesheet injection API. + +**Q: Why defer generic conversation slots and badges?** + +The concrete product surfaces already cover the known use cases without opening a generic app-root or arbitrary placement API. Right panels give extensions a substantial conversation-adjacent surface. Tool visualizers handle event-specific rendering. Header/footer/badge slots should wait for a workflow that cannot be handled by those two surfaces. + +**Q: Where do settings panels render?** + +Only in the settings experience, grouped under a visible Extensions header after built-in settings sections. They should not be interleaved with built-in settings and should not create a new top-level Extensions hub. Canvas owns the settings page chrome, persistence affordances, loading states, and error boundaries. + +**Q: Do Canvas Extension settings panels appear in settings navigation?** + +MVP recommendation: no separate nav item per extension panel unless scanning becomes poor. Start with one Extensions grouping after built-in settings and render installed extension panels in a stacked/ordered list. Clarification requested: should each Canvas Extension settings panel get a left-nav anchor, or should the MVP keep one grouped Extensions section only? + +**Q: Where do conversation right panels render?** + +They render in the existing conversation right-panel/tab region beside Files, Browser, and Terminal. Canvas owns tab placement, collapsed/expanded behavior, focus handling, loading state, and error boundaries. + +**Q: How should mobile handle Canvas Extension right panels?** + +MVP recommendation: reuse the existing mobile conversation panel pattern and show extension panels as additional entries in that panel navigation, if technically straightforward. If not, hide extension right panels on small viewports with a clear disabled/unsupported reason until a mobile design is intentionally added. Clarification requested: is mobile parity required for the first proof? + +**Q: What happens if multiple extensions add left-nav entries or right panels?** + +Sort deterministically by declared `order`, extension display name, then contribution ID. Disabled or invalid extensions do not render. The host should keep enough metadata in the registry for stable ordering and diagnostics. + +### 30.3 Security And Trust + +**Q: What is the MVP trust model?** + +MVP Canvas Extensions are trusted local code. Browser modules run same-origin with Canvas after explicit install/enable consent. This is not a browser-enforced sandbox. The trust model is consent, local install provenance, permission display, diagnostics, and a process-level kill switch. + +**Q: What can a malicious enabled Canvas Extension access?** + +In MVP, trusted same-origin browser code may be able to inspect browser-visible Canvas state, call same-origin routes if it can access credentials/session context, manipulate DOM, and contact declared or undeclared network origins unless stricter CSP enforcement is added. The RFC therefore does not claim strong isolation until the reserved iframe runtime is implemented. + +**Q: Is install-time consent enough?** + +For first-party/local/dev Canvas Extensions, yes as an MVP starting point. For a community marketplace, untrusted publishers, or enterprise policy enforcement, no. Those cases should trigger the reserved iframe runtime, stronger provenance checks, and likely tighter network/CSP controls before broad distribution. + +**Q: Why reserve `browser.entry` for iframes now?** + +It keeps the manifest and runtime API compatible with a future sandboxed document model. The async API shape can later cross `postMessage` without redesigning the extension author contract. + +**Q: How does disable/revocation work?** + +Disabling an extension removes its contributions from the registry and should unmount active browser modules on the next registry refresh or route/panel remount. Development and implementation should also provide a process-level `--disable-extensions` kill switch. Clarification requested: do we need immediate live unmount across all currently mounted surfaces in MVP, or is refresh/remount behavior acceptable for the first proof? + +**Q: Can an extension impersonate built-in UI?** + +Canvas should visibly mark extension-owned pages/panels and should own shell/chrome for settings panels and conversation right panels. Extension content fills declared bodies only; it should not own placement, top-level navigation shell, or settings page chrome. + +### 30.4 Runtime And Host Architecture + +**Q: Why use a local Extension Host instead of bundling extensions at build time?** + +Global npm installs cannot rebuild Canvas for each installed extension. A local Extension Host can install packages, validate manifests, serve browser assets, and provide registry/diagnostic APIs without requiring a source checkout or rebuild. + +**Q: Does the Extension Host run in every mode?** + +It should run in packaged CLI, `npm run dev`, `dev:minimal`, and static mode when the frontend needs to manage or render Canvas Extensions. Frontend-only can expose local management/assets with agent-runtime diagnostics. Backend-only should skip browser asset hosting unless a future CLI-only host mode needs it. CLI management commands must still work before full stack startup. + +**Q: Why `/api/canvas/canvas-extensions/*` for management and `/canvas-extension-assets/*` for assets?** + +The management prefix clearly identifies local Canvas-owned management APIs and avoids confusing them with Agent Server `/api/*` traffic. The asset prefix is separate because it serves extension-authored static files and benefits from being easy to identify in logs and future CSP rules. + +**Q: What happens if the Extension Host is unavailable?** + +Canvas should degrade gracefully: built-in app behavior continues, extension contributions are absent, and the Extensions page or relevant surfaces show an Extension Host unavailable diagnostic. The Extension Host should not be a dependency for ordinary Agent Server API calls. + +**Q: How are extension registry updates invalidated?** + +MVP should use React Query invalidation after install/enable/disable/remove/settings mutations and a dev-mode version token for browser modules. Dev watch should bump a cache-busting token so static dynamic imports remount with new output. + +### 30.5 Manifest And Packaging + +**Q: Is one `browser.module` per extension enough?** + +For MVP, yes. A single browser module plus contribution IDs keeps packaging simple. Settings panels, right panels, views, and visualizers can reference `viewId` or register contributions through that module. If this becomes awkward, a later schema can allow per-contribution modules. + +**Q: Should settings panels and right panels reference `viewId`, modules, or exported component IDs?** + +Current recommendation: allow them to reference a declared `viewId` for MVP so the host has one browser-module loading path. Runtime registration can provide the actual panel renderer. Clarification requested: do we want the manifest to declare only metadata and let the module register implementations, or should each contribution explicitly name an exported symbol? + +**Q: How are path traversal and package containment handled?** + +Manifest paths for browser modules, icons, SDK plugin roots, and other assets must resolve inside the installed package root. Any `..` traversal or absolute path from a normal installed package should be rejected. Dev-mode local paths are allowed only through explicit `--dev` registration. + +**Q: What happens when permissions change on update?** + +Permission-expanding updates should move the extension back to a disabled or needs-review state until the user re-approves. The registry should preserve diagnostics and show the permission diff. + +### 30.6 Tool Visualizers + +**Q: Can an extension override built-in visualizers?** + +Yes, intentionally. Extension visualizers are considered before built-ins, with `priority` and `matches()` to narrow overrides. If no extension matches or an extension fails, Canvas falls back to built-ins and then markdown. + +**Q: What if two extensions match the same event?** + +Higher priority wins. Ties sort by extension ID and visualizer ID so behavior is deterministic. Diagnostics should expose which visualizer was selected when debugging is enabled. + +**Q: How do thrown visualizers fall through safely?** + +The dispatcher should wrap extension visualizers in a failure boundary that records a local diagnostic and tries the next matching renderer. This may require rendering candidates through an isolation wrapper instead of a single broad React error boundary. The implementation spike should prove this before committing the public API. + +**Q: Can visualizers fetch data or read settings while rendering?** + +MVP recommendation: visualizers should receive narrow props and host helpers, but should avoid arbitrary store or React Query access. If a visualizer needs extension settings, it should receive stable settings data through the extension context or a host-provided hook/API, not import Canvas internals. + +### 30.7 Agent Runtime Boundary + +**Q: Do Canvas Extensions load code into the Agent Server?** + +No. Canvas does not patch or load Canvas Extension code inside the Agent Server. Agent-side behavior is forwarded only through existing SDK-supported surfaces: `plugins`, MCP settings/templates, and `AgentContext.system_message_suffix`. + +**Q: Which contributions affect conversation creation?** + +SDK plugin descriptors, selected MCP requirements/templates, launch templates, and context blocks can affect new conversation payloads. Pure UI contributions such as left nav, color themes, settings panels, right panels, and visualizers do not by themselves change the agent runtime. + +**Q: How do local-path SDK plugin contributions work with remote/cloud/ACP runtimes?** + +Package-relative or local SDK plugin paths are allowed only when `localInstallStoreReadable=true`, meaning the Agent Server process was launched by the same local Canvas stack and can read the install store. Remote/cloud runtimes get disabled reasons instead of local paths. ACP runtimes should skip SDK plugin contributions unless a smoke test proves the exact plugin shape is compatible. + +**Q: What is the audit trail for extension contributions?** + +MVP should include extension IDs and contribution IDs in launch preflight UI and in the composed extension system suffix. Recommended follow-up: persist extension IDs/versions and selected contributions on conversation metadata when the backend exposes an appropriate field. + +**Q: What if an extension is disabled after a conversation starts?** + +Disabling should stop future UI contributions and future conversation launches from using it. Existing conversations may already contain context suffixes or SDK plugin behavior from creation time; disabling cannot reliably remove already-applied agent runtime state from an active conversation. + +### 30.8 Testing And Proof + +**Q: What is the smallest useful fixture extension?** + +`hello.canvas` should prove the full vertical path: install, registry, Extensions page row/status, left nav entry, browser-module view, color theme, settings panel, conversation right panel, one tool visualizer, launch preflight/context contribution, SDK plugin compatibility path, and dev remount. + +**Q: Should all UI surfaces land in one PR?** + +No. The RFC already splits the work. PR 3 covers view host and left navigation, PR 3a covers tool visualizers, and PR 3b covers color themes, settings panels, and conversation right panels. PR 2 can land the Extensions management page independently once the registry shape is stable. + +**Q: What tests are required before sharing broadly?** + +At minimum: manifest validation, route/path containment tests, no-direct-Agent-Server API guard tests, Extension Host route tests, visualizer ordering/fallback tests, component tests for Extensions page/settings/right-panel surfaces, and a packaged/static dynamic-import smoke. Full `npm run typecheck && npm test && npm run build` remains the final verification tier. + +### 30.9 Clarifications Requested + +These are the decisions that would benefit from product/engineering direction before PR 0: + +1. Keep `agent-canvas.extension.json`, or rename the manifest to make "Canvas Extension" even more explicit? +2. Keep public subpaths as `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers`, or shorten them? +3. Should settings panels appear in one grouped Extensions section only, or should each installed Canvas Extension get a settings nav anchor? +4. Is mobile support for conversation right panels required in the first proof? +5. Do disabled extensions need immediate live unmount across all mounted surfaces in MVP? +6. Should manifest contributions declare implementation exports explicitly, or should browser modules register implementations at runtime? +7. What provenance bar is required before any non-first-party/community Canvas Extension distribution: npm integrity, signatures, first-party allowlist, enterprise policy, or some combination? From fa2a3259316691a55b38b0fe364667608b383c5f Mon Sep 17 00:00:00 2001 From: Devin Date: Thu, 11 Jun 2026 16:51:11 -0400 Subject: [PATCH 10/14] Continue fine tuning, include ref to draft pr on visualizer registry --- docs/ExtensionsSystemAgentExecutionPlan.md | 70 +++++--- docs/ExtensionsSystemPoCPlan.md | 52 +++--- docs/ExtensionsSystemRFC.md | 184 +++++++++++++-------- 3 files changed, 194 insertions(+), 112 deletions(-) diff --git a/docs/ExtensionsSystemAgentExecutionPlan.md b/docs/ExtensionsSystemAgentExecutionPlan.md index d031f6952..cd922f209 100644 --- a/docs/ExtensionsSystemAgentExecutionPlan.md +++ b/docs/ExtensionsSystemAgentExecutionPlan.md @@ -4,7 +4,7 @@ Status: executable handoff plan Source RFC: [ExtensionsSystemRFC.md](./ExtensionsSystemRFC.md) Source PoC plan: [ExtensionsSystemPoCPlan.md](./ExtensionsSystemPoCPlan.md) Target: a working proof of concept that can be built by multiple agents without one agent holding all context -Current repo baseline: merged through `upstream/main` at `40844533`. This checklist assumes `agentServer` `1.27.0`, the current local/public auth modes, frontend-only/backend-only partial stacks, MCP catalog data from `@openhands/extensions/integrations`, the merged PR #1246 tool visualizer registry, and public skills defaulting on unless `VITE_LOAD_PUBLIC_SKILLS=false`. +Current repo baseline: merged through `upstream/main` at `40844533`. This checklist assumes `agentServer` `1.27.0`, the current local/public auth modes, frontend-only/backend-only partial stacks, MCP catalog data from `@openhands/extensions/integrations`, the merged PR #1246 tool visualizer registry, draft PR #1277 as the current lead for extension-facing visualizer registration, and public skills defaulting on unless `VITE_LOAD_PUBLIC_SKILLS=false`. ## 1. How To Use This Plan @@ -18,6 +18,7 @@ Rules for every agent: - Keep each task scoped to its listed files unless implementation discovers a real dependency. - End every task with: files changed, tests run, remaining risks, and the next task ID that is unblocked. - Do not weaken repo rules: frontend Agent Server calls use typed clients; Extension Host routes use the dedicated wrapper exception; UI strings go through i18n; package dependencies stay exact-pinned. +- Install, update, and remove are CLI-only in every mode; never add browser controls for them. Enable, disable, and new-extension discovery are CLI + restart by default, but become live browser actions on the development source stack when the launcher reports `liveExtensionManagement` (the Vite-served `npm run dev` / `dev:minimal` stack). Gate the host's `enable`/`disable`/`rescan` routes and the Extensions page toggles/rescan behind that capability; the packaged/Docker/static page stays read-only. See RFC §15.2 and §27 Decision 16. Suggested verification tiers: @@ -40,7 +41,7 @@ Use gates to coordinate merge order. Tasks inside a gate can be split, but the g | G0 | Risk burners and contracts | Mostly serial | Everything else | | G1 | Manager, CLI, install store, host routes | Manager/CLI and launcher work can split after shared contracts land | UI, view host, conversation merge | | G2 | Example extension and package release path | Can run alongside G1 after contracts land | View host, acceptance demo | -| G3 | Frontend Extensions page and CanvasExtensionService | Can run once Extension Host registry shape is stable | View host UX, dev mode UX | +| G3 | Frontend Extensions inventory page and CanvasExtensionService | Can run once Extension Host registry shape is stable | View host UX, dev mode UX | | G4 | Browser-module view host, left navigation, settings panels, right panels, and visualizers | Can run after registry/assets exist; visualizers can split after shared types land | Acceptance demo | | G5 | Conversation contribution merge | Can run after launch-contributions endpoint exists | Final proof | | G6 | Dev mode watch/remount | Can run after manager, host, and view host exist | Final proof | @@ -117,7 +118,7 @@ Avoid these overlapping edits unless one agent owns the integration: - Validation for color theme IDs and allowed theme token keys. - Artifact detection order: Canvas Extension, SDK plugin, skill, MCP placeholder. - Storage helpers for `~/.openhands/agent-canvas/installations`. - - Public package exports `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` with emitted `dist/extensions/*` and `dist/visualizers/*` outputs. + - Public package exports `@openhands/agent-canvas/canvas-extensions` and the OpenHands agent-team-approved visualizer export, with emitted `dist/extensions/*` and matching visualizer output. - [ ] Tests: - Valid extension manifest. - Missing required fields. @@ -137,7 +138,7 @@ npm run build:lib ``` - [ ] Done when: - - Type consumers can import from `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers`. + - Type consumers can import from `@openhands/agent-canvas/canvas-extensions` and the final visualizer authoring export. - No runtime host, CLI store, or UI behavior is introduced in this gate. - [ ] Handoff notes: @@ -156,11 +157,11 @@ npm run build:lib - Read/write registry state with atomic-ish writes where practical. - Install Canvas Extension manifests from local paths first; tarball/npm spec can follow in the same gate if scoped. - Run npm installs in the private store with `--ignore-scripts` by default. - - Enable/disable/remove/update state transitions. + - Enable/disable/remove/update state transitions exposed as manager functions; the CLI calls them directly and the host calls enable/disable/rescan only when live management is enabled. The manager itself is mode-agnostic. - Disabled wins over enabled if config is manually inconsistent. - Typed unsupported diagnostics for standalone SDK plugin, `SKILL.md`, and MCP-template artifacts. - Registry projection with `assetBaseUrl`, diagnostics, state, version, package name. - - Launch contribution projection from enabled extensions. + - Launch contribution projection from extensions enabled at process startup. - [ ] Tests: - Store bootstrap. - Local extension install and enable. @@ -223,18 +224,19 @@ npm test -- __tests__/canvas-extensions/extension-cli.test.ts - [ ] Implement: - `GET /api/canvas/canvas-extensions/registry` - `GET /api/canvas/canvas-extensions/diagnostics` - - `POST /api/canvas/canvas-extensions/install` - - `POST /api/canvas/canvas-extensions/:id/enable` - - `POST /api/canvas/canvas-extensions/:id/disable` - - `DELETE /api/canvas/canvas-extensions/:id` + - `GET /api/canvas/canvas-extensions/:id/settings` - `PATCH /api/canvas/canvas-extensions/:id/settings` - `GET /api/canvas/canvas-extensions/launch-contributions` - `GET /canvas-extension-assets/:id/*assetPath` - - Session API key guard for mutating routes. + - `POST /api/canvas/canvas-extensions/:id/enable`, `POST /api/canvas/canvas-extensions/:id/disable`, and `POST /api/canvas/canvas-extensions/rescan` — registered only when the launcher reports `liveExtensionManagement`. + - Session API key guard for settings mutation and for the gated enable/disable/rescan routes. - Static asset path containment and content types. - [ ] Tests: - Registry and diagnostics read. - - Mutating routes reject missing/bad API key. + - Settings mutation rejects missing/bad API key. + - Install/update/remove browser routes are absent in every mode. + - `enable`/`disable`/`rescan` routes are absent when `liveExtensionManagement` is false and present + API-key-guarded when true. + - Live enable/disable mutates `config.json`; rescan surfaces a newly installed extension. - Asset serving works and traversal fails. - Launch-contributions filters disabled/invalid extensions. - [ ] Verification: @@ -264,11 +266,13 @@ npm test -- __tests__/canvas-extensions/extension-host.test.ts - Add Vite proxy support for direct Vite access. - Add static server route support for direct static-port access. - Cover `--frontend-only` and `--backend-only`: frontend-only may expose local Extensions/browser assets with agent-runtime diagnostics; backend-only should skip browser asset hosting unless a future CLI-only host mode explicitly needs it. - - Emit frontend env/runtime metadata: extension enabled flag, `localInstallStoreReadable`, route/base if needed. + - Emit frontend env/runtime metadata: extension enabled flag, `localInstallStoreReadable`, `liveExtensionManagement`, route/base if needed. + - Derive `liveExtensionManagement` from the existing `services.frontend.kind` in `scripts/runtime-services-info.mjs`: true for the Vite dev server (`npm run dev` / `dev:minimal`), false for the static server (packaged CLI, Docker, `dev:static`). Use it to register/skip the host enable/disable/rescan routes. - Do not expose a write-capable Extension Host key in ``. - [ ] Tests: - Route precedence unit tests for `scripts/static-server.mjs` and ingress route ordering. - Launcher config test for `localInstallStoreReadable=true` only when this launcher starts the local Agent Server. + - Launcher config test for `liveExtensionManagement=true` only on the Vite dev stack and false for static/packaged/Docker. - Partial-stack route tests for frontend-only/backend-only behavior. - Vite config contains extension routes before generic `/api` where applicable. - [ ] Verification: @@ -322,7 +326,7 @@ npm test -- __tests__/canvas-extensions - `__tests__/canvas-extensions/package-smoke.test.ts` if automated - [ ] Implement: - Ensure `files` includes every runtime script/build artifact needed by the global CLI and Extension Host. - - Ensure `./extensions` export resolves from packed package. + - Ensure `./canvas-extensions` and the final visualizer export resolve from the packed package. - Add a repeatable smoke for packed tarball install into a temp npm prefix if practical. - [ ] Verification: @@ -337,7 +341,7 @@ npm pack --dry-run - `agent-canvas list canvas-extensions` and `agent-canvas doctor` work from packed/global install. - [ ] Handoff notes: -## 6. Gate G3: Frontend Extensions Management +## 6. Gate G3: Frontend Extensions Inventory ### G3.1 CanvasExtensionService API Wrapper @@ -350,10 +354,12 @@ npm pack --dry-run - [ ] Implement: - Dedicated wrapper for every `/api/canvas/canvas-extensions/*` call. - Narrow test exception for this wrapper, including the current `fetch('/api/...')` guard. - - React Query hooks for registry, diagnostics, install, enable, disable, remove, settings patch, launch contributions. + - React Query hooks for registry, diagnostics, settings read/patch, and launch contributions. + - Capability-gated enable/disable/rescan mutation methods that call the host routes only when `liveExtensionManagement` is true and invalidate the registry query on success; never expose install/update/remove mutations. - [ ] Tests: - No-direct-Agent-Server guard still fails arbitrary `/api/*` fetches outside the wrapper. - Wrapper builds the expected routes and auth headers. + - Enable/disable/rescan mutations invalidate the registry query and are not exposed (or no-op) when `liveExtensionManagement` is false. - [ ] Verification: ```sh @@ -384,10 +390,13 @@ npm run typecheck - Simple stacked view of installed Canvas Extensions and their status. - Status grouping/filtering only as needed for scanability: Enabled, Disabled, Invalid, Dev. - Rows/cards show display name, package, version, state, contribution badges, required secrets, diagnostics, source path for dev entries. - - Actions for install, enable, disable, remove. + - Read `liveExtensionManagement` from launcher metadata. When false: read-only with CLI guidance for enable/disable plus a restart note. When true: per-row enable/disable toggles and a rescan action wired to the gated service methods, with registry-query invalidation on success. + - CLI guidance for install, update, and remove in every mode; never render browser install/update/remove actions. - User-facing copy goes through i18n keys. - [ ] Tests: - - Component tests for empty/enabled/invalid/dev states. + - Component tests for empty/enabled/invalid/dev/needs-review states. + - Read-only CLI guidance when `liveExtensionManagement` is false; no toggles rendered. + - Enable/disable toggles and rescan rendered and functional when `liveExtensionManagement` is true, including registry invalidation on a successful toggle. - Snapshot tests for key states if scope allows in the PR. - [ ] Verification: @@ -398,7 +407,7 @@ npm test ``` - [ ] Done when: - - A user can see and manage `hello.canvas` from the UI. + - A user can inspect `hello.canvas` status and diagnostics from the UI, with management handled by CLI and restart. - [ ] Handoff notes: ## 7. Gate G4: Browser Module View Host And UI Surfaces @@ -472,6 +481,7 @@ npm run test:e2e:snapshots -- --grep "sidebar" - [ ] Owner: - [ ] Depends on: G0.3, G1.3, G3.1 +- [ ] Coordination required: OpenHands agent team decision on PR #1277 or its successor - [ ] Files likely touched: - `src/components/features/chat/tool-visualizers/*` - `src/canvas-extensions/visualizers/*` @@ -481,15 +491,16 @@ npm run test:e2e:snapshots -- --grep "sidebar" - visualizer tests under `__tests__/components/features/chat/tool-visualizers/` - extension tests under `__tests__/canvas-extensions/` - [ ] Implement: - - Public `@openhands/agent-canvas/canvas-visualizers` subpath with stable authoring types and primitives. - - Extension visualizer registry layer that composes before built-in visualizers and markdown fallback. - - `priority` plus `matches()` support. + - Public visualizer authoring export matched to the OpenHands agent team's approved API; use PR #1277 as the current starting point. + - Extension visualizer registry layer that composes before built-in visualizers and markdown/default fallback. + - `matches()` support for narrowing one event variant. + - No Canvas-only `priority` field unless the agent team approves it. - Manifest projection for `contributes.toolVisualizers`. - Error boundary/fallback behavior: thrown extension renderer records a diagnostic and falls through to the next renderer/default. - Fixture extension visualizer for one MCP/tool variant. - [ ] Tests: - Extension visualizer wins over built-in when matching. - - Higher priority wins among extension visualizers. + - Multiple matching extension visualizers follow the approved registry order. For PR #1277 this means latest registration first. - `matches()` narrows one action/observation variant without replacing the whole kind. - Built-in visualizer still handles unmatched events. - Markdown fallback still handles unregistered events. @@ -525,8 +536,10 @@ npm run typecheck - Support theme-only extensions with no view/panel/visualizer contribution. - Fall back to the default built-in theme if the selected extension theme is disabled, removed, or invalid. - Settings panel registry for `contributes.settingsPanels`. + - Settings panel manifests declare metadata only; browser modules register implementations by contribution ID. - Render settings panels only under a visible Extensions header after built-in settings sections. - Conversation right-panel registry for `contributes.conversationRightPanels`. + - Conversation right-panel manifests declare metadata only; browser modules register implementations by contribution ID. - Render conversation right panels alongside Files, Browser, and Terminal. - Stable panel props: conversation ID/status where relevant, active backend summary, workspace summary, extension settings helpers, navigation/toast helpers. - Deterministic ordering by `order`, extension display name, and panel contribution ID. @@ -537,7 +550,9 @@ npm run typecheck - Theme-only extension contributes no other UI and remains valid. - Selected extension theme falls back when disabled/invalid. - Settings panels render only under the Extensions header after built-in settings. + - Missing settings panel implementations produce diagnostics instead of breaking Settings. - Conversation right panels render beside Files, Browser, and Terminal. + - Missing right-panel implementations produce diagnostics instead of breaking the conversation UI. - Ordering is deterministic. - Disabled/invalid extensions do not render panel content. - Panel errors are localized and do not break settings or conversation pages. @@ -626,6 +641,7 @@ npm run typecheck - Serve assets directly from registered source folder. - Watch manifest and declared output files. - Manifest changes revalidate and update diagnostics. + - Permission, network, secret, or agent-affecting contribution expansion moves the dev extension to needs-review/disabled-for-next-run until CLI re-approval and restart. - Browser module changes bump version token and remount affected views. - Agent-side changes require new conversation. - [ ] Tests: @@ -633,6 +649,7 @@ npm run typecheck - Absolute path storage. - Traversal rejection. - Invalid manifest update transitions to invalid diagnostics. + - Permission-expanding manifest update transitions to needs-review/disabled-for-next-run. - Asset change emits remount/cache-bust signal. - [ ] Done when: - A developer can rebuild `hello.canvas` and see the view remount without rebuilding Canvas. @@ -657,7 +674,8 @@ node bin/agent-canvas.mjs ``` - [ ] Browser checks: - - Extensions shows `hello.canvas` enabled with no diagnostics. + - Extensions shows `hello.canvas` enabled with no diagnostics in the read-only inventory. + - Extensions shows CLI/restart guidance rather than browser management actions. - Left navigation entry appears after Automations and before conversations. - Extension view renders. - Extension color theme appears in Settings > Application > Color Theme and can be selected. @@ -668,6 +686,7 @@ node bin/agent-canvas.mjs - Conversation payload includes extension suffix. - Remote/cloud compatibility path shows disabled reason for local plugin path. - Dev registration and remount path works in `npm run dev`. + - In `npm run dev`, the Extensions page enable/disable toggle reconciles UI contributions without a restart, and rescan surfaces a newly installed extension; the same build served via the static/packaged path shows the page read-only with no toggles. - [ ] Done when: - The PoC works from a clean checkout. - [ ] Handoff notes: @@ -721,6 +740,9 @@ The PoC is not complete unless all of these are true: - [ ] Browser modules are trusted same-origin DOM islands and documented/consented as such. - [ ] Bare runtime imports in extension browser modules are rejected for MVP. - [ ] Local SDK plugin paths are sent only when `localInstallStoreReadable=true`. +- [ ] Install, update, and remove are CLI-only with no browser routes or controls in any mode. +- [ ] Enable/disable/rescan host routes and Extensions page controls exist only when `liveExtensionManagement=true` (the Vite dev stack); the packaged/Docker/static page is read-only and restart-bounded. +- [ ] Live enable/disable reconciles UI contributions without a restart; SDK plugin and context changes still apply only to conversations created after the change. - [ ] ACP runtimes do not receive incompatible extension plugin/MCP contributions. - [ ] `conversationInstructions` never receives extension system context. - [ ] Extension context composes with existing `` suffix. diff --git a/docs/ExtensionsSystemPoCPlan.md b/docs/ExtensionsSystemPoCPlan.md index f97929a8d..a249ca0ec 100644 --- a/docs/ExtensionsSystemPoCPlan.md +++ b/docs/ExtensionsSystemPoCPlan.md @@ -4,7 +4,7 @@ Status: working build plan Source RFC: [ExtensionsSystemRFC.md](./ExtensionsSystemRFC.md) Agent execution checklist: [ExtensionsSystemAgentExecutionPlan.md](./ExtensionsSystemAgentExecutionPlan.md) Target branch: `dv/extensions-poc-v1` -Current repo baseline: merged through `upstream/main` at `40844533`. The plan below has been refreshed for the current launcher/auth split, partial-stack modes, MCP integrations catalog, `agentServer` pin `1.27.0`, the merged PR #1246 tool visualizer registry, and public skills defaulting on. +Current repo baseline: merged through `upstream/main` at `40844533`. The plan below has been refreshed for the current launcher/auth split, partial-stack modes, MCP integrations catalog, `agentServer` pin `1.27.0`, the merged PR #1246 tool visualizer registry, draft PR #1277 as the lead extension-facing visualizer proposal, and public skills defaulting on. ## 1. Purpose @@ -15,7 +15,7 @@ For multi-agent implementation, use [ExtensionsSystemAgentExecutionPlan.md](./Ex The MVP should be built as a vertical proof, not as isolated architecture layers. The first complete proof should demonstrate: 1. A local or npm-packed Canvas Extension installs into `~/.openhands/agent-canvas/installations`. -2. The Extensions page under Customize shows the installed Canvas Extension and its diagnostics. +2. The Extensions page under Customize shows the installed Canvas Extension and its diagnostics (read-only in packaged/static mode; with live enable/disable on the `npm run dev` stack). 3. A trusted `browser.module` view renders at `/canvas-extensions/:extensionId/:viewId/*`. 4. A view can contribute a left navigation entry after Automations and before the conversation list, matching the current `SidebarRailBody` order (`New Chat`, `Customize`, `Automations`, then conversations). 5. A Canvas Extension can register a color theme that appears in Settings > Application > Color Theme; a theme-only extension is valid. @@ -24,6 +24,7 @@ The MVP should be built as a vertical proof, not as isolated architecture layers 8. A local Canvas Extension can register a custom tool visualizer for one MCP/action variant and safely fall back to built-in rendering when it does not match or throws. 9. A launch template can append extension context and include an SDK plugin when the runtime is filesystem-local. 10. Dev mode can register a local source folder, detect output changes, and remount the view without rebuilding Canvas. +11. In a `npm run dev` stack, the Extensions page can enable/disable an extension and pick up a newly installed extension without restarting Canvas; the same stack run from a packaged/static build stays read-only and restart-bounded. ## 1.1 Scope Locks From The RFC @@ -32,7 +33,8 @@ These decisions should keep the build from spreading sideways: - First-class install/enable support is for Canvas Extension manifests only. Standalone SDK plugins, `SKILL.md` folders, and future MCP-template artifacts can be detected, but they return an explicit unsupported-in-MVP diagnostic unless wrapped by a Canvas Extension. - CLI management commands must dispatch before stack startup, before the static `build/` existence check, and before importing `scripts/dev-with-automation.mjs`. - CLI management commands must also dispatch before `--public`, `--frontend-only`, and `--backend-only` stack validation, because `install/list/doctor` should work even when no frontend/backend is being launched. -- Extension author types need real package subpaths: `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers`, emitted under `dist/extensions/*` and `dist/visualizers/*` and included in npm release checks. +- Extension install, update, and remove are CLI-only in every mode. Enable/disable and new-extension discovery are CLI + restart by default; users shut down Canvas, run the CLI command, then restart. The one exception is the development source stack (`npm run dev` / `dev:minimal`): when the launcher reports `liveExtensionManagement`, the Customize > Extensions page gains enable/disable toggles and a rescan action that take effect without a restart, and the host registers gated `enable`/`disable`/`rescan` routes. In packaged, Docker, and static modes the page stays read-only inventory and diagnostics. This `vite`-vs-`static` distinction is what lets an in-Canvas agent author an extension and have the user enable it in the same dev session. +- Extension author types need real package subpaths. Keep Canvas Extension management types under `@openhands/agent-canvas/canvas-extensions`; for visualizer authoring, follow the OpenHands agent team's approved export from PR #1277 or its successor. The current placeholder is `@openhands/agent-canvas/canvas-visualizers`, emitted under `dist/visualizers/*`, but do not freeze that name before the agent-team decision. - Conversation contribution work targets the current start payload: top-level `agent_settings` plus optional top-level `plugins`. Do not reintroduce the legacy `agent` payload shape. - Package-relative/local SDK plugin sources are MVP only when the launcher says `localInstallStoreReadable=true`. ACP runtimes skip extension SDK plugins unless a pinned-version smoke proves the exact plugin shape is compatible. - The first route-less/registry-backed workstreams are color themes, custom tool visualizers, settings panels, and conversation right panels, not arbitrary root-mounted components. Generic conversation slots remain deferred until a concrete workflow cannot be handled by a right panel or visualizer. @@ -41,13 +43,13 @@ These decisions should keep the build from spreading sideways: The issue feedback and product feedback point to five concrete places where users should be able to add content without forking Agent Canvas: -1. **Custom tool/event visualizers.** PR #1246 added an internal visualizer dispatcher that already asks a registry for a React body and falls back to markdown. Extensions should expose that seam first through `registerToolVisualizer()` or a manifest-projected equivalent. +1. **Custom tool/event visualizers.** PR #1246 added an internal visualizer dispatcher that already asks a registry for a React body and falls back to markdown. Extensions should expose that work through the OpenHands agent-team-approved API. Draft PR #1277 is the current lead: custom/addon visualizers register through a narrow API, run before built-ins, fall through to built-ins/default markdown, support `matches()`, and intentionally avoid `priority`. 2. **Left navigation entries.** A Canvas Extension can add a top-level navigation item after Automations and before the conversation list. 3. **Color themes.** A Canvas Extension can add one or more color themes to Settings > Application > Color Theme, including a theme-only package with no view or panel. 4. **Settings panels.** A Canvas Extension can add configuration UI only under a visible Extensions header after built-in settings. 5. **Conversation right panels.** A Canvas Extension can add a panel beside Files, Browser, and Terminal for conversation-adjacent tools and context. -The implementation order should be visualizers first where possible because they are narrow, already have a merged internal seam, and have a clear failure mode: try extension renderer, then built-in renderer, then markdown fallback. Color themes are also a narrow early surface because the app already centralizes built-in themes in `src/themes/color-themes.ts` and exposes them through Settings > Application > Color Theme. Settings panels and conversation right panels should follow as explicit product surfaces. Generic "mount anything at app root" slots remain deferred. +The implementation order should be visualizers first where possible because they are narrow, already have a merged internal renderer path, and have a clear failure mode: try extension renderer, then built-in renderer, then markdown/default fallback. The visualizer implementation should not fork from the OpenHands agent team's design; use #1277 or its successor as the source of truth. Color themes are also a narrow early surface because the app already centralizes built-in themes in `src/themes/color-themes.ts` and exposes them through Settings > Application > Color Theme. Settings panels and conversation right panels should follow as explicit product surfaces. Generic "mount anything at app root" slots remain deferred. ## 2. Risk-Burner Spikes @@ -71,7 +73,7 @@ Serve a tiny browser-ready ESM file from a local test host and dynamically impor The extension module must use only bundled or relative imports. This proves the DOM-island `mount()` contract works without Vite, shared React, import maps, or module federation. -Add a negative fixture with a bare runtime import such as `react` and verify validation rejects it with an actionable diagnostic. Type-only imports from `@openhands/agent-canvas/canvas-extensions` are fine because the extension build erases them. Author source may import from `@openhands/agent-canvas/canvas-visualizers`, but emitted browser modules must bundle those helpers; raw runtime imports from Agent Canvas package subpaths are not MVP-supported. +Add a negative fixture with a bare runtime import such as `react` and verify validation rejects it with an actionable diagnostic. Type-only imports from `@openhands/agent-canvas/canvas-extensions` are fine because the extension build erases them. Author source may import from the final visualizer authoring subpath, but emitted browser modules must bundle those helpers; raw runtime imports from Agent Canvas package subpaths are not MVP-supported. ### 2.3 Launcher Route Smoke @@ -115,7 +117,7 @@ Add: - storage-path helpers - path normalization - manifest validation -- public package export wiring for `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` +- public package export wiring for `@openhands/agent-canvas/canvas-extensions` and the agent-team-approved visualizer export Keep validation deliberately boring: @@ -127,7 +129,7 @@ Keep validation deliberately boring: Avoid a large JSON Schema dependency unless configuration forms force it. -PR 0 should also update `package.json#exports` for `./canvas-extensions` and `./canvas-visualizers` and make sure the library build emits `dist/extensions/*` and `dist/visualizers/*`. Keep the actual Extension Host and CLI behavior out of PR 0. +PR 0 should also update `package.json#exports` for `./canvas-extensions` and, once decided, the visualizer authoring export. Make sure the library build emits `dist/extensions/*` and the matching visualizer output. Keep the actual Extension Host and CLI behavior out of PR 0. ### 3.2 Manager Library @@ -139,7 +141,7 @@ It owns: - Artifact detection. - Install Canvas Extension manifests from local path, tarball, npm spec. - Typed unsupported diagnostics for detected standalone SDK plugins, standalone skills, and placeholder MCP templates. -- Enable/disable/remove/update. +- Enable/disable/remove/update through CLI state transitions only. - Registry and launch-contribution projection. - Dev registration reads/writes. @@ -168,11 +170,9 @@ It should expose: - diagnostics - launch contributions - settings -- enable/disable/remove -- install - static asset routes -Mutating routes require the same session API key model the frontend already uses. +The host never exposes HTTP routes for install, update, or remove; those are CLI-only in every mode. It registers `POST /:id/enable`, `POST /:id/disable`, and `POST /rescan` **only when the launcher reports `liveExtensionManagement`** (the Vite dev stack); in packaged/Docker/static modes those routes are absent and lifecycle stays CLI-only and restart-bounded. The gated routes mutate `config.json` / re-read the store through `extension-manager` and require the same session API key model as settings mutations. ### 3.5 Launcher Integration @@ -187,6 +187,7 @@ The launcher also emits frontend env/runtime metadata for: - `VITE_EXTENSIONS_ENABLED` or equivalent kill-switch state. - `VITE_LOCAL_INSTALL_STORE_READABLE=true` only when this `agent-canvas` process started the local Agent Server. +- `VITE_LIVE_EXTENSION_MANAGEMENT=true` only when the frontend is served by the Vite dev server (`npm run dev` / `dev:minimal`), false for the static server used by the packaged CLI, Docker, and `dev:static`. Derive it from the existing `services.frontend.kind` value in `scripts/runtime-services-info.mjs` rather than introducing a separate detector. This flag gates both the host's enable/disable/rescan routes and the Extensions page management controls. - Extension Host route/base metadata if direct Vite/static access needs it. - Extension Host URL for dev tooling if needed. @@ -213,8 +214,10 @@ The page should: - Add an Extensions nav item in the Customize navigation. - Treat legacy file names such as `extensions-hub.tsx` and `extensions-navigation.tsx` as existing Customize implementation details, not product vocabulary for the new Canvas Extensions system. - Show a simple stacked view of installed Canvas Extensions and their status. -- Use status grouping/filtering only as needed for scanability: Enabled, Disabled, Invalid, and Dev. -- Show install source, version, contribution badges, required secrets, diagnostics, enable/disable/remove actions. +- Use status grouping/filtering only as needed for scanability: Enabled, Disabled, Invalid, Needs review, and Dev. +- Show install source, version, contribution badges, required secrets, diagnostics, dev source paths, and restart-required / needs-review messaging. +- Read `liveExtensionManagement` from the launcher metadata. When false (packaged/Docker/static), the page is read-only and shows CLI guidance for enable/disable plus a restart note. When true (`npm run dev`), render per-row enable/disable toggles and a rescan action wired to the host routes via `canvas-extensions-service`, and invalidate the registry query on success so changes reflect immediately. +- Always show CLI guidance for install, update, and remove; never render browser install/update/remove actions in any mode. - Stay dense and operational; no marketplace browsing in MVP. - Route all visible strings, tooltips, and action labels through i18n keys. @@ -265,9 +268,10 @@ Use the merged built-in visualizer implementation as the first route-less JS pro MVP scope: -- Add public authoring types under `@openhands/agent-canvas/canvas-visualizers`. +- Align public authoring types and export naming with the OpenHands agent team's approved visualizer API; use PR #1277 as the current starting point. - Project enabled extension visualizers into a registry that is consulted before built-in visualizers. -- Support `priority` and `matches()` so an extension can override one specific MCP tool or event variant without replacing an entire action/observation kind. +- Support `matches()` so an extension can override one specific MCP tool or event variant without replacing an entire action/observation kind. +- Do not add a Canvas-only `priority` field unless the agent team approves it. - Preserve the existing fallback chain: extension visualizer, built-in visualizer, markdown fallback. - Wrap extension visualizer bodies in error boundaries; a thrown renderer should record a diagnostic and fall through to the next renderer/default. - Keep visualizer modules in the trusted local browser-code model. No marketplace or sandbox claim yet. @@ -300,6 +304,7 @@ MVP scope: - Disabled/invalid extensions do not render settings panels. - Canvas owns the panel chrome, save/cancel affordances, loading states, and error boundaries. - Panel renderers receive extension settings helpers only; no raw settings store or React Query client access. +- Manifest entries declare metadata only. The browser module registers implementations by panel contribution ID at runtime; missing registrations show diagnostics. ### 4.8 Conversation Right Panels @@ -312,6 +317,7 @@ MVP scope: - Disabled/invalid extensions do not render panels. - Panels receive active conversation ID/status, workspace summary, backend summary, and host APIs for navigation, toasts, and extension settings. - Canvas owns panel placement, collapsed/expanded behavior, loading state, and error boundaries. +- Manifest entries declare metadata only. The browser module registers implementations by panel contribution ID at runtime; missing registrations show diagnostics. ## 5. Conversation Contributions @@ -363,11 +369,14 @@ Rules: - The extension project owns its build/watch command. - Canvas watches the manifest and declared output files. - Manifest changes revalidate and update diagnostics. +- Permission, network, secret, or agent-affecting contribution expansion moves the dev extension to needs-review/disabled-for-next-run until CLI re-approval and restart. - Browser module changes bump a version token and remount only affected views. - Agent-side contribution changes require a new conversation. The example extension should include a minimal `npm run build -- --watch` authoring flow in docs, but Canvas should not run that command automatically. +Dev-folder watch (above) and live management (§1.1, §3.4, §4.2) share the same `npm run dev` tier but solve different problems: watch reloads an extension's *code*, while live management enables/disables and discovers *installed* extensions without a restart. Both are gated by the launcher and absent in packaged/Docker/static runs. Live management reuses the dev-watch remount path to mount/unmount reconcilable UI contributions when enablement or the registry changes; SDK plugin and context changes still require a new conversation. + ## 7. Acceptance Demo The MVP is proven when this scriptable path works from a clean checkout: @@ -384,7 +393,7 @@ node bin/agent-canvas.mjs Then in the browser: -1. Open Extensions and see `hello.canvas` enabled with no diagnostics. +1. Open Extensions and see `hello.canvas` enabled with no diagnostics in the read-only inventory. 2. See its left navigation entry after Automations and before the conversation list. 3. Open its extension view from the left navigation and see browser-module UI render. 4. Open Settings > Application > Color Theme and select the extension-provided color theme. @@ -394,18 +403,19 @@ Then in the browser: 8. Trigger its fixture tool/event and verify the Canvas Extension visualizer renders with fallback still intact. 9. Start its launch template and verify the conversation payload includes the extension context suffix. 10. In `npm run dev`, register the same extension with `--dev`, rebuild the extension output, and see the view remount with the new code. +11. In `npm run dev`, toggle `hello.canvas` off and back on from the Extensions page and see its UI contributions unmount/remount without restarting Canvas; install a second extension from another terminal, trigger rescan, and see it appear. Then confirm the same build served by the packaged/static path shows the page read-only with no toggles. Release-path acceptance should also install the packed `@openhands/agent-canvas` tarball into a temp npm prefix and repeat `agent-canvas list`, `agent-canvas doctor`, and one packed/local extension install without relying on repo-local files outside the package. ## 8. Testing Strategy -**Unit:** manifest validation; reserved `browser.entry` diagnostics; path traversal rejection; artifact detection; unsupported standalone artifact diagnostics; install-store bootstrap; enable/disable/remove/update state transitions; duplicate ID handling; color theme projection/validation/fallback; asset route path validation; CLI arg parsing before build checks; no-install-scripts default; visualizer contribution projection; visualizer ordering/fallback/error-boundary behavior; settings panel projection/ordering; conversation right-panel projection/ordering; launch contribution projection; context suffix rendering; plugin merge/dedupe; runtime compatibility classification; dev registration and manifest revalidation. +**Unit:** manifest validation; reserved `browser.entry` diagnostics; path traversal rejection; artifact detection; unsupported standalone artifact diagnostics; install-store bootstrap; enable/disable/remove/update state transitions through the shared manager (driven by CLI, and by the host enable/disable/rescan routes when live management is enabled); capability gating of those routes by `liveExtensionManagement`; duplicate ID handling; color theme projection/validation/fallback; asset route path validation; CLI arg parsing before build checks; no-install-scripts default; visualizer contribution projection; visualizer ordering/fallback/error-boundary behavior aligned with #1277 or its successor; settings panel projection/registration/ordering; conversation right-panel projection/registration/ordering; launch contribution projection; context suffix rendering; plugin merge/dedupe; runtime compatibility classification; dev registration, manifest revalidation, and permission-drift re-approval. -**Node integration:** run the Extension Host against a temp install store; install the `hello.canvas` fixture from local path and npm-packed tarball; fetch registry and asset URLs; verify route precedence before `/api/*` in Vite, ingress, static server, and packaged CLI paths; verify mutating routes require the session API key; verify `doctor` reports invalid manifests, unsupported standalone artifacts, and missing assets. +**Node integration:** run the Extension Host against a temp install store; install the `hello.canvas` fixture from local path and npm-packed tarball through the CLI; fetch registry and asset URLs; verify route precedence before `/api/*` in Vite, ingress, static server, and packaged CLI paths; verify settings mutation routes require the session API key; verify no install/update/remove browser routes exist in any mode; verify `enable`/`disable`/`rescan` routes are absent when `liveExtensionManagement` is false and present (and API-key-guarded) when it is true; verify rescan picks up a newly installed extension and live enable/disable mutates `config.json`; verify `doctor` reports invalid manifests, unsupported standalone artifacts, and missing assets. -**Release packaging:** `npm pack --dry-run`; packed-tarball install into a temp npm prefix; verify `agent-canvas list`, `agent-canvas doctor`, and an extension install work without a source checkout; verify `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` resolve for type consumers. +**Release packaging:** `npm pack --dry-run`; packed-tarball install into a temp npm prefix; verify `agent-canvas list`, `agent-canvas doctor`, and an extension install work without a source checkout; verify `@openhands/agent-canvas/canvas-extensions` and the final visualizer export resolve for type consumers. -**Component:** Extensions page empty/installed/enabled/invalid/dev states under Customize; install/enable/disable/remove action wiring; left navigation entries in expanded/collapsed/mobile states; extension view loading/error/remount; extension color theme appears in Settings > Application > Color Theme and applies through the existing theme input; settings panel placement under the visible Extensions header; extension settings read/patch; extension visualizer renders/falls back inside the existing event wrapper; conversation right panels render beside Files/Browser/Terminal; launch template preflight for incompatible SDK plugin paths. +**Component:** Extensions page empty/installed/enabled/invalid/dev/needs-review states under Customize in both presentations — read-only with CLI/restart guidance when `liveExtensionManagement` is false, and with enable/disable toggles plus rescan when it is true (including registry-query invalidation on a successful toggle); CLI/restart guidance; left navigation entries in expanded/collapsed/mobile states; extension view loading/error/remount; extension color theme appears in Settings > Application > Color Theme and applies through the existing theme input; settings panel placement under the visible Extensions header; extension settings read/patch; extension visualizer renders/falls back inside the existing event wrapper; conversation right panels render beside Files/Browser/Terminal; launch template preflight for incompatible SDK plugin paths. **E2E snapshots:** Extensions page with enabled extension under Customize; invalid extension diagnostics; left navigation entry after Automations; extension color theme visible in Settings > Application; settings panel under Extensions header; conversation right panel beside Files/Browser/Terminal; extension view rendered from browser module; launch template showing context/plugin contribution; remote-backend disabled reason for local plugin path; dev extension view remount after output change. diff --git a/docs/ExtensionsSystemRFC.md b/docs/ExtensionsSystemRFC.md index 7e8d03572..cd9e8c9c1 100644 --- a/docs/ExtensionsSystemRFC.md +++ b/docs/ExtensionsSystemRFC.md @@ -12,7 +12,14 @@ Agent Canvas should ship a first-class **Canvas Extensions** system: user-instal A Canvas Extension can contribute UI views, left navigation entries, color themes, settings panels, conversation right panels, custom tool/event visualizers, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Canvas Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces; Canvas does not patch or load code inside the Agent Server. -The MVP delivers CLI install for Canvas Extension packages, an Extensions page under Customize, trusted same-origin extension views, dev-mode authoring with live reload, and focused UI contribution surfaces: left navigation entries, color themes that appear in Settings > Application > Color Theme, settings panels under a visible Extensions header, conversation right panels beside Files/Browser/Terminal, and extension-provided tool visualizers that compose with the built-in visualizer registry introduced by PR #1246. Broad root-mounted components remain deferred. The CLI can detect adjacent artifact types such as standalone SDK plugins or `SKILL.md` folders, but the first executable slice should install/enable only packages with a Canvas Extension manifest. Marketplace, signing, sandboxed iframe views, arbitrary app-root component/shared dependency runtimes, standalone skill/plugin management, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. +The MVP delivers CLI install/enable/disable/remove for Canvas Extension packages, an Extensions page under Customize for inventory and diagnostics, trusted same-origin extension views, dev-mode authoring with live reload, and focused UI contribution surfaces: left navigation entries, color themes that appear in Settings > Application > Color Theme, settings panels under a visible Extensions header, conversation right panels beside Files/Browser/Terminal, and extension-provided tool visualizers that compose with the built-in visualizer registry introduced by PR #1246. The extension-facing tool visualizer contract should follow the OpenHands agent team's direction, with draft PR #1277 as the current lead implementation, rather than inventing a parallel Canvas-only renderer model. Broad root-mounted components remain deferred. The CLI can detect adjacent artifact types such as standalone SDK plugins or `SKILL.md` folders, but the first executable slice should install/enable only packages with a Canvas Extension manifest. Marketplace, signing, sandboxed iframe views, arbitrary app-root component/shared dependency runtimes, standalone skill/plugin management, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. + +**Lifecycle tiers (capability-gated by launch mode).** Extension lifecycle has two tiers, selected by how Canvas was launched, not by a per-extension setting: + +- **Restart-bounded (default).** Packaged global installs, Docker images, and any static-served build are CLI-only: install, enable, disable, update, and remove run from the CLI, and changes apply on the next process start. The Extensions page is read-only inventory and diagnostics. This is the baseline assumed throughout the rest of this RFC. +- **Live management (development source stack only).** A stack started from a source checkout with `npm run dev` or `npm run dev:minimal` additionally gets live management. The launcher issues a `liveExtensionManagement` capability flag, the Customize > Extensions page exposes enable/disable controls, and newly installed extensions are picked up by re-reading the install store and refetching the registry — without a full Canvas restart. This is the mode in which an in-Canvas agent can build an extension and the user can enable it in the same session, mirroring how Canvas already lets users add an LLM profile and refresh what is available. + +Live management removes only the restart step for enable/disable and new-extension discovery on a trusted local dev machine. It does not relax the consent model, the permission re-approval rules, or the agent-runtime boundary: newly discovered extensions still arrive disabled, permission-expanding states still require review, and agent-side contributions (SDK plugins, context suffix) still apply only at conversation creation. See §15.2, §17, §20, §24, and §27 Decision 1. ## 2. Motivation @@ -43,7 +50,8 @@ User-facing UI should use "Canvas Extension" when the distinction matters, and m - Install from npm package names, version ranges, tarballs, or local paths. - Works when the user globally installs Agent Canvas and runs `agent-canvas`. -- Supports CLI install/manage commands, persisted enablement, and a process-level diagnostic kill switch. +- Supports CLI install/manage commands, persisted enablement, restart-bounded lifecycle changes, and a process-level diagnostic kill switch. +- In a development source stack (`npm run dev`), additionally supports live enable/disable from the Extensions page and live discovery of newly installed extensions without a full restart, so an in-Canvas agent can author an extension and the user can enable it in the same session. - Management commands work from a globally installed npm package without starting the full stack and without requiring the static `build/` directory. - Keeps the Agent Server unmodified for MVP. - Aligns with the OpenHands SDK plugin format rather than inventing a competing format. @@ -116,13 +124,13 @@ The Extension Host **must not** proxy arbitrary requests to the Agent Server. Wh The Canvas frontend owns: -- Extension management UI (the Extensions page). +- Read-only extension inventory/diagnostics UI (the Extensions page). - Navigation for extension views. - The extension view host that mounts trusted extension browser modules into Canvas-owned route containers (with a reserved future iframe runtime; see §12.1). -- Installation consent UX, permission display, secret setup prompts. +- Permission display, CLI management guidance, and secret setup prompts. - Settings forms generated from extension JSON Schemas. - Merging enabled extension launch contributions into conversation create payloads. -- Toasts, query invalidation, and error boundaries for extension actions. +- Toasts, settings query invalidation, and error boundaries for extension surfaces. ### 7.3 Agent Runtime (Agent Server) @@ -137,6 +145,10 @@ The Agent Server is unchanged for MVP. Canvas treats it as a black box and only Command dispatch is part of the release contract. `agent-canvas install`, `list`, `enable`, `disable`, `remove`, `update`, and `doctor` must parse and run before the launcher checks for a static `build/` directory or imports `scripts/dev-with-automation.mjs`. The full stack still requires packaged frontend assets, but management commands must work from a global npm install and from source checkouts that have not run `npm run build`. +Default lifecycle is CLI-only and restart-bounded. Users enable, disable, update, or remove Canvas Extensions from the CLI, then restart `agent-canvas`. In packaged, Docker, and static-served modes the Customize > Extensions page is read-only inventory and diagnostics; it does not install, enable, disable, update, or uninstall packages in the browser, and a running Canvas process does not promise live revocation after an out-of-band CLI state change. + +**Development source stack exception.** When Canvas is launched with `npm run dev` or `npm run dev:minimal`, the launcher issues the `liveExtensionManagement` capability and the Extensions page additionally exposes enable/disable controls plus discovery of newly installed extensions without a restart (see §15.2, §17, §24). Install, update, and remove remain CLI verbs even in this mode; live management covers enable/disable and registry discovery, which are the steps an in-Canvas agent authoring loop needs. The CLI verbs below behave identically in both tiers. + ### Global install and launch ```sh @@ -163,7 +175,7 @@ Detection order: 4. Future MCP template manifest → MCP template. 5. Multiple markers present → the explicit Canvas Extension manifest wins (it may intentionally wrap SDK plugin, skill, or MCP contributions). -MVP behavior: Canvas Extension artifacts install and can be enabled. Standalone SDK plugin, skill, or MCP-template detection returns a typed "detected but unsupported in MVP" diagnostic unless the artifact is wrapped by a Canvas Extension manifest. This keeps the single `install` verb and detector extensible without silently mutating SDK/user skill state before Canvas has the matching management UX. +MVP behavior: Canvas Extension artifacts install and can be enabled through the CLI. Standalone SDK plugin, skill, or MCP-template detection returns a typed "detected but unsupported in MVP" diagnostic unless the artifact is wrapped by a Canvas Extension manifest. This keeps the single `install` verb and detector extensible without silently mutating SDK/user skill state before Canvas has the matching product UX. For Canvas Extensions, install validates the manifest, shows permissions, installs with scripts denied by default, and enables the extension after consent. Non-interactive flags: @@ -250,7 +262,7 @@ The manifest path must resolve inside the package root. Path traversal is reject The host package must publish everything the global CLI and extension authors need: - `bin/agent-canvas.mjs`, launcher scripts, static `build/`, and any Extension Host scripts must be included by `package.json#files`. -- Extension author types must be exported as `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers`. The release build must emit those files under `dist/extensions/*` and `dist/visualizers/*` and add matching subpath exports. +- Extension management author types must be exported as `@openhands/agent-canvas/canvas-extensions`. Visualizer authoring types must follow the OpenHands agent-team-approved export from PR #1277 or its successor; the earlier placeholder was `@openhands/agent-canvas/canvas-visualizers`, but that name should not be frozen until the visualizer API decision is made. The release build must emit `dist/extensions/*` plus matching visualizer output and add matching subpath exports. - If JSON Schemas are advertised by URL or package path, the schemas must either be served remotely or included in the npm package. Do not point authors at files that are excluded by `package.json#files`. - Example extensions under `examples/` are source-repo fixtures unless `package.json#files` explicitly includes them. The acceptance path for a globally installed package should use either a packed fixture tarball or a separately published example package, not an unpublished repo-local path. - Every release candidate should run `npm pack --dry-run` or an equivalent packed-tarball smoke so missing `build`, `scripts`, `dist/extensions`, `dist/visualizers`, or schema files are caught before publish. @@ -337,8 +349,7 @@ The host package must publish everything the global CLI and extension authors ne "id": "github-pr-check", "module": "./dist/github-pr-check-visualizer.js", "actionKinds": ["MCPToolAction"], - "observationKinds": ["MCPToolObservation"], - "priority": 25 + "observationKinds": ["MCPToolObservation"] } ], "agentPlugins": [ @@ -504,7 +515,8 @@ Manifest contribution shape: Rules: - Canvas Extension settings panels never appear intertwined with built-in settings. They render only under the visible Extensions header after other settings. -- The panel renderer may reuse a declared `viewId` or a dedicated browser module entry, but Canvas owns the settings chrome, heading, save/cancel affordances, loading state, and error boundary. +- The manifest declares panel metadata only: ID, title, order, and optional icon/display hints. The extension browser module registers the implementation for that declared panel ID at runtime through the settings-panel registry. Missing registrations produce diagnostics and an empty/error panel body rather than breaking Settings. +- Canvas owns the settings chrome, heading, save/cancel affordances, loading state, and error boundary. - Settings panels receive only extension metadata/settings, schema-derived values, and host APIs for `readExtensionSettings()` / `patchExtensionSettings()`. They do not receive raw global settings stores. - If an extension is disabled or invalid, its settings panel is omitted and its persisted settings remain in the install store until removal/purge. @@ -536,6 +548,7 @@ Rules: - Panels receive active conversation ID/status, selected workspace summary, backend summary, and narrow host APIs for navigation/toasts/settings. - Canvas owns panel tab placement, collapsed/expanded behavior, focus management, loading state, and error boundaries. - Panels must degrade cleanly when no conversation is active, when the extension is disabled, or when the current runtime does not support an agent-side contribution. +- The manifest declares panel metadata only. The extension browser module registers the implementation for the declared panel ID at runtime through the conversation-panel registry. Missing registrations produce diagnostics and leave the panel unavailable. **MVP runtime: trusted same-origin browser module.** The extension's `browser.module` is loaded by the Canvas frontend with dynamic `import()` and mounted into a Canvas-owned DOM container at the view route. The extension exports a `mount()` function, receives the typed context (§13), and owns rendering inside that container. It may use React, Svelte, vanilla DOM, or another framework as long as the shipped module is browser-ready. It should use Canvas CSS variables/design tokens and host-provided APIs instead of importing Canvas internals. @@ -545,8 +558,8 @@ Inline extension code runs in the same browser origin as Canvas. That is intenti - `browser.module` must point to a built ESM file that a browser can import directly. - The Extension Host serves static files only; it does not transpile TypeScript/JSX or run bundlers for installed packages. -- Runtime imports must be relative URLs inside the extension asset tree or bundled into the module. Bare runtime imports such as `react`, `@heroui/react`, `@openhands/agent-canvas/canvas-extensions`, or `@openhands/agent-canvas/canvas-visualizers` are rejected for MVP because the packaged CLI cannot guarantee a Vite resolver or shared dependency graph. -- Type-only imports from `@openhands/agent-canvas/canvas-extensions` are allowed in author source and erased by the extension's build. Author source may import `@openhands/agent-canvas/canvas-visualizers`, but emitted browser modules must bundle those helpers rather than leaving a bare package import. +- Runtime imports must be relative URLs inside the extension asset tree or bundled into the module. Bare runtime imports such as `react`, `@heroui/react`, `@openhands/agent-canvas/canvas-extensions`, or the final visualizer authoring subpath are rejected for MVP because the packaged CLI cannot guarantee a Vite resolver or shared dependency graph. +- Type-only imports from `@openhands/agent-canvas/canvas-extensions` are allowed in author source and erased by the extension's build. Author source may import the final visualizer authoring subpath, but emitted browser modules must bundle those helpers rather than leaving a bare package import. - The default export shape is: ```ts @@ -611,7 +624,9 @@ Extension context **must not** be merged into `conversationInstructions`; that t ### 12.9 `toolVisualizers` -Custom event rendering is the first concrete route-less JavaScript extension surface. PR #1246 added the internal `src/components/features/chat/tool-visualizers` registry and dispatcher: `getEventContent()` asks the registry for a React body by action/observation kind, and falls back to the existing markdown renderer when no visualizer matches. Extensions should build on that seam instead of introducing an unrelated event-rendering model. +Custom event rendering is the first concrete route-less JavaScript extension surface. PR #1246 added the internal `src/components/features/chat/tool-visualizers` registry and dispatcher: `getEventContent()` asks the registry for a React body by action/observation kind, and falls back to the existing markdown renderer when no visualizer matches. Extensions should build on that work instead of introducing an unrelated event-rendering model. + +The final extension-facing visualizer contract is owned by the OpenHands agent team. Draft PR #1277 is the current lead implementation to follow where possible: a narrow registration API, custom/addon visualizers composed before built-ins, built-ins before primitive markdown/default fallback, `matches()` for narrowing, error-boundary fallthrough, and no `priority` field. If the agent team changes that API before it lands, the Canvas Extensions RFC and implementation should follow their approved shape rather than preserving the older Canvas-only proposal below. Manifest contribution shape: @@ -623,8 +638,7 @@ Manifest contribution shape: "id": "acme.kubernetes.apply", "module": "./dist/kubernetes-apply-visualizer.js", "actionKinds": ["MCPToolAction"], - "observationKinds": ["MCPToolObservation"], - "priority": 50 + "observationKinds": ["MCPToolObservation"] } ] } @@ -639,7 +653,6 @@ export default function register(api: AgentCanvasExtensionApi) { id: "acme.kubernetes.apply", actionKinds: ["MCPToolAction"], observationKinds: ["MCPToolObservation"], - priority: 50, matches({ action }) { return ( action?.action.kind === "MCPToolAction" && @@ -656,12 +669,12 @@ export default function register(api: AgentCanvasExtensionApi) { Selection rules: - Extension visualizers are considered before built-in visualizers. -- Higher `priority` wins within extension visualizers; ties sort by extension ID then visualizer ID. +- Ordering should follow the agent-team-approved registry. Current PR #1277 uses latest registration first for custom/addon visualizers and intentionally has no `priority` field. - `matches()` narrows within a kind so one extension can override a specific MCP tool without replacing all `MCPToolAction` rendering. - If an extension visualizer throws, Canvas catches the error, reports a local diagnostic, and tries the next matching visualizer before falling back to the built-in renderer or markdown fallback. - Visualizer modules use the same trusted local browser-code model as `browser.module`; no sandbox claim is made in MVP. -Public authoring types and primitives should be exported from `@openhands/agent-canvas/canvas-visualizers`, separate from the broader `@openhands/agent-canvas/canvas-extensions` management contract. The public surface should wrap or re-export stable versions of `defineVisualizer`, `VisualizerProps`, shared primitives such as code blocks/diff/output panes, and action/observation kind types. Do not expose internal file paths under `src/components/features/chat/tool-visualizers/*` as the authoring API. +Public visualizer authoring types and primitives should be exported from the OpenHands agent-team-approved visualizer subpath, separate from the broader `@openhands/agent-canvas/canvas-extensions` management contract. PR #1277 currently proposes `@openhands/agent-canvas/visualizers`; earlier Canvas planning used `@openhands/agent-canvas/canvas-visualizers`. Follow the approved PR #1277 successor rather than adding a second public visualizer API. The public surface should wrap or re-export stable versions of `defineVisualizer`, `VisualizerProps`, shared primitives such as code blocks/diff/output panes, and action/observation kind types. Do not expose internal file paths under `src/components/features/chat/tool-visualizers/*` as the authoring API. ### 12.10 Deferred Generic Conversation Slots @@ -744,7 +757,7 @@ interface AgentCanvasExtensionContext { } ``` -For the first implementation slice, the route-less registration APIs are limited to `visualizers.registerToolVisualizer()`, `colorThemes.registerColorTheme()`, `settingsPanels.registerSettingsPanel()`, and `conversationPanels.registerRightPanel()`. Manifest-projected `contributes.colorThemes` should be enough for most theme-only packages; the runtime API exists for the same lifecycle/disposal model as other UI registries. There is **no** supported generic `agentServer.request(path)`. The repo already routes Agent Server traffic through typed service wrappers; the extension surface follows the same pattern with named, permission-declared capabilities. For MVP inline browser modules, this is an API support boundary rather than a browser security boundary (see §19). +For the first implementation slice, the route-less registration APIs are limited to the agent-team-approved visualizer registration API, `colorThemes.registerColorTheme()`, `settingsPanels.registerSettingsPanel()`, and `conversationPanels.registerRightPanel()`. Manifest-projected `contributes.colorThemes` should be enough for most theme-only packages; the runtime API exists for the same lifecycle/disposal model as other UI registries. Settings and conversation panel manifests declare metadata; runtime registration binds that metadata to implementations by contribution ID. There is **no** supported generic `agentServer.request(path)`. The repo already routes Agent Server traffic through typed service wrappers; the extension surface follows the same pattern with named, permission-declared capabilities. For MVP inline browser modules, this is an API support boundary rather than a browser security boundary (see §19). ## 14. Shared Type Contract @@ -824,11 +837,9 @@ Permission groups: - `ui.settingsPanels` — add settings panels only under the visible Extensions header after built-in settings. - `ui.conversationPanels` — add right-side conversation panels alongside Files, Browser, and Terminal. - `ui.toolVisualizers` — register custom action/observation renderers in the conversation event pipeline. -- `ui.commands` — register commands. - `agent.sdkPlugins` — include SDK plugins in conversation launches. - `agent.context` — append extension context to system suffix. - `mcp.templates` — offer MCP server templates. -- `agentServer.read` / `agentServer.write` — read/write Agent Server APIs through the Canvas broker. - `secrets.declare` — request secret setup. - `network` — list of external origins the extension says it may contact (advisory; not strictly enforced by CSP in MVP). @@ -847,6 +858,28 @@ Every artifact in the registry has exactly one persisted state: **Conflict rule.** Disabled wins over enabled if a manual config edit creates both persisted entries for the same ID. The process-level `--disable-extensions` kill switch suppresses all extension loading for the current run but does not create a registry state. +### 15.2 Lifecycle Capability And Live Management + +Extension lifecycle behavior is gated by a launcher-issued capability rather than by per-extension state. The launcher emits a `liveExtensionManagement` boolean to the frontend alongside the existing `localInstallStoreReadable` flag and runtime-services metadata. It is derived from the same launch-mode signal Canvas already computes (`services.frontend.kind` in `scripts/runtime-services-info.mjs`): `liveExtensionManagement` is true only when the frontend is served by the Vite dev server (`npm run dev`, `npm run dev:minimal`), and false for the static server used by the packaged global CLI, Docker images, and `dev:static`. + +| Mode | `liveExtensionManagement` | Enable/disable | New-extension discovery | Install / update / remove | +|---|---|---|---|---| +| Packaged global CLI | false | CLI + restart | restart | CLI | +| Docker / static-served | false | CLI + restart | restart | CLI | +| `npm run dev` / `dev:minimal` | true | Extensions page or CLI, no restart | re-read store + refetch registry, no restart | CLI | + +**Live management semantics (capability true):** + +- Enable/disable from the Extensions page calls the host enable/disable routes in §17, which mutate `config.json` through the shared `extension-manager`. The frontend then invalidates the registry query so the new state reflects immediately — the same persist-then-invalidate pattern Canvas already uses for LLM profiles, not a separate live store. +- Newly installed extensions are discovered by re-reading the install store on each registry request and refetching/invalidating the registry query (on the Extensions page, on a manual rescan action, and on the dev-watch cache-bust signal). Filesystem watching of the store is an optional enhancement, not a requirement; the baseline is re-read-on-request plus query invalidation. +- Reconcilable contributions (left navigation, color themes, settings panels, conversation right panels, tool visualizers, and views) re-project from the refreshed registry and mount/unmount accordingly, reusing the dev-watch remount path. +- Non-reconcilable contributions are unchanged: SDK plugins and context suffix apply only at conversation creation, so enabling or disabling them mid-session affects future conversations only. +- Consent is preserved: live-discovered extensions arrive in `installed`/`disabled` state and never auto-enable; enabling from the Extensions page is the explicit consent action. Permission-expanding updates still drop to `disabled`/needs-review. + +**Restart-bounded semantics (capability false):** unchanged from the rest of this RFC. The Extensions page is read-only, the host exposes no enable/disable routes, and state changes apply on next process start. + +The `--disable-extensions` kill switch overrides both tiers and suppresses all extension loading for the run regardless of `liveExtensionManagement`. + ## 16. Conversation Runtime Awareness Agent Canvas is always local. The active Agent Server backend may be local, remote, OpenHands Cloud, or an ACP subprocess. The launch path computes a runtime compatibility report before applying extension contributions. @@ -897,18 +930,20 @@ Mounted through ingress **before** `/api/*` is forwarded to the Agent Server, us ```text GET /api/canvas/canvas-extensions/registry GET /api/canvas/canvas-extensions/diagnostics -POST /api/canvas/canvas-extensions/install -POST /api/canvas/canvas-extensions/:id/enable -POST /api/canvas/canvas-extensions/:id/disable -DELETE /api/canvas/canvas-extensions/:id +GET /api/canvas/canvas-extensions/:id/settings PATCH /api/canvas/canvas-extensions/:id/settings GET /api/canvas/canvas-extensions/launch-contributions GET /canvas-extension-assets/:id/*assetPath + +# Live-management routes: registered only when liveExtensionManagement is true +POST /api/canvas/canvas-extensions/:id/enable +POST /api/canvas/canvas-extensions/:id/disable +POST /api/canvas/canvas-extensions/rescan ``` -Note the consistent `/api/canvas/canvas-extensions/*` prefix for management — including `launch-contributions`, which was previously inconsistent. Asset serving keeps its own `/canvas-extension-assets/*` prefix because it serves untrusted third-party static files and benefits from being easy to identify in logs and CSP rules. +Note the consistent `/api/canvas/canvas-extensions/*` prefix for local Canvas extension runtime APIs — including `launch-contributions`, which was previously inconsistent. Asset serving keeps its own `/canvas-extension-assets/*` prefix because it serves untrusted third-party static files and benefits from being easy to identify in logs and CSP rules. -The `install` route uses the same artifact detector as `agent-canvas install`. Mutating routes require the existing session API key. +The host never exposes browser routes for install, update, or remove; those stay CLI-only in every mode. The `enable`, `disable`, and `rescan` routes are registered **only when the launcher reports `liveExtensionManagement` is true** (the `npm run dev` source stack). In packaged, Docker, and static modes they are absent, so the read-only restart-bounded contract is unchanged and cannot be reached from the browser. When present, `enable`/`disable` mutate `config.json` through the shared `extension-manager` and `rescan` re-reads the install store; all three require the existing session API key, exactly like `PATCH /settings`. `PATCH /settings` is retained in both modes because settings panels need a narrow persistence surface. Frontend code must call these routes only through a dedicated `src/api/canvas-extensions-service.ts` wrapper. Because the route prefix begins with `/api/` but targets the local Extension Host rather than the Agent Server, PR 2 must update `src/api/no-direct-agent-server-calls.test.ts` with a narrow allowlist entry for that wrapper, mirroring the existing automation-service exception. This keeps the `/api/canvas/canvas-extensions/*` ingress shape while preserving the repo rule that ordinary Agent Server traffic goes through `@openhands/typescript-client`. @@ -980,7 +1015,7 @@ Merge rules: ## 20. Operational Semantics -**Install defaults.** Interactive install validates, shows permissions, enables on confirmation. `--no-enable` installs disabled. `--yes` skips prompts but still fails closed on new permissions unless paired with an explicit non-interactive policy flag. Install scripts denied by default. +**Install defaults.** Interactive CLI install validates, shows permissions, enables on confirmation. `--no-enable` installs disabled. `--yes` skips prompts but still fails closed on new permissions unless paired with an explicit non-interactive policy flag. Install scripts denied by default. Browser UI install is out of scope for MVP. **Update defaults.** Updates preserve enabled/disabled state only when the manifest ID is unchanged and permissions do not expand. If an update adds agent-affecting contributions, new secrets, broader network origins, or new write permissions, the artifact moves to `installed`/disabled until re-approval. Downgrades require explicit CLI input and are recorded in diagnostics. @@ -990,12 +1025,18 @@ Merge rules: **Failure handling.** Invalid manifest → `invalid` state with diagnostics. Missing `browser.module` → keep installed, disable affected UI contribution. Manifest declares the reserved `browser.entry` field → `invalid` state with a "reserved, not yet supported" diagnostic. Extension view error → host-level error boundary and cleanup around the DOM container, rest of Canvas stays alive. Incompatible SDK plugin source → skip with preflight warning. Missing required secret → setup action surfaced, dependent contribution withheld. Extension Host unavailable → Canvas loads in degraded mode with a banner. +**Restart-bounded lifecycle (default).** In packaged, Docker, and static modes extension enablement is loaded when the Canvas process starts. CLI install/enable/disable/update/remove commands mutate persisted state for the next run; the running app does not watch the store or guarantee live unmount/revocation. The read-only Extensions page may show the current process state and diagnostics, but it does not attempt to reconcile out-of-band CLI changes until restart. + +**Live management lifecycle (development source stack).** When `liveExtensionManagement` is true (§15.2), the running app reconciles enable/disable and newly installed extensions without a restart. Enable/disable mutate `config.json` through the shared `extension-manager` and the frontend re-reads the registry; newly installed extensions are discovered on the next registry read or rescan. Reconcilable UI contributions mount/unmount from the refreshed registry; SDK plugin and context contributions still apply only to conversations created after the change. Install, update, and remove remain CLI verbs in this mode as well. This tier exists for trusted local development, including the agent-authored-extension loop, and is never enabled for packaged or Docker runs. + **Package inspection rule.** The Extension Host never imports extension Node code to inspect an artifact. It reads only static files: `package.json`, `agent-canvas.extension.json`, `.plugin/plugin.json`, `SKILL.md`, and static asset paths. Extension-authored code runs only as the inline browser module loaded into the Canvas frontend, or as SDK/MCP/runtime code, in both cases only after explicit consent. ## 21. Development Mode Authoring Dev mode lets contributors and agents iterate on extensions without publishing to npm or rebuilding Canvas. It is available only in development-capable stacks (`npm run dev`) and documented in contributor docs, not the end-user install path. +The same development source stack also carries the `liveExtensionManagement` capability (§15.2). Dev-folder watch/remount (this section) handles *authoring an extension's code*; live management handles *enabling, disabling, and discovering installed extensions* without a restart. Together they are what makes the agent-authored-extension loop work locally: an in-Canvas agent can scaffold and install an extension, the Extensions page picks it up on the next registry read, and the user enables it from the browser — all without leaving the session. Neither capability is present in packaged, Docker, or static runs. + ### CLI ```sh @@ -1082,9 +1123,14 @@ Current navigation state: `/customize` is the Customize entry in the primary sid Implementation note: some current files still use legacy "extensions" names, such as `src/routes/extensions-hub.tsx` and `src/components/features/skills/extensions-navigation.tsx`. Treat those as existing Customize implementation details. New Canvas Extension code should use `canvas-extensions` names, and user-facing copy should say Customize or Canvas Extensions as appropriate. -The Extensions page is a simple stacked view of installed Canvas Extensions and their status. MVP rows should be grouped or filtered by status only as needed for scanability: Enabled, Disabled, Invalid, and Dev. Install-from-package/dev-path controls can sit above or below the stack, but marketplace-style browsing is out of scope. +The Extensions page is a simple stacked view of installed Canvas Extensions and their status. MVP rows should be grouped or filtered by status only as needed for scanability: Enabled, Disabled, Invalid, Needs review, and Dev. Browser install, update, and remove controls are out of scope in every mode; the page points users to CLI management for those verbs. Marketplace-style browsing is out of scope. + +The page adapts to the `liveExtensionManagement` capability (§15.2): -Each extension row/card shows: display name, package name, version; state; contribution badges; required secrets status; enable/disable/remove actions; diagnostics. +- **Capability false** (packaged, Docker, static): the page is read-only. It shows state and diagnostics, points users to CLI management, and notes that enable/disable changes require restarting Canvas. +- **Capability true** (`npm run dev`): each row additionally renders an enable/disable toggle wired to the host enable/disable routes, plus a manual rescan action so newly installed extensions appear without a restart. After a successful toggle or rescan the page invalidates the registry query so the change reflects immediately. Install, update, and remove still route to CLI guidance. + +Each extension row/card shows: display name, package name, version; state; contribution badges; required secrets status; dev source path when relevant; restart-required or needs-review diagnostics when relevant; and diagnostics. Management actions are limited to enable/disable and rescan, and only when the capability is present. ### Launch UX @@ -1118,7 +1164,7 @@ The multi-agent, checkable execution checklist for this plan lives in [Extension Files: `docs/ExtensionsSystemRFC.md`, `src/canvas-extensions/types.ts`, `src/canvas-extensions/manifest-schema.ts`, `src/canvas-extensions/manifest-validation.ts`, `src/canvas-extensions/artifact-detection.ts`, `src/themes/color-themes.ts`, `__tests__/canvas-extensions/manifest-validation.test.ts`. -Deliverables: shared manifest and registry types; generic installable artifact kind definitions; contribution types for views, left navigation, color themes, settings panels, conversation right panels, tool visualizers, launch templates, SDK plugins, MCP templates, and context; artifact detection rules for extension packages, SDK plugins, skills, and placeholder MCP templates; schema-version constant; manifest validation helpers; storage-path helper for `~/.openhands/agent-canvas/installations`; fixtures for valid/invalid manifests, including a theme-only extension; `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` subpath export plan wired to generated `dist/extensions/*` and `dist/visualizers/*`; release packaging checklist. No Extension Host, no frontend UI, no agent contribution merging. +Deliverables: shared manifest and registry types; generic installable artifact kind definitions; contribution types for views, left navigation, color themes, settings panels, conversation right panels, tool visualizers, launch templates, SDK plugins, MCP templates, and context; artifact detection rules for extension packages, SDK plugins, skills, and placeholder MCP templates; schema-version constant; manifest validation helpers; storage-path helper for `~/.openhands/agent-canvas/installations`; fixtures for valid/invalid manifests, including a theme-only extension; `@openhands/agent-canvas/canvas-extensions` subpath export plan plus a visualizer export plan aligned with PR #1277 or its successor; release packaging checklist. No Extension Host, no frontend UI, no agent contribution merging. **Iframe-runtime forward compatibility (minimal, PR 0 scope):** the manifest schema and TypeScript contract accept both `browser.module` (active) and `browser.entry` (reserved). The validator recognizes `browser.entry` as syntactically valid but emits a `reserved-not-yet-supported` diagnostic so authors can't accidentally ship extensions that depend on an unimplemented runtime. The API contract in `src/canvas-extensions/types.ts` is defined async-only, which is the only change needed so that a future iframe runtime can satisfy the same interface over postMessage without an API redesign. No iframe host, no postMessage bridge, no asset-mode switching, and no parallel runtime code lands here — that work is deferred to a post-MVP PR triggered by one of the trust-boundary conditions in §27 Decision 11. The total PR 0 cost of keeping the option open is a handful of schema fields, one validation rule, one fixture, and a short authoring note in the type comments. @@ -1128,13 +1174,13 @@ Deliverables: shared manifest and registry types; generic installable artifact k Files: `bin/agent-canvas.mjs`, `scripts/dev-safe.mjs`, `scripts/dev-with-automation.mjs`, `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, extension config helper, tests under `__tests__/canvas-extensions/`. -Deliverables: generic install/manage CLI parsing; `install/list/enable/disable/remove/update/doctor` dispatched before stack startup, before `--public` / `--frontend-only` / `--backend-only` stack validation, before the static `build/` existence check, and before importing stack launcher scripts; install store under `~/.openhands/agent-canvas/installations/`; artifact detection for all kinds with independent install/enable support limited to Canvas Extension manifests; typed unsupported diagnostics for standalone SDK plugins, skills, and MCP templates; npm install with `--ignore-scripts`; manifest validation; Extension Host startup; ingress routes for `/api/canvas/canvas-extensions/*` and `/canvas-extension-assets/*` across all launch modes (Vite dev proxy, automation ingress, static serving, packaged CLI, plus the current frontend-only/backend-only partial-stack modes); dev extension registration store and `--dev` registration; launcher-issued `localInstallStoreReadable` capability flag; example package fixture using SDK plugin and context contributions; `npm pack --dry-run` or packed-tarball smoke for the global CLI path. +Deliverables: generic install/manage CLI parsing; `install/list/enable/disable/remove/update/doctor` dispatched before stack startup, before `--public` / `--frontend-only` / `--backend-only` stack validation, before the static `build/` existence check, and before importing stack launcher scripts; install store under `~/.openhands/agent-canvas/installations/`; artifact detection for all kinds with independent install/enable support limited to Canvas Extension manifests; typed unsupported diagnostics for standalone SDK plugins, skills, and MCP templates; npm install with `--ignore-scripts`; manifest validation; Extension Host startup; ingress routes for `/api/canvas/canvas-extensions/*` and `/canvas-extension-assets/*` across all launch modes (Vite dev proxy, automation ingress, static serving, packaged CLI, plus the current frontend-only/backend-only partial-stack modes); host `enable`/`disable`/`rescan` routes registered only when `liveExtensionManagement` is true; dev extension registration store and `--dev` registration; launcher-issued `localInstallStoreReadable` and `liveExtensionManagement` capability flags (the latter derived from the Vite-vs-static frontend kind in `scripts/runtime-services-info.mjs`); example package fixture using SDK plugin and context contributions; `npm pack --dry-run` or packed-tarball smoke for the global CLI path. -### PR 2 — Frontend management UI +### PR 2 — Frontend inventory UI Files: `src/routes.ts`, `src/routes/canvas-extensions-page.tsx`, legacy Customize navigation files such as `src/components/features/skills/extensions-navigation.tsx`, `src/api/canvas-extensions-service.ts`, `src/api/no-direct-agent-server-calls.test.ts`, `src/hooks/query/use-extensions.ts`, i18n. -Deliverables: Extensions page under Customize replaces "Plugins coming soon"; a simple stacked installed-extensions/status view; install/enable/disable/remove flows; dedicated CanvasExtensionsService wrapper and narrow no-direct-Agent-Server test exception for `/api/canvas/canvas-extensions/*`; dev extension badges, source paths, diagnostics; snapshot coverage for empty/installed/enabled/invalid states. Do not reintroduce a top-level Extensions section. +Deliverables: Extensions page under Customize replaces "Plugins coming soon"; a simple stacked installed-extensions/status view; CLI guidance for install/update/remove (and for enable/disable plus restart in restart-bounded mode); capability-gated enable/disable toggles and a rescan action wired to the host routes when `liveExtensionManagement` is true, with registry-query invalidation after a successful mutation; dedicated CanvasExtensionsService wrapper and narrow no-direct-Agent-Server test exception for `/api/canvas/canvas-extensions/*`; dev extension badges, source paths, needs-review state, diagnostics; snapshot coverage for empty/installed/enabled/invalid/needs-review states in both the read-only and live-management presentations. Do not reintroduce a top-level Extensions section. ### PR 3 — Trusted browser-module view host @@ -1146,19 +1192,19 @@ Deliverables: route `/canvas-extensions/:extensionId/:viewId/*`; DOM-container h Files: `src/components/features/chat/tool-visualizers/*`, `src/canvas-extensions/visualizers/*`, `src/api/canvas-extensions-service.ts`, `src/routes/extension-view-host.tsx` or equivalent runtime loader, `package.json`, `tsconfig.lib.json`, tests under `__tests__/canvas-extensions/` and existing visualizer tests. -Deliverables: public `@openhands/agent-canvas/canvas-visualizers` subpath; extension visualizer registry layer that composes before built-ins and then markdown fallback; `priority` and `matches()` support; error boundary/fallback behavior for extension renderers; manifest projection for `contributes.toolVisualizers`; `hello.canvas` or a focused fixture overriding one MCP tool visualizer; unit tests proving extension-first order, built-in fallback, markdown fallback, and throw-to-next-renderer behavior. +Deliverables: extension visualizer API aligned with the OpenHands agent team's approved direction, using PR #1277 as the current starting point; public visualizer authoring export name/path matched to that decision; extension visualizer registry layer that composes before built-ins and then markdown/default fallback; `matches()` support; no `priority` field unless the agent team adds one; error boundary/fallback behavior for extension renderers; manifest projection for `contributes.toolVisualizers`; `hello.canvas` or a focused fixture overriding one MCP tool visualizer; unit tests proving extension-first order, built-in fallback, markdown fallback, and throw-to-next-renderer behavior. ### PR 3b — Color themes, settings panels, and conversation right panels Files: [src/themes/color-themes.ts](../src/themes/color-themes.ts), [src/components/features/settings/app-settings/theme-input.tsx](../src/components/features/settings/app-settings/theme-input.tsx), settings route/components, conversation right-panel/tab components, `src/canvas-extensions/panels/*`, `src/i18n/translation.json`, tests under `__tests__/canvas-extensions/` and focused component tests. -Deliverables: extension color themes appear in Settings > Application > Color Theme; theme-only extensions install and work without views or panels; unavailable extension themes fall back to the default built-in theme; settings panels under a visible Extensions header after built-in settings; conversation right panels beside Files, Browser, and Terminal; stable props with conversation/backend/workspace summaries where relevant; no raw store or React Query client in the public API; per-panel error boundaries; deterministic ordering by priority/order and extension ID; visible examples for a color theme, settings panel, and PR/context right panel. +Deliverables: extension color themes appear in Settings > Application > Color Theme; theme-only extensions install and work without views or panels; unavailable extension themes fall back to the default built-in theme; settings panels under a visible Extensions header after built-in settings; conversation right panels beside Files, Browser, and Terminal; stable props with conversation/backend/workspace summaries where relevant; no raw store or React Query client in the public API; per-panel error boundaries; deterministic ordering by declared order, extension display name, and contribution ID; visible examples for a color theme, settings panel, and PR/context right panel. ### PR 3c — Dev extension watch mode Files: `scripts/extension-host.mjs`, `scripts/extension-manager.mjs`, `bin/agent-canvas.mjs`, `src/api/canvas-extensions-service.ts`, `src/routes/canvas-extensions-page.tsx`, contributor docs. -Deliverables: `--dev` install and `dev-extension` subcommands; dev registration store; source folder file watching; manifest revalidation on change; browser-module cache-bust/remount after asset changes; visible dev-mode labeling; example extension and contributor guide; tests for registration, traversal rejection, invalid manifest updates, and view remount signaling. +Deliverables: `--dev` install and `dev-extension` subcommands; dev registration store; source folder file watching; manifest revalidation on change; permission-expanding dev manifest changes move the extension to needs-review/disabled-for-next-run until CLI re-approval and restart; browser-module cache-bust/remount after non-permission-expanding asset changes; visible dev-mode labeling; example extension and contributor guide; tests for registration, traversal rejection, invalid manifest updates, permission drift, and view remount signaling. ### PR 4 — Agent contributions @@ -1172,11 +1218,11 @@ Permission consent modal; secret setup flow; `doctor` command with actionable di ## 26. Testing Strategy -**Unit:** manifest schema validation; package path traversal rejection; duplicate ID handling; color theme contribution validation/fallback; enable/disable precedence; registry sort order; asset route path validation; CLI arg parsing; dev extension registration and path validation; dev manifest revalidation after source changes; launch contribution merge and dedupe; extension context suffix rendering; runtime compatibility classification across all five runtime classes; permission drift / update re-approval; uninstall preserve vs purge. +**Unit:** manifest schema validation; package path traversal rejection; duplicate ID handling; color theme contribution validation/fallback; enable/disable precedence at process startup; registry sort order; asset route path validation; CLI arg parsing; dev extension registration and path validation; dev manifest revalidation after source changes; launch contribution merge and dedupe; extension context suffix rendering; runtime compatibility classification across all five runtime classes; permission drift / update re-approval; uninstall preserve vs purge. **Release packaging:** `npm pack --dry-run`; install from the packed tarball into a temp global/prefix; verify `agent-canvas list`, `agent-canvas doctor`, and `agent-canvas install --yes` work without a source checkout; verify full `agent-canvas` launch still finds `build/`, `scripts/`, and Extension Host routes. -**Component:** Extensions page states under Customize; permission display; browser-module view loading/error; extension color theme appears in Settings > Application > Color Theme and can be selected; settings panel placement under the visible Extensions header; conversation right-panel tab rendering/error boundaries; MCP required preflight; incompatible runtime warning; dev badge. +**Component:** Extensions page states under Customize in both the read-only (capability false) and live-management (capability true, with enable/disable toggles and rescan) presentations; permission display; CLI/restart guidance; browser-module view loading/error; extension color theme appears in Settings > Application > Color Theme and can be selected; settings panel placement under the visible Extensions header; conversation right-panel tab rendering/error boundaries; MCP required preflight; incompatible runtime warning; dev and needs-review badges. **E2E snapshots:** Extensions page empty state under Customize; enabled extension with one view; invalid extension diagnostics; extension color theme option in Settings > Application; settings panel under Extensions header; conversation right panel beside Files/Browser/Terminal; launch template requiring MCP; launch preflight with local-path SDK plugin disabled on remote backend; dev extension view remount after file change; future: agent-mediated install proposal flow. @@ -1194,11 +1240,11 @@ These were open questions in the earlier draft. Decisions for the RFC: | # | Question | Decision | |---|---|---| -| 1 | Web UI install in MVP, or CLI-only first? | **CLI-first.** Web UI install lands in PR 2 once CLI plumbing is proven; CLI remains the supported automation surface. | +| 1 | Web UI install in MVP, or CLI-only first? | **CLI-only by default, with a development-stack exception.** Install, update, and remove are CLI-only in every mode. Enable, disable, and new-extension discovery are CLI + restart in packaged/Docker/static modes, but are available live from the Extensions page when the launcher reports `liveExtensionManagement` (the `npm run dev` source stack). Web UI install/update/remove remain deferred. See §15.2 and Decision 16. | | 2 | Iframe sandbox vs inline browser modules for extension views? | **Trusted same-origin browser modules as MVP default** (`browser.module`); reserve `browser.entry` in the manifest schema for a future sandboxed-iframe runtime aimed at untrusted/marketplace extensions. Trust comes from explicit install/enable consent, not browser-enforced isolation. See §11.1, §12.1, and §19. | | 3 | What signals an Agent Server is filesystem-local? | **Launcher-issued `localInstallStoreReadable` capability flag.** `backend.kind === "local"` alone is insufficient. | | 4 | `src/installations/` vs `src/canvas-extensions/` directory split? | **Single `src/canvas-extensions/` directory** until a real consumer requires the split. | -| 5 | API route prefix consistency? | **`/api/canvas/canvas-extensions/*`** for all management routes; `/canvas-extension-assets/*` reserved for asset serving. | +| 5 | API route prefix consistency? | **`/api/canvas/canvas-extensions/*`** for local Canvas extension runtime APIs; `/canvas-extension-assets/*` reserved for asset serving. Browser management routes are deferred. | | 6 | Empty `src/addons/` directory on `main`? | **No longer present.** PR 0 should avoid reviving the old `addons` namespace and use `src/canvas-extensions/`. | | 7 | Should the first MVP independently install standalone SDK plugins, standalone skills, or MCP templates? | **No.** Detect them and return explicit unsupported diagnostics; require a Canvas Extension wrapper for MVP. | | 8 | Should package-relative SDK plugin paths be MVP? | **Yes, but only when `localInstallStoreReadable` is true** and the pinned Agent Server smoke passes. Remote/cloud runtimes get disabled reasons rather than local paths. | @@ -1209,6 +1255,7 @@ These were open questions in the earlier draft. Decisions for the RFC: | 13 | Which create-conversation payload shape should extensions target? | **Current SDK settings shape:** top-level `agent_settings` plus optional top-level `plugins`; do not reintroduce legacy `agent` payloads. | | 14 | What route-less and registry-backed UI surfaces are in scope? | **Color themes, tool visualizers, settings panels, and conversation right panels.** They map to concrete existing product regions and avoid arbitrary app-root mounting. Color themes are registry data, not arbitrary JavaScript placement. | | 15 | Should generic conversation slots ship before these surfaces? | **No.** Header/footer/badge/sidebar slots remain deferred until a workflow cannot be handled by a right panel or tool visualizer. | +| 16 | Should the running app ever change extension state without a restart? | **Only on the development source stack.** A launcher-issued `liveExtensionManagement` capability (true only for the Vite-served `npm run dev` / `dev:minimal` stack) enables live enable/disable and discovery of newly installed extensions, so an in-Canvas agent can author an extension and the user can enable it in the same session. Packaged global installs, Docker, and static-served builds stay restart-bounded. Install, update, and remove remain CLI-only in all modes. The capability never relaxes consent, permission re-approval, or the agent-runtime boundary. See §15.2. | ## 28. Remaining Open Questions @@ -1222,7 +1269,7 @@ Build the smallest powerful slice: 1. CLI-managed npm extension install/enable/list. 2. Extension Host registry and asset serving. -3. Extensions management page. +3. Extensions inventory/diagnostics page. 4. Trusted same-origin browser-module extension views (with the iframe runtime reserved in the manifest schema). 5. Left navigation entries for Canvas Extension views. 6. Color themes in Settings > Application > Color Theme. @@ -1230,8 +1277,9 @@ Build the smallest powerful slice: 8. Conversation right panels beside Files, Browser, and Terminal. 9. Extension tool visualizers. 10. Dev-mode extension registration / watch / reload. -11. SDK plugin contribution merge for local conversations. -12. Context contribution merge behind explicit permission. +11. Capability-gated live enable/disable and new-extension discovery on the `npm run dev` source stack (`liveExtensionManagement`); restart-bounded everywhere else. +12. SDK plugin contribution merge for local conversations. +13. Context contribution merge behind explicit permission. Defer: marketplace, package signing, **sandboxed iframe runtime** (manifest field reserved as `browser.entry` but unimplemented), arbitrary root-mounted route-less components/shared dependency runtime, rich extension RPC APIs beyond the visualizer/slot surfaces, agent-mediated installation, SDK/server install management, plugin parameters, and per-run extension enablement. @@ -1251,19 +1299,19 @@ A Canvas Extension is an Agent Canvas-specific package that extends the Canvas U **Q: Why is the product name "Canvas Extensions" if the app section was renamed from Extensions to Customize?** -The app-level section remains Customize. Inside Customize, the Canvas Extension management item can be named Extensions because it is specifically about installed Canvas Extensions. The RFC avoids reintroducing a top-level Extensions hub and explicitly treats legacy file names like `extensions-navigation.tsx` as Customize implementation details. +The app-level section remains Customize. Inside Customize, the Canvas Extension inventory item can be named Extensions because it is specifically about installed Canvas Extensions. The RFC avoids reintroducing a top-level Extensions hub and explicitly treats legacy file names like `extensions-navigation.tsx` as Customize implementation details. -**Q: Why not call the management page "Packages"?** +**Q: Why not call the inventory page "Packages"?** -"Packages" is too broad and makes this feature sound like a generic npm/package manager. The user-facing surface should be an Extensions page under Customize. The page should be a simple stacked view of installed Canvas Extensions and their status. Package terminology stays in CLI/install mechanics where npm package behavior is actually being discussed. +"Packages" is too broad and makes this feature sound like a generic npm/package manager. The user-facing surface should be an Extensions page under Customize — read-only in packaged/static mode, with capability-gated enable/disable on the dev stack (§15.2). The page should be a simple stacked view of installed Canvas Extensions and their status/diagnostics. Package terminology stays in CLI/install mechanics where npm package behavior is actually being discussed. **Q: Should the manifest still be named `agent-canvas.extension.json`?** Current recommendation: keep it for MVP because it is explicit to Agent Canvas, short, and already used throughout the plan. Renaming it to something like `agent-canvas.canvas-extension.json` would reduce ambiguity but adds churn. Clarification requested: do we want to pay that naming churn before PR 0, or keep the shorter manifest name and rely on docs/product vocabulary? -**Q: Are `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers` the right public subpaths?** +**Q: Are `@openhands/agent-canvas/canvas-extensions` and the visualizer authoring subpath the right public exports?** -They are intentionally verbose to avoid confusion with `@openhands/extensions`. They also distinguish Canvas Extension authoring types from visualizer authoring helpers. Clarification requested: should we keep these explicit subpaths, or prefer shorter `./extensions` and `./visualizers` exports with stronger documentation? +`@openhands/agent-canvas/canvas-extensions` remains intentionally explicit to avoid confusion with `@openhands/extensions`. The visualizer authoring export should follow the OpenHands agent team's approved API from PR #1277 or its successor; PR #1277 currently proposes `@openhands/agent-canvas/visualizers`. Do not create both `./visualizers` and `./canvas-visualizers` unless the agent team explicitly wants aliases. ### 30.2 UI Contribution Surfaces @@ -1297,7 +1345,7 @@ Only in the settings experience, grouped under a visible Extensions header after **Q: Do Canvas Extension settings panels appear in settings navigation?** -MVP recommendation: no separate nav item per extension panel unless scanning becomes poor. Start with one Extensions grouping after built-in settings and render installed extension panels in a stacked/ordered list. Clarification requested: should each Canvas Extension settings panel get a left-nav anchor, or should the MVP keep one grouped Extensions section only? +MVP decision: keep one grouped Extensions section after built-in settings. Do not add a settings nav anchor per installed Canvas Extension in the first proof; revisit only if scanning becomes poor. **Q: Where do conversation right panels render?** @@ -1331,7 +1379,11 @@ It keeps the manifest and runtime API compatible with a future sandboxed documen **Q: How does disable/revocation work?** -Disabling an extension removes its contributions from the registry and should unmount active browser modules on the next registry refresh or route/panel remount. Development and implementation should also provide a process-level `--disable-extensions` kill switch. Clarification requested: do we need immediate live unmount across all currently mounted surfaces in MVP, or is refresh/remount behavior acceptable for the first proof? +By default disable/revocation is restart-bounded. Users run CLI management commands while Canvas is stopped, then restart. On the next process start, disabled extensions do not enter the registry, do not render UI contributions, and do not contribute to future conversation launches. In packaged, Docker, and static modes the browser UI does not offer live disable/uninstall controls and does not promise immediate unmount after out-of-band CLI edits. + +On the development source stack (`liveExtensionManagement` true; see §15.2), disable is live: the Extensions page toggle mutates `config.json` and the refreshed registry unmounts reconcilable UI contributions (nav, themes, panels, visualizers, views) without a restart. Agent-side contributions are the exception — disabling an SDK plugin or context block stops it from being added to *future* conversations but cannot retract state already applied to a running conversation, so a new conversation is required. Uninstall/remove remains a CLI verb in this mode too. + +The process-level `--disable-extensions` kill switch still suppresses all extension loading for that run, in either tier. **Q: Can an extension impersonate built-in UI?** @@ -1345,11 +1397,11 @@ Global npm installs cannot rebuild Canvas for each installed extension. A local **Q: Does the Extension Host run in every mode?** -It should run in packaged CLI, `npm run dev`, `dev:minimal`, and static mode when the frontend needs to manage or render Canvas Extensions. Frontend-only can expose local management/assets with agent-runtime diagnostics. Backend-only should skip browser asset hosting unless a future CLI-only host mode needs it. CLI management commands must still work before full stack startup. +It should run in packaged CLI, `npm run dev`, `dev:minimal`, and static mode when the frontend needs to inspect or render Canvas Extensions. Frontend-only can expose local inventory/assets with agent-runtime diagnostics. Backend-only should skip browser asset hosting unless a future CLI-only host mode needs it. CLI management commands must still work before full stack startup. -**Q: Why `/api/canvas/canvas-extensions/*` for management and `/canvas-extension-assets/*` for assets?** +**Q: Why `/api/canvas/canvas-extensions/*` for extension APIs and `/canvas-extension-assets/*` for assets?** -The management prefix clearly identifies local Canvas-owned management APIs and avoids confusing them with Agent Server `/api/*` traffic. The asset prefix is separate because it serves extension-authored static files and benefits from being easy to identify in logs and future CSP rules. +The API prefix clearly identifies local Canvas-owned extension APIs and avoids confusing them with Agent Server `/api/*` traffic. The asset prefix is separate because it serves extension-authored static files and benefits from being easy to identify in logs and future CSP rules. **Q: What happens if the Extension Host is unavailable?** @@ -1357,17 +1409,17 @@ Canvas should degrade gracefully: built-in app behavior continues, extension con **Q: How are extension registry updates invalidated?** -MVP should use React Query invalidation after install/enable/disable/remove/settings mutations and a dev-mode version token for browser modules. Dev watch should bump a cache-busting token so static dynamic imports remount with new output. +MVP should use React Query for read-only registry/diagnostic/settings reads and for settings mutations. Install, update, and remove are CLI-only in every mode, so the browser never invalidates for those. Enable, disable, and rescan are CLI-only and restart-bounded in packaged/Docker/static modes, but on the development source stack (`liveExtensionManagement` true) they are browser mutations that invalidate the registry query on success — the same persist-then-invalidate pattern Canvas already uses for LLM profiles. Dev watch should bump a cache-busting token so static dynamic imports remount with new output when the manifest change does not require re-approval. ### 30.5 Manifest And Packaging **Q: Is one `browser.module` per extension enough?** -For MVP, yes. A single browser module plus contribution IDs keeps packaging simple. Settings panels, right panels, views, and visualizers can reference `viewId` or register contributions through that module. If this becomes awkward, a later schema can allow per-contribution modules. +For MVP, yes. A single browser module plus contribution IDs keeps packaging simple. Views mount through the view host. Settings panels and right panels declare metadata in the manifest and register implementations by contribution ID at runtime. Visualizers follow the agent-team-approved registry API. If this becomes awkward, a later schema can allow per-contribution modules. **Q: Should settings panels and right panels reference `viewId`, modules, or exported component IDs?** -Current recommendation: allow them to reference a declared `viewId` for MVP so the host has one browser-module loading path. Runtime registration can provide the actual panel renderer. Clarification requested: do we want the manifest to declare only metadata and let the module register implementations, or should each contribution explicitly name an exported symbol? +MVP decision: the manifest declares metadata only and the browser module registers implementations by contribution ID at runtime. Do not require exported component names in the manifest for the first proof. The host validates metadata, the runtime validates registration, and missing implementations produce diagnostics. **Q: How are path traversal and package containment handled?** @@ -1381,15 +1433,15 @@ Permission-expanding updates should move the extension back to a disabled or nee **Q: Can an extension override built-in visualizers?** -Yes, intentionally. Extension visualizers are considered before built-ins, with `priority` and `matches()` to narrow overrides. If no extension matches or an extension fails, Canvas falls back to built-ins and then markdown. +Yes, intentionally, subject to the OpenHands agent team's approved visualizer API. Current draft PR #1277 considers custom/addon visualizers before built-ins, uses `matches()` to narrow overrides, and falls back to built-ins and then markdown/default rendering. It intentionally does not include `priority`. **Q: What if two extensions match the same event?** -Higher priority wins. Ties sort by extension ID and visualizer ID so behavior is deterministic. Diagnostics should expose which visualizer was selected when debugging is enabled. +Follow the agent-team-approved registry ordering. Current draft PR #1277 uses latest registration first for custom/addon visualizers, then built-ins, then primitive markdown/default fallback. Diagnostics should expose which visualizer was selected when debugging is enabled. **Q: How do thrown visualizers fall through safely?** -The dispatcher should wrap extension visualizers in a failure boundary that records a local diagnostic and tries the next matching renderer. This may require rendering candidates through an isolation wrapper instead of a single broad React error boundary. The implementation spike should prove this before committing the public API. +The dispatcher should wrap extension visualizers in a failure boundary that records a local diagnostic and tries the next matching renderer. PR #1277 already sketches this fallthrough behavior; the implementation should reuse or adapt that approach rather than building a separate Canvas-only renderer path. **Q: Can visualizers fetch data or read settings while rendering?** @@ -1415,7 +1467,7 @@ MVP should include extension IDs and contribution IDs in launch preflight UI and **Q: What if an extension is disabled after a conversation starts?** -Disabling should stop future UI contributions and future conversation launches from using it. Existing conversations may already contain context suffixes or SDK plugin behavior from creation time; disabling cannot reliably remove already-applied agent runtime state from an active conversation. +After restart, disabling stops future UI contributions and future conversation launches from using the extension. Existing conversations may already contain context suffixes or SDK plugin behavior from creation time; disabling cannot reliably remove already-applied agent runtime state from an active conversation. ### 30.8 Testing And Proof @@ -1425,7 +1477,7 @@ Disabling should stop future UI contributions and future conversation launches f **Q: Should all UI surfaces land in one PR?** -No. The RFC already splits the work. PR 3 covers view host and left navigation, PR 3a covers tool visualizers, and PR 3b covers color themes, settings panels, and conversation right panels. PR 2 can land the Extensions management page independently once the registry shape is stable. +No. The RFC already splits the work. PR 3 covers view host and left navigation, PR 3a covers tool visualizers, and PR 3b covers color themes, settings panels, and conversation right panels. PR 2 can land the read-only Extensions inventory page independently once the registry shape is stable. **Q: What tests are required before sharing broadly?** @@ -1436,9 +1488,7 @@ At minimum: manifest validation, route/path containment tests, no-direct-Agent-S These are the decisions that would benefit from product/engineering direction before PR 0: 1. Keep `agent-canvas.extension.json`, or rename the manifest to make "Canvas Extension" even more explicit? -2. Keep public subpaths as `@openhands/agent-canvas/canvas-extensions` and `@openhands/agent-canvas/canvas-visualizers`, or shorten them? -3. Should settings panels appear in one grouped Extensions section only, or should each installed Canvas Extension get a settings nav anchor? +2. Keep public extension-management subpath as `@openhands/agent-canvas/canvas-extensions`; for visualizers, follow the OpenHands agent team's approved export from PR #1277 or its successor. +3. Confirm the OpenHands agent team wants PR #1277's visualizer API shape: registration order instead of priority, custom/addon before built-ins, error-boundary fallthrough, and public export naming. 4. Is mobile support for conversation right panels required in the first proof? -5. Do disabled extensions need immediate live unmount across all mounted surfaces in MVP? -6. Should manifest contributions declare implementation exports explicitly, or should browser modules register implementations at runtime? -7. What provenance bar is required before any non-first-party/community Canvas Extension distribution: npm integrity, signatures, first-party allowlist, enterprise policy, or some combination? +5. What provenance bar is required before any non-first-party/community Canvas Extension distribution: npm integrity, signatures, first-party allowlist, enterprise policy, or some combination? From 121058bf964bc9cc8353e947f9581e876975bc51 Mon Sep 17 00:00:00 2001 From: Devin Date: Thu, 11 Jun 2026 17:29:54 -0400 Subject: [PATCH 11/14] Reduce and reword so RFC is easier to digest --- docs/ExtensionsSystemRFC.md | 63 ++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/docs/ExtensionsSystemRFC.md b/docs/ExtensionsSystemRFC.md index cd9e8c9c1..ee4865fe8 100644 --- a/docs/ExtensionsSystemRFC.md +++ b/docs/ExtensionsSystemRFC.md @@ -12,7 +12,15 @@ Agent Canvas should ship a first-class **Canvas Extensions** system: user-instal A Canvas Extension can contribute UI views, left navigation entries, color themes, settings panels, conversation right panels, custom tool/event visualizers, launch templates, OpenHands SDK plugin sources, MCP server templates, and system-prompt context blocks. Canvas Extensions are installed and managed by a small local Node service started by the `agent-canvas` launcher. Agent-side behavior is forwarded only through already-supported SDK surfaces; Canvas does not patch or load code inside the Agent Server. -The MVP delivers CLI install/enable/disable/remove for Canvas Extension packages, an Extensions page under Customize for inventory and diagnostics, trusted same-origin extension views, dev-mode authoring with live reload, and focused UI contribution surfaces: left navigation entries, color themes that appear in Settings > Application > Color Theme, settings panels under a visible Extensions header, conversation right panels beside Files/Browser/Terminal, and extension-provided tool visualizers that compose with the built-in visualizer registry introduced by PR #1246. The extension-facing tool visualizer contract should follow the OpenHands agent team's direction, with draft PR #1277 as the current lead implementation, rather than inventing a parallel Canvas-only renderer model. Broad root-mounted components remain deferred. The CLI can detect adjacent artifact types such as standalone SDK plugins or `SKILL.md` folders, but the first executable slice should install/enable only packages with a Canvas Extension manifest. Marketplace, signing, sandboxed iframe views, arbitrary app-root component/shared dependency runtimes, standalone skill/plugin management, and agent-mediated installation are explicitly deferred. The manifest reserves a future iframe entry point so that stronger isolation can be added later without changing extension package shape. +**MVP delivers:** + +- CLI install/enable/disable/remove for Canvas Extension packages, plus an Extensions page under Customize for inventory and diagnostics. +- Trusted same-origin extension views and dev-mode authoring with live reload. +- Focused UI contribution surfaces: left navigation entries; color themes in Settings > Application > Color Theme; settings panels under an Extensions header; conversation right panels beside Files/Browser/Terminal; and tool visualizers that compose with the built-in registry from PR #1246. + +The tool visualizer contract follows the OpenHands agent team's direction (draft PR #1277 is the current lead) rather than a parallel Canvas-only renderer; see §12.9. The CLI can detect adjacent artifact types (standalone SDK plugins, `SKILL.md` folders), but the first slice installs and enables only packages with a Canvas Extension manifest. + +**Deferred:** marketplace, signing, sandboxed iframe views, broad root-mounted/shared-dependency runtimes, standalone skill/plugin management, and agent-mediated installation. The manifest reserves a future iframe entry point (`browser.entry`) so stronger isolation can be added later without changing package shape; see §11.1. **Lifecycle tiers (capability-gated by launch mode).** Extension lifecycle has two tiers, selected by how Canvas was launched, not by a per-extension setting: @@ -1257,15 +1265,20 @@ These were open questions in the earlier draft. Decisions for the RFC: | 15 | Should generic conversation slots ship before these surfaces? | **No.** Header/footer/badge/sidebar slots remain deferred until a workflow cannot be handled by a right panel or tool visualizer. | | 16 | Should the running app ever change extension state without a restart? | **Only on the development source stack.** A launcher-issued `liveExtensionManagement` capability (true only for the Vite-served `npm run dev` / `dev:minimal` stack) enables live enable/disable and discovery of newly installed extensions, so an in-Canvas agent can author an extension and the user can enable it in the same session. Packaged global installs, Docker, and static-served builds stay restart-bounded. Install, update, and remove remain CLI-only in all modes. The capability never relaxes consent, permission re-approval, or the agent-runtime boundary. See §15.2. | -## 28. Remaining Open Questions +## 28. Open Questions + +The single list of decisions that would benefit from product/engineering direction. (Other sections point here rather than re-listing.) -1. Should the `@openhands/extensions` catalog eventually become one first-party Canvas Extension package, or remain a plain dependency for built-in catalogs? -2. What is the graduation path for standalone SDK plugin, standalone skill, and MCP-template installers after the extension-package MVP proves out? -3. What provenance UI is required before enabling a public/community extension marketplace: npm integrity only, signatures, first-party allowlists, or enterprise policy? +1. **Manifest name.** Keep `agent-canvas.extension.json`, or rename it to make "Canvas Extension" more explicit (e.g. `agent-canvas.canvas-extension.json`)? Renaming reduces ambiguity but adds churn; default is to keep the shorter name for MVP. +2. **Visualizer API.** Confirm the OpenHands agent team's PR #1277 visualizer API shape — registration order instead of `priority`, custom/addon before built-ins, error-boundary fallthrough — and the public export name (PR #1277 proposes `@openhands/agent-canvas/visualizers`). Canvas follows their decision rather than shipping a parallel API. +3. **Mobile.** Is mobile support for conversation right panels required in the first proof, or can small viewports show a disabled/unsupported reason until a mobile design lands? +4. **Provenance.** What provenance bar is required before any non-first-party/community distribution: npm integrity, signatures, first-party allowlist, enterprise policy, or a combination? This gates a public marketplace. +5. **Catalog.** Should the `@openhands/extensions` catalog eventually become a first-party Canvas Extension package, or remain a plain dependency for built-in catalogs? +6. **Graduation.** What is the graduation path for standalone SDK plugin, standalone skill, and MCP-template installers after the extension-package MVP proves out? ## 29. Recommended MVP Cut -Build the smallest powerful slice: +The build sequence for the MVP scope in §1. (What ships and what defers is defined in §1 and §5; this section is only the ordering.) 1. CLI-managed npm extension install/enable/list. 2. Extension Host registry and asset serving. @@ -1281,10 +1294,6 @@ Build the smallest powerful slice: 12. SDK plugin contribution merge for local conversations. 13. Context contribution merge behind explicit permission. -Defer: marketplace, package signing, **sandboxed iframe runtime** (manifest field reserved as `browser.entry` but unimplemented), arbitrary root-mounted route-less components/shared dependency runtime, rich extension RPC APIs beyond the visualizer/slot surfaces, agent-mediated installation, SDK/server install management, plugin parameters, and per-run extension enablement. - -This delivers a useful end-to-end extension system while keeping the Agent Server boundary clean. - ## 30. Engineering FAQ ### 30.1 Naming And Product Scope @@ -1307,11 +1316,11 @@ The app-level section remains Customize. Inside Customize, the Canvas Extension **Q: Should the manifest still be named `agent-canvas.extension.json`?** -Current recommendation: keep it for MVP because it is explicit to Agent Canvas, short, and already used throughout the plan. Renaming it to something like `agent-canvas.canvas-extension.json` would reduce ambiguity but adds churn. Clarification requested: do we want to pay that naming churn before PR 0, or keep the shorter manifest name and rely on docs/product vocabulary? +Keep it for MVP: it is explicit to Agent Canvas, short, and already used throughout the plan. A rename is an open question (§28, item 1). **Q: Are `@openhands/agent-canvas/canvas-extensions` and the visualizer authoring subpath the right public exports?** -`@openhands/agent-canvas/canvas-extensions` remains intentionally explicit to avoid confusion with `@openhands/extensions`. The visualizer authoring export should follow the OpenHands agent team's approved API from PR #1277 or its successor; PR #1277 currently proposes `@openhands/agent-canvas/visualizers`. Do not create both `./visualizers` and `./canvas-visualizers` unless the agent team explicitly wants aliases. +`@openhands/agent-canvas/canvas-extensions` is settled — intentionally explicit to avoid confusion with `@openhands/extensions`. The visualizer authoring export is still open and follows the agent team's PR #1277 decision (§28, item 2). Do not create both `./visualizers` and `./canvas-visualizers` unless the agent team wants aliases. ### 30.2 UI Contribution Surfaces @@ -1353,7 +1362,7 @@ They render in the existing conversation right-panel/tab region beside Files, Br **Q: How should mobile handle Canvas Extension right panels?** -MVP recommendation: reuse the existing mobile conversation panel pattern and show extension panels as additional entries in that panel navigation, if technically straightforward. If not, hide extension right panels on small viewports with a clear disabled/unsupported reason until a mobile design is intentionally added. Clarification requested: is mobile parity required for the first proof? +MVP recommendation: reuse the existing mobile conversation panel pattern and show extension panels as additional entries in that panel navigation, if technically straightforward. If not, hide extension right panels on small viewports with a clear disabled/unsupported reason until a mobile design is intentionally added. Whether mobile parity is required for the first proof is an open question (§28, item 3). **Q: What happens if multiple extensions add left-nav entries or right panels?** @@ -1361,13 +1370,9 @@ Sort deterministically by declared `order`, extension display name, then contrib ### 30.3 Security And Trust -**Q: What is the MVP trust model?** - -MVP Canvas Extensions are trusted local code. Browser modules run same-origin with Canvas after explicit install/enable consent. This is not a browser-enforced sandbox. The trust model is consent, local install provenance, permission display, diagnostics, and a process-level kill switch. +**Q: What is the MVP trust model, and what can a malicious extension do?** -**Q: What can a malicious enabled Canvas Extension access?** - -In MVP, trusted same-origin browser code may be able to inspect browser-visible Canvas state, call same-origin routes if it can access credentials/session context, manipulate DOM, and contact declared or undeclared network origins unless stricter CSP enforcement is added. The RFC therefore does not claim strong isolation until the reserved iframe runtime is implemented. +MVP browser modules are trusted local code, not sandboxed — see §19 for the full posture and threat list. In short: an enabled extension runs same-origin and can inspect browser-visible Canvas state, call same-origin routes with the session's credentials, manipulate the DOM, and reach network origins. Trust comes from explicit install/enable consent, local install provenance, permission display, diagnostics, and the kill switch — not from browser-enforced isolation, which is deferred to the reserved iframe runtime. **Q: Is install-time consent enough?** @@ -1431,17 +1436,9 @@ Permission-expanding updates should move the extension back to a disabled or nee ### 30.6 Tool Visualizers -**Q: Can an extension override built-in visualizers?** - -Yes, intentionally, subject to the OpenHands agent team's approved visualizer API. Current draft PR #1277 considers custom/addon visualizers before built-ins, uses `matches()` to narrow overrides, and falls back to built-ins and then markdown/default rendering. It intentionally does not include `priority`. - -**Q: What if two extensions match the same event?** +**Q: Can an extension override built-ins, and what wins when several match?** -Follow the agent-team-approved registry ordering. Current draft PR #1277 uses latest registration first for custom/addon visualizers, then built-ins, then primitive markdown/default fallback. Diagnostics should expose which visualizer was selected when debugging is enabled. - -**Q: How do thrown visualizers fall through safely?** - -The dispatcher should wrap extension visualizers in a failure boundary that records a local diagnostic and tries the next matching renderer. PR #1277 already sketches this fallthrough behavior; the implementation should reuse or adapt that approach rather than building a separate Canvas-only renderer path. +Yes, overriding is intentional. The selection order and fallthrough are defined in §12.9 (per the agent team's PR #1277): custom/addon visualizers before built-ins before markdown/default, `matches()` narrows overrides, latest registration wins among custom visualizers, and there is no `priority` field. A thrown extension visualizer is caught by a failure boundary, records a diagnostic, and falls through to the next matching renderer. Diagnostics should expose which visualizer was selected when debugging is enabled. **Q: Can visualizers fetch data or read settings while rendering?** @@ -1485,10 +1482,4 @@ At minimum: manifest validation, route/path containment tests, no-direct-Agent-S ### 30.9 Clarifications Requested -These are the decisions that would benefit from product/engineering direction before PR 0: - -1. Keep `agent-canvas.extension.json`, or rename the manifest to make "Canvas Extension" even more explicit? -2. Keep public extension-management subpath as `@openhands/agent-canvas/canvas-extensions`; for visualizers, follow the OpenHands agent team's approved export from PR #1277 or its successor. -3. Confirm the OpenHands agent team wants PR #1277's visualizer API shape: registration order instead of priority, custom/addon before built-ins, error-boundary fallthrough, and public export naming. -4. Is mobile support for conversation right panels required in the first proof? -5. What provenance bar is required before any non-first-party/community Canvas Extension distribution: npm integrity, signatures, first-party allowlist, enterprise policy, or some combination? +See §28 Open Questions — the manifest name, visualizer API shape, mobile right-panel support, and provenance bar are tracked there. The extension-management subpath stays `@openhands/agent-canvas/canvas-extensions` (intentionally explicit to avoid confusion with `@openhands/extensions`). From 0c5543b9de3bc1e57e70c2749cbe01db444b3ba1 Mon Sep 17 00:00:00 2001 From: Devin Date: Thu, 11 Jun 2026 18:04:24 -0400 Subject: [PATCH 12/14] Challenge and clarify pass --- docs/ExtensionsSystemAgentExecutionPlan.md | 18 +++-- docs/ExtensionsSystemPoCPlan.md | 11 +-- docs/ExtensionsSystemRFC.md | 87 ++++++++++++++++------ 3 files changed, 83 insertions(+), 33 deletions(-) diff --git a/docs/ExtensionsSystemAgentExecutionPlan.md b/docs/ExtensionsSystemAgentExecutionPlan.md index cd922f209..68ec372ec 100644 --- a/docs/ExtensionsSystemAgentExecutionPlan.md +++ b/docs/ExtensionsSystemAgentExecutionPlan.md @@ -154,7 +154,7 @@ npm run build:lib - `__tests__/canvas-extensions/fixtures/**` - [ ] Implement: - Store bootstrap: private `package.json`, `package-lock.json`, `artifacts.json`, `config.json`, `logs/`, `dev/`, `node_modules/`. - - Read/write registry state with atomic-ish writes where practical. + - Read/write registry state with atomic writes (temp-file + rename) and single-read loads. Guard multi-step install/update/remove with an exclusive `installations/.lock`; `rescan` reads a consistent snapshot and backs off (waits/retries or reports `install in progress`) if the lock is held. - Install Canvas Extension manifests from local paths first; tarball/npm spec can follow in the same gate if scoped. - Run npm installs in the private store with `--ignore-scripts` by default. - Enable/disable/remove/update state transitions exposed as manager functions; the CLI calls them directly and the host calls enable/disable/rescan only when live management is enabled. The manager itself is mode-agnostic. @@ -171,6 +171,7 @@ npm run build:lib - Invalid manifest state. - Remove preserves settings by default. - Path traversal rejection. + - Concurrent CLI write + Host read never yields a torn/partial `config.json`/`artifacts.json`; rescan during a held lock waits or reports `install in progress`. - [ ] Verification: ```sh @@ -301,7 +302,7 @@ npm test -- __tests__/canvas-extensions - optional README under the fixture - [ ] Implement: - Manifest with `id: "hello.canvas"`. - - One `browser.module` view. + - One `browser.module` exporting both `activate(context)` (registers the visualizer, settings panel, and right panel — see RFC §13.1) and `mount(params)` (renders the view). - One primary sidebar contribution after Automations. - One launch template. - One context block with a recognizable marker. @@ -426,10 +427,12 @@ npm test - Fetch registry, resolve enabled view, import `assetBaseUrl + browser.module`. - Use `import(/* @vite-ignore */ url)` or equivalent for served extension modules. - Add cache-busting version token. - - Call `mount({ root, context })`; call `dispose()` on unmount/remount. + - Startup activation pass: for each enabled extension with a `browser.module`, import it once and call `activate(context)` so route-less contributions register independent of any open view (RFC §13.1). Call the `activate()` disposable on disable/remove. + - Call the module's `mount({ root, context })` for the view; call `dispose()` on unmount/remount. - Provide minimal async context: metadata/settings, `navigation.navigate`, `navigation.openExternal`, `ui.toast`, settings read/patch. - Local error boundary and diagnostics link. - [ ] Tests: + - `activate()` runs once at startup for an enabled extension and registers route-less contributions with no view open; a throwing `activate()` is isolated to that extension. - Mount success. - Mount failure shows local error and does not crash Canvas. - Dispose called on unmount/remount. @@ -492,7 +495,7 @@ npm run test:e2e:snapshots -- --grep "sidebar" - extension tests under `__tests__/canvas-extensions/` - [ ] Implement: - Public visualizer authoring export matched to the OpenHands agent team's approved API; use PR #1277 as the current starting point. - - Extension visualizer registry layer that composes before built-in visualizers and markdown/default fallback. + - Extension visualizer registry layer that composes before built-in visualizers and markdown/default fallback. Extensions register visualizers during the module's `activate(context)` pass (RFC §13.1), not a standalone default-export function. - `matches()` support for narrowing one event variant. - No Canvas-only `priority` field unless the agent team approves it. - Manifest projection for `contributes.toolVisualizers`. @@ -536,10 +539,10 @@ npm run typecheck - Support theme-only extensions with no view/panel/visualizer contribution. - Fall back to the default built-in theme if the selected extension theme is disabled, removed, or invalid. - Settings panel registry for `contributes.settingsPanels`. - - Settings panel manifests declare metadata only; browser modules register implementations by contribution ID. + - Settings panel manifests declare metadata only; browser modules register implementations by contribution ID during `activate()` (RFC §13.1). - Render settings panels only under a visible Extensions header after built-in settings sections. - Conversation right-panel registry for `contributes.conversationRightPanels`. - - Conversation right-panel manifests declare metadata only; browser modules register implementations by contribution ID. + - Conversation right-panel manifests declare metadata only; browser modules register implementations by contribution ID during `activate()` (RFC §13.1). - Render conversation right panels alongside Files, Browser, and Terminal. - Stable panel props: conversation ID/status where relevant, active backend summary, workspace summary, extension settings helpers, navigation/toast helpers. - Deterministic ordering by `order`, extension display name, and panel contribution ID. @@ -742,7 +745,10 @@ The PoC is not complete unless all of these are true: - [ ] Local SDK plugin paths are sent only when `localInstallStoreReadable=true`. - [ ] Install, update, and remove are CLI-only with no browser routes or controls in any mode. - [ ] Enable/disable/rescan host routes and Extensions page controls exist only when `liveExtensionManagement=true` (the Vite dev stack); the packaged/Docker/static page is read-only and restart-bounded. +- [ ] Route gating is enforced server-side (the Host registers live routes from its own launch-mode computation); the Host validates the session API key on settings and the live routes; a spoofed client flag yields 404s, not privilege. - [ ] Live enable/disable reconciles UI contributions without a restart; SDK plugin and context changes still apply only to conversations created after the change. +- [ ] Route-less contributions (visualizers, themes, settings/right panels) register during the module's `activate()` pass and work with no view open; views render via `mount()`. +- [ ] The install store survives concurrent CLI writes and Host reads without torn state (atomic write+rename, `installations/.lock`). - [ ] ACP runtimes do not receive incompatible extension plugin/MCP contributions. - [ ] `conversationInstructions` never receives extension system context. - [ ] Extension context composes with existing `` suffix. diff --git a/docs/ExtensionsSystemPoCPlan.md b/docs/ExtensionsSystemPoCPlan.md index a249ca0ec..fd78890ad 100644 --- a/docs/ExtensionsSystemPoCPlan.md +++ b/docs/ExtensionsSystemPoCPlan.md @@ -172,7 +172,7 @@ It should expose: - settings - static asset routes -The host never exposes HTTP routes for install, update, or remove; those are CLI-only in every mode. It registers `POST /:id/enable`, `POST /:id/disable`, and `POST /rescan` **only when the launcher reports `liveExtensionManagement`** (the Vite dev stack); in packaged/Docker/static modes those routes are absent and lifecycle stays CLI-only and restart-bounded. The gated routes mutate `config.json` / re-read the store through `extension-manager` and require the same session API key model as settings mutations. +The host never exposes HTTP routes for install, update, or remove; those are CLI-only in every mode. It registers `POST /:id/enable`, `POST /:id/disable`, and `POST /rescan` **only when the launcher reports `liveExtensionManagement`** (the Vite dev stack); in packaged/Docker/static modes those routes are absent and lifecycle stays CLI-only and restart-bounded. The gated routes mutate `config.json` / re-read the store through `extension-manager` and require the same session API key model as settings mutations. The launcher passes the session key to the Host at startup and the Host validates it on settings and the gated routes; route gating is enforced server-side (the Host registers the live routes from its own launch-mode computation), so the frontend `liveExtensionManagement` flag is only a UX mirror. ### 3.5 Launcher Integration @@ -228,7 +228,7 @@ Add `/canvas-extensions/:extensionId/:viewId/*` and a route component that: - Fetches registry data. - Resolves `assetBaseUrl + browser.module`. - Dynamically imports the module with a cache-busting version. -- Calls `mount({ root, context })`. +- Calls the module's `mount({ root, context })` for the view. Route-less contributions (visualizers, themes, settings/right panels) are registered separately during the startup `activate(context)` pass, not here (see RFC §13.1). - Calls `dispose()` on unmount/remount. - Provides minimal context: extension metadata/settings, `navigation.navigate`, `navigation.openExternal`, `ui.toast`, `settings.readExtensionSettings`, and `settings.patchExtensionSettings`. @@ -304,7 +304,7 @@ MVP scope: - Disabled/invalid extensions do not render settings panels. - Canvas owns the panel chrome, save/cancel affordances, loading states, and error boundaries. - Panel renderers receive extension settings helpers only; no raw settings store or React Query client access. -- Manifest entries declare metadata only. The browser module registers implementations by panel contribution ID at runtime; missing registrations show diagnostics. +- Manifest entries declare metadata only. The browser module registers implementations by panel contribution ID during `activate()` (see RFC §13.1); missing registrations show diagnostics. ### 4.8 Conversation Right Panels @@ -317,7 +317,7 @@ MVP scope: - Disabled/invalid extensions do not render panels. - Panels receive active conversation ID/status, workspace summary, backend summary, and host APIs for navigation, toasts, and extension settings. - Canvas owns panel placement, collapsed/expanded behavior, loading state, and error boundaries. -- Manifest entries declare metadata only. The browser module registers implementations by panel contribution ID at runtime; missing registrations show diagnostics. +- Manifest entries declare metadata only. The browser module registers implementations by panel contribution ID during `activate()` (see RFC §13.1); missing registrations show diagnostics. ## 5. Conversation Contributions @@ -372,6 +372,7 @@ Rules: - Permission, network, secret, or agent-affecting contribution expansion moves the dev extension to needs-review/disabled-for-next-run until CLI re-approval and restart. - Browser module changes bump a version token and remount only affected views. - Agent-side contribution changes require a new conversation. +- The install store is shared between the CLI and the running Host. Writes use temp-file-then-rename and multi-step transactions take `installations/.lock`, so a CLI install followed by a Host `rescan` (the agent-authoring loop) never reads torn state (see RFC §9). The example extension should include a minimal `npm run build -- --watch` authoring flow in docs, but Canvas should not run that command automatically. @@ -411,7 +412,7 @@ Release-path acceptance should also install the packed `@openhands/agent-canvas` **Unit:** manifest validation; reserved `browser.entry` diagnostics; path traversal rejection; artifact detection; unsupported standalone artifact diagnostics; install-store bootstrap; enable/disable/remove/update state transitions through the shared manager (driven by CLI, and by the host enable/disable/rescan routes when live management is enabled); capability gating of those routes by `liveExtensionManagement`; duplicate ID handling; color theme projection/validation/fallback; asset route path validation; CLI arg parsing before build checks; no-install-scripts default; visualizer contribution projection; visualizer ordering/fallback/error-boundary behavior aligned with #1277 or its successor; settings panel projection/registration/ordering; conversation right-panel projection/registration/ordering; launch contribution projection; context suffix rendering; plugin merge/dedupe; runtime compatibility classification; dev registration, manifest revalidation, and permission-drift re-approval. -**Node integration:** run the Extension Host against a temp install store; install the `hello.canvas` fixture from local path and npm-packed tarball through the CLI; fetch registry and asset URLs; verify route precedence before `/api/*` in Vite, ingress, static server, and packaged CLI paths; verify settings mutation routes require the session API key; verify no install/update/remove browser routes exist in any mode; verify `enable`/`disable`/`rescan` routes are absent when `liveExtensionManagement` is false and present (and API-key-guarded) when it is true; verify rescan picks up a newly installed extension and live enable/disable mutates `config.json`; verify `doctor` reports invalid manifests, unsupported standalone artifacts, and missing assets. +**Node integration:** run the Extension Host against a temp install store; install the `hello.canvas` fixture from local path and npm-packed tarball through the CLI; fetch registry and asset URLs; verify route precedence before `/api/*` in Vite, ingress, static server, and packaged CLI paths; verify settings mutation routes require the session API key; verify no install/update/remove browser routes exist in any mode; verify `enable`/`disable`/`rescan` routes are absent when `liveExtensionManagement` is false and present (and API-key-guarded) when it is true; verify rescan picks up a newly installed extension and live enable/disable mutates `config.json`; verify a concurrent CLI install while the Host is live (then rescan) never yields a torn/partial `config.json`/`artifacts.json` and that rescan waits or reports `install in progress` while the lock is held; verify `doctor` reports invalid manifests, unsupported standalone artifacts, and missing assets. **Release packaging:** `npm pack --dry-run`; packed-tarball install into a temp npm prefix; verify `agent-canvas list`, `agent-canvas doctor`, and an extension install work without a source checkout; verify `@openhands/agent-canvas/canvas-extensions` and the final visualizer export resolve for type consumers. diff --git a/docs/ExtensionsSystemRFC.md b/docs/ExtensionsSystemRFC.md index ee4865fe8..0bf48adc4 100644 --- a/docs/ExtensionsSystemRFC.md +++ b/docs/ExtensionsSystemRFC.md @@ -134,6 +134,7 @@ The Canvas frontend owns: - Read-only extension inventory/diagnostics UI (the Extensions page). - Navigation for extension views. +- Activation: importing each enabled extension's `browser.module` once at startup and calling `activate(context)` so route-less contributions register independent of view mounting (see §13.1). - The extension view host that mounts trusted extension browser modules into Canvas-owned route containers (with a reserved future iframe runtime; see §12.1). - Permission display, CLI management guidance, and secret setup prompts. - Settings forms generated from extension JSON Schemas. @@ -247,6 +248,8 @@ Canvas already stores client state at `~/.openhands/agent-canvas/` (session API Secrets are never stored in this file. They are saved through the existing Agent Server secrets service. +**Concurrency and atomic writes.** Canvas writes the store's JSON files (`config.json`, `artifacts.json`) with temp-file-then-rename and reads each in a single read, so a reader never observes a partial write. Multi-step transactions — install, update, remove — take an exclusive lock at `installations/.lock` so a CLI mutation and a running Host's `rescan` cannot interleave mid-transaction; npm's own `package-lock.json` covers the dependency tree. `rescan` reads a consistent snapshot and, if the lock is held, briefly waits and retries or reports `install in progress` rather than reading torn state. This path is exercised by design: the agent-authoring loop runs a CLI install while the Host is live, then rescans. + All installation is local to the Agent Canvas client. `agent-canvas install` never installs into a remote Agent Server, cloud sandbox, or team backend. ## 10. Npm Package Contract @@ -653,27 +656,31 @@ Manifest contribution shape: } ``` -Runtime registration shape: +Runtime registration shape (registered during `activate()`; see §13.1): ```ts -export default function register(api: AgentCanvasExtensionApi) { - api.registerToolVisualizer({ - id: "acme.kubernetes.apply", - actionKinds: ["MCPToolAction"], - observationKinds: ["MCPToolObservation"], - matches({ action }) { - return ( - action?.action.kind === "MCPToolAction" && - action.action.tool_name === "kubectl_apply" - ); - }, - Body({ action, observation }) { - return ; - }, - }); -} +export default { + activate(context) { + context.visualizers.registerToolVisualizer({ + id: "acme.kubernetes.apply", + actionKinds: ["MCPToolAction"], + observationKinds: ["MCPToolObservation"], + matches({ action }) { + return ( + action?.action.kind === "MCPToolAction" && + action.action.tool_name === "kubectl_apply" + ); + }, + Body({ action, observation }) { + return ; + }, + }); + }, +}; ``` +The visualizer body shape follows the agent-team-approved API (§28); the `activate()`-based registration is the Canvas-side contract regardless of what #1277 settles on. + Selection rules: - Extension visualizers are considered before built-in visualizers. @@ -694,7 +701,7 @@ Generic conversation slots remain deferred. The concrete MVP surfaces are `conve ## 13. Extension Runtime API -Extensions receive a typed context object exposing host-mediated operations. In MVP, the context is passed to the extension's `mount()` function along with a Canvas-owned DOM root. The API is defined async-only so that a future iframe runtime can satisfy the same interface via a postMessage RPC bridge without changing extension authoring. +Extensions receive a typed context object exposing host-mediated operations. A `browser.module` has one default export with two optional entry points: `activate(context)`, called once when the extension loads, and `mount(params)`, called each time one of its views opens. The API is defined async-only so a future iframe runtime can satisfy the same interface via a postMessage RPC bridge without changing extension authoring. MVP module shape: @@ -709,7 +716,20 @@ interface AgentCanvasExtensionDisposable { } interface AgentCanvasExtensionModule { - mount( + // Called once when the extension is activated. Register route-less + // contributions here (tool visualizers, color themes, settings panels, + // conversation right panels) via the context registries. The returned + // disposable, if any, is called on disable/remove/dev-remount. + activate?( + context: AgentCanvasExtensionContext, + ): + | void + | AgentCanvasExtensionDisposable + | Promise; + + // Called each time one of the extension's views is opened at its route. + // Renders into the Canvas-owned container. Omit if the extension has no views. + mount?( params: AgentCanvasExtensionMountParams, ): | void @@ -718,6 +738,8 @@ interface AgentCanvasExtensionModule { } ``` +A module must export at least one of `activate`/`mount`. A theme-only package needs **neither** — its themes come from manifest projection (§12.2) and it may ship no `browser.module` at all. + MVP API surface: ```ts @@ -767,6 +789,15 @@ interface AgentCanvasExtensionContext { For the first implementation slice, the route-less registration APIs are limited to the agent-team-approved visualizer registration API, `colorThemes.registerColorTheme()`, `settingsPanels.registerSettingsPanel()`, and `conversationPanels.registerRightPanel()`. Manifest-projected `contributes.colorThemes` should be enough for most theme-only packages; the runtime API exists for the same lifecycle/disposal model as other UI registries. Settings and conversation panel manifests declare metadata; runtime registration binds that metadata to implementations by contribution ID. There is **no** supported generic `agentServer.request(path)`. The repo already routes Agent Server traffic through typed service wrappers; the extension surface follows the same pattern with named, permission-declared capabilities. For MVP inline browser modules, this is an API support boundary rather than a browser security boundary (see §19). +### 13.1 Activation lifecycle + +Route-less contributions must register independent of whether any view is open, so activation is a global step, not part of the view host: + +- On app startup, Canvas reads the registry and, for each enabled extension whose manifest declares a `browser.module`, imports the module once and calls `activate(context)`. Registrations populate the visualizer, color-theme, settings-panel, and right-panel registries. +- `mount(params)` is called lazily by the view host when a view route is navigated to, and its disposable is called on unmount/remount. +- In live mode (§15.2), enabling an extension triggers an `activate()`; disabling calls the `activate()` disposable to unregister its route-less contributions, then unmounts any open view. +- Activation failures are caught per-extension: a throwing `activate()` records a diagnostic and disables that extension's contributions without affecting other extensions or the rest of Canvas. + ## 14. Shared Type Contract Initial home: `src/canvas-extensions/types.ts`, exported as `@openhands/agent-canvas/canvas-extensions` once stable. (PR 0 will collapse generic installable-artifact types and extension-specific types into a single directory; splitting them across `src/installations/` and `src/canvas-extensions/` is premature before any consumer exists.) @@ -799,7 +830,13 @@ export interface AgentCanvasExtensionDisposable { } export interface AgentCanvasExtensionModule { - mount( + activate?( + context: AgentCanvasExtensionContext, + ): + | void + | AgentCanvasExtensionDisposable + | Promise; + mount?( params: AgentCanvasExtensionMountParams, ): | void @@ -866,6 +903,8 @@ Every artifact in the registry has exactly one persisted state: **Conflict rule.** Disabled wins over enabled if a manual config edit creates both persisted entries for the same ID. The process-level `--disable-extensions` kill switch suppresses all extension loading for the current run but does not create a registry state. +**"Needs review" is not a separate state.** The persisted `state` enum is exactly `enabled | disabled | installed | invalid`. "Needs review" is a presentation grouping for a `disabled` (or `installed`) entry carrying a `needs-review` diagnostic — produced by a permission-expanding update (§20) or a permission-expanding dev manifest change (§21). The registry `state` gains no fifth value; UI filters group by the diagnostic, and re-approval clears it so the user re-enables normally. + ### 15.2 Lifecycle Capability And Live Management Extension lifecycle behavior is gated by a launcher-issued capability rather than by per-extension state. The launcher emits a `liveExtensionManagement` boolean to the frontend alongside the existing `localInstallStoreReadable` flag and runtime-services metadata. It is derived from the same launch-mode signal Canvas already computes (`services.frontend.kind` in `scripts/runtime-services-info.mjs`): `liveExtensionManagement` is true only when the frontend is served by the Vite dev server (`npm run dev`, `npm run dev:minimal`), and false for the static server used by the packaged global CLI, Docker images, and `dev:static`. @@ -882,7 +921,9 @@ Extension lifecycle behavior is gated by a launcher-issued capability rather tha - Newly installed extensions are discovered by re-reading the install store on each registry request and refetching/invalidating the registry query (on the Extensions page, on a manual rescan action, and on the dev-watch cache-bust signal). Filesystem watching of the store is an optional enhancement, not a requirement; the baseline is re-read-on-request plus query invalidation. - Reconcilable contributions (left navigation, color themes, settings panels, conversation right panels, tool visualizers, and views) re-project from the refreshed registry and mount/unmount accordingly, reusing the dev-watch remount path. - Non-reconcilable contributions are unchanged: SDK plugins and context suffix apply only at conversation creation, so enabling or disabling them mid-session affects future conversations only. -- Consent is preserved: live-discovered extensions arrive in `installed`/`disabled` state and never auto-enable; enabling from the Extensions page is the explicit consent action. Permission-expanding updates still drop to `disabled`/needs-review. +- Newly discovered extensions arrive `installed`/`disabled` and are never auto-enabled by discovery; enabling from the Extensions page is the explicit consent action. Permission-expanding updates still drop to `disabled` with a needs-review diagnostic. + +**Residual risk on the dev tier.** The live routes are same-origin, so an already-enabled extension on the dev stack can in principle call `/enable` for another *installed* extension — enabled browser code shares Canvas's origin and session (§19). This is bounded and accepted for a dev-only tier: the routes exist only under `liveExtensionManagement`; the caller is already enabled, hence already trusted; and install/update/remove stay CLI-only, so no new code can be introduced — only a package the user already placed on disk can be flipped. Packaged, Docker, and static runs do not expose the routes at all. We therefore do not claim the live tier is a hard consent boundary against already-trusted extension code; the boundary is the dev-only gate plus CLI-only install. **Restart-bounded semantics (capability false):** unchanged from the rest of this RFC. The Extensions page is read-only, the host exposes no enable/disable routes, and state changes apply on next process start. @@ -953,6 +994,8 @@ Note the consistent `/api/canvas/canvas-extensions/*` prefix for local Canvas ex The host never exposes browser routes for install, update, or remove; those stay CLI-only in every mode. The `enable`, `disable`, and `rescan` routes are registered **only when the launcher reports `liveExtensionManagement` is true** (the `npm run dev` source stack). In packaged, Docker, and static modes they are absent, so the read-only restart-bounded contract is unchanged and cannot be reached from the browser. When present, `enable`/`disable` mutate `config.json` through the shared `extension-manager` and `rescan` re-reads the install store; all three require the existing session API key, exactly like `PATCH /settings`. `PATCH /settings` is retained in both modes because settings panels need a narrow persistence surface. +**Auth and capability gating.** The launcher starts the Extension Host and hands it the session API key, so the Host validates the *same* key the frontend already holds (baked/injected via the existing session-key mechanism) on `PATCH /settings` and on the gated `enable`/`disable`/`rescan` routes; the `canvas-extensions-service` wrapper attaches it. Capability gating is enforced **server-side**: the Host decides whether to register the live routes from its own launch-mode computation, never from a client-supplied value. The `liveExtensionManagement` flag sent to the frontend is only a UX mirror controlling whether management controls render — a packaged/static build whose client flag is spoofed still reaches a Host that never registered the routes and gets 404s, not privilege. + Frontend code must call these routes only through a dedicated `src/api/canvas-extensions-service.ts` wrapper. Because the route prefix begins with `/api/` but targets the local Extension Host rather than the Agent Server, PR 2 must update `src/api/no-direct-agent-server-calls.test.ts` with a narrow allowlist entry for that wrapper, mirroring the existing automation-service exception. This keeps the `/api/canvas/canvas-extensions/*` ingress shape while preserving the repo rule that ordinary Agent Server traffic goes through `@openhands/typescript-client`. Implementation note: the current guard has an axios allowlist and a separate blanket `fetch('/api/...')` check. Adding `src/api/canvas-extensions-service.ts` to the axios allowlist is not enough if the wrapper uses `fetch`; the test must explicitly allow only `/api/canvas/canvas-extensions/*` calls from that wrapper, or the wrapper must use an approved local helper. Do not weaken the guard for arbitrary `/api/*` traffic. @@ -1131,7 +1174,7 @@ Current navigation state: `/customize` is the Customize entry in the primary sid Implementation note: some current files still use legacy "extensions" names, such as `src/routes/extensions-hub.tsx` and `src/components/features/skills/extensions-navigation.tsx`. Treat those as existing Customize implementation details. New Canvas Extension code should use `canvas-extensions` names, and user-facing copy should say Customize or Canvas Extensions as appropriate. -The Extensions page is a simple stacked view of installed Canvas Extensions and their status. MVP rows should be grouped or filtered by status only as needed for scanability: Enabled, Disabled, Invalid, Needs review, and Dev. Browser install, update, and remove controls are out of scope in every mode; the page points users to CLI management for those verbs. Marketplace-style browsing is out of scope. +The Extensions page is a simple stacked view of installed Canvas Extensions and their status. MVP rows should be grouped or filtered by status only as needed for scanability: Enabled, Disabled, Invalid, Needs review (a `disabled`/`installed` entry with a needs-review diagnostic — see §15.1), and Dev. Browser install, update, and remove controls are out of scope in every mode; the page points users to CLI management for those verbs. Marketplace-style browsing is out of scope. The page adapts to the `liveExtensionManagement` capability (§15.2): From fca9c31c7104af258302c2191aa3844eb3bb9d74 Mon Sep 17 00:00:00 2001 From: Devin Date: Thu, 11 Jun 2026 18:19:37 -0400 Subject: [PATCH 13/14] Add in the example extensions --- examples/extensions/README.md | 57 +++++++++++++++++++ examples/extensions/app-panel/README.md | 44 ++++++++++++++ .../app-panel/agent-canvas.extension.json | 30 ++++++++++ examples/extensions/app-panel/package.json | 9 +++ examples/extensions/hello-page/README.md | 40 +++++++++++++ .../hello-page/agent-canvas.extension.json | 35 ++++++++++++ examples/extensions/hello-page/package.json | 9 +++ examples/extensions/sunset-theme/README.md | 31 ++++++++++ .../sunset-theme/agent-canvas.extension.json | 51 +++++++++++++++++ examples/extensions/sunset-theme/package.json | 9 +++ 10 files changed, 315 insertions(+) create mode 100644 examples/extensions/README.md create mode 100644 examples/extensions/app-panel/README.md create mode 100644 examples/extensions/app-panel/agent-canvas.extension.json create mode 100644 examples/extensions/app-panel/package.json create mode 100644 examples/extensions/hello-page/README.md create mode 100644 examples/extensions/hello-page/agent-canvas.extension.json create mode 100644 examples/extensions/hello-page/package.json create mode 100644 examples/extensions/sunset-theme/README.md create mode 100644 examples/extensions/sunset-theme/agent-canvas.extension.json create mode 100644 examples/extensions/sunset-theme/package.json diff --git a/examples/extensions/README.md b/examples/extensions/README.md new file mode 100644 index 000000000..61023a8c6 --- /dev/null +++ b/examples/extensions/README.md @@ -0,0 +1,57 @@ +# 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 | 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 | + +Together they cover the three module shapes: **no module** (theme-only), **`mount()` +only** (a view), and **`activate()` only** (a route-less registration). + +## 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 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). + +## 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. diff --git a/examples/extensions/app-panel/README.md b/examples/extensions/app-panel/README.md new file mode 100644 index 000000000..f7e84aca7 --- /dev/null +++ b/examples/extensions/app-panel/README.md @@ -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. diff --git a/examples/extensions/app-panel/agent-canvas.extension.json b/examples/extensions/app-panel/agent-canvas.extension.json new file mode 100644 index 000000000..f1a18387c --- /dev/null +++ b/examples/extensions/app-panel/agent-canvas.extension.json @@ -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 + } + ] + } +} diff --git a/examples/extensions/app-panel/package.json b/examples/extensions/app-panel/package.json new file mode 100644 index 000000000..e0a94c50b --- /dev/null +++ b/examples/extensions/app-panel/package.json @@ -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"] +} diff --git a/examples/extensions/hello-page/README.md b/examples/extensions/hello-page/README.md new file mode 100644 index 000000000..85f19ac11 --- /dev/null +++ b/examples/extensions/hello-page/README.md @@ -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). diff --git a/examples/extensions/hello-page/agent-canvas.extension.json b/examples/extensions/hello-page/agent-canvas.extension.json new file mode 100644 index 000000000..03c46d3bd --- /dev/null +++ b/examples/extensions/hello-page/agent-canvas.extension.json @@ -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" + } + } + ] + } +} diff --git a/examples/extensions/hello-page/package.json b/examples/extensions/hello-page/package.json new file mode 100644 index 000000000..de91236ac --- /dev/null +++ b/examples/extensions/hello-page/package.json @@ -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"] +} diff --git a/examples/extensions/sunset-theme/README.md b/examples/extensions/sunset-theme/README.md new file mode 100644 index 000000000..2b034fda7 --- /dev/null +++ b/examples/extensions/sunset-theme/README.md @@ -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. diff --git a/examples/extensions/sunset-theme/agent-canvas.extension.json b/examples/extensions/sunset-theme/agent-canvas.extension.json new file mode 100644 index 000000000..81f0e2d7a --- /dev/null +++ b/examples/extensions/sunset-theme/agent-canvas.extension.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://schemas.openhands.dev/agent-canvas/extension.v1.json", + "schemaVersion": 1, + "id": "example.sunset-theme", + "displayName": "Sunset Theme", + "version": "0.1.0", + "description": "Adds a warm 'Sunset' color theme to Settings > Application > Color Theme.", + "publisher": { "name": "Agent Canvas Examples" }, + "license": "MIT", + "compatibility": { + "agentCanvas": ">=1.0.0-rc.6 <2.0.0", + "extensionApi": 1 + }, + "permissions": { + "ui": ["colorThemes"] + }, + "contributes": { + "colorThemes": [ + { + "id": "example.sunset-theme.sunset", + "label": "Sunset", + "scale": { + "--cool-grey-50": "#FBF6F2", + "--cool-grey-100": "#F4E9E1", + "--cool-grey-200": "#E7D3C4", + "--cool-grey-300": "#D4B49E", + "--cool-grey-400": "#B98C70", + "--cool-grey-500": "#9A6A4E", + "--cool-grey-600": "#7C5038", + "--cool-grey-700": "#5E3B29", + "--cool-grey-800": "#462C20", + "--cool-grey-900": "#34211A", + "--cool-grey-925": "#271913", + "--cool-grey-950": "#1A0F0B", + "--cool-grey-975": "#0F0805" + }, + "heroui": { + "--heroui-background": "18 30% 7%", + "--heroui-background-foreground": "28 40% 92%", + "--heroui-foreground": "28 30% 85%", + "--heroui-content1": "18 25% 12%", + "--heroui-default": "20 20% 30%" + }, + "tokens": { + "--oh-color-primary": "#FB923C", + "--oh-accent": "#F97316" + } + } + ] + } +} diff --git a/examples/extensions/sunset-theme/package.json b/examples/extensions/sunset-theme/package.json new file mode 100644 index 000000000..f4c5d69b1 --- /dev/null +++ b/examples/extensions/sunset-theme/package.json @@ -0,0 +1,9 @@ +{ + "name": "@example/agent-canvas-sunset-theme", + "version": "0.1.0", + "type": "module", + "private": true, + "description": "Theme-only Canvas Extension that adds a 'Sunset' color theme.", + "agentCanvas": { "manifest": "./agent-canvas.extension.json" }, + "files": ["agent-canvas.extension.json"] +} From 07e0b6f5891964ce12eef84151ce2ceeaee29df8 Mon Sep 17 00:00:00 2001 From: Devin Date: Thu, 11 Jun 2026 18:32:02 -0400 Subject: [PATCH 14/14] Cover multi surface example Still just some guessing on exact entry points --- examples/extensions/README.md | 18 ++++--- examples/extensions/shell-toolkit/README.md | 53 +++++++++++++++++++ .../shell-toolkit/agent-canvas.extension.json | 50 +++++++++++++++++ .../extensions/shell-toolkit/package.json | 9 ++++ 4 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 examples/extensions/shell-toolkit/README.md create mode 100644 examples/extensions/shell-toolkit/agent-canvas.extension.json create mode 100644 examples/extensions/shell-toolkit/package.json diff --git a/examples/extensions/README.md b/examples/extensions/README.md index 61023a8c6..558696d72 100644 --- a/examples/extensions/README.md +++ b/examples/extensions/README.md @@ -5,21 +5,25 @@ Three minimal Canvas Extensions that each exercise one contribution surface from (`private: true`), not published packages, and ship hand-written browser ESM so they install and run with **no build step**. -| Example | Surface | Browser module | RFC | +| 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 | -Together they cover the three module shapes: **no module** (theme-only), **`mount()` -only** (a view), and **`activate()` only** (a route-less registration). +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/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 ``` @@ -44,6 +48,8 @@ Lifecycle depends on launch mode (RFC §15.2): 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 diff --git a/examples/extensions/shell-toolkit/README.md b/examples/extensions/shell-toolkit/README.md new file mode 100644 index 000000000..11c27c595 --- /dev/null +++ b/examples/extensions/shell-toolkit/README.md @@ -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. diff --git a/examples/extensions/shell-toolkit/agent-canvas.extension.json b/examples/extensions/shell-toolkit/agent-canvas.extension.json new file mode 100644 index 000000000..ac7771c09 --- /dev/null +++ b/examples/extensions/shell-toolkit/agent-canvas.extension.json @@ -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" + } + } + ] + } +} diff --git a/examples/extensions/shell-toolkit/package.json b/examples/extensions/shell-toolkit/package.json new file mode 100644 index 000000000..d6e36a9e0 --- /dev/null +++ b/examples/extensions/shell-toolkit/package.json @@ -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"] +}