Serve bundled agent-canvas frontend from agent-server#3676
Conversation
Co-authored-by: openhands <openhands@all-hands.dev>
Python API breakage checks — ✅ PASSEDResult: ✅ PASSED |
REST API breakage checks (OpenAPI) — ✅ PASSEDResult: ✅ PASSED |
all-hands-bot
left a comment
There was a problem hiding this comment.
⚠️ QA Report: PASS WITH ISSUES
Functional behavior passes: the bundled agent-canvas frontend served from agent-server root in source, wheel/sdist, and PyInstaller binary runs; the remaining issue is a failing PR-description CI check.
Does this PR achieve its stated goal?
Yes, functionally. I generated the real @openhands/agent-canvas bundle, ran the agent-server as an actual HTTP service, opened it in a real browser, and confirmed / and SPA routes serve the OpenHands frontend while backend/API routes remain JSON/404 as expected. I also verified configured static_files_path still takes precedence and built distributions/binary serve or contain the bundled frontend assets.
| Phase | Result |
|---|---|
| Environment Setup | ✅ make build completed successfully; no tests/linters were run. |
| CI Status | |
| Functional Verification | ✅ Source server, browser UI, static override, wheel/sdist packaging, and PyInstaller runtime behavior verified. |
Functional Verification
Test 1: Root and SPA route behavior before/after PR
Step 1 — Establish baseline without the fix:
Checked out origin/main at e3a2a4a2, kept the generated _frontend files present, then ran:
uv run python -m openhands.agent_server --host 127.0.0.1 --port 18101
curl http://127.0.0.1:18101/
curl http://127.0.0.1:18101/settings/llmObserved:
SERVER_READY_STATUS=200
ROOT_STATUS=200 application/json
ROOT_SNIPPET={"uptime":1.0,"idle_time":1.0,"title":"OpenHands Agent Server",...}
SPA_STATUS=404 application/json
SPA_SNIPPET={"detail":"Not Found"}
This confirms the base server did not serve the bundled frontend from / or use SPA fallback routes.
Step 2 — Apply the PR's changes:
Checked out openhands/agent-canvas-frontend at 8cabec83, ran make agent-canvas-frontend, then started:
uv run python -m openhands.agent_server --host 127.0.0.1 --port 18102Step 3 — Re-run with the fix in place:
Observed:
SERVER_READY_STATUS=200 application/json
ROOT_STATUS=200 text/html; charset=utf-8
ROOT_SNIPPET=<!DOCTYPE html><html lang="en"><head>...<title>OpenHands</title>...
SPA_STATUS=200 text/html; charset=utf-8
SPA_SAME_AS_ROOT=yes
ASSET_PATH=/assets/react-Dy05vyj5.js
ASSET_STATUS=200 text/javascript; charset=utf-8
API_MISSING_STATUS=404 application/json
API_MISSING_BODY={"detail":"Not Found"}
This shows the PR changes the user-facing behavior as intended: root and SPA paths serve the frontend, static JS assets load, and API paths remain backend-owned instead of falling through to the SPA.
Test 2: Real browser rendering
With the PR server still running, I opened http://127.0.0.1:18102/ in a real browser. The browser landed on http://127.0.0.1:18102/conversations with title OpenHands and rendered the agent-canvas UI (Manage backends, Add Backend). Direct navigation to http://127.0.0.1:18102/settings/llm also loaded with title OpenHands and the same frontend shell, confirming browser-visible SPA fallback behavior.
Test 3: Explicit static_files_path precedence
Step 1 — Establish PR default behavior:
Without static_files_path, the PR server returned bundled frontend HTML from / and /settings/llm as shown above.
Step 2 — Configure explicit static files:
Started the actual ASGI app with Config(static_files_path=Path("/tmp/qa-static")) and a custom /tmp/qa-static/index.html:
uv run python -c 'from pathlib import Path; import uvicorn; from openhands.agent_server.api import create_app; from openhands.agent_server.config import Config; uvicorn.run(create_app(Config(static_files_path=Path("/tmp/qa-static"))), host="127.0.0.1", port=18104)'Step 3 — Verify override behavior:
Observed:
STATIC_API_READY_STATUS=200 application/json
STATIC_API_ROOT_STATUS=302
STATIC_API_ROOT_LOCATION=/static/index.html
STATIC_API_INDEX_STATUS=200 text/html; charset=utf-8
STATIC_API_INDEX_BODY=<html><body>Configured Static QA</body></html>
STATIC_API_SPA_STATUS=404 application/json
STATIC_API_SPA_BODY={"detail":"Not Found"}
This confirms explicit configured static files still take precedence over the bundled frontend.
Test 4: Packaging and PyInstaller runtime
After make agent-canvas-frontend, I built the agent-server distributions:
uv build --package openhands-agent-server --out-dir /tmp/qa-distObserved:
Successfully built /tmp/qa-dist/openhands_agent_server-1.28.0.tar.gz
Successfully built /tmp/qa-dist/openhands_agent_server-1.28.0-py3-none-any.whl
WHEEL_FRONTEND_INDEX=openhands/agent_server/_frontend/index.html
SDIST_FRONTEND_INDEX=openhands_agent_server-1.28.0/openhands/agent_server/_frontend/index.html
SDIST_FRONTEND_ASSET_COUNT=283
Then I built and ran the PyInstaller binary:
uv run pyinstaller openhands-agent-server/openhands/agent_server/agent-server.spec --distpath /tmp/qa-pyi-dist --workpath /tmp/qa-pyi-work --noconfirm --clean
/tmp/qa-pyi-dist/openhands-agent-server --host 127.0.0.1 --port 18105Observed:
PYINSTALLER_RC=0
PYI_SERVER_READY_STATUS=200 application/json
PYI_ROOT_STATUS=200 text/html; charset=utf-8
PYI_ROOT_SNIPPET=<!DOCTYPE html><html lang="en"><head>...<title>OpenHands</title>...
PYI_SPA_STATUS=200 text/html; charset=utf-8
This confirms release artifacts include the bundle and the PyInstaller binary can serve it at runtime.
Issues Found
- 🟠 Issue: The PR Description Check is failing. The validator reports: first visible line must be
HUMAN:, add a human-written note, keep the unchecked human-tested checkbox, keepAGENT:, and keep the## Why/## How to Testsections. Per repo policy, those HUMAN fields are reserved for a human contributor, so I did not edit them.
No inline comments: I did not find any code-tied functional defects during QA.
This QA review was created by an AI agent (OpenHands) on behalf of the requester.
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
|
👀 OpenHands is reviewing this pull request. Conversation: https://nestable-nonremittably-sha.ngrok-free.dev/conversations/6f7e2e74-9581-45d9-a05e-2fb0554548a1 This comment was generated by an AI agent (OpenHands) on behalf of the user. |
HUMAN:
AGENT:
Why
Users who clone
software-agent-sdkshould be able to run the same agent-canvas stack from this repository aftermake build, without manually installing the npm package separately or changing agent-canvas's process model. The npm@openhands/agent-canvasCLI runs separate agent-server, automation backend, static frontend, and ingress processes, so this PR now reuses that launcher instead of serving frontend assets from FastAPI.Summary
make buildnow also downloads pinned prebuilt@openhands/agent-canvas@1.0.0-rc.7intoagent-canvas/.make runto launch that package withOH_AGENT_SERVER_LOCAL_PATHpointed at this checkout, so agent-canvas starts the local SDK's agent-server plus the automation backend, static frontend server, and ingress router.--frontend-only,--backend-only, and--portthroughmake run ARGS="..."instead of duplicating its routing/static-server behavior in agent-server.make canvasremains as an alias formake run.Issue Number
Closes #3658.
How to Test
Commands run by OpenHands:
Result: completed successfully;
uv sync --devwas up to date, pre-commit hooks were installed, and the prebuilt agent-canvas package was downloaded toagent-canvas/.Result: completed successfully and printed
1.0.0-rc.7, confirming the Makefile fetches the pinned agent-canvas package into the package-root-shapedagent-canvas/directory.make run ARGS="--info"Result: completed successfully and printed
@openhands/agent-canvas 1.0.0-rc.7with the default stack versions and ports, confirming the local launcher is using the pinned downloaded package.Result: completed successfully and confirmed the launcher exposes the current agent-canvas modes, including
--frontend-only,--backend-only, and the automation backend stack behavior.Result: all hooks passed, including ruff format/lint, pycodestyle, pyright, import dependency rules, and tool subclass registration.
Result:
41 passed, 5 warnings in 4.08s, confirming the previous direct FastAPI static-serving changes were removed without regressing existing agent-server API tests.Video/Screenshots
No screenshot was captured. The relevant end-to-end smoke check here is
make run ARGS="--info"/--help, which executes the downloaded agent-canvas CLI fromagent-canvasand verifies the exposed modes. A human should still run the full interactive UI flow before marking this ready for review.Type
Notes
The downloaded
agent-canvas/directory is intentionally ignored in git. This PR no longer packages frontend assets intoopenhands-agent-serveror serves them from FastAPI; it keeps agent-canvas's existing separated static frontend, ingress, agent-server, and automation backend process model.This PR description was updated by an AI agent (OpenHands) on behalf of the requester.
Agent Server images for this PR
• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server
Variants & Base Images
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.13-nodejs22-slimgolang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:fdee1b1-pythonRun
All tags pushed for this build
About Multi-Architecture Support
fdee1b1-python) is a multi-arch manifest supporting both amd64 and arm64fdee1b1-python-amd64) are also available if needed