Files
OpenHands/package.json
T
Rohit Malhotraandopenhands 979e64fe19 feat: add Docker CI to build all-in-one image with agent-server + automation + frontend (#634)
* feat: add Docker CI to build all-in-one image with agent-server + automation + frontend

Adds a GitHub Actions workflow (.github/workflows/docker.yml) that builds and
publishes ghcr.io/openhands/agent-canvas — a single Docker image combining:

  1. Agent Server (ghcr.io/openhands/agent-server base image from SDK repo)
  2. Automation server (pip-installed from openhands-automation)
  3. agent-canvas frontend (static build from this repo)

The automation server is pip-installed rather than copied from its Docker image
because both services share openhands-sdk, fastapi, uvicorn, pydantic, httpx
etc. — installing into the agent-server's Python 3.13 deduplicates all shared
packages. Only automation-specific deps (asyncpg, sqlalchemy, boto3, …) are
added on top.

An entrypoint script starts all three services and a static-server proxy that
unifies them behind a single port (default 8000):
  /api/automation/* → automation backend (:18001)
  /api/*            → agent-server (:18000)
  /*                → static frontend + SPA fallback

Workflow triggers:
  - Push to main: builds and pushes with branch + SHA tags
  - v* tags (releases): also pushes semver tags (1.2.3, 1.2, 1, latest)
  - PRs: builds, pushes SHA-tagged image, updates PR description with
    pull/run instructions (same pattern as the SDK repo)
  - workflow_dispatch: supports overriding base image and automation version

Files added:
  - docker/Dockerfile (multi-stage: frontend build + agent-server base)
  - docker/entrypoint.sh (process manager for all three services)
  - .dockerignore
  - .github/workflows/docker.yml

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

* fix: build multi-arch Docker images (amd64 + arm64)

Adds QEMU setup for cross-compilation and defaults the platform matrix
to linux/amd64,linux/arm64 so the image works on both Intel and Apple
Silicon machines.

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

* refactor: rewrite Docker workflow to match SDK repo structure

Replace the single-job QEMU approach with the same architecture-matrix
pattern used by the SDK repo's server.yml:

  1. build-and-push-image — matrix over {amd64, arm64} with native runners
     (ubuntu-24.04 for amd64, ubuntu-24.04-arm for arm64). Each job pushes
     arch-suffixed tags (e.g. sha-abc1234-amd64) and uploads build-info
     artifacts.

  2. merge-manifests — downloads both arch build-infos, strips the -amd64
     suffix from amd64 tags to derive manifest tags, and creates multi-arch
     manifests via `docker buildx imagetools create`.

  3. consolidate-build-info — aggregates all build-info and manifest-info
     artifacts into a single JSON summary (PR-only).

  4. update-pr-description — renders the summary into the PR body between
     AGENT_CANVAS_DOCKER_START/END markers.

Native runners avoid the 3-5× slowdown of QEMU emulation for arm64
builds.

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

* fix: sanitize branch names in Docker tags (/ is not allowed)

Branch names like 'feat/docker-ci' produce invalid Docker tags because
'/' is forbidden in tag names. Replace '/' with '-' so the tag becomes
'feat-docker-ci-amd64'.

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

* fix: default automation to SQLite and fix wait blocking proxy startup

Two bugs:

1. The automation server defaults to PostgreSQL on localhost, which
   doesn't exist in the all-in-one container. Default AUTOMATION_DB_URL
   to sqlite+aiosqlite:// so it works out of the box. Users can override
   with a real Postgres URL for production.

2. The bare 'wait' command waited for ALL background children — including
   the long-running agent-server and automation processes — so the
   static-server/proxy on port 8000 never started. Fix by waiting only
   for the wait_for_port subshell PIDs.

Verified locally: all three services start, endpoints respond correctly,
no more scheduler ConnectionRefusedError.

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

* feat: add VOLUME directives for persistence and project mounts

Declare /home/openhands/.openhands (settings, secrets, conversations,
automation SQLite DB) and /projects (user code) as Docker volumes so
data survives container restarts by default. Users should bind-mount
these for durable persistence:

  docker run -v ~/.openhands:/home/openhands/.openhands \
             -v ~/projects:/projects \
             -p 8000:8000 ghcr.io/openhands/agent-canvas

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

* fix: set OH_SECRET_KEY default and pre-create persistence dirs

Three issues fixed:

1. OH_SECRET_KEY was not set → agent-server refused to return encrypted
   secrets → conversation creation failed with 503. Set the same static
   default used by dev-safe.mjs / dev-docker.mjs.

2. Persistence dirs (conversations, bash_events, automation DB) were not
   pre-created → the openhands user got PermissionError when the VOLUME
   directive created them as root. Pre-create with correct ownership
   before the USER switch in the Dockerfile.

3. Set OH_PERSISTENCE_DIR, OH_CONVERSATIONS_PATH, OH_BASH_EVENTS_DIR
   defaults in the entrypoint (matching dev-docker.mjs) so data lands
   under the well-known ~/.openhands tree.

Verified locally: all three services start clean, no warnings about
OH_SECRET_KEY, SQLite migrations apply successfully.

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

* chore: merge main and remove stale dev-docker.mjs references

Main removed scripts/dev-docker.mjs (Docker is no longer a dependency of
the npm package flow). Update comments in docker.yml, entrypoint.sh, and
AGENTS.md that referenced the deleted file.

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

* feat: centralize config into config/defaults.json (single source of truth)

All version pins, port defaults, persistence paths, package names, and
the dev secret key now live in config/defaults.json. Consumers read from
it instead of hardcoding values:

- scripts/dev-safe.mjs: reads via JSON.parse(readFileSync(...))
- scripts/dev-with-automation.mjs: same
- scripts/check-sdk-version-sync.mjs: same (no longer regex-parses JS)
- docker/Dockerfile: config-gen build stage converts JSON to
  /opt/agent-canvas/defaults.env (shell-sourceable)
- docker/entrypoint.sh: sources defaults.env at startup; also adds
  session API key auto-generation so the image doesn't run wide-open
- .github/workflows/docker.yml: reads versions from JSON in a setup
  step (no more hardcoded env vars)

To bump a version, edit config/defaults.json only.

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

* fix: address PR review feedback (#634)

- Fix PID tracking bug: move PIDS+=($!) inside if/elif branches so the
  else (automation-not-found) path doesn't add a stale PID
- chmod 600 session API key file to prevent credential leak
- Warn when using insecure default OH_SECRET_KEY in Docker entrypoint
- Add try/catch + field validation for config/defaults.json loading in
  check-sdk-version-sync.mjs
- Fix semver tag parsing: strip pre-release/build metadata, only create
  abbreviated tags (major.minor, major, latest) for stable releases
- Sanitize branch names for Docker tags (tr invalid chars, strip leading
  dot/dash) to handle branches with #, @, spaces, etc.
- Add arch validation before manifest merge (assert both amd64.json and
  arm64.json exist)
- Remove $schema reference to non-existent defaults.schema.json

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

* fix: remove hardcoded version defaults from Dockerfile

Replace hardcoded ARG defaults (AGENT_SERVER_IMAGE, AUTOMATION_VERSION)
with empty ARGs. Values are always derived from config/defaults.json:
- CI: reads JSON in the workflow config step, passes --build-arg
- Local: new scripts/docker-build.mjs helper reads JSON and invokes
  docker build with the correct --build-arg values

Added npm run build:docker convenience script.

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

* fix: stabilize snapshot tests and auto-generate Docker secret key

Two fixes:

1. **Flaky snapshot tests**: The 'Local pagination fixture' mock conversation
   used a fixed absolute timestamp (PAGINATION_BASE_TIME = May 13, 2026) for
   its created_at/updated_at, while 'Errored Project' used a relative
   timestamp (now - 7d). As real time progressed past the crossover point,
   their sort order in the sidebar flipped, causing 30/73 snapshot diffs on
   every PR. Fix: use relative timestamps (now - 6d) for the pagination
   fixture's conversation listing fields. The internal event timestamps
   (used by pagination tests) still use PAGINATION_BASE_TIME — only the
   sidebar ordering is affected.

2. **Docker OH_SECRET_KEY**: The entrypoint used a static insecure default
   for OH_SECRET_KEY and warned about it. Now mirrors the session API key
   pattern: auto-generate a cryptographic random key on first run, persist
   it to ~/.openhands/agent-canvas/secret-key.txt, and reuse on restart.
   Users can still override via the OH_SECRET_KEY env var. Removed the
   now-unused CONFIG_SECRET_KEY from the Docker defaults.env generation.
   Also deduped STATE_DIR computation (was repeated for session key path).

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

* docs: update AGENTS.md with mock timestamp and Docker secret key notes

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

* fix: include canvas_ui tool in Docker image

The Docker image was missing the tools/ directory and OH_EXTRA_PYTHON_PATH,
so the agent-server couldn't import canvas_ui_tool.py when the frontend
sent canvas_ui in the conversation tools list. This caused:

  HTTP 500: ToolDefinition 'canvas_ui' is not registered

Fix: COPY tools/ into the image and set OH_EXTRA_PYTHON_PATH in the
entrypoint, matching what scripts/dev-safe.mjs already does for local dev.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-20 04:24:45 +00:00

226 lines
7.7 KiB
JSON

{
"name": "@openhands/agent-canvas",
"version": "1.0.0-alpha.3",
"description": "Agent Canvas UI for OpenHands - run AI coding agents with a visual interface",
"license": "MIT",
"private": false,
"type": "module",
"repository": {
"type": "git",
"url": "https://github.com/OpenHands/agent-canvas"
},
"homepage": "https://github.com/OpenHands/agent-canvas#readme",
"bugs": {
"url": "https://github.com/OpenHands/agent-canvas/issues"
},
"bin": {
"agent-canvas": "bin/agent-canvas.mjs"
},
"engines": {
"node": ">=22.12.0"
},
"dependencies": {
"@heroui/react": "2.8.10",
"@microlink/react-json-view": "1.31.20",
"@monaco-editor/react": "4.7.0",
"@openhands/extensions": "git+https://github.com/OpenHands/extensions.git#7b33f64ffccf95f7ccd2ec640aeae84ab1cc2c75",
"@openhands/typescript-client": "git+https://github.com/OpenHands/typescript-client.git#v0.6.0",
"@react-router/node": "7.14.2",
"@react-router/serve": "7.14.2",
"@tailwindcss/vite": "4.2.4",
"@tanstack/react-query": "5.100.9",
"@types/shell-quote": "^1.7.5",
"@uidotdev/usehooks": "2.4.1",
"@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "6.0.0",
"axios": "1.16.0",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"downshift": "9.3.2",
"framer-motion": "12.38.0",
"i18next": "26.0.8",
"i18next-browser-languagedetector": "8.2.1",
"i18next-http-backend": "4.0.0",
"isbot": "5.1.39",
"lucide-react": "1.14.0",
"monaco-editor": "0.55.1",
"posthog-js": "1.372.6",
"react": "19.2.5",
"react-dom": "19.2.5",
"react-hot-toast": "2.6.0",
"react-i18next": "17.0.6",
"react-icons": "5.6.0",
"react-markdown": "10.1.0",
"react-router": "7.14.2",
"react-syntax-highlighter": "16.1.1",
"rehype-raw": "7.0.0",
"rehype-sanitize": "6.0.0",
"remark-breaks": "4.0.0",
"remark-gfm": "4.0.1",
"shell-quote": "^1.8.3",
"sirv-cli": "3.0.1",
"socket.io-client": "4.8.3",
"tailwind-merge": "3.5.0",
"tailwind-scrollbar": "4.0.2",
"unist-util-visit": "5.1.0",
"uuid": "14.0.0",
"vite": "8.0.10",
"zustand": "5.0.12"
},
"scripts": {
"dev": "node --env-file-if-exists=.env scripts/dev-with-automation.mjs",
"dev:static": "node --env-file-if-exists=.env scripts/dev-static.mjs",
"dev:extra-backend": "node --env-file-if-exists=.env scripts/dev-extra-backend.mjs",
"dev:minimal": "node --env-file-if-exists=.env scripts/dev-safe.mjs",
"dev:frontend": "npm run make-i18n && cross-env VITE_MOCK_API=false react-router dev",
"dev:mock": "npm run make-i18n && cross-env VITE_MOCK_API=true react-router dev",
"build": "npm run build:app",
"build:mock": "npm run make-i18n && cross-env VITE_MOCK_API=true react-router build",
"start": "npx sirv-cli build/ --single",
"test": "npm run make-i18n && vitest run",
"test:e2e": "playwright test --pass-with-no-tests",
"test:e2e:live": "node --env-file-if-exists=.env tests/e2e/live/scripts/run-live-e2e.mjs",
"test:e2e:snapshots": "playwright test tests/e2e/snapshots --project=chromium --retries=0",
"test:e2e:snapshots:update": "playwright test tests/e2e/snapshots --project=chromium --update-snapshots",
"test:coverage": "npm run make-i18n && vitest run --coverage",
"dev_wsl": "VITE_WATCH_USE_POLLING=true vite",
"preview": "vite preview",
"make-i18n": "node scripts/make-i18n-translations.cjs",
"prelint": "npm run make-i18n",
"lint": "npm run typecheck && eslint src && prettier --check src/**/*.{ts,tsx}",
"lint:fix": "eslint src --fix && prettier --write src/**/*.{ts,tsx}",
"prepare": "husky",
"typecheck": "react-router typegen && tsc",
"typecheck:staged": "react-router typegen && npx tsc --noEmit --skipLibCheck",
"check-translation-completeness": "node scripts/check-translation-completeness.cjs",
"build:app": "npm run make-i18n && react-router build",
"build:lib": "npm run make-i18n && react-router typegen && cross-env BUILD_LIB=true VITE_APP_ENV=production vite build && tsc -p tsconfig.lib.json",
"build:docker": "node scripts/docker-build.mjs"
},
"lint-staged": {
"src/**/*.{ts,tsx,js}": [
"eslint --fix",
"prettier --write"
],
"src/**/*.{ts,tsx}": [
"bash -c 'npm run typecheck:staged'"
],
"src/**/*": [
"npm run check-translation-completeness"
]
},
"devDependencies": {
"@eslint/eslintrc": "3.3.1",
"@eslint/js": "9.39.4",
"@mswjs/socket.io-binding": "0.2.0",
"@playwright/test": "1.59.1",
"@react-router/dev": "7.14.2",
"@tailwindcss/typography": "0.5.19",
"@tanstack/eslint-plugin-query": "5.100.9",
"@testing-library/dom": "10.4.1",
"@testing-library/jest-dom": "6.9.1",
"@testing-library/react": "16.3.2",
"@testing-library/user-event": "14.6.1",
"@types/mdast": "4.0.4",
"@types/node": "25.6.0",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@types/react-syntax-highlighter": "15.5.13",
"@typescript-eslint/eslint-plugin": "8.59.2",
"@typescript-eslint/parser": "8.59.2",
"@vercel/react-router": "1.3.0",
"@vitest/coverage-v8": "4.1.5",
"cross-env": "10.1.0",
"eslint": "9.39.4",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-i18next": "6.1.4",
"eslint-plugin-import-x": "4.16.2",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-prettier": "5.5.5",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "7.1.1",
"eslint-plugin-unused-imports": "4.4.1",
"globals": "16.5.0",
"husky": "9.1.7",
"jsdom": "29.1.1",
"lint-staged": "16.4.0",
"msw": "2.14.2",
"postcss-prefix-selector": "2.1.1",
"prettier": "3.8.3",
"tailwindcss": "4.2.4",
"typescript": "6.0.3",
"vite-plugin-svgr": "5.2.0",
"vitest": "4.1.5"
},
"packageManager": "npm@10.5.0",
"volta": {
"node": "22.12.0"
},
"msw": {
"workerDirectory": [
"public"
]
},
"overrides": {
"dompurify": "3.3.2"
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"bin",
"build",
"scripts"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./browser": {
"types": "./dist/components/browser/index.d.ts",
"import": "./dist/components/browser/index.js",
"require": "./dist/components/browser/index.cjs"
},
"./conversation": {
"types": "./dist/components/conversation/index.d.ts",
"import": "./dist/components/conversation/index.js",
"require": "./dist/components/conversation/index.cjs"
},
"./files": {
"types": "./dist/components/files/index.d.ts",
"import": "./dist/components/files/index.js",
"require": "./dist/components/files/index.cjs"
},
"./settings": {
"types": "./dist/components/settings/index.d.ts",
"import": "./dist/components/settings/index.js",
"require": "./dist/components/settings/index.cjs"
},
"./sidebar": {
"types": "./dist/components/sidebar/index.d.ts",
"import": "./dist/components/sidebar/index.js",
"require": "./dist/components/sidebar/index.cjs"
},
"./terminal": {
"types": "./dist/components/terminal/index.d.ts",
"import": "./dist/components/terminal/index.js",
"require": "./dist/components/terminal/index.cjs"
},
"./i18n": {
"types": "./dist/i18n/index.d.ts",
"import": "./dist/i18n/index.js",
"require": "./dist/i18n/index.cjs"
},
"./package.json": "./package.json"
},
"peerDependencies": {
"react": "19.2.5",
"react-dom": "19.2.5",
"react-router": "7.14.2"
}
}