e9b5e07293 ci: store snapshot baselines as GitHub Actions artifacts instead of git (#482)
* ci: store snapshot baselines as GitHub Actions artifacts, not in git

Move baseline PNG storage from git to a 90-day GitHub Actions artifact
named 'snapshot-baselines', uploaded on every push to main.

- PRs download the latest main-branch artifact and run Playwright
  comparison against it; no more checked-in PNGs causing merge conflicts.
- New 'post-snapshot-comment.mjs' script classifies each snapshot as
  Changed/New/Unchanged, commits images to .pr/snapshots/<run_id>/ and
  posts a PR comment with collapsed <details> sections showing
  side-by-side expected/actual/diff images via raw.githubusercontent.com.
- For fork PRs or if push fails, falls back to a workflow run link for
  downloading the 'snapshot-test-results' artifact.
- Force-refresh baselines any time via workflow_dispatch force_update=true
  (replaces the old update_snapshots=true flow that committed PNGs to git).
- Remove 44 baseline PNGs from git; gitignore tests/e2e/__snapshots__/.
- Update AGENTS.md with the new workflow model.

Co-authored-by: openhands <openhands@all-hands.dev>

* ci: fix bootstrap case — pass CI when no baseline artifact exists yet

When no main-branch 'snapshot-baselines' artifact has been uploaded yet
(e.g. this very first run after merging from an old baseline-in-git flow),
the comparison step fails because Playwright has nothing to compare against.
Gate the 'Fail if differences' step on has_baselines==true so the bootstrap
PR passes with all snapshots shown as new. Once it merges to main the
artifact is created and subsequent PRs compare normally.

Also derive the PR comment status from the classification (changed.length > 0)
rather than from TEST_OUTCOME, which is 'failure' in the bootstrap case
despite zero actual regressions.

Co-authored-by: openhands <openhands@all-hands.dev>

* ci: delete stale comment and re-post on each push; always embed new snapshot images

- Replace PATCH-in-place with DELETE + POST so every push posts a fresh
  comment whose image URLs reference the current run's .pr/snapshots/<run_id>/.
  Editing in-place would leave raw.githubusercontent.com URLs pointing at
  the previous run's images once new images are committed under a new run_id.
- Embed new snapshot images inside the collapsed <details> section when
  commitSha is available; add a fallback artifact-download link when the
  push fails (e.g. fork PRs).
- Handle 204 No Content returned by DELETE in githubFetch.

Co-authored-by: openhands <openhands@all-hands.dev>

* ci: fix find-run to query artifacts API by name, not workflow runs by status

The previous approach (find latest successful run of snapshot-tests.yml on
main) would match old runs that predate the artifact upload step, causing
actions/download-artifact to hard-fail with 'Artifact not found' before the
comparison or comment steps could run.

Fix: query the artifacts REST API directly for name=snapshot-baselines,
filtering to non-expired artifacts from the main branch. This guarantees
we only match runs that actually uploaded the baseline artifact.

Also add continue-on-error: true to the download step as a safety net
against the artifact expiring between the API lookup and the download.

Co-authored-by: openhands <openhands@all-hands.dev>

* chore: snapshot images for run 25929925639 [skip ci]

* ci: replace .pr/snapshots on each run instead of accumulating per-run dirs

Previously each CI run committed images under .pr/snapshots/<run_id>/, so
reruns would accumulate multiple directories on the PR branch. The PR comment
always pointed to the current run's images (via SHA in the raw.githubusercontent
URL), but old directories silently piled up.

Fix: use a fixed .pr/snapshots/ path and git rm -rf --ignore-unmatch it before
staging new images. Each run completely replaces the previous images rather than
appending alongside them. Raw URLs still use the commit SHA so they remain stable
per push.

Co-authored-by: openhands <openhands@all-hands.dev>

* chore: snapshot images for run 25930435218 [skip ci]

* ci: allow review_requested on draft same-repo PRs to trigger pr-review

GitHub does not fire pull_request events for review_requested on draft PRs —
only pull_request_target fires. The existing if condition rejected
pull_request_target for same-repo PRs via the fork check, so requesting
all-hands-bot or openhands-agent on a draft PR was always silently skipped.

Add a carve-out: pull_request_target is also accepted for same-repo PRs
when draft==true AND action==review_requested. Non-draft same-repo PRs are
unaffected — they continue to be handled by the pull_request event, and the
draft==true guard prevents pull_request_target from also running (no duplicate).

Co-authored-by: openhands <openhands@all-hands.dev>

* ci: add update-snapshots label bypass for intentional snapshot changes

