From 0bb4dcad1c087ea482bccfe72a9b9954500e3b40 Mon Sep 17 00:00:00 2001 From: Devin Date: Wed, 16 Sep 2026 09:38:57 -0400 Subject: [PATCH 1/2] Add Canvas Extensions API skill Co-authored-by: openhands --- README.md | 5 +- marketplaces/openhands-extensions.json | 13 + skills/canvas-extension-api/.claude-plugin | 1 + skills/canvas-extension-api/.codex-plugin | 1 + .../canvas-extension-api/.plugin/plugin.json | 19 ++ skills/canvas-extension-api/README.md | 33 ++ skills/canvas-extension-api/SKILL.md | 253 ++++++++++++++++ .../references/acceptance-checklist.md | 20 ++ .../references/backend-safety.md | 9 + .../references/connections.md | 124 ++++++++ .../references/packaging-recipes.md | 35 +++ .../references/sidecar-pattern.md | 49 +++ .../references/testing-and-installation.md | 286 ++++++++++++++++++ .../references/v1-contract.md | 253 ++++++++++++++++ .../scripts/validate-extension.mjs | 92 ++++++ .../scripts/validate-extension.test.mjs | 44 +++ skills/index.js | 7 + 17 files changed, 1242 insertions(+), 2 deletions(-) create mode 120000 skills/canvas-extension-api/.claude-plugin create mode 120000 skills/canvas-extension-api/.codex-plugin create mode 100644 skills/canvas-extension-api/.plugin/plugin.json create mode 100644 skills/canvas-extension-api/README.md create mode 100644 skills/canvas-extension-api/SKILL.md create mode 100644 skills/canvas-extension-api/references/acceptance-checklist.md create mode 100644 skills/canvas-extension-api/references/backend-safety.md create mode 100644 skills/canvas-extension-api/references/connections.md create mode 100644 skills/canvas-extension-api/references/packaging-recipes.md create mode 100644 skills/canvas-extension-api/references/sidecar-pattern.md create mode 100644 skills/canvas-extension-api/references/testing-and-installation.md create mode 100644 skills/canvas-extension-api/references/v1-contract.md create mode 100755 skills/canvas-extension-api/scripts/validate-extension.mjs create mode 100644 skills/canvas-extension-api/scripts/validate-extension.test.mjs diff --git a/README.md b/README.md index 743421f4..6dd0741f 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ The JS and Python versions are kept in lock-step by `release-please` and guarded ## Extensions 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 @@ -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 | |------|------|-------------|----------| @@ -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` | diff --git a/marketplaces/openhands-extensions.json b/marketplaces/openhands-extensions.json index 0cfb9e9a..d6c32b31 100644 --- a/marketplaces/openhands-extensions.json +++ b/marketplaces/openhands-extensions.json @@ -106,6 +106,19 @@ "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", + "keywords": [ + "agent-canvas", + "canvas-app", + "canvas-extension", + "register-page", + "app-development" + ] + }, { "name": "openhands-sdk", "source": "./skills/openhands-sdk", diff --git a/skills/canvas-extension-api/.claude-plugin b/skills/canvas-extension-api/.claude-plugin new file mode 120000 index 00000000..665797f0 --- /dev/null +++ b/skills/canvas-extension-api/.claude-plugin @@ -0,0 +1 @@ +.plugin \ No newline at end of file diff --git a/skills/canvas-extension-api/.codex-plugin b/skills/canvas-extension-api/.codex-plugin new file mode 120000 index 00000000..665797f0 --- /dev/null +++ b/skills/canvas-extension-api/.codex-plugin @@ -0,0 +1 @@ +.plugin \ No newline at end of file diff --git a/skills/canvas-extension-api/.plugin/plugin.json b/skills/canvas-extension-api/.plugin/plugin.json new file mode 100644 index 00000000..e45dc8f7 --- /dev/null +++ b/skills/canvas-extension-api/.plugin/plugin.json @@ -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" + ] +} diff --git a/skills/canvas-extension-api/README.md b/skills/canvas-extension-api/README.md new file mode 100644 index 00000000..80826021 --- /dev/null +++ b/skills/canvas-extension-api/README.md @@ -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. diff --git a/skills/canvas-extension-api/SKILL.md b/skills/canvas-extension-api/SKILL.md new file mode 100644 index 00000000..b8fcfdd7 --- /dev/null +++ b/skills/canvas-extension-api/SKILL.md @@ -0,0 +1,253 @@ +--- +name: canvas-extension-api +description: This skill should be used when the user asks to "create an OpenHands App", "scaffold a Canvas App", "build an app with the Canvas Extensions API", "build an Agent Canvas App", "bundle a single-file Canvas App", "build a Sidecar-backed Canvas App", "add onboarding for an App service", "add a custom interface to Agent Canvas", "validate an OpenHands App", or mentions Canvas Apps, Agent Canvas extensions, Blob-importable app bundles, registerPage, app pages, Sidecars, or canvas extension packages. +--- + +# Canvas Extensions API + +Build and validate OpenHands Apps with the Canvas Extensions API, targeting the currently implemented manifest schema 1 and host API 1 routed-page ABI. + +Use the naming layers consistently: + +- **OpenHands Apps** - customer-facing category for interfaces people discover, install, enable, and open. +- **Apps for Agent Canvas** - product phrasing that clarifies where apps run. +- **Canvas Extensions API** - developer platform used to build apps. +- **Canvas extension package** - technical artifact containing `canvas-extension.json` and its entrypoint. +- **OpenHands Extensions** - ecosystem and repository umbrella for apps, skills, plugins, automations, and integrations. + +Use current product labels in user-facing instructions: **Apps for Agent Canvas**, **Add app**, **Installed apps**, **App source**, **Enable trusted app**, and **Build an app**. Keep technical identifiers such as `canvas-extension.json`, `/api/canvas-extensions/*`, `host.extension`, and `/extensions/*` unchanged unless the target implementation changes them. + +Treat apps as trusted, same-realm browser code owned by the active Agent Server. Keep the categories distinct: apps extend what people can do in Agent Canvas, plugins extend agent runtime capabilities, and skills provide agent instructions and knowledge. + + +## Prefer the proven App authoring loop + +Treat an App as one authenticated browser dependency graph that Canvas imports from a Blob URL. Do not build a hosted SPA: the production result must be exactly one self-contained browser ESM entrypoint that exports `activate(host)`. Always ship this UI package even when it uses a separately installed Sidecar; make the App page the guided UI for Sidecar setup after the App is opened. Never treat a Sidecar as another browser chunk or an automatic extension install hook. + +For a new App, create an independent package in the target repository and implement its own UI, tests, and build tooling. Follow the Vite library-build reference in `references/packaging-recipes.md`; do not copy a shared starter or introduce a repository-wide runtime/workspace unless the target repository explicitly requires it. + +Keep all source, scripts, dependencies, tests, and checked-in `extension.js` inside the App package. Implement one declared page with explicit root, nested, and unknown-route behavior. Inject scoped CSS per mount and clean it up with framework roots, requests, listeners, timers, Workers, and object URLs. Build validation must verify `dist/extension.js` before synchronizing it to the App root. Do not synchronize an unverified artifact. Use `CHROME_PATH` when the Chrome executable is elsewhere. + +For an existing App, run the reusable static gate before and after its own checks: + +```sh +node /path/to/canvas-extension-api/scripts/validate-extension.mjs /path/to/app +node /path/to/canvas-extension-api/scripts/validate-extension.mjs /path/to/app --dist --marker +``` + +Treat the static validator as a gate, not a replacement for the browser Blob smoke test. Read `references/packaging-recipes.md` before adding CSS, raw assets, dynamic modules, Workers, or WASM. Read `references/backend-safety.md` before any Agent Server integration or persistence. Read `references/sidecar-pattern.md` before designing a separately installed service or its onboarding. Read `references/acceptance-checklist.md` before reporting local Canvas compatibility. + +## Establish the target + +Start by locating the target directory instead of assuming the current workspace. Inspect repository instructions, existing package management, build tooling, tests, and git status before editing. + +Make an early architecture decision: browser-only App, Agent Server-integrated App, Sidecar-backed App, or explicitly deployment-specific App. Keep the portable host API 1 surface separate from deployment-owned capabilities. + +Clarify only choices that materially affect implementation: + +- app purpose and page behavior; +- target repository and subdirectory; +- dependency-free JavaScript versus a bundled TypeScript/framework project; +- Agent Server endpoints or host metadata required; +- whether to build only, install locally, or prepare publication instructions; +- for a Sidecar: execution location, owner, connection route, secrets and data, lifecycle actions, and portability across Agent Canvas deployments. + +Default to one app with one routed page when requirements are otherwise clear. Keep the first implementation small and dependency-free unless the requested UI clearly benefits from a framework or the repository already has a bundler. + +When creating multiple Apps in one repository, give every App an independent package root containing its own `canvas-extension.json`, entrypoint, version, tests, README, and build tooling. Do not introduce a monorepo, shared runtime, or common build foundation unless the target repository explicitly requires one. Keep app manifest names globally distinct within the Agent Server installation. + +Treat installation as one app per request. The current Customize -> Apps flow accepts one `source`, optional `ref`, and optional `repo_path`; it does not recursively discover or bulk-install every manifest in a repository. Add each app separately using the same source/ref and its own `repo_path`. + +Read `references/v1-contract.md` before implementing unfamiliar Canvas Extensions API behavior. Read `references/connections.md` before connecting to Agent Server, the Automation service, or WebSocket endpoints. Read `references/sidecar-pattern.md` before proposing a Sidecar bridge, onboarding, or operator action. Read `references/testing-and-installation.md` before installing or testing inside Agent Canvas, especially for a multi-app repository. + +## Inspect current upstream behavior + +Treat the v1 contract as young and subject to change. Before substantial work, compare the installed or target OpenHands version with the current upstream sources when access is available: + +- `specs/canvas-extensions.md` +- `src/types/canvas-extension.ts` +- `src/components/features/canvas-extensions/canvas-extensions-runtime.tsx` +- `src/routes/canvas-extension-page.tsx` +- `docs/CANVAS_EXTENSIONS_TESTING.md` + +Do not invent planned surfaces such as conversation tabs, slots, themes, or visualizer replacement. Implement only contributions supported by the target version. Routed pages are the only implemented contribution in host API 1. + +## Create the app package + +Place `canvas-extension.json` at the app package root. Point `entrypoint` to one self-contained browser ESM file inside that root. + +Use this minimal manifest shape: + +```json +{ + "schema_version": 1, + "name": "example-dashboard", + "display_name": "Example dashboard", + "version": "0.1.0", + "description": "A backend-specific project dashboard.", + "entrypoint": "extension.js", + "contributes": { + "pages": [ + { + "id": "dashboard", + "title": "Dashboard", + "path": "/dashboard", + "nav_label": "Dashboard" + } + ] + } +} +``` + +Follow these invariants: + +- Use lowercase letters, digits, and hyphens for app names and page IDs. +- Use absolute kebab-case page paths with a leading slash. +- Keep every manifest path within the app package root. +- Give each page ID and path a unique value. +- Register only page IDs declared in the manifest. +- Keep the entrypoint free of unresolved bare imports and external chunks. +- Bundle dependencies, CSS, and small assets into the entrypoint when using build tooling. + +Add a concise `README.md` when the app is intended for reuse or publication. Document purpose, build command if any, output entrypoint, installation coordinate, and verification steps. Avoid adding explanatory change-log documents. + +## Implement activation and pages + +Export an `activate` function from the ESM entrypoint: + +```js +export function activate(host) { + if (host.apiVersion !== "1") { + throw new Error(`This app requires host API 1.`); + } + + return host.registerPage("dashboard", ({ container, path, navigate }) => { + const root = document.createElement("section"); + root.textContent = path ? `Nested route: ${path}` : "Dashboard"; + container.append(root); + + return () => root.remove(); + }); +} +``` + +Use the host contract deliberately: + +- Read immutable metadata from `host.extension` and `host.backend`. +- Call `host.registerPage(id, mount)` during activation. +- Use the mount callback's `path` as the route remainder below the declared page path. +- Use `navigate(absoluteCanvasPath)` for Canvas-aware routing. +- Use `host.agentServer.request({ path, method, body, headers })` for authenticated calls to the owning Agent Server. +- Pass only root-relative request paths beginning with one `/`; never pass absolute URLs or `//` paths. + +Prefer [`@openhands/typescript-client`](https://github.com/OpenHands/software-agent-sdk/tree/main/clients/typescript) for bundled TypeScript integrations whenever it covers the required Agent Server API and the necessary connection inputs are available. Prefer its typed clients, models, compatibility checks, and WebSocket lifecycle over hand-written transport code. Bundle the client into the self-contained app entrypoint; never leave a bare package import or external runtime chunk. + +Respect the host API 1 connection boundary: + +- Treat `host.agentServer.request` as the portable Agent Server HTTP path; Canvas selects the active backend and supplies authentication. +- Do not derive an Agent Server URL from `window.location` or extract session keys from Canvas internals. +- Do not send Automation service requests through `host.agentServer.request` unless the backend explicitly documents an Agent Server proxy for them. Automation is a separate service with deployment-provided base URL and authentication. +- Do not open a direct Agent Server WebSocket from a portable v1 app. The host currently exposes neither the owning server origin nor a WebSocket/auth capability. Poll through `host.agentServer.request`, add a backend-owned bridge, or feature-detect a future host subscription API. +- Use the TypeScript client's Agent Server WebSocket support in trusted standalone or future host-enabled contexts, and stop it during disposal. Do not treat the Agent Server client as an Automation service client. + +Return cleanup from every layer that creates effects: + +- Return the unregister function or an activation disposer from `activate`. +- Return a mount disposer for DOM nodes, timers, listeners, observers, subscriptions, and outstanding state. +- Make async work disposal-aware so late responses cannot mutate an unmounted page. +- Remove injected styles when the page unmounts. + +Assume activation, mounting, and disposal may happen repeatedly during hot enable/disable, updates, backend switches, reconnects, and route changes. + +## Build a production-quality page + +Render within the supplied `container`; do not replace unrelated Canvas DOM. Scope CSS under an app-specific root class. Prefer Canvas CSS variables with sensible fallbacks rather than copying host implementation classes. + +Provide: + +- semantic structure and an accessible page label; +- keyboard-operable controls and visible focus; +- responsive behavior for narrow layouts; +- loading, empty, stale, and error states; +- reduced-motion handling for animation; +- text-safe rendering through DOM APIs or escaping rather than untrusted `innerHTML`; +- clear handling for malformed or partial Agent Server responses. + +Avoid global event handlers, prototype changes, global CSS selectors, and ambient state unless unavoidable. Same-realm execution means these effects have full Canvas authority and cleanup is only best-effort. + +## Add tests + +Test real app code with a DOM environment and a small Canvas Extensions API host test double. Test at minimum: + +1. `activate` registers every declared page exactly once. +2. The mount renders its initial state. +3. Agent Server requests use the expected root-relative path and method. +4. Nested route remainders render correctly or navigate correctly. +5. The mount disposer removes DOM and stops effects. +6. The activation disposer unregisters contributions. +7. Unsupported host API versions fail clearly when compatibility is checked. +8. Network and malformed-response errors render safely. + +Use the repository's existing test infrastructure. Do not introduce a new framework when adequate tests already exist. For dependency-free fixtures, Vitest with a DOM environment matches the upstream example, but it is not part of the Canvas Extensions API runtime contract. + +## Validate the package + +Run the bundled validator before reporting completion: + +```sh +node /path/to/canvas-extension-api/scripts/validate-extension.mjs /path/to/app-package +``` + +Then run the target repository's formatter, linter, tests, and build. Inspect the final entrypoint rather than assuming the bundler configuration worked: + +- confirm the file exists at the manifest path; +- confirm it exports `activate`; +- reject bare imports and dynamic external chunks; +- confirm CSS and required small assets are bundled or embedded; +- confirm the output runs as browser ESM without Node globals. + +Treat validator warnings as prompts for inspection, not proof of invalidity. The helper uses conservative static checks and cannot replace loading the app bundle in Canvas. + +## Install and verify safely + +Install only when requested. Installation and enablement are separate product actions: installation must leave the app disabled, and enabling executes trusted same-realm code. + +For a backend-local app, install the path as interpreted on the Agent Server machine. For a Git-hosted app, provide `source`, optional `ref`, and optional `repo_path`. Never assume a frontend-local path exists inside a remote or containerized backend. + +For multiple apps in one repository, produce an install matrix listing app name, manifest directory, source, ref, and `repo_path`. Submit one Add app operation per row. Omit `repo_path` only when the selected app lives at the repository root. Do not claim that selecting the repository root installs nested apps. + +Validate and test every app package independently, then run shared repository checks once. Report partial failures by app name rather than treating one passing app package as validation of the entire repository. + +Verify the lifecycle in Canvas: + +1. Install and confirm the inventory entry is disabled. +2. Review source, revision, manifest metadata, and contributions. +3. Enable and accept the trusted-code disclosure: “This app runs trusted JavaScript inside Agent Canvas and can make authenticated requests to the active Agent Server. Review its source and revision before enabling it.” +4. Open the navigation item and exercise root and nested routes. +5. Disable and confirm navigation and mounted UI disappear. +6. Re-enable without restarting Canvas. +7. Switch backend or reconnect when relevant and confirm isolation. +8. Uninstall only when requested. + +Do not enable, uninstall, publish, push, or open a pull request without the user's authorization. + +## Report completion + +Summarize: + +- app location and contributed pages; +- manifest schema and host API targeted; +- build output and validation results; +- tests run and their results; +- installation status, explicitly stating whether it remains disabled; +- remaining limitations tied to the current v1 ABI. + +## Resources + +- `references/v1-contract.md` - exact manifest, host API, lifecycle, routing, trust, and current limitations. +- `references/connections.md` - Agent Server HTTP, Automation service, WebSocket, and TypeScript client connection guidance. +- `references/testing-and-installation.md` - test strategy, manual Canvas workflow, and installation coordinates. +- `references/packaging-recipes.md` - one-file Vite, CSS, assets, Workers, and WASM rules. +- `references/backend-safety.md` - authenticated requests, command safety, persistence, and prerequisite onboarding. +- `references/acceptance-checklist.md` - automated checks and the local install/enable/reload lifecycle. +- `scripts/validate-extension.mjs` - dependency-free static artifact validator for an App package directory. diff --git a/skills/canvas-extension-api/references/acceptance-checklist.md b/skills/canvas-extension-api/references/acceptance-checklist.md new file mode 100644 index 00000000..8270acae --- /dev/null +++ b/skills/canvas-extension-api/references/acceptance-checklist.md @@ -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. diff --git a/skills/canvas-extension-api/references/backend-safety.md b/skills/canvas-extension-api/references/backend-safety.md new file mode 100644 index 00000000..a9cb4737 --- /dev/null +++ b/skills/canvas-extension-api/references/backend-safety.md @@ -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 `/.openhands/apps//`, 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. diff --git a/skills/canvas-extension-api/references/connections.md b/skills/canvas-extension-api/references/connections.md new file mode 100644 index 00000000..ff5cccba --- /dev/null +++ b/skills/canvas-extension-api/references/connections.md @@ -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. diff --git a/skills/canvas-extension-api/references/packaging-recipes.md b/skills/canvas-extension-api/references/packaging-recipes.md new file mode 100644 index 00000000..8f214fc7 --- /dev/null +++ b/skills/canvas-extension-api/references/packaging-recipes.md @@ -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 `