mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 16:08:23 +08:00
d8143482639ffd53faec69e7dcce8dffe5aef74b
18
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2d7a3985b3 |
fix: spawn dev services without a shell on Windows (#1859)
On Windows, spawnService ran uvx through cmd.exe (shell: true), so the `<` in the `agent-client-protocol<0.11` version constraint was parsed as input redirection and agent-server exited immediately with "The system cannot find the file specified." Resolve the command to its absolute path with where.exe and spawn it directly, with no shell, so argument metacharacters stay literal. npm is unaffected: it is already wrapped in cmd.exe by buildNpmScriptCommand before it reaches spawnService. |
||
|
|
519c856c37 |
feat: support serving Canvas under a subpath (#1796)
* feat: support serving canvas under subpath Co-authored-by: openhands <openhands@all-hands.dev> * fix: redirect root app routes to canvas base path Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
b1ece3d1d3 |
fix: use dual-stack (::) binding for static-server to fix Docker e2e connection errors (#1104)
* fix: use dual-stack (::) binding for static-server to fix Docker e2e connection errors The Docker e2e tests suffered frequent ECONNREFUSED errors because static-server.mjs defaulted to 0.0.0.0 (IPv4-only), while localhost can resolve to ::1 (IPv6) on CI runners. Meanwhile, ingress.mjs (used by the npm path) already bound to :: (dual-stack) and never had this problem. Changes: - static-server.mjs: default host from 0.0.0.0 → :: (dual-stack) - docker/entrypoint.sh: --host 0.0.0.0 → --host :: for both static-server instances - playwright.mock-llm-docker.config.ts: switch URLs from 127.0.0.1 to localhost (now safe since the server accepts both IPv4 and IPv6) - playwright.mock-llm.config.ts: drop explicit --host 0.0.0.0 from public-mode server (inherits the new :: default) - dev-static.mjs, dev-with-automation.mjs: drop explicit --host 0.0.0.0 (inherits the new :: default) - AGENTS.md: replace IPv4-only guidance with dual-stack documentation Co-authored-by: openhands <openhands@all-hands.dev> * fix: skip partial-stack/cross-connect tests when build/ is absent (Docker e2e) The partial-stack and cross-connect tests spawn bin/agent-canvas.mjs locally, which requires a pre-built build/ directory. In the Docker e2e workflow there is no host-side build — the frontend lives inside the Docker image. These tests are already covered by the npm e2e workflow. Convert the hard expect(existsSync(...)).toBe(true) assertions to test.skip() so they are gracefully skipped instead of failing. Co-authored-by: openhands <openhands@all-hands.dev> * fix: add test.skip to port-conflict test for missing build dir The port-conflict test also spawns bin/agent-canvas.mjs --frontend-only, which fails before reaching the port conflict when no build/ exists. Co-authored-by: openhands <openhands@all-hands.dev> * fix: handle EPIPE/socket errors in static-server proxy to prevent crashes The static-server reverse proxy crashed with an unhandled 'error' event (EPIPE) when a client disconnected mid-response — e.g. during browser navigation or health-check probes. This killed the entire process and caused cascading ECONNREFUSED in subsequent Docker e2e tests. Add error handlers on all piped sockets (req, res, proxySocket, socket) so write errors from client disconnects are absorbed instead of crashing the server process. Also add test.skip for the port-conflict test when build/ is missing. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
5a3011c001 |
fix: align dev-stack OH_PERSISTENCE_DIR and automation DB path with Docker (#960)
* fix: align dev-stack OH_PERSISTENCE_DIR and automation DB path with Docker Two path mismatches between `npm run dev` and Docker prevented config and automation data from being shared across the two modes: 1. **OH_PERSISTENCE_DIR wrong (supersedes PR #959)** Docker's entrypoint.sh sets OH_PERSISTENCE_DIR to $HOME/.openhands (OPENHANDS_DIR). PR #959 added the env var to buildAgentServerEnv() but used config.stateDir (~/.openhands/agent-canvas) — one level too deep. Settings and secrets written by Docker live at ~/.openhands/settings.toml etc; dev wrote to ~/.openhands/agent-canvas/settings.toml. Fix: use path.dirname(config.stateDir) = ~/.openhands, matching Docker. 2. **Automation DB in wrong directory** dev-with-automation.mjs put the SQLite DB at ~/.openhands/agent-canvas/automations.db. Docker puts it at ~/.openhands/automation/automations.db (from config/defaults.json paths.automationDb = "automation/automations.db" relative to OPENHANDS_DIR). Fix: use join(dirname(config.stateDir), SHARED_DEFAULTS.paths.automationDb) so both modes resolve to ~/.openhands/automation/automations.db. Also ensure the directory is created on startup (mirrors Docker's mkdir -p). 3. **Mock-LLM test cleanup** Update playwright.mock-llm.config.ts to clean both STATE_DIR and the new AUTOMATION_DB_DIR (.tmp/automation/) before each test run, since the DB now lives outside STATE_DIR. Co-authored-by: openhands <openhands@all-hands.dev> * fix: use dev_conversations dir for npm to isolate from Docker conversations npm dev mode now writes conversations to ~/.openhands/agent-canvas/dev_conversations instead of conversations, keeping them separate from Docker's conversations dir. OH_CONVERSATIONS_PATH (set via buildAgentServerEnv) is the single control point; all mkdir and releaseStaleConversationLeases calls updated to match. Also condense verbose multi-line comments on OH_PERSISTENCE_DIR and AUTOMATION_DB_URL to single lines. Co-authored-by: openhands <openhands@all-hands.dev> * fix: use join(config.stateDir, dev_conversations) in ensureDirectories The outer config in dev-with-automation.mjs does not have conversationsPath (that is a SafeDevConfig property). Use the same join(stateDir, ...) pattern as the other dirs in the list. Also removes the debug console.log and restores dev-safe.mjs and dev-static.mjs to dev_conversations after the manual revert. Co-authored-by: openhands <openhands@all-hands.dev> * test: update conversationsPath assertion to match dev_conversations The implementation in buildConfigFromPorts was changed to use 'dev_conversations' as the subdirectory name, but the corresponding test assertion was not updated. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
14b1b1e8ad |
feat: two auth modes — local (auto-key) and public (paste-key) (#790)
* feat: two auth modes — local (auto-key) and public (paste-key) Local mode (agent-canvas, no flags): - Ingress binds to 127.0.0.1 only - Auto-generates session API key - Writes /backends.json to static dir so frontend auto-authenticates - Zero setup for localhost use Public mode (agent-canvas --public): - Ingress binds to 0.0.0.0 (all interfaces) - Requires LOCAL_BACKEND_API_KEY env var - Does NOT write /backends.json - Frontend shows API key entry screen on 401 Co-authored-by: openhands <openhands@all-hands.dev> * refactor: reuse BackendForm in public-mode API key entry screen Replace the bespoke ApiKeyEntryScreen form with BackendForm configured for the public-auth use case: - Host field is auto-filled from window.location.origin and read-only - Name field is hidden (auto-derived from the existing backend) - Only the API key input is exposed to the user - Uses the same SettingsInput / BrandButton components as the backend connection modals for visual consistency BackendForm gains three optional props to support this: - hideName: hides the name input and uses a fallback name - hostReadOnly: disables the host input - onSubmitPayload: receives the submitted payload for side-effects (the API key screen uses it to persist to agent-server-config and reload the page) Co-authored-by: openhands <openhands@all-hands.dev> * docs: update AGENTS.md with ApiKeyEntryScreen BackendForm reuse details Co-authored-by: openhands <openhands@all-hands.dev> * feat: implement public mode auth flow (--public flag) - Add --public flag to dev-with-automation.mjs and bin/agent-canvas.mjs - In public mode: require LOCAL_BACKEND_API_KEY, use as session key, don't bake into frontend (no VITE_SESSION_API_KEY / --session-api-key) - Add isAgentServerAuthError() to detect 401 from /server_info probe - root.tsx shows ApiKeyEntryScreen when 401 detected (lazy loaded) - useConfig skips retries on 401 for instant auth screen display - ApiKeyEntryScreen now has default export for React.lazy compatibility Co-authored-by: openhands <openhands@all-hands.dev> * fix: use VITE_AUTH_REQUIRED flag instead of 401 detection for public mode The 401-based approach was unreliable — /server_info may not require auth on all server versions. Instead: - dev-with-automation.mjs sets VITE_AUTH_REQUIRED=true in public mode - isAuthRequiredAndMissing() checks the flag + localStorage for a key - root.tsx gates on the flag BEFORE the /server_info probe, so the auth screen appears instantly with zero network round-trips - 401 fallback kept as safety net for edge cases Co-authored-by: openhands <openhands@all-hands.dev> * fix: handle stale key via 401 detection in public mode When the server restarts with a new LOCAL_BACKEND_API_KEY, the browser still has the old key in localStorage. isAuthRequiredAndMissing() returns false (key exists), so the /server_info probe fires and 401s. isAgentServerAuthError() now checks VITE_AUTH_REQUIRED=true AND 401 status, so it only triggers in public mode (a 401 in local mode is a misconfiguration, not a key-rotation event). useConfig skips retries on 401 to show the auth screen immediately. Two gates, one screen: - No key at all → flag check, instant, no network - Stale key → /server_info 401, one round-trip Co-authored-by: openhands <openhands@all-hands.dev> * fix: validate stale keys against GET /api/settings (protected) /server_info is unprotected — it returns 200 even with a wrong key. In public mode, after the /server_info probe succeeds, we now hit GET /api/settings to verify the stored key is still valid. A 401 from that endpoint triggers the auth screen via isAgentServerAuthError(). Co-authored-by: openhands <openhands@all-hands.dev> * fix: rewrite ApiKeyEntryScreen — validate before save, always empty key, match add-modal UI Three fixes: 1. Stale key conflict: The form now always starts with an empty API key field instead of pre-filling from the backend registry. Stale credentials from a previous session never bleed into the input. 2. Wrong key indicator: On submit, the key is validated against GET /api/settings (protected endpoint) BEFORE persisting. Wrong keys show an inline red status dot + 'Invalid API key' error via BackendStatusDot. Only validated keys trigger the reload. 3. UI parity with add-backend modal: Replaced BackendForm wrapper with direct SettingsInput fields matching ManualConnectionColumn's layout — host (read-only + helper text), API key (password with placeholder), status indicator, and Connect button. No cloud OAuth column. New i18n key: AUTH$INVALID_KEY (all 15 languages). Co-authored-by: openhands <openhands@all-hands.dev> * feat: match add-backend modal UI + add test coverage ApiKeyEntryScreen now renders the exact same card chrome as BackendFormModal add-mode: same title ('Add a Backend'), same Name/Host/API Key fields, same Connect button styling. Host is pre-filled and read-only; no cloud OAuth column. New tests (12 total): - api-key-entry-screen.test.tsx (7 tests): - UI field parity with add-backend modal - Stale key wipe (empty API key field despite stale localStorage) - Connect disabled until name + key filled - Valid key: validates → persists → reloads - Invalid key: error indicator, no persist, no reload - Retry flow: wrong key → error → correct key → success - Stale key isolation: only fresh key persisted - agent-server-config.test.ts (5 new tests for isAuthRequiredAndMissing): - Flag unset → false - Flag set, no key → true - Flag set, localStorage key → false - Flag set, VITE_SESSION_API_KEY → false - Flag not 'true' → false Co-authored-by: openhands <openhands@all-hands.dev> * fix: distinguish 401 from other errors in ApiKeyEntryScreen The catch-all was showing 'Invalid API key' for EVERY failure — including 500s, network errors, and timeouts — even when the key was correct. Now: - 401 → 'Invalid API key. Please check the key and try again.' - Anything else → 'Connection failed: <actual error message>' This reveals the real problem when a correct key fails for a non-auth reason (e.g. server misconfiguration, missing OH_SECRET_KEY). New i18n key: AUTH$CONNECTION_FAILED (all 15 languages). New test: non-401 errors show 'Connection failed' + detail. Co-authored-by: openhands <openhands@all-hands.dev> * fix: agent-server receives wrong session key in public mode startAgentServer() called buildSafeDevConfig() which generated its own random session key, ignoring config.sessionApiKey (which holds LOCAL_BACKEND_API_KEY in public mode). The agent-server was started with a random key while users were told to paste the LOCAL_BACKEND_API_KEY value — every key was rejected with 401. Fix: override OH_SESSION_API_KEYS_0 in the agent-server env with config.sessionApiKey so both the agent-server and the frontend agree on which key is valid. Co-authored-by: openhands <openhands@all-hands.dev> * refactor: address review comments on ApiKeyEntryScreen 1. Remove dead BackendFormProps (hideName, hostReadOnly, onSubmitPayload) — ApiKeyEntryScreen is standalone so no caller used these props. 2. Auto-generate backend name from window.location.hostname instead of requiring users to type one. Only the API key field is required now, reducing public-mode auth to a single-field flow. 3. Simplify redundant ternary: connectionStatus === 'success' ? true : false → connectionStatus === 'success'. Co-authored-by: openhands <openhands@all-hands.dev> * fix: address second round of review comments 1. Use shared isSdkHttpError() helper in ApiKeyEntryScreen instead of duplicating the SDK error shape check inline. Exported the helper from agent-server-compatibility.ts. 2. Add code comment acknowledging the edge case where a network hiccup between /server_info and getSettings() probes lets the app load with an unvalidated key. Acceptable since the window is narrow and a page refresh recovers. 3. Add --auth-required flag to static-server.mjs so pre-built static binaries (npx @openhands/agent-canvas --public) show the API key entry screen without needing VITE_AUTH_REQUIRED baked in at build time. The flag injects window.__AGENT_CANVAS_AUTH_REQUIRED__=true into index.html at runtime. Frontend isAuthRequired() checks both the build-time env var and the runtime window flag. Co-authored-by: openhands <openhands@all-hands.dev> * fix: use double cast (unknown) to satisfy strict TS on window flag access window cannot be cast directly to Record<string, unknown> — TypeScript requires going through unknown first for unrelated types. (window as unknown as Record<string, unknown>).__AGENT_CANVAS_AUTH_REQUIRED__ This fixes the CI typecheck failure introduced in aa76c01a. Co-authored-by: openhands <openhands@all-hands.dev> * fix: address remaining review comments on auth modes PR - Use isAuthRequired() instead of raw import.meta.env.VITE_AUTH_REQUIRED in isAgentServerAuthError() so the runtime window flag injected by static-server.mjs in pre-built binaries is also honoured (bug fix). - Preserve existing backend name during re-authentication flow in ApiKeyEntryScreen; only fall back to window.location.hostname for the initial entry so users don't lose custom labels on key rotation. Co-authored-by: openhands <openhands@all-hands.dev> * fix: restore MCP-to-integrations migration from main A prior merge into this branch incorrectly kept the old @openhands/extensions/mcps imports instead of the @openhands/extensions/integrations paths introduced by d41bfe15 on main. Restore all affected files from origin/main so the extensions package (which no longer exports ./mcps) resolves correctly. Files restored from main: - src/utils/mcp-marketplace-utils.ts - src/routes/mcp.tsx - src/components/features/mcp-logo-badge.tsx - src/components/features/mcp-page/* (6 files) - src/components/features/automations/* (2 files) - __tests__/ (4 test files) Co-authored-by: openhands <openhands@all-hands.dev> * style: fix prettier formatting and remove unused eslint-disable in api-key-entry-screen Co-authored-by: openhands <openhands@all-hands.dev> * fix: address remaining PR review comments - Extract isSdkHttpStatusError() helper in agent-server-compatibility.ts to DRY up the SDK error status check (review comment #3321202503). Both isAgentServerAuthError() and ApiKeyEntryScreen now use it. - Use AUTH i18n keys in api-key-entry-screen.tsx: • Heading: AUTH$API_KEY_REQUIRED_TITLE ('API Key Required') • Description: AUTH$API_KEY_REQUIRED_DESCRIPTION added below heading • Button: AUTH$CONNECT ('Connect') (review comments #3325478721, #3325478729) - Fix nested <main> landmark in root.tsx: remove the outer <main> wrapper since ApiKeyEntryScreen already provides its own semantic container (review comment #3325478704). - Change ApiKeyEntryScreen root element from <main> to <div> so the Layout's own landmarks are not violated. Co-authored-by: openhands <openhands@all-hands.dev> * test: add coverage for window.__AGENT_CANVAS_AUTH_REQUIRED__ runtime flag Add isAuthRequired() test block covering the window flag path used by pre-built static binaries (static-server.mjs --auth-required). Also add window-flag variants to isAuthRequiredAndMissing() tests. Addresses review comment #3325587577. Co-authored-by: openhands <openhands@all-hands.dev> * refactor!: deduplicate SESSION_API_KEY into LOCAL_BACKEND_API_KEY BREAKING CHANGE: The user-facing env var for setting the API key is now `LOCAL_BACKEND_API_KEY` everywhere. The old `SESSION_API_KEY`, `OH_SESSION_API_KEYS_0`, and `VITE_SESSION_API_KEY` env vars are no longer read by launchers as user-facing configuration. Internal plumbing (`config.sessionApiKey`, `VITE_SESSION_API_KEY` build injection, `OH_SESSION_API_KEYS_0` agent-server env) is unchanged — only the user-facing surface is unified into a single env var. Changes: - scripts/dev-safe.mjs: read LOCAL_BACKEND_API_KEY instead of SESSION_API_KEY / OH_SESSION_API_KEYS_0 / VITE_SESSION_API_KEY - scripts/dev-with-automation.mjs: unify key resolution through buildSafeDevConfig for both public and local modes - bin/agent-canvas.mjs: update CLI help text and examples - docker/entrypoint.sh: read LOCAL_BACKEND_API_KEY, migrate legacy session-api-key.txt → api-key.txt - scripts/static-server.mjs: add mutual-exclusion guard for --session-api-key + --auth-required flags - playwright configs: pass LOCAL_BACKEND_API_KEY instead of the old trio - test helpers: prefer LOCAL_BACKEND_API_KEY fallback chain - Update tests and documentation Co-authored-by: openhands <openhands@all-hands.dev> * fix: address review comments — narrow settings probe rethrow and use shared client options - loadAgentServerInfo: narrow getSettings() catch to rethrow only 401 errors. Other HTTP errors (403, 5xx) and non-HTTP errors (network, timeout) are now swallowed with a console.warn, since the server is confirmed up (via /server_info) and the probe is best-effort. This prevents misconfigured servers from silently falling through to <Outlet /> without showing either the auth or unavailable screen. - ApiKeyEntryScreen: replace hand-rolled SettingsClient options with getAgentServerClientOptions() so transport-level settings (e.g. VITE_INSECURE_SKIP_VERIFY) are honoured. Uses the sessionApiKey override to pass the freshly-entered key. Co-authored-by: openhands <openhands@all-hands.dev> * fix: rewrite git+ssh to git+https for @openhands/extensions in lockfile npm normalizes GitHub URLs to git+ssh:// in the lockfile, but machines without SSH keys for GitHub (or with stale npm caches) can end up installing a wrong version of the package. This causes the Vite resolve error: "./integrations" is not exported under the conditions [...] The same pattern was already fixed for @openhands/typescript-client (see #384). vercel-install.sh already does a blanket sed rewrite, but the committed lockfile itself should use git+https:// so local npm ci works without SSH keys. Co-authored-by: openhands <openhands@all-hands.dev> * refactor: align public auth screen with Add Backend form layout Replace the custom 'API Key Required' screen with the same form layout used by the 'Add a Backend' left column in BackendFormModal: - Heading changed from 'API Key Required' to 'Add a Backend' - Added backend Name field (required, same as ManualConnectionColumn) - Host field remains pre-filled and disabled (from window.location.origin) - API Key field unchanged - Submit button now uses BACKEND$CONNECT label (matching the modal) - Removed the subtitle description paragraph for cleaner parity - Name is persisted to the backend registry on submit Tests updated: fillApiKey → fillRequiredFields (name + apiKey), assertions cover the new name field and dual-field submit gating. Co-authored-by: openhands <openhands@all-hands.dev> * chore: remove unused AUTH$ i18n keys from this PR The UI refactor (02faa0b0) switched ApiKeyEntryScreen to the BACKEND$* keys. Drop the three AUTH$ entries that were introduced and then superseded within this same PR: - AUTH$API_KEY_REQUIRED_TITLE - AUTH$API_KEY_REQUIRED_DESCRIPTION - AUTH$CONNECT Co-authored-by: openhands <openhands@all-hands.dev> * fix: sync stale session API key on boot when LOCAL_BACKEND_API_KEY changes When a user restarts the stack with a different LOCAL_BACKEND_API_KEY, the new VITE_SESSION_API_KEY is baked in correctly, but localStorage may still hold the old key in two places: 1. openhands-agent-server-config.sessionApiKey (written by onboarding or the Settings page) 2. openhands-backends[].apiKey (seeded on first load, never re-synced) The existing syncDefaultLocalBackendAuth() in storage.ts already tries to fix #2 by comparing against makeDefaultLocalBackend(), but that function reads through getConfiguredSessionApiKey() which hits #1 (stale localStorage) before falling back to VITE_SESSION_API_KEY. So a stale #1 defeats the #2 sync. Fix: add syncBakedSessionApiKey() which runs from readStoredBackends() before any key resolution. When VITE_SESSION_API_KEY is set and the stored key in openhands-agent-server-config differs, overwrite it. This ensures getConfiguredSessionApiKey() and makeDefaultLocalBackend() both return the correct key, and the downstream backend-registry sync works as intended. Also fix the static-server.mjs injection script to always overwrite a stored key that differs from the runtime key (was guarded by `if(!_c.sessionApiKey)` which skipped updates when any key existed). Add mock-LLM E2E tests for: - Key rotation recovery: seeds stale localStorage, verifies app loads - Public-mode auth gate: tests auth screen visibility, wrong key rejection, and correct key acceptance Co-authored-by: openhands <openhands@all-hands.dev> * docs: document key rotation resilience in AGENTS.md Co-authored-by: openhands <openhands@all-hands.dev> * fix: add syncBakedSessionApiKey to vi.mock stubs and use click-then-fill in E2E Three test files mock #/api/agent-server-config without exporting syncBakedSessionApiKey, which storage.ts now calls at import time. Add the missing vi.fn() stub to all three. Also fix the public-mode auth E2E test: use the click() → fill() pattern for React controlled inputs (matching the established convention in mock-llm-conversation.spec.ts) so the SettingsInput onChange fires reliably in Playwright. Co-authored-by: openhands <openhands@all-hands.dev> * test: add public-mode key rotation E2E test Simulates a server key rotation: localStorage holds a stale key from a previous session, the server now has a new key. Verifies the app detects the 401 from the stale key probe, shows the auth screen, and accepts the new key. Flow: stale key in localStorage → probe /server_info → 401 → isAgentServerAuthError → ApiKeyEntryScreen → user pastes new key → reload → app loads normally. Co-authored-by: openhands <openhands@all-hands.dev> * fix(e2e): suppress consent modal in public-mode auth tests The analytics consent modal overlays the auth screen on first visit (clean localStorage). Playwright's click() on the form inputs was intercepted by the modal overlay, causing a 60s timeout loop (121 retries). Add a beforeEach that seeds 'analytics-consent' and 'openhands-telemetry-consent' in localStorage before navigation. Also deduplicate the consent seeding from the key-rotation test's addInitScript since the beforeEach now handles it. Co-authored-by: openhands <openhands@all-hands.dev> * refactor: deduplicate readStoredConfig() call in syncBakedSessionApiKey Capture the first readStoredConfig() result and reuse it in the spread instead of hitting localStorage twice. Co-authored-by: openhands <openhands@all-hands.dev> * chore(docker): remove legacy session-api-key.txt migration The backwards-compatibility shim that migrated the old session-api-key.txt to api-key.txt is no longer needed — a breaking change here is acceptable. Co-authored-by: openhands <openhands@all-hands.dev> * refactor: dedup ApiKeyEntryScreen against BackendForm ApiKeyEntryScreen now renders BackendForm with three new props instead of reimplementing the name/host/API-key inputs from scratch: - hostReadOnly: locks the host field (pre-filled from window.origin) - requireApiKey: forces a non-empty API key for local backends - onSubmitOverride: replaces the default sync persist with async server-side validation (GET /api/settings) before persisting The auth-gate-specific chrome (full-screen wrapper, connection status indicator, validating/error state) stays in ApiKeyEntryScreen via the existing renderActions slot. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: chuckbutkus <chuck@openhands.dev> |
||
|
|
cbeeee002e |
fix: inject runtime session key into index.html for published binary (#795)
* fix: inject runtime session key into index.html for published binary
The globally installed agent-canvas binary starts the agent-server with a
persisted session API key (~/.openhands/agent-canvas/session-api-key.txt)
as OH_SESSION_API_KEYS_0, making auth required. However, the pre-built
static frontend in the npm package has a different (or empty)
VITE_SESSION_API_KEY baked in at publish time, so every API request gets
401 Unauthorized.
Fix: static-server.mjs now accepts --session-api-key <key> and injects a
tiny bootstrap <script> before </head> in every index.html response. The
script seeds the key into localStorage['openhands-agent-server-config']
only if no key is already stored there, so explicit user overrides (via
Settings > Agent Server) are always preserved.
dev-with-automation.mjs and dev-static.mjs both pass
--session-api-key ${config.sessionApiKey} when spawning the static server,
so the runtime key is always available regardless of what was baked into
the bundle.
Tests: added 8 new cases to __tests__/scripts/static-server.test.ts
covering parseArgs, injection in direct and SPA-fallback index.html
responses, no injection for non-html assets, cache headers, and null key.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(docker): pass runtime session key to static-server so frontend can authenticate
The entrypoint computed EFFECTIVE_SESSION_KEY and forwarded it to the
agent-server (OH_SESSION_API_KEYS_0) and automation backends, but did not
pass it to the static-server. As a result the pre-built index.html served
with no session key injected, so every browser API call received 401.
Wire --session-api-key "$EFFECTIVE_SESSION_KEY" into the static-server
launch command so the runtime key is injected into index.html responses
(via the mechanism added in this branch to static-server.mjs).
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: address review suggestions on session key injection
- static-server.mjs: add comment clarifying replace() targets first
</head> only; fall back to inserting before </body> when </head> is
absent (avoids prepending before <!DOCTYPE html>)
- docker/entrypoint.sh: add comment documenting source of
EFFECTIVE_SESSION_KEY before the static-server invocation
- static-server.test.ts: add test for </head>-absent fallback path
confirming injection lands before </body>
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Rohit Malhotra <rohitvinodmalhotra@gmail.com>
|
||
|
|
e2dd1b5f17 |
fix: unify session and automation API keys into a single credential with consistent header (#681)
* fix: unify session and automation API keys into a single credential Both the agent-server and automation backend now share the same API key value. The agent-server validates it via `X-Session-API-Key` and the automation backend validates it via `Authorization: Bearer …` — different header formats, same credential. Changes: - Frontend: automation axios client reads `VITE_SESSION_API_KEY` instead of the now-removed `VITE_AUTOMATION_API_KEY` - Dev launcher: removed separate `AUTOMATION_LOCAL_API_KEY` generation and persistence (`automation-api-key.txt`); `localApiKey` is set to `sessionApiKey` so both backends receive the same value - Static build: stopped baking `VITE_AUTOMATION_API_KEY` (the frontend reads from `VITE_SESSION_API_KEY`) - Docker entrypoint: `OPENHANDS_AUTOMATION_API_KEY`, `AUTOMATION_LOCAL_API_KEY`, and `AUTOMATION_AGENT_SERVER_API_KEY` all default to the session key when not explicitly overridden - Tests updated to verify unified key behavior Fixes the 401 on `/api/automation/v1` when the automation backend is running but no separate `VITE_AUTOMATION_API_KEY` was configured. Co-authored-by: openhands <openhands@all-hands.dev> * fix: use X-Session-API-Key header for automation backend auth (consistent with agent-server) Switch automation backend requests from `Authorization: Bearer …` to `X-Session-API-Key` header, matching the agent-server's auth pattern. Both backends now authenticate using the same header and the same key value (`VITE_SESSION_API_KEY`). Co-authored-by: openhands <openhands@all-hands.dev> * fix: address review — remove localApiKey alias, dead constant, add entrypoint guard - Remove `localApiKey` from config; all call sites now use `config.sessionApiKey` directly, making the unified-key intent obvious. - Delete `DEFAULT_AUTOMATION_API_KEY_PATH` constant and its export (no downstream consumers in beta). - Add fail-fast guard in docker/entrypoint.sh when no session key is available, instead of silently exporting empty strings. Co-authored-by: openhands <openhands@all-hands.dev> * fix: update stale comment on AUTOMATION_LOCAL_API_KEY to reflect unified session key Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
59058c7609 |
fix(ui): left navigation rail, mobile drawer, and responsive chrome (#623)
* fix: archived row icon, default-local config sync, and dockerless dev fixes Archived MISSING sandboxes show an archive icon in the status column instead of a gray dot and pill; ERROR sandboxes keep the error pill. Harden port checks and automation CORS for alternate frontend ports, set agent-server HOME to the host home on macOS, sync legacy agent-server config when editing the default-local backend, and clarify static stack rebuild/skip-build behavior. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): sidebar conversation list flush to rail with stable gutter Drop expanded aside `md:pr-0` so the thread scrollbar aligns with the rail while nav, logo row, and footer keep horizontal padding. Use scrollbar-gutter on the conversation list for consistent right inset with or without overflow. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): 40px backend selector and non-italic combobox text Pin the backend Dropdown trigger to h-10, add an italicPlaceholder escape hatch, and force upright type for value/placeholder in the selector. Add a subtle top border above Add Workspace in the new-conversation menu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): full-bleed sidebar footer divider to left rail Pull the backend block border past aside `pl-2` with matching width calc; keep inner `px-2` so controls stay aligned with nav. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): align context menus with sidebar filter menu and muted row icons Match list padding, border, and shadow to the conversations filter surface; use theme foreground/muted tokens; reserve leading icons as muted until row hover/focus. Simplify list-item rows and remove unused context-menu height constant. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): unify menu dividers, section labels, and filter actions Standardize full-bleed menu dividers via Divider inset="menu", give filter menu section headings consistent pt-1 padding, and add icons to hide/show and delete-all rows in the conversation panel filter menu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): rename sidebar conversations link to New Chat The /conversations nav entry should read "New Chat" instead of "Code" to match user expectations for starting a conversation. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): polish cloud new-thread popover search and repo list Use a flat search row with icon and menu divider, align horizontal padding with list items, and show a custom scrollbar on the repository list. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): unify filter menu section headings and action icons Share pt-1 section label padding via MenuHeading, fold hide/delete rows into MenuRow with Eye and Trash icons, and add a regression test for both actions. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): add settings tooltip and neutral delete-all row Show a white hover tooltip on the backend selector settings button and style Delete all like other filter menu actions instead of danger red. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): theme scrollbars/skeletons and improve settings tooltips Derive scrollbar colors from the cool-grey scale, use interactive-active for skeleton loaders, and fix settings tooltip placement when the drawer is open plus collapsed-sidebar coverage. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): replace mobile top nav with left drawer On small screens, hide the horizontal sidebar strip and open the full vertical nav from a top-bar chevron toggle, matching the desktop collapse control icon. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): restore mobile page gutters and center home title Apply 14px horizontal padding in the root outlet on mobile, keep conversation full-bleed, and center the home heading with w-full text-center. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): correct mobile nav icons and home title line height Use PanelLeft for the mobile menu trigger, ChevronLeft to close the drawer, and relax the home headline leading when it wraps. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): remove border under mobile nav menu bar The top-bar menu trigger no longer draws a divider above page content. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ui): mobile settings/customize hubs and animated nav drawer On mobile, Settings and Customize open list hubs matching desktop left nav; detail pages show drawer and back controls in the top bar. The slide-out nav drawer now animates open and closed with a fading scrim. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): keep sidebar icons aligned when collapsing the rail Use shared h-10 row geometry and a fixed icon column so collapsed and expanded states share padding and gap; labels clip instead of recentering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): stabilize collapsed sidebar icon column and hover targets Use a shared 18px icon slot in both rail states, square collapsed controls for hover/active, and symmetric rail padding so icons and the logo stay aligned without full-width row highlights. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): refine collapsed sidebar slots and empty-list load-more Use consistent px-2.5 rail padding with 40×40 collapsed icon slots via SidebarCollapsedIconSlot, fix backend dot anchoring and logo alignment, and hide “Load more” when no conversations are visible after filtering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): keep conversations header on one line during sidebar resize Use flex-nowrap with a truncating title so the toolbar row does not wrap while the drawer animates, and nudge the collapsed backend status dot up-left. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): align DropdownMenu padding and tighten backend footer actions Use uniform `p-1` on the combobox menu panel to match other menus, and remove vertical gap between Add/Manage backend items in the selector footer. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): trim vertical padding on cloud repo search row Drop wrapper `py-1` so the new-conversation repo search aligns with the dropdown chrome; horizontal inset stays `px-2`. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): flush cloud repo menu divider against search and list Drop flex `gap-1` on the popover so the rule sits tight to the search row and repository list; keep tab stripe spacing with `py-1` on the provider row. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): show backend settings tooltip above when sidebar is expanded Use `useSidebarCollapsed()` so the gear tooltip uses top placement on the full-width rail while keeping left placement for the icon-only strip unless the conversation right drawer is open. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): use full-screen route for mobile tools panel and tidy conversation chrome Restore horizontal inset on the conversation route, fold the sidebar opener into the chat header, and open Files/Tools via `/conversations/:id/panel` with a dedicated back affordance instead of a bottom sheet. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): only show sidebar drawer toggle when the rail is hidden The hamburger used the 1024px layout breakpoint while the sidebar stays visible from the `md` rail width up, so it duplicated chrome between tablet widths. Gate the toggle and header padding on the same max-md width as the rail (<=767px). Mirror the right-panel icon horizontally for the right-side drawer. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): align mobile conversation chrome, chat padding, and settings scroll Unify mobile top-bar icon buttons with shared classes; add compact conversation tabs and consistent horizontal padding for chat plus stable composer/git chrome. Move settings and extensions horizontal inset into layout helpers and drop the root outlet gutter so pages control their own padding. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): home mobile padding and Lucide sizes in chat mobile header Add px-4 to the home shell on small viewports and shift launcher inset to md+ only. Pin PanelLeft/ChevronLeft to 20px to match the right-panel icon and restore chat header left inset when the rail menu toggle shows. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): use block-drawer icon for mobile sidebar toggle Match the right-panel control’s SVG so both header icons share the same optical weight instead of Lucide PanelLeft filling the hit target. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: remove unrelated file * refactor: remove unrelated files * fix: failing tests --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
edb998220a |
Remove Docker dependency from dev workflow (#635)
- Delete scripts/dev-docker.mjs and its test - Simplify package.json: 'npm run dev' now runs local uvx stack directly (agent-server + automation + Vite + ingress), no Docker needed - Remove dev:docker, dev:docker:dynamic, dev:dangerously-dockerless scripts - Add dev:static for production-build frontend variant - Update bin/agent-canvas.mjs CLI to use uvx-based stack - Rename Docker-specific variables: DOCKER_PROJECTS_PATH → PROJECTS_PATH, shouldDefaultToDockerProjects → shouldDefaultToProjectsPath - Update i18n: HOST_HOME_NOT_MOUNTED_HINT no longer references Docker - Update all docs (README, DEVELOPMENT, SELF_HOSTING, AGENTS.md, CHANGELOG) - Rename e2e snapshot: docker-workspace-browser → projects-workspace-browser - Fix all tests to reflect new script names and remove Docker references Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
90ad71dbd9 |
Add recommended automations and MCP marketplace setup flow (#504)
* Add recommended automations marketplace flow Co-authored-by: openhands <openhands@all-hands.dev> * Update GitHub MCP QA findings Co-authored-by: openhands <openhands@all-hands.dev> * Polish MCP and automation marketplace UI Add a shared MCP logo badge, make marketplace cards more compact, and show required MCP logos prominently on recommended automation cards. Update the extensions package lock to the per-entry catalog split and save QA screenshots/results in .pr/. Co-authored-by: openhands <openhands@all-hands.dev> * Align recommended automations styling Remove the custom gradient treatment and match the recommended automation cards and setup modal to the existing Automations and MCP page surfaces, spacing, borders, and typography. Co-authored-by: openhands <openhands@all-hands.dev> * Show existing automations before recommendations Move the recommended automations section below the current automation list and creation guidance so the page prioritizes the user existing automation state. Co-authored-by: openhands <openhands@all-hands.dev> * Add recommendations to onboarding Show recommended automations below the Say Hello input so new users can launch a curated automation from the final onboarding step. Co-authored-by: openhands <openhands@all-hands.dev> * Use extensions MCP marketplace exports Remove the local MCP marketplace wrapper and consume MCP catalog data plus logo mappings directly from @openhands/extensions/mcps. Co-authored-by: openhands <openhands@all-hands.dev> * Archive previous PR QA and add refreshed artifacts Co-authored-by: openhands <openhands@all-hands.dev> * Add scheduled automation QA evidence Co-authored-by: openhands <openhands@all-hands.dev> * Streamline recommended automation launch Co-authored-by: openhands <openhands@all-hands.dev> * Refresh QA evidence and remove old artifacts Co-authored-by: openhands <openhands@all-hands.dev> * Ensure canvas tools are on Python path * Require MCP installs before launching recommendations * Remove archived PR artifacts * Drop redundant PYTHONPATH launcher changes * Inline recommended automation catalog * Point extensions dependency at main * Augment recommended automation prompts with explicit API instructions When a recommended automation is selected, the pre-filled prompt now includes backend-specific API instructions so the agent calls the correct endpoint: - Local backends: directs the agent to use the local automation API from <RUNTIME_SERVICES> with $OPENHANDS_AUTOMATION_API_KEY auth, and explicitly tells it NOT to call the cloud API at app.all-hands.dev. - Cloud backends: directs the agent to use the OpenHands Cloud Automations API at app.all-hands.dev with Bearer $OPENHANDS_API_KEY. The buildAutomationPrompt() helper is exported for testability. Three new unit tests cover both backend kinds and prompt preservation. Co-authored-by: openhands <openhands@all-hands.dev> * Fix recommended automation launch regressions Co-authored-by: openhands <openhands@all-hands.dev> * Expose local automation API key to agent terminals Co-authored-by: openhands <openhands@all-hands.dev> * Stabilize conversation panel stop menu test Co-authored-by: openhands <openhands@all-hands.dev> * chore: Remove PR-only artifacts * fix: pin @openhands/extensions to specific commit for reproducibility Updates the dependency from #main to the exact commit SHA (3bba8e3b) that contains the MCP and automation catalogs added in extensions#237. This ensures reproducible builds since npm ci will use the locked SHA instead of potentially picking up a newer main. * Address final automation marketplace review comments Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com> |
||
|
|
4db59b8b94 |
Proxy agent-server FastAPI docs (/docs, /redoc, /openapi.json) through the ingress (#501)
* feat(ingress): route /docs to the agent server Add /docs to the list of prefixes proxied to the agent-server in: - vite.config.ts (Vite dev server proxy) - scripts/dev-with-automation.mjs (ingress + static-server fallback) - scripts/dev-static.mjs (ingress + static-server fallback) Update the explanatory comment in scripts/static-server.mjs to match. This exposes the agent-server's FastAPI Swagger UI at `/docs` on the ingress port, alongside the automation backend's existing `/api/automation/docs`. Co-authored-by: openhands <openhands@all-hands.dev> * feat(ingress): also route /redoc and /openapi.json to the agent server Without /openapi.json, the Swagger UI page served at /docs (added in the previous commit) renders but fails to load any spec. /redoc is the FastAPI-served ReDoc alternative and benefits from the same fix. Routes are added everywhere /docs already is: - vite.config.ts (Vite dev server proxy) - scripts/dev-with-automation.mjs (ingress + static-server fallback) - scripts/dev-static.mjs (ingress + static-server fallback) Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
e5711e74b2 |
Clean up dev stack process trees on shutdown (#475)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
3f28f2d625 |
Fix static automation agent-server auth (#442)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
a4089e0e4a |
Default user launchers to static frontend (#434)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
18e51fa84d |
fix: move TMUX_TMPDIR to /tmp to avoid socket errors on mounted volumes (#325)
* fix: move TMUX_TMPDIR to /tmp to avoid socket errors on mounted volumes Some filesystems (NFS, CIFS, certain FUSE/overlay mounts used by Docker bind-mounts) do not support Unix domain sockets. When TMUX_TMPDIR pointed to ~/.openhands/agent-canvas/tmux/ inside a container, tmux failed with: error connecting to .../tmux-10001/openhands (Operation not supported) Move tmux socket directory to /tmp/openhands-agent-canvas-tmux which is always on a local/tmpfs filesystem that supports Unix sockets. Tmux sockets are ephemeral and don't need persistence across restarts. Co-authored-by: openhands <openhands@all-hands.dev> * refactor: drop explicit TMUX_TMPDIR from dev-docker.mjs, use system default Per review feedback — the container's default TMUX_TMPDIR (/tmp) already supports Unix domain sockets, so there's no need to set it explicitly. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
3207d90e72 |
feat: add dynamic port allocation with preferred port fallback (#223)
* feat: add dynamic port allocation with preferred port fallback Implement dynamic port allocation for dev entrypoint scripts to gracefully handle port conflicts. When a preferred port is busy, the system automatically finds an alternative available port. Changes: - Add findFreePort() and findFreePorts() utilities to dev-safe.mjs - Add buildSafeDevConfigAsync() for async config with dynamic allocation - Update dev-with-automation.mjs to use async buildConfig with dynamic ports - Update dev-static.mjs to use async buildConfig - Add strictPort: true to vite.config.ts to fail-fast on conflicts - Update tests for async buildConfig The utilities try the preferred/default ports first, falling back to OS-assigned ports only when needed. This preserves predictable defaults while gracefully handling port conflicts. Closes #222 * fix: address review feedback - add max retry, document race condition, improve tests - Add max retry count (100 attempts) to port allocation loop to prevent infinite loops - Fix findFreePort to handle preferredPort=0 correctly by skipping the port check and going straight to OS assignment - Document race condition limitation in findFreePort JSDoc (accepted limitation with guidance on handling EADDRINUSE) - Clarify JSDoc for buildSafeDevConfig vs buildSafeDevConfigAsync with clear guidance on when to use each - Remove misleading 'must be after prereq check' comment - Add comprehensive tests for findFreePort, findFreePorts, and buildSafeDevConfigAsync using actual port blocking - Improve buildConfig tests with port uniqueness verification and fallback tests using high ports - Use high ports (19xxx range) in tests to avoid conflicts with system services Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
ffd19e977f |
Fix Windows dev script startup (#199)
* Fix Windows dev script startup * Run CI on Windows * Disable npm cache on Windows CI |
||
|
|
9c3b936d16 |
Add npm run dev:static for offline / high-latency development (#168)
Mirrors the dev:automation backend stack (agent-server + automation + ingress) but serves a production frontend build through a small static server instead of Vite. Designed for use over flaky / high-RTT links where Vite's ~1000 ESM module fetches make full reloads painfully slow: hashed assets are now sent with public/immutable cache headers, so an SPA reload is ~1 round-trip (304 on index.html) and zero asset fetches. scripts/static-server.mjs: combined static-file server + reverse proxy. A drop-in for sirv-cli that additionally proxies the same prefixes Vite proxies in dev (/api, /api/automation, /sockets, /server_info, /alive, /health, /ready) so hitting :3001 directly behaves like Vite's dev server — without it, sirv-cli's --single fallback turns /server_info into the SPA shell whenever a tunnel exposes the static port instead of the ingress port. Caches /assets/* immutable, index.html no-cache, weak ETags. scripts/dev-static.mjs: orchestrator that builds the frontend, then spawns agent-server, automation, static-server, and the existing ingress with the same route table as dev-with-automation. scripts/dev-safe.mjs: add isPortBusy() and releaseStaleConversationLeases() helpers. The agent-server tags each conversation directory with an owner_lease.json keyed to a per-process owner_instance_id (45 s TTL, heartbeat-renewed) and skip-loads any conversation whose lease is held by a different instance. If the previous agent-server died ungracefully — or you restart inside the TTL window — every existing conversation becomes invisible to the new instance until the leases age out. dev:static now port-checks for a live agent-server (aborts on conflict), then unlinks stale leases so conversations created by npm run dev are immediately visible. Co-authored-by: openhands <openhands@all-hands.dev> |