* feat: add automation backend integration with standalone ingress proxy - Add scripts/ingress.mjs: standalone HTTP reverse proxy for routing traffic to multiple backends based on URL path prefix - Add scripts/dev-with-automation.mjs: orchestrates full stack with agent-server, automation backend (both via uvx), Vite, and ingress - Make 'npm run dev' run full stack by default (was dev:safe, now dev:automation) - Rename 'npm run dev:safe' to 'npm run dev:minimal' for agent-server + Vite only - Update README with new quickstart showing full stack as default - Update AGENTS.md with architecture documentation Architecture: http://localhost:8000 (Ingress) ├── /api/automation/* → Automation Backend (:18001) ├── /api/*, /sockets → Agent Server (:18000) └── /* (default) → Vite Dev Server (:3001) * test: add tests for ingress and dev-with-automation scripts - Add __tests__/scripts/ingress.test.ts with 14 tests covering: - CLI argument parsing (--help, --port, --route, --default) - Route matching (exact, prefix, longest-match-first) - Proxy functionality (forwarding, query params, error handling) - 502 response when backend unavailable - 503 response for unmatched routes with no default - Add __tests__/scripts/dev-with-automation.test.ts with 19 tests covering: - buildAutomationCommand() with various git refs/repos - buildConfig() port and path configuration - CLI --help output - Graceful exit when uvx is missing - Export testable functions from dev-with-automation.mjs * fix: prevent dev-with-automation from auto-executing when imported The script was calling main() unconditionally, which caused test failures when vitest imported the module. Now check if the module is the main entry point before executing. --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: hieptl <hieptl.developer@gmail.com>
20 KiB
Repository Notes
-
This repository is a near-direct port of the OpenHands frontend, adapted to talk straight to
software-agent-sdk/agent_serverwithout the usual OpenHands app backend. -
Frontend API adaptation lives mainly in
src/api/:option-servicefabricates an OSS web-client config and reads models/providers fromagent_serverLLM endpoints.settings-serviceuses agent server/api/settingsendpoints for persistence; reads schemas from/api/settings/agent-schemaand/api/settings/conversation-schema, fetches settings with optionalX-Expose-Secrets: encryptedheader for conversation start payloads, and saves settings via PATCH with diffs.v1-conversation-service,event-service,git-service, andskills-serviceare mapped directly toagent_serverREST endpoints.open-hands-axiosinjects the optionalX-Session-API-Keyfrom env/local config for all requests.
-
Supported env vars for deployment:
VITE_BACKEND_BASE_URLfor the agent server base URL.VITE_SESSION_API_KEYfor optional session auth.VITE_WORKING_DIRfor the default workspace path sent when starting conversations.VITE_WORKER_URLSas a comma-separated list of browser worker URLs if you want the Browser tab to probe exposed app hosts.VITE_ENABLE_BROWSER_TOOLS=falseto omitBrowserToolSetfrom new conversation payloads.
-
Default working-dir fallback is now the relative path
workspace/project(exported asDEFAULT_WORKING_DIRfromsrc/api/agent-server-config.ts); git-path heuristics and the default PLAN preview path should reuse that constant instead of hardcoding/workspace/project. -
The UI keeps most OpenHands routes/layout intact, but hosted-only behavior (org, account management, integrations) has been removed via the fabricated OSS config because there is no separate app backend.
-
Verification command:
npm run typecheck && npm run build. -
GitHub automation now includes
.github/workflows/ci.ymlfornpm ci,npm test, andnpm run build, plus.github/dependabot.ymlwith weekly npm/github-actions updates gated by a 7-day cooldown. -
Direct
dependenciesanddevDependenciesinpackage.jsonare exact-pinned (no caret ranges); reproducible installs should use the committedpackage-lock.jsonplusnpm ci, and targeted transitive fixes still belong inoverrides. -
package-lock.jsonmust also retain the optional peer entry fornode_modules/vite-tsconfig-paths/node_modules/typescript@5.9.3; without that nested lock entry, cleannpm ciinstalls on CI fail withMissing: typescript@5.9.3 from lock file. -
npm testnow runsnpm run make-i18nfirst so clean environments generatesrc/i18n/declaration.tsbefore Vitest loads aliased imports. -
__tests__/vite-config.test.tsshould importvite.configdirectly under// @vitest-environment node; spawning plainnode -e 'import ./vite.config.ts'is not portable across Node patch releases in CI. -
vitest.setup.tsmust guard DOM-specific globals (HTMLCanvasElement,HTMLElement,window) because some suites run in the Node environment instead of jsdom. -
__tests__/components/providers/posthog-wrapper.test.tsxmust wrapPostHogWrapperin aQueryClientProvider; the wrapper now reads its client from React Query context instead of importing the global singleton. -
@openhands/typescript-clientis consumed directly fromgithub:OpenHands/typescript-client#4716d2e; that package ships the needed subpath exports forclient/http-client,events/remote-events-list, andworkspace/remote-workspace. -
Shared TypeScript-client adapters live in
src/api/typescript-client.ts; prefer those helpers for agent-server-backed REST/workspace/event/VS Code calls before falling back toopen-hands-axios. -
Local verification/build gotchas:
npm run typecheckassumes generated translation types exist; runnpm run make-i18nfirst ifsrc/i18n/declaration.tsis missing.
-
The OSS cleanup removed hosted-only auth, org, account, onboarding, and invitation codepaths, routes, and tests. Keep
integrations,git-settings,secrets, MCP settings, and other local/self-hosted flows intact when simplifying OSS behavior. -
When merging main into this branch, keep the new agent-server compatibility bootstrap in
src/root.tsx, but do not reintroduce hosted-only invitation cleanup or marketing CTA chrome in the OSS user menu; the OSS account menu should just render settings links plus Docs. -
During the OSS cleanup audit, the runtime removals held up, but route-level regression coverage for still-active OSS settings pages had been deleted too aggressively. Keep focused tests for local/self-hosted screens like
app-settings,llm-settings,git-settings,mcp-settings, andsecrets-settingseven when stripping hosted-only code. -
npm run dev:mockneeds MSW handlers for the direct agent-server routes used by the adapted frontend, not the original OpenHands mock paths. Key routes that must stay covered are:- bootstrap/model loading:
/server_info,/api/llm/models/verified,/api/llm/providers - settings schemas:
/api/settings/agent-schema,/api/settings/conversation-schema - settings CRUD:
GET /api/settings,PATCH /api/settings - secrets CRUD:
GET /api/settings/secrets(list),GET /api/settings/secrets/:name(value),PUT /api/settings/secrets(upsert),DELETE /api/settings/secrets/:name - conversation browsing/loading:
/api/conversations/search,/api/conversations?ids=...,/api/conversations/:id,/api/conversations/:id/events/* - runtime git panels:
/api/git/changes,/api/git/diff
- bootstrap/model loading:
-
Static mock verification needs a build created with
VITE_MOCK_API=true(usenpm run build:mock); the client must start MSW whenever that flag is enabled, even in production/static builds, otherwise routes like/settingsand the conversations pane fall through to the static server and crash on undefined.filter/.mapassumptions. -
Frontend compatibility guard:
OptionService.getConfig()now uses/server_info.versionto block unsupported agent-server versions before the app loads. Git history insoftware-agent-sdkshows/api/settings/agent-schemaand/api/settings/conversation-schemafirst shipped in tagv1.17.0, so Agent Canvas currently treats< 1.17.0(or unknown/unparseable versions) as incompatible,useConfigstops retrying that case, andsrc/root.tsxrenders a blocking unsupported-version notice on every route. -
Useful regression tests for mock mode live in
__tests__/api/option-service.test.ts,__tests__/api/mock-conversation-handlers.test.ts, and__tests__/api/mock-settings-handlers.test.ts. -
Browser-verified mock-mode tour artifact was generated at
artifacts/frontend-tour.gif. -
Live
agent_servercompatibility quirks discovered during browser verification:- Latest
openhands-agent-serverlive-mode notes (verified against 1.18.1):/api/settings/agent-schemaand/api/settings/conversation-schemaexist on recent servers, but they return401when the server was started withSESSION_API_KEYorOH_SESSION_API_KEYS_0; the frontend must send the same value asVITE_SESSION_API_KEY/X-Session-API-Key.- The provider/model picker should use
/api/llm/providers,/api/llm/models, and/api/llm/models/verified;/api/v1/config/providers/searchand/api/v1/config/models/search404 on current live agent-server releases. - When the browser is accessing the frontend through a remote host (for example an All Hands work URL) but
VITE_BACKEND_BASE_URLpoints at127.0.0.1/localhost, browser-side REST calls must fall back to the frontend origin so Vite can proxy/apiand/socketsto the local backend.
GET /api/conversationsexpects repeatedidsparams (?ids=a&ids=b), not Axios's default bracket form (ids[]=a), so the shared Axios client needs a custom params serializer.- Runtime git panels should prefer the conversation's reported
workspace.working_dirwhen present; falling back to/workspace/projectcan produce 500s likeNot a git repositoryfor direct local workspaces such as/workspace/project/agent-canvas. - For development,
npm run devnow usesuvxto run a temporary agent-server installation, so no permanentuv tool installis required. For standalone installations,uv tool install -U --with openhands-tools --with openhands-workspace openhands-agent-serverwould expose the executable asagent-server(notopenhands-agent-server), possibly requiring~/.local/binonPATH. - Current SDK / agent-server conversation start payloads must use SDK-registered snake_case tool names, not the old class-style names. Working names against SDK v1.18.1 were:
terminalfile_editortask_trackerbrowser_tool_setUsingTerminalTool/FileEditorTool/TaskTrackerTool/BrowserToolSetcaused live/api/conversations/{id}/eventsruns to fail withToolDefinition '<name>' is not registered.
- The root compatibility bootstrap now treats
/server_infonetwork/timeout failures as a first-classAgentServerUnavailableError, uses a short 5s timeout for that probe, and disables React Query retries/toasts for the initial config fetch so missing backends fail fast with an explicit full-screen notice. - For local verification in this repo, setting
VITE_WORKING_DIR=/workspace/project/agent-canvasavoids initial Changes-tab 500s from pointing conversations at the non-repo parent/workspace/project. - A successful end-to-end live run in this environment required a real LLM config (
LLM_MODEL+LLM_API_KEY). The defaultlitellm_proxy/...model with nollm_api_keyfailed at runtime with alitellm.AuthenticationError.
- Latest
-
Agent-server recovery UX gotchas:
- Keep
/settings/agent-serverin the intermediate-page bypass path (use-is-on-intermediate-page) souseConfig()-driven layout/sidebar queries do not block the recovery screen behind a global spinner. PostHogWrappershould treat config-fetch failures as silent/optional (no user-facing toast), otherwise onboarding/recovery screens show a duplicate incompatible-server toast on top of the friendly guidance.- Keep the settings route on the compact
AgentServerConnectionFormvariant withshowSectionHeader={false}and no checklist; the blocked root onboarding should stay similarly minimal, with only the status card plus a single sentence that links to the repo setup instructions. - For local screenshot/GIF capture of SPA routes, serve
build/with an SPA fallback (for examplesirv build --single) and restart the static server after each rebuild so hashed asset URLs stay in sync.
- Keep
-
Git provider token persistence note:
src/api/secrets-service.tsstores git provider tokens in TWO places:- Agent-server secrets API (
PUT /api/settings/secrets) with naming conventionGIT_PROVIDER_{PROVIDER}_TOKEN- for agent runtime use - localStorage (
openhands-agent-server-git-provider-tokens) - for frontend git API calls (repo search, branches, etc.) TheaddGitProvidermethod stores to server FIRST (must succeed), then updates localStorage. This ensures server-side persistence is the source of truth.
- Agent-server secrets API (
-
Agent server connection settings now live at
Settings > Agent Server(/settings/agent-server). The page reads deployment defaults fromVITE_BACKEND_BASE_URL/VITE_SESSION_API_KEY, saves user overrides in theopenhands-agent-server-configlocalStorage key, and must stay reachable even when the backend compatibility probe fails so users can recover from missing or wrong backend configuration. -
SDK Dependency for Settings Persistence (PR #98): The settings persistence API changes depend on software-agent-sdk PR #3060 which adds:
/api/settingsGET/PATCH withX-Expose-Secrets: encryptedheader support/api/settings/secretsCRUD endpoints for custom secretsOH_SECRET_KEYenvironment variable for encryption
IMPORTANT: Until PR #3060 is merged and released,
npm run devmust useOH_AGENT_SERVER_GIT_REF=mainto point at the SDK main branch (or the feature branch), not a released PyPI version. The dev scripts now default tomainfor this reason. Once released, updatedev-safe.mjsto use the minimum required version. -
README expectation: keep the first section as a concrete, chronological from-scratch quickstart for running this frontend against a real
openhands-agent-server(clone, install uv, optional.env, runnpm run dev). -
Keep README user-focused and move contributor/developer-specific workflows (
dev:safe, mock mode, detailed env vars/build-test notes) intoDEVELOPMENT.md. -
scripts/dev-safe.mjsusesuvxfor temporary agent-server installation — no permanentuv tool installneeded. Environment variables:OH_AGENT_SERVER_VERSION— specific PyPI version (e.g., "1.18.0")OH_AGENT_SERVER_GIT_REF— git commit SHA or branch name (takes precedence over version)OH_SECRET_KEY— secret key for settings encryption; uses a default value for local dev, override for production- Default: latest released version from PyPI
-
scripts/dev-safe.mjsshould fail fast ifuvxcannot be spawned (for example missing PATH entries). -
npm run devnow runs the full stack with automation by default (viadev:automation). Usenpm run dev:minimalfor agent-server + Vite only. -
scripts/dev-with-automation.mjsruns the full stack: agent-server, automation backend (both via uvx), Vite dev server, and ingress proxy. Uses a standalone ingress proxy (scripts/ingress.mjs) to route traffic:/api/automation/*→ automation backend (:18001)/api/*,/sockets, etc. → agent server (:18000)/*(default) → Vite dev server (:3001)- Environment variables:
PORT(ingress port, default: 8000),OH_AUTOMATION_GIT_REF(default:main) - Access points:
http://localhost:8000/(main UI),http://localhost:8000/api/automation/docs(API docs)
-
scripts/ingress.mjsis a standalone HTTP reverse proxy that can be used independently to route traffic to multiple backends based on URL path prefix. -
scripts/dev-safe.mjs(nownpm run dev:minimal) runs just agent-server + Vite without automation. -
Vite dev mode can black-screen on first load with
504 Outdated Optimize Depif core client-entry deps are not prebundled; keepreact,react/jsx-runtime,react-dom/client, andreact-router/dominoptimizeDeps.include. -
Vercel deployment note: React Router builds for this repo must keep
build/clientintact on actual Vercel builds and includepresets: [vercelPreset()]from@vercel/react-router/vite; flatteningbuild/clientduring a Vercel build produces deployments with empty outputs (routes: null, no static files) and a production 404. -
The repo should include a root
LICENSEfile to satisfy the incubator-program requirements. -
OpenHands repo bootstrap files live under
.openhands/:.openhands/setup.shinstallsuv(viacurl -LsSf https://astral.sh/uv/install.sh | sh) if not present, installs frontend dependencies withnpm ciwhen needed, creates.envfrom.env.sampleif missing, appendsVITE_WORKING_DIRfor this repo when unset, and generatessrc/i18n/declaration.tsvianpm run make-i18n..openhands/pre-commit.shmirrors the repo's local quality gate withnpm run lint && npm run test.
-
GitHub PR-review automation should stay aligned with the current OpenHands repo conventions: keep the review workflow at
.github/workflows/pr-review-by-openhands.yml, keep the companion.github/workflows/pr-review-evaluation.yml, auto-run on newly opened non-draft PRs andready_for_reviewevents from established contributors, still support thereview-thislabel /openhands-agent/all-hands-botreviewer triggers, use the OpenHands app LLM proxy defaults, and use the dual-trigger pattern (pull_requestfor same-repo PRs,pull_request_targetfor forks) so workflow changes can self-verify without widening fork secret exposure. -
The repo now includes
.agents/skills/custom-codereview-guide.md, adapted fromOpenHands/software-agent-sdk, to force PR reviews to always leave either an APPROVE or COMMENT review instead of silently finishing with no review object. -
HeroUI rollback / migration notes:
- The attempted HeroUI v3 upgrade changed global theme wiring and homepage design tokens enough that the repo currently prefers
@heroui/react@2.8.10until a broader visual validation pass is done. - Keep the v2 Tailwind integration active via
@plugin '../hero.ts'insrc/tailwind.cssand source HeroUI classes fromnode_modules/@heroui/theme/dist/**/*. - The settings UI currently relies on the v2
Autocomplete+AutocompleteItem/AutocompleteSectionAPIs insettings-dropdown-input.tsxandmodel-selector.tsx; a future v3 retry will need to replace those controls again.
- The attempted HeroUI v3 upgrade changed global theme wiring and homepage design tokens enough that the repo currently prefers
-
Library i18n is now namespace-scoped under
openhands:src/i18n/index.tsexportsOPENHANDS_I18N_NAMESPACE,translationResources, andwaitForI18n(),scripts/make-i18n-translations.cjsemitspublic/locales/<lang>/openhands.json, standalonesrc/entry.client.tsxexplicitly awaits i18n init, and host apps can register bundles via the@openhands/agent-canvas/i18nsubpath export. -
Route decoupling note:
src/components/should stay free of directreact-routerimports. Route state now flows throughsrc/context/navigation-context.tsx, the standalone app bridges router state withsrc/routes/react-router-navigation-provider.tsx, and link-like UI should usesrc/components/shared/navigation-link.tsx. -
Test helper note:
test-utils.tsxnow wraps renders with a defaultNavigationProvider(currentPath: "/",conversationId: "test-conversation-id"). Navigation-sensitive tests can override that viarenderWithProviders(..., { navigation: { ... } }). -
CSS isolation for embeddable/hosted use now relies on a scoped wrapper attribute: all bundled CSS is prefixed under
[data-agent-server-ui]viapostcss-prefix-selectorinvite.config.ts, with selector exceptions handled bytransformAgentServerUISelector()insrc/styles/agent-server-ui-style-scope.ts. That transform must remap global selectors like:root,html, andbodydirectly onto the scoped shell instead of emitting impossible descendants such as[data-agent-server-ui] :root. -
Public embedding entry points should use
AgentServerUIProviders(scoped root on by default) orAgentServerUIRootfor manual control. The standalone app already renders its own scoped root insrc/root.tsx, sosrc/entry.client.tsxmust passwithStyleRoot={false}to avoid nesting duplicate shells. KeepAgentServerUIRootand the scoping constants re-exported fromsrc/lib/index.tsso library consumers can customize the host wrapper without reaching into private paths. -
AgentServerUIRoot's themed inner wrapper must set a defaultcolor: var(--foreground)in addition to thedark/data-thememarkers; otherwise inherited text andcurrentColorSVG icons fall back to dark browser defaults after CSS scoping, causing dark-on-dark regressions on pages like the home screen. -
Theme/customization tokens for the embedded shell are exposed as
--oh-*CSS variables. Override them throughstyleOverrides,style, or host CSS targeting[data-agent-server-ui]; Tailwind theme tokens insrc/tailwind.cssshould continue to reference those variables with@theme inlineso host apps can restyle the UI without reworking component class names. -
Regression coverage for the CSS isolation work lives in
__tests__/agent-server-ui-providers.test.tsx,__tests__/agent-server-ui-style-scope.test.ts, and the browser-leveltests/css-isolation.spec.tsPlaywright test. -
Library packaging notes:
- Public npm entrypoints now come from
src/index.ts→src/lib/index.ts, with domain barrels undersrc/components/{conversation,terminal,browser,files,settings,sidebar}/index.ts. npm run buildremains the standalone app build (react-router build), whilenpm run build:librunsvite buildin library mode plustsc -p tsconfig.lib.jsonto emit.d.tsfiles intodist/.- The library build relies on
vite.config.tswithBUILD_LIB=true, preserved modules indist/, and packageexportsentries that map root/subpaths todist/**/*.jsplus matching declaration files. - Declaration emit needs
src/library-env.d.tsand the narrowedtsconfig.lib.json; broadsrc/**/*.tsxdeclaration builds pulled in route-only files and missed?react/window globals.
- Public npm entrypoints now come from