When snapshot diffs are expected (UI redesign, intentional change, etc.) the
author now adds the 'update-snapshots' label to the PR to acknowledge them:

- Fail step gains a !contains(labels, 'update-snapshots') guard so CI passes
  even when Playwright reports differences.
- The PR comment status adjusts: ❌ 'N snapshots differ — add label to
  acknowledge' when unapproved, ✅ 'N snapshots changed — acknowledged via
  label' when approved.
- The snapshot workflow now triggers on labeled/unlabeled events so that adding
  or removing the label immediately re-runs CI with the current label state in
  scope (no manual re-run or empty commit needed).
- New baselines are uploaded automatically when the PR merges to main, so no
  separate 'regenerate on main' step is needed.

Co-authored-by: openhands <openhands@all-hands.dev>

* docs: update snapshot testing section in AGENTS.md

Add details on: artifact lookup by name, delete-then-post comment behavior,
fixed .pr/snapshots/ path (no accumulation), update-snapshots label bypass
for intentional changes, labeled/unlabeled triggers, bootstrap behavior.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: git rm must run before copyFile, not after

On the second CI run the branch already has .pr/snapshots/ tracked from the
previous run. The old order was: copyFile → git rm → git add. git rm removes
tracked files from disk, which deleted the freshly written images, leaving the
directory empty and causing 'fatal: pathspec did not match any files'.

Fix: run git rm --ignore-unmatch before copyFile so the tracked files are
cleared from disk first; then copyFile writes clean new files with nothing
to conflict; then git add finds them as expected.

Co-authored-by: openhands <openhands@all-hands.dev>

* chore: snapshot images for run 25931876588 [skip ci]

* fix: push snapshot images to orphan branch, not PR branch

Pushing to the PR branch with [skip ci] caused required checks to never
run on the HEAD commit, permanently blocking the PR.

New approach:
- publishImages() creates a fresh git repo in a temp directory, adds the
  images, and force-pushes to snapshot-artifacts/pr-<N> — a dedicated
  ephemeral branch that no CI workflow watches.
- The PR branch is never touched by CI, so required checks always run on
  the actual code commits.
- [skip ci] is removed; no loop prevention is needed because nothing
  triggers snapshot CI on the artifacts branch.
- Images in the orphan commit live at changed/<relPath>-{actual,expected,diff}.png
  and new/<relPath>.png (no .pr/snapshots/ prefix).
- raw.githubusercontent.com/<owner>/<repo>/<sha>/changed/... URLs are
  stable because they pin the orphan commit SHA.
- pr-artifacts.yml gains a closed trigger + cleanup-snapshot-artifacts job
  that deletes snapshot-artifacts/pr-<N> when the PR merges or is abandoned.
- Stale .pr/snapshots/ files from previous CI runs removed from this branch.

Co-authored-by: openhands <openhands@all-hands.dev>

* docs: update AGENTS.md — orphan branch image storage, branch cleanup

* docs: tighten AGENTS.md snapshot section and pr-artifacts description

- Remove duplicate gitignore mention (already stated in baseline-storage line)
- Update pr-artifacts.yml description to cover both cleanup responsibilities:
  .pr/live-e2e/ (on approval) and snapshot-artifacts/pr-<N> (on close)

Co-authored-by: openhands <openhands@all-hands.dev>

---------

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-15 14:12:53 -04:00
2026-04-24 17:33:22 -04:00

agent-canvas

Warning

This project is in sandbox phase. It may be vibecoded, untested, or out of date. OpenHands takes no responsibility for the code or its support. Learn more.

Agent Canvas is a web frontend for managing agents. You can:

  • ⌨️ prompt them manually
  • 🕐 run them on a schedule
  • ⚡ trigger them automatically—e.g. from Slack or GitHub.

Agents can run anywhere:

  • 🧑‍💻 on your laptop
  • 🖥️ on a remote virtual machine
  • ☁️ in our hosted cloud
  • 🏢 or inside your company’s infrastructure

You can work with any agent (e.g. Claude Code, Codex) or connect directly to an LLM (e.g. Anthropic, OpenAI, Gemini, Mistral, Minimax, Kimi).

If you have questions or feedback, please open a GitHub issue or join the #proj-agent-canvas channel in Slack

Screenshot 2026-05-11 at 10 13 19 AM

Quickstart

Prerequisites:

  • Node.js 22.12.x or later
  • npm
  • Docker

Set $PROJECT_PATH to the directory on your machine where your projects live (e.g. /path/to/your/projects). The agent server will mount this directory so the agent can read and edit your code.

By default the container runs as your host UID/GID so files written to bind mounts remain writable from your host account. The container is still kept isolated from your host home: its /home/openhands is a temporary writable home, and only ~/.openhands, ~/.claude, ~/.codex, and ~/.ssh are mounted individually under it (and only if they exist). If you want the Add Workspace dialog to browse your real host filesystem, set OH_MOUNT_HOST_HOME=1 before npm run dev:docker to bind-mount your entire host home onto /home/openhands in the container. The Add Workspace modal also shows this hint inline when it detects the mount is off. Watch the video on how to run this on Mac or Windows.

export PROJECT_PATH=/path/to/your/projects
git clone https://github.com/OpenHands/agent-canvas.git
cd agent-canvas
npm install
npm run dev:docker

This serves a static production build of the frontend behind the local ingress proxy. That is the recommended mode for normal use, remote access, and tunnels such as ngrok because it avoids Vite hot-reload restarts and large dev-module request bursts. If you are developing the Agent Canvas frontend itself and want live reload, use npm run dev:docker:dynamic instead.

Windows PowerShell exception: if npm run dev:docker starts the backend but localhost:8000 shows Bad Gateway, start the same stack directly with Node instead. Replace the path below with your projects folder, and do not include any prompt characters or a trailing > in the value.

$env:PROJECT_PATH = "/path/to/your/projects"
git clone https://github.com/OpenHands/agent-canvas.git
cd agent-canvas
npm install
node --env-file-if-exists=.env .\scripts\dev-docker.mjs

Access the UI at http://localhost:8000

Without Docker

Warning

This runs the agent-server directly on the machine you're installing on--the agent will have full access to your filesystem!

Running without docker is great if you're running Agent Canvas on a VM. See SELF_HOSTING.md for details, especially with respect to security hardening. Notably, you can run the backend on multiple different VMs and switch between them from the same Agent Canvas frontend!

Prerequisites:

  • Node.js 22.12.x or later
  • npm
  • uv (for running the agent server via uvx)
git clone https://github.com/OpenHands/agent-canvas.git
cd agent-canvas
npm install
npm run dev:dangerously-dockerless

Access the UI at http://localhost:8000

This also serves a static production build for stability. If you are developing the Agent Canvas frontend itself and want live reload, use the dynamic dockerless command instead:

npm run dev:dangerously-dockerless:dynamic

Architecture

Agent Canvas is powered by the OpenHands Agent Server, a REST API for running multiple agents on a single machine. Each Agent Server runs on a single host/port; the Agent Canvas can connect to multiple Agent Servers and easily flip between them.

You can run an Agent Server anywhere:

  • Directly on your laptop (be careful!)
  • Inside a Docker container
  • On a dedicated machine like a Mac Mini
  • On a virtual machine in the cloud
  • Inside a Kubernetes Pod
  • Inside OpenHands Cloud (our commercial offering)

The Agent Server is often paired with an Automation Server, which lets you set up agents that run on a schedule or in response to events.

image

npm Package

Agent Canvas is also available as an npm package for embedding in your own applications:

Warning

Agent Canvas has not published a stable release yet. Until the first stable version is available, the npm latest dist-tag may point to alpha, beta, or release-candidate builds, so npm install @openhands/agent-canvas can install a prerelease. Pin an exact version if you need predictable behavior. This temporary behavior is tracked in #395; retag latest to the first stable release when it ships.

npm install @openhands/agent-canvas

Usage

Import the full package or specific components:

// Full package
import { AgentServerUIProviders } from '@openhands/agent-canvas';

// Individual component packages
import { BrowserPanel } from '@openhands/agent-canvas/browser';
import { ChatPanel } from '@openhands/agent-canvas/conversation';
import { FileExplorer } from '@openhands/agent-canvas/files';
import { TerminalPanel } from '@openhands/agent-canvas/terminal';

Available Subpath Exports

Subpath Description
@openhands/agent-canvas Main entry with providers and core components
@openhands/agent-canvas/browser Browser/preview panel components
@openhands/agent-canvas/conversation Chat interface and message components
@openhands/agent-canvas/files File explorer and editor components
@openhands/agent-canvas/settings Settings screens and forms
@openhands/agent-canvas/sidebar Sidebar navigation components
@openhands/agent-canvas/terminal Terminal emulator component
@openhands/agent-canvas/i18n Internationalization resources

More documentation

For contributor and developer workflows, including frontend-only mode, mock mode, environment variables, and build/test commands, see DEVELOPMENT.md.

S
Languages
TypeScript 93.7%
JavaScript 4.7%
Python 0.9%
Shell 0.3%
CSS 0.2%
Other 0.1%