Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ The JS and Python versions are kept in lock-step by `release-please` and guarded
## Extensions Catalog

<!-- BEGIN AUTO-GENERATED CATALOG -->
This repository contains **2 marketplace(s)** with **71 extensions** (61 skills, 10 plugins).
This repository contains **2 marketplace(s)** with **72 extensions** (62 skills, 10 plugins).

### large-codebase

Expand All @@ -115,7 +115,7 @@ OpenHands skills for interacting, improving, and refactoring large codebases

Official skills and plugins for OpenHands — the open-source AI software engineer.

**67 extensions** (59 skills, 8 plugins)
**68 extensions** (60 skills, 8 plugins)

| Name | Type | Description | Commands |
|------|------|-------------|----------|
Expand All @@ -128,6 +128,7 @@ Official skills and plugins for OpenHands — the open-source AI software engine
| bitbucket | skill | Bitbucket integration hub. Detects whether the repository is on Bitbucket Cloud or Bitbucket Data Center and directs ... | — |
| bitbucket-cloud | skill | Bitbucket Cloud (bitbucket.org) specifics — authenticate with BITBUCKET_TOKEN, use the REST API v2, workspace/repo_sl... | — |
| bitbucket-data-center | skill | Bitbucket Data Center (self-hosted Bitbucket Server) specifics — authenticate with BITBUCKET_DATA_CENTER_TOKEN, use t... | — |
| canvas-extension-api | skill | Build and validate Apps for Agent Canvas using the supported Canvas Extensions API v1 routed-page contract. | — |
| city-weather | plugin | Get current weather, time, and precipitation forecast for any city using the free Open-Meteo API. Provides slash comm... | — |
| code-review | skill | Rigorous code review focusing on data structures, simplicity, security, pragmatism, and risk/safety evaluation. Provi... | `/codereview`, `/codereview-roasted` |
| code-simplifier | skill | Simplifies and refines code across three dimensions - code reuse, code quality, and efficiency - while preserving all... | `/simplify` |
Expand Down
14 changes: 14 additions & 0 deletions marketplaces/openhands-extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@
"delegation"
]
},
{
"name": "canvas-extension-api",
"source": "./skills/canvas-extension-api",
"description": "Build and validate Apps for Agent Canvas using the supported Canvas Extensions API v1 routed-page contract.",
"category": "agent-authoring",
"defaultEnabled": true,
"keywords": [
"agent-canvas",
"canvas-app",
"canvas-extension",
"register-page",
"app-development"
]
},
{
"name": "openhands-sdk",
"source": "./skills/openhands-sdk",
Expand Down
1 change: 1 addition & 0 deletions skills/canvas-extension-api/.claude-plugin
1 change: 1 addition & 0 deletions skills/canvas-extension-api/.codex-plugin
19 changes: 19 additions & 0 deletions skills/canvas-extension-api/.plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "canvas-extension-api",
"version": "1.0.0",
"description": "Build and validate Apps for Agent Canvas using the supported Canvas Extensions API v1 routed-page contract.",
"author": {
"name": "OpenHands",
"email": "contact@all-hands.dev"
},
"homepage": "https://github.com/OpenHands/extensions",
"repository": "https://github.com/OpenHands/extensions",
"license": "MIT",
"keywords": [
"agent-canvas",
"canvas-app",
"canvas-extension",
"register-page",
"app-development"
]
}
33 changes: 33 additions & 0 deletions skills/canvas-extension-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Canvas Extensions API

Build and validate Apps for Agent Canvas using manifest schema 1 and host API 1.

This skill covers the routed-page contract currently implemented by Agent Canvas, including:

- `canvas-extension.json` manifests;
- self-contained browser ESM entrypoints exporting `activate(host)`;
- `host.registerPage` lifecycle and routing;
- authenticated Agent Server requests;
- one-file packaging, Blob import testing, and installation;
- optional Sidecar architecture and backend safety;
- a dependency-free package validator.

## Validate an app package

```sh
node scripts/validate-extension.mjs /path/to/app-package
```

For a build output staged under `dist/`:

```sh
node scripts/validate-extension.mjs /path/to/app-package --dist
```

Run the validator tests with:

```sh
node --test scripts/validate-extension.test.mjs
```

See [SKILL.md](SKILL.md) for the complete agent workflow and the `references/` directory for focused implementation guidance.
253 changes: 253 additions & 0 deletions skills/canvas-extension-api/SKILL.md

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions skills/canvas-extension-api/references/acceptance-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Verification and local acceptance

Run type-checking, build, unit/integration tests, static artifact validation, and a real Chromium Blob-import smoke test. A Vite preview or Node-only module import is not an adequate production test.

Test manifest registrations, unsupported API rejection, root/nested/unknown routes, loading/empty/error/malformed-response states, exact Agent Server request shape, cancellation/stale response suppression, mount cleanup, activation cleanup, and remounting. Confirm one-file browser output even for a multi-process architecture and confirm no service secret or origin assumption is embedded. For Workers/WASM/native integrations, exercise the genuine core behavior in the browser or focused temporary integration fixture.

For a Sidecar-backed App, verify explicit consent and idempotent onboarding, restart and version-mismatch recovery, backend switching, and the distinction between App removal, service uninstall, and data deletion.

For local Agent Canvas acceptance:

1. Run `npm install` and `npm run check` from the App directory.
2. Obtain the absolute directory path.
3. Install that one App from the local path; it must remain disabled initially.
4. Enable it and exercise primary and nested routes.
5. Reload Canvas.
6. Disable it and confirm its page disappears.
7. Re-enable it and repeat the primary flow.
8. After changing a bundle, rebuild, uninstall, and reinstall before retesting. The current Apps screen has no refresh action.

Record unavailable manual checks as not run. Do not enable, uninstall, publish, push, or open a pull request without explicit authorization.
9 changes: 9 additions & 0 deletions skills/canvas-extension-api/references/backend-safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Backend safety and persistence

Use `host.agentServer.request({ method, path, body })` only with root-relative paths that begin with exactly one `/`. Do not derive the Agent Server URL from `window.location`, read Canvas credentials, put secrets in `VITE_*`, or open a direct Agent Server WebSocket under host API 1. Validate complete response shapes and suppress late updates after unmount.

For command-backed Apps, expose fixed command templates only. Validate paths, pass them as structured `cwd`, encode variable input structurally (for example JSON plus base64), validate it again in the helper, and emit structured responses. Never interpolate user text into shell source or expose a general terminal.

Keep browser-local data namespaced by App name and backend ID in IndexedDB/OPFS/localStorage. Put backend-shared mutable state below `<agent-server-home>/.openhands/apps/<app-name>/`, after discovering the home through `GET /api/file/home` and validating the absolute path. Never write mutable state beside the installed manifest or bundle.

For a CLI, binary, compiler, database, or Sidecar, read [sidecar-pattern.md](sidecar-pattern.md). Probe without mutation; disclose exact downloads, builds, data paths, checksums, and process behavior; and gate installation behind acknowledgement. Do not expose general command execution, shell interpolation, silent or unpinned downloads, unauthenticated non-loopback binding, mutable state beside the installed bundle, or automatic data deletion when an App is removed.
124 changes: 124 additions & 0 deletions skills/canvas-extension-api/references/connections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Connecting an OpenHands App

Use the narrowest supported connection surface. Treat backend URLs, session keys, and automation credentials as capabilities supplied by Agent Canvas or a backend, not values to discover from browser globals or guessed ports.

## Prefer the TypeScript client

Prefer [`@openhands/typescript-client`](https://github.com/OpenHands/software-agent-sdk/tree/main/clients/typescript) whenever a bundled TypeScript app has the connection inputs required by the client and the client covers the needed Agent Server API. It provides maintained request models, typed clients, event types, compatibility checks, and WebSocket lifecycle code that track the canonical Agent Server contract.

Use direct `fetch` or raw `WebSocket` only when:

- the client does not yet expose the required endpoint or transport option;
- host API 1 supplies only its narrower request adapter;
- a small dependency-free app does not justify bundling the client; or
- an integration targets the separate Automation service, which the Agent Server client does not model.

Pin a compatible client version, bundle it into the app's self-contained ESM entrypoint, and test the built bundle. Do not leave `@openhands/typescript-client` as a browser bare import or external chunk. Check the current package exports before selecting a client class; the repository evolves with the Agent Server API.

## Agent Server HTTP

For a host API 1 app, use the host-provided authenticated adapter:

```js
const conversations = await host.agentServer.request({
method: "POST",
path: "/api/conversations/search",
body: { limit: 20 },
});
```

Pass exactly one root-relative path. Do not pass an origin, protocol-relative URL, or path without a leading slash. Let Canvas select the active backend and attach its authentication. This preserves backend switching and avoids placing a session key in app code.

Do not construct an `HttpClient` from `host.backend`: host API 1 exposes backend identity (`id`, `kind`, and `orgId`), not an Agent Server origin or session API key. Do not inspect Canvas internals, Redux stores, local storage, DOM attributes, or undocumented globals to recover those values.

When developing a trusted standalone browser client outside Agent Canvas, prefer the TypeScript client:

```ts
import { HttpClient } from "@openhands/typescript-client";

const client = new HttpClient({
baseUrl: agentServerOrigin,
apiKey: sessionApiKey,
});

const response = await client.get("/server_info");
```

Supply the actual Agent Server origin, including any reverse-proxy path prefix. The client attaches `X-Session-API-Key` when `apiKey` is set. Consult the live `/openapi.json` and `/server_info` before relying on endpoints that may differ across server versions.

If typed high-level clients cover the operation, prefer those over the generic `HttpClient`. Use the generic client for uncovered Agent Server endpoints, not as a reason to duplicate authentication and error handling with `fetch`.

## Automation backend

Treat the Automation service as a separate backend. Its API is normally mounted under `/api/automation/v1`, but the origin, path prefix, and authentication mode are deployment-provided values rather than Agent Server defaults.

Host API 1 does not expose an automation request helper, automation base URL, or automation credential. Therefore, an app must not:

- route `/api/automation/...` through `host.agentServer.request` unless the owning Agent Server explicitly documents a proxy at that path;
- assume the Automation service shares the Agent Server origin;
- hardcode `localhost`, cloud hosts, or a default API key;
- read ambient Agent Canvas implementation state to obtain private configuration.

Choose one explicit architecture:

1. Add a backend-owned Agent Server endpoint that performs the required automation operation, then call that endpoint through `host.agentServer.request`. Keep automation credentials server-side and scope the proxy to the minimum required operations.
2. Target a future or deployment-specific Canvas host capability that deliberately supplies an authenticated automation request adapter. Feature-detect it and document the required host version.
3. For a separately deployed trusted application, receive the Automation base URL and credential from deployment configuration and call `${automationBaseUrl}/api/automation/v1/...` using that deployment's documented authentication.

Do not use `@openhands/typescript-client` for Automation service routes unless that repository explicitly adds Automation support. The client currently targets Agent Server APIs.

## Agent Server WebSocket

Use WebSocket for live Agent Server events and REST for initial state and writes. Preserve the Agent Server origin's reverse-proxy path prefix when changing `http` to `ws` or `https` to `wss`.

For trusted standalone TypeScript clients, prefer the maintained client:

```ts
import { WebSocketCallbackClient } from "@openhands/typescript-client";

const events = new WebSocketCallbackClient({
host: agentServerOrigin,
conversationId,
apiKey: sessionApiKey,
callback: (event) => handleEvent(event),
onError: (error) => showConnectionError(error),
});

events.start();
// Call events.stop() during disposal.
```

Verify the installed client version's browser authentication behavior before shipping. When implementing a raw browser socket against current Agent Server behavior, prefer first-message authentication after opening `/sockets/events/{conversationId}`:

```js
const socket = new WebSocket(eventsUrl);
socket.addEventListener("open", () => {
socket.send(JSON.stringify({
type: "auth",
session_api_key: sessionApiKey,
}));
});
```

Browsers cannot set arbitrary WebSocket headers. Query-string authentication is a legacy fallback and may leak through URLs or logs; use it only when required by the selected client/server compatibility window.

Host API 1 does not expose an Agent Server origin, session API key, or WebSocket factory. A portable v1 app therefore cannot open an authenticated Agent Server WebSocket directly. Do not derive a socket from `window.location`: Canvas and the active Agent Server may use different origins or proxy prefixes, and backend switching would leave the socket attached to the wrong server.

For live app data under host API 1, choose one of these approaches:

- poll through `host.agentServer.request` with bounded intervals, visibility awareness, abort/disposal guards, and a stale state;
- add a backend endpoint that aggregates the required state and expose it through the authenticated host request adapter; or
- require and feature-detect a future host WebSocket/subscription capability.

Always close sockets, stop TypeScript client instances, clear reconnect timers, and suppress late callbacks when the page unmounts or the app deactivates.

## Testing connection behavior

Test the supported boundary rather than hidden Canvas implementation details:

- assert Agent Server HTTP uses `host.agentServer.request` with a root-relative path;
- assert no session key or backend origin is embedded in the built app;
- assert an unavailable Automation capability produces a clear unsupported state rather than a guessed request;
- assert polling, sockets, and TypeScript client instances stop during disposal;
- assert backend changes cause old connection state to be discarded;
- inspect the final bundle for unresolved client imports and external chunks.
35 changes: 35 additions & 0 deletions skills/canvas-extension-api/references/packaging-recipes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Packaging recipes

The observable requirement is exactly one Blob-importable `extension.js`, not a particular framework or starter. When using Vite, configure library mode with one ES input, `assetsInlineLimit: Number.MAX_SAFE_INTEGER`, `cssCodeSplit: false`, code splitting disabled, and source maps disabled. Check the installed Vite major before changing compatibility option names.

```ts
import { resolve } from "node:path";
import { defineConfig } from "vite";

export default defineConfig({
build: {
assetsInlineLimit: Number.MAX_SAFE_INTEGER,
cssCodeSplit: false,
emptyOutDir: true,
lib: {
entry: resolve(import.meta.dirname, "src/extension.ts"),
formats: ["es"],
fileName: () => "extension.js",
},
outDir: "dist",
rollupOptions: { output: { codeSplitting: false } },
sourcemap: false,
},
});
```

Add the framework's Vite plugin when applicable. Vite 8 also exposes `build.rolldownOptions`; retain or choose the option name supported by the installed version. The required outcome remains one output file.

- CSS: import `./styles.css?inline`, inject one App-marked `<style>` node per mount, and remove it during cleanup. `cssCodeSplit: false` alone can emit a sibling stylesheet.
- Text fixtures, SQL, templates, helper source: import with `?raw`.
- JSON, SVG, small assets: use ordinary imports and audit the output.
- Dynamic source modules: allow `import()` only when output remains one file; validator and Blob smoke must prove it.
- Workers: import with `?worker&inline`; terminate, remove listeners, and reject pending RPC calls in cleanup.
- WASM: import inside an inline Worker and confirm the built file has no unresolved `.wasm` URL. Build tools are contributor prerequisites; checked-in bundled artifacts are runtime requirements.

Never use `public/` for required runtime files, externalize React, use CDNs, leave a sibling CSS/Worker/WASM asset, use CommonJS, or rely on normal `import.meta.url` deployment semantics.
Loading
Loading