mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 13:18:19 +08:00
4e7d1ec475cf024246db4e6e64bf38f9b7818fc5
95
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2738be2025 | fix(scripts): use 127.0.0.1 for remaining localhost service URLs (#16409) | ||
|
|
475a73162f |
feat(automations): add a Git Sync page (#16521)
Co-authored-by: Graham Neubig <neubig@gmail.com> |
||
|
|
bab1baf2de |
fix(dev): stop baking absolute VITE_BACKEND_BASE_URL in npm run dev (#16605)
Co-authored-by: neubig <neubig@users.noreply.github.com> Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
83ba34cce6 |
chore: bump SDK deps (software-agent-sdk 1.42.1, automation 1.7.1, typescript-client 1.38.0) (#16554)
Co-authored-by: neubig <neubig@users.noreply.github.com> Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
2e1502f39d |
fix: spawn launcher services without implicit shell (#16093)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19deb2d0-75af-4fcf-ac1d-a6933c00769f |
||
|
|
68de5c5887 |
fix: configure agent-server telemetry in Canvas launchers (#16348)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
9a023b28bf | fix: rename desktop app to OpenHands Agent Canvas (#16353) | ||
|
|
31b94014e0 | fix: ship multi-size app icons for Windows and macOS (#16352) | ||
|
|
f191d3f115 | chore: bump agent-server 1.40.1 and automation 1.6.0 (#16334) | ||
|
|
0ffcb659d1 | fix: use IPv4 loopback for local proxy targets (#16290) | ||
|
|
54d9f30cd3 | fix: clean up dev services on SIGHUP (#16239) | ||
|
|
e0b115757b |
fix: route runtime services through server_info (#16090)
Co-authored-by: neubig <398875+neubig@users.noreply.github.com> Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: neubig <neubig@users.noreply.github.com> |
||
|
|
ca982d66bb | chore: bump agent-server 1.39.1, automation 1.5.0, typescript-client 1.36.1 (#16197) | ||
|
|
7fbc01824a |
test: add Stryker mutation testing (#16184)
Co-authored-by: Ai Vong <ai.vong@openhands.dev> Co-authored-by: Engel Nyst <engel.nyst@gmail.com> |
||
|
|
1afddcdf67 | chore: bump software-agent-sdk to 1.38.0 and automation to 1.4.1 (#16129) | ||
|
|
22fe594133 |
feat: forward automation telemetry context (#1917)
* feat: forward telemetry context to automations Co-authored-by: openhands <openhands@all-hands.dev> * feat: sync automation telemetry consent Co-authored-by: openhands <openhands@all-hands.dev> * fix: default automation telemetry key in launchers Co-authored-by: openhands <openhands@all-hands.dev> * fix: bake production telemetry defaults into npm package Co-authored-by: openhands <openhands@all-hands.dev> * fix: dedupe automation consent sync Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump automation version to 1.3.0 Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
1450d21308 | chore: bump software-agent-sdk to 1.37.0 and automation to 1.2.0 (#1906) | ||
|
|
4e9ea1334a | feat: add electron desktop app (#1864) | ||
|
|
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. |
||
|
|
321c682d3d |
chore: bump software-agent-sdk to 1.36.1 and automation to 1.1.7 (#1810)
* chore: bump software-agent-sdk to 1.36.1 and automation to 1.1.7 * fix: failing tests |
||
|
|
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> |
||
|
|
4baec98fef |
chore: bump agent-server SDK to 1.35.0 and automation to 1.1.6 (#1666)
Bump the pinned agent-server/openhands-sdk version from 1.33.0 to 1.35.0 and the automation package from 1.1.4 to 1.1.6. openhands-automation 1.1.6 (published to PyPI) depends on openhands-sdk/openhands-workspace 1.35.0, so this keeps Canvas in sync with the released automation package. Verified with: EXPECTED_SDK_VERSION=1.35.0 node scripts/check-sdk-version-sync.mjs --check-pypi -> 'All SDK versions are in sync!'. dev-safe tests pass (52/52). Co-authored-by: Engel Nyst <engel.nyst@gmail.com> |
||
|
|
ebeaca4f4d |
chore: bump agent-server SDK to 1.33.0 and automation to 1.1.4 (#1622)
* chore: bump agent-server SDK to 1.33.0 and automation to 1.1.4 Bump config/defaults.json pins: - versions.agentServer 1.32.0 -> 1.33.0 - versions.automation 1.1.3 -> 1.1.4 Everything else (dev-safe.mjs, docker.yml, mock-llm workflows) reads these from defaults.json. Updated the dev-safe.test.ts expectations and the two concrete AGENTS.md version references to match. The agent-client-protocol<0.11 guard stays: openhands-sdk 1.33.0 still pins agent-client-protocol>=0.10.1 (unchanged from 1.32.0), so acp 0.11.0 would still break the ACP client. Blocked until openhands-automation 1.1.4 (pinned to SDK 1.33.0) publishes to PyPI, since the sdk-version-sync check resolves the released automation's SDK deps. Draft until then. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: sync remaining 1.32.0 version examples to 1.33.0 The drift-detection test (docs-version-sync) requires JSDoc examples in scripts/dev-safe.mjs and scripts/check-sdk-version-sync.mjs to match the config/defaults.json agent-server pin. Also refresh the acp-constraint comments in mock-llm-e2e.yml and defaults.json for consistency. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c552545926 |
feat(mcp): add OAuth support to MCP install flow
Squash merge PR #1583. This merge commit was created by an AI agent (OpenHands) on behalf of Graham Neubig. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
2e63845a3f |
chore: bump software-agent-sdk to 1.31.1 and automation to 1.1.2 (#1609)
* chore: bump software-agent-sdk to 1.31.1 and automation to 1.1.2 * fix: pin agent-client-protocol <0.11 and sync version docs |
||
|
|
b2ba5889d3 |
fix(examples): inherit acp-docker image from config/defaults.json (#1434)
* fix(examples): inherit acp-docker image from config/defaults.json
examples/acp-docker/docker-compose.yml hardcoded the agent-server image at
`1.25.0-python`. Canvas enforces `compatibility.minimumAgentServer` (1.28.0)
from the repo's single source of truth, so the example default fell below the
floor and rendered "Disconnected — requires 1.28.0 or newer" — a reviewer
following the quickstart as written never reached the feature.
examples/acp-docker was the lone in-repo file hardcoding a version instead of
inheriting from config/defaults.json (14 other files read it; check-sdk-version
-sync only validates the released PyPI package, not in-repo files).
- scripts/gen-acp-docker-env.mjs: read defaults.json, pin AGENT_SERVER_IMAGE to
`${images.agentServer}:${versions.agentServer}-python` in examples/acp-docker
/.env (idempotent upsert; mirrors scripts/docker-build.mjs).
- package.json: `npm run example:acp-docker:env`.
- docker-compose.yml: no-config fallback `1.25.0-python` -> `latest-python`,
always >= the compatibility floor, so zero-config `docker compose up` never
shows "Disconnected"; the generated .env overrides with the pinned SoT
version for the reproducible path.
- .env.example / README.md: document both paths; correct the version narrative
(floor is the defaults.json compatibility pin; #3510 is the deeper functional
floor at/below it).
- __tests__/scripts/acp-docker-env-sync.test.ts: assert the generator's tag
matches defaults.json, the pin satisfies the floor, and the compose fallback
stays `latest-python`. Mirrors docs-version-sync.test.ts — the guard that
makes "can't silently drift" true.
* test(examples): harden acp-docker env-sync per review
Addresses the cli-review-panel findings worth acting on (the rest were
cosmetic or matched the no-validation idiom of scripts/docker-build.mjs):
- gte() in the test guarded with parseSemver — a non-numeric pin (sha /
pre-release) now fails the floor check loudly instead of silently
comparing NaN. The floor check is a CI gate; its one piece of logic
shouldn't mis-compare in silence.
- compose-fallback assertion derives the registry from config.images
.agentServer instead of hardcoding ghcr.io/openhands/... — a registry
change no longer false-fails a test that only cares about the latest-python
tag.
- upsertEnvLine now has unit tests (append / replace-in-place+preserve /
idempotent / commented-template-line / keyless-line guard), making the
"idempotent upsert" claim defensible. It was the one untested piece of real
logic.
- upsertEnvLine guards a keyless line (no "=") with a clear throw, instead of
an empty key matching every line and rewriting the whole file.
* fix(examples): guard acp-docker env-sync entrypoint against undefined argv[1]
The CLI entrypoint guard called pathToFileURL(process.argv[1]) unconditionally.
process.argv[1] is undefined in some ESM contexts (e.g. importing the module for
its exports via `node --input-type=module -e "import(...)"`), so the guard threw
ERR_INVALID_ARG_TYPE at import, before any exported helper was reachable.
Short-circuit on process.argv[1] before pathToFileURL so importing the module is
side-effect-free while the CLI path is unchanged. Add a regression test that
reproduces the bare-import context and asserts a clean exit.
Addresses the review finding on #1434.
* docs(acp-docker): trim verbose comments per review
Address all-hands-bot's review suggestions on #1434:
- test header describes the current invariant, not the prior-state history
(that narration belonged in the PR description)
- docker-compose.yml: condense the image-pin comment to the how-to-override;
the compatibility-floor / #3510 rationale already lives in README §1 + the test
- .env.example: 7-line pin explainer down to 2
Comment-only; env-sync test still 10/10 green, prettier clean.
* Clarify ACP Docker image version guidance
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: enyst <engel.nyst@gmail.com>
Co-authored-by: openhands <openhands@all-hands.dev>
|
||
|
|
c3f20e0df5 |
chore: bump typescript-client 1.28.0 + agent-server/openhands-sdk 1.29.3 (#1507)
* chore: bump typescript-client 1.28.0 + agent-server/openhands-sdk 1.29.3 * chore: automation |
||
|
|
32d2e12042 |
chore(deps): bump typescript-client 1.27.0 + agent-server/openhands-sdk 1.29.0 (#1486)
* chore(deps): bump @openhands/typescript-client to 1.27.0 * test: update ACP provider/model fixtures for typescript-client 1.27.0 1.27.0 refreshed the claude-code/codex ACP registry data: provider command versions (claude-agent-acp 0.30.0->0.44.0, codex-acp 0.15.0->0.16.0), claude-code model ids (claude-opus-4-8->opus[1m], claude-sonnet-4-6->sonnet, claude-haiku-4-5->haiku) plus a new well-labeled "default" option, and the codex default (gpt-5.5/medium->gpt-5.5). Canvas sources these lists from the client registry (closes #740), so the source was already correct -- only the hardcoded test expectations were stale. Also relaxed the acp-providers placeholder guard to accept the SDK's intentional "Default (recommended)" entry. * chore(deps): bump agent-server/openhands-sdk to 1.29.0 Align the spawned agent-server SDK release train (openhands-sdk, openhands-tools, openhands-workspace, openhands-agent-server) with the version @openhands/typescript-client 1.27.0 is validated against (agent-server 1.29.0-python). Bump the coupled openhands-automation pin to 1.0.0a12, whose SDK deps resolve to 1.29.0, to satisfy the check-sdk-version-sync gate. minimumAgentServer compat floor unchanged. Doc/JSDoc/test references updated to keep docs-version-sync green. |
||
|
|
a1c68313b2 |
Add lock-to-cloud backend setup mode (#1389)
* Show onboarding before public backend auth gate Co-authored-by: openhands <openhands@all-hands.dev> * Make backend setup the first onboarding step Co-authored-by: openhands <openhands@all-hands.dev> * Restore Cloud backend option in onboarding Co-authored-by: openhands <openhands@all-hands.dev> * Make first-run backend onboarding calmer Co-authored-by: openhands <openhands@all-hands.dev> * fix: update public onboarding e2e expectation * fix: cover onboarding-first public auth e2e * test: keep ProgressEvent polyfill through teardown * chore: refresh PR checks after QA * Add lock-to-cloud backend setup mode Co-authored-by: openhands <openhands@all-hands.dev> * Hide skip on locked Cloud backend onboarding Co-authored-by: openhands <openhands@all-hands.dev> * Remove add-backend onboarding subtitle Co-authored-by: openhands <openhands@all-hands.dev> * Skip healthy backend onboarding step Co-authored-by: openhands <openhands@all-hands.dev> * fix: support skipped backend step in onboarding e2e * chore: Remove PR-only artifacts * fix: address onboarding review nits * fix: show onboarding for locked cloud first run * ci: support stacked mock llm runs * test: assert scoped shell background * fix: resolve merge conflicts with main (fix-public-onboarding stacking) - Remove duplicate handleConnected/actionRowClassName/titleKey declarations in check-backend-step.tsx that resulted from merging the parent PR's changes on top of our lock-to-cloud additions - Remove erroneous waitFor(onboarding-backend-connected) steps from the 'shows a connection error' test which uses a no-backend context where the connection banner is never shown Co-authored-by: openhands <openhands@all-hands.dev> * fix: remove unused isLockedToCloud export All callsites use getLockedCloudHost() !== null directly. Remove the redundant helper to keep the public API intentional. Co-authored-by: openhands <openhands@all-hands.dev> * fix: show onboarding first in locked-cloud mode when a session key is present On PR #1389 Hiep reported that `static-server.mjs --lock-to-cloud ...` landed on the Manage Backends recovery modal ("Add Backend") instead of first-run onboarding after a fresh `~/.openhands`. Root cause: when the build had a baked-in `VITE_SESSION_API_KEY` (or one was injected via `--session-api-key`), `makeDefaultLocalBackend()` seeded a Local backend even in locked-to-Cloud mode. That made `isNoBackend()` false, so `lockedNoBackend` was false and first-run onboarding was skipped; the subsequent `/server_info` probe failed and `root.tsx` rendered `MissingAgentServerScreen` (Manage Backends recovery modal). Fix: - `makeDefaultLocalBackend()` returns null when `getLockedCloudHost()` is set, so locked mode never auto-seeds a Local backend. - `root.tsx` broadens the gate to `lockedNeedsOnboarding`: locked + (no backend OR active backend is not Cloud) triggers onboarding, covering a stale persisted Local backend from a previous non-locked session too. Verified by building with a baked `VITE_SESSION_API_KEY` and serving with `--lock-to-cloud`: the app now shows the first-run onboarding Cloud-login screen instead of the recovery modal, and no Local backend is seeded. Non-locked mode still seeds the Local backend as before. Co-authored-by: openhands <openhands@all-hands.dev> * fix: locked-cloud onboarding layout + restore CI test mock CI fix: - `use-create-conversation-metadata.test.ts` mocks the whole `agent-server-config` module but was missing `getLockedCloudHost`, which `makeDefaultLocalBackend()` now imports. Add it (returning null) so the default local backend seeds and the create-conversation mutation succeeds again. Onboarding layout (locked-to-Cloud first-run step): - Drop the `max-w-sm` cap on the locked CloudLoginColumn so the "Skip the setup — connect instantly with your OpenHands Cloud account." text fills the modal content width instead of wrapping in a narrow centered column. - Add `pb-7` to the onboarding scroll area so the "Login with OpenHands Cloud" button is no longer flush with / cut off by the modal bottom. Widening the text (fewer lines) plus the bottom padding together give the button breathing room. Co-authored-by: openhands <openhands@all-hands.dev> * test: add getLockedCloudHost to agent-server-config test mocks `makeDefaultLocalBackend()` now imports `getLockedCloudHost` from `agent-server-config`. Two tests that fully mock that module were missing the export, so the default local backend never seeded and every create-/ read-conversation path threw `NoBackendAvailableError`: - `agent-server-conversation-service.test.ts` (23 failures on ubuntu CI) - `use-create-conversation-metadata.test.ts` (already fixed in prev commit) Add `getLockedCloudHost: vi.fn(() => null)` to both mocks so the non-locked default-backend seeding path works again. Co-authored-by: openhands <openhands@all-hands.dev> * Enhance conversation sidebar with pinned section and grouped organization (#1144) * Add pinned conversations and reorderable workspace folders to the sidebar. Persist pins per backend with a capped pinned section, pin-on-hover cards that keep the icon aligned with hover actions via an invisible ellipsis spacer, and drag-and-drop folder ordering stored in panel preferences. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify grouped folder rows for drag and expand. Drop the grip and chevron controls, remove selection highlight and layout animation, and drag or click the folder label directly while keeping row hover feedback. Co-authored-by: Cursor <cursoragent@cursor.com> * Polish folder drag-and-drop and pinned section visuals. Drag the whole folder (and contents) as the drag image, show an accent drop line between folders with position-aware reordering, and animate sibling folders into place only around a reorder. Swap the folder icon to its open or closed counterpart on hover, add a chronological-view divider plus an outline pin icon to the pinned section header, and render that header in normal weight. Co-authored-by: Cursor <cursoragent@cursor.com> * Add hover metadata popover for sidebar conversations. Show a modal-styled popover on conversation hover with the full title, status dot, and repo/branch-or-directory, model, and created-date rows. Reserve the action overlay width so titles truncate instead of colliding with the pin, drop the small status tooltip, and gate the popover behind a new "Hover metadata" toggle in the filter dropdown (persisted, on by default). Co-authored-by: Cursor <cursoragent@cursor.com> * Improve folder drag preview and placeholder. Show a rounded, surfaced drag image anchored to the grab point and blank the original row (preserving its height) via opacity so Chrome does not cancel the native drag. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden sidebar "Load more" pagination Dedupe loaded conversations by id and keep fetching pages until the visible list actually grows, so a single "Load more" click reliably surfaces new rows despite the 10s background refetch dropping in-flight fetchNextPage calls or pages yielding zero visible rows. Show the skeleton throughout. Also drop the native title tooltip on card titles and record the still-intermittent double-click symptom as a KNOWN ISSUE. Co-authored-by: Cursor <cursoragent@cursor.com> * Keep pinned conversations exclusive to the pinned section. Filter pinned threads out of grouped/chronological lists to prevent duplicates, add regression coverage for both list modes, and add the missing upgrade-button translation key with typed i18n usage. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: failing tests * fix: lint --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> * Fix locked cloud onboarding follow-ups Co-authored-by: openhands <openhands@all-hands.dev> * Slow down onboarding follow-up GIFs Co-authored-by: openhands <openhands@all-hands.dev> * Skip onboarding when active backend already has a configured LLM Detect returning users via flat `llm_api_key_set` + `agent_settings.llm.model` (or subscription auth), regardless of backend kind. Locked-Cloud-not-logged-in and stale local backend still fall through to the modal so the existing recovery paths kick in. Co-authored-by: openhands <openhands@all-hands.dev> * Scope onboarding skip rule to Cloud backends only Local agent-servers can be started with an env-injected `LLM_API_KEY`, which makes `llm_api_key_set` an unreliable returning-user signal — Mock-LLM E2E fresh-install tests were tripping on the SDK default model + env key combo. For Local backends the skip stays driven by the existing `openhands-onboarded` localStorage flag; Cloud backends continue to use the settings-based rule. Co-authored-by: openhands <openhands@all-hands.dev> * Trigger CI re-run (empty commit) Workflows didn't fire on 80ea575a — pushing empty commit to nudge the webhook. Co-authored-by: openhands <openhands@all-hands.dev> * Always pre-fill onboarding LLM step with OpenAI GPT-5.5 default The returning-Cloud-user case is now handled at the host level (OnboardingHost skips the whole modal). Users who actually reach the LLM step are first-time installs who want the default pre-filled — restoring the pre-PR-1389 behavior that the onboarding-regressions E2E asserts. Also drops the now-empty unit test that mirrored the old step-level preservation. Co-authored-by: openhands <openhands@all-hands.dev> * chore: Update PR QA artifacts * Generalize onboarding-skip to Local backends with configured LLMs Hiep flagged that the onboarding modal still walks users through Set Up your LLM after they connect to a pre-configured backend. Investigation: * On Cloud, the fast-path keyed off settings.llm_api_key_set + a non-empty llm.model. That worked. * On Local, the fast-path bailed early on backend.kind !== 'cloud'. But the local agent-server reports the exact same readiness signal via llm_api_key_is_set (and the local settings-service mapper already remaps that to llm_api_key_set on the way through). The only reason the skip didn't fire was the explicit kind gate. Drop the gate, accept either field name, and rename the predicate to reflect what it actually checks (isBackendLlmReady). A truly fresh agent-server reports both flags as false, so the modal still shows for genuine first-run setup. Tests: * Updated 'does not skip onboarding for a Local backend' to its inverse: 'skips for a Local backend with an LLM already configured'. * Added 'still shows the modal for a fresh Local agent-server with no API key set' to lock in the fresh-install case. * All 3328 vitest tests pass; typecheck clean. Refs Hiep's review comment on PR #1389. Co-authored-by: openhands <openhands@all-hands.dev> * fix(onboarding): address Hiep's review on PR #1389 (#1389) Resolves the three issues Hiep reported on PR #1389: 1. **Choose Agent step gets skipped after Cloud login.** When the backend slide finished via Cloud login and `skipBackendStep` flipped true, the slide indices renumbered (agent: 1→0, setup: 2→1). The user's numeric `currentStep` of 1 — pointing at Choose Agent before the flip — now pointed at Set Up LLM, and the corrective effect that decremented it ran a render too late. Track the user's *phase* ("backend" | "agent" | "setup" | "hello") instead of a numeric step. The visible slide index is derived from phase + slideOrder, so renumbering can never move the user onto a different logical step. The previous `wasSkippingBackendStep` ref + decrement effect is replaced by a single effect that snaps phase forward only when the current phase is no longer in slideOrder (e.g. "backend" right after the slide collapsed). 2. **Existing Cloud LLM settings not shown to returning users.** The skip-onboarding fix from commit 78254e1b already routes returning users with a configured LLM around the onboarding modal entirely, so they never hit the Set Up LLM step in the first place. The new phase-based flow preserves that behavior; no further change needed. 3. **Redundant 'Or' divider** between manual and Cloud columns in BackendConnectionOptions. Both columns have prominent titles ("OpenHands Cloud" with logo on the right) and a generous gap already; the explicit divider added visual noise without information. Remove the divider markup. Also gitignores local static-server runtime artifacts (workspace/, build-fresh/) that were getting picked up by 'git add -A'. Two regression tests cover the standard (non-locked-cloud) flow: one verifies the user stays on Choose Agent after completing Cloud login from the side-by-side picker, and one verifies the 'Or' divider is gone. All 3,241 unit tests pass; lint and typecheck are clean. Co-authored-by: openhands <openhands@all-hands.dev> * fix: suppress Add Backend modal in locked-to-cloud mode Resolves hieptl's review feedback on PR #1389: when the static server is launched with --lock-to-cloud, navigating to the app showed the Manage Backends recovery modal ("Add Backend") instead of going straight to Cloud onboarding/login. Root cause: the `openhands-onboarded` localStorage flag is origin-scoped and persists across deployments. A user who previously completed onboarding in a non-locked session on the same origin carries that flag into a locked-to-Cloud session. The stale flag suppressed first-run onboarding (`shouldShowFirstRunOnboarding` was gated on `!onboardingCompleted`), so the app fell through to the `/server_info` probe. With no usable local backend in locked mode the probe throws `AgentServerUnavailableError`, and root.tsx renders the `MissingAgentServerScreen` / `ManageBackendsModal` recovery modal. Fix: when `lockedNeedsOnboarding` is true, ignore the completion flag and force first-run onboarding (which owns the Cloud login). The non-locked path is unchanged — `onboardingCompleted` still suppresses the modal for returning users with a configured backend. Also confirms the minor cleanup from the bot review: `isLockedToCloud()` was already removed in commit addda40e; no remaining references. Adds a regression test reproducing hieptl's exact scenario (stale `openhands-onboarded` flag + locked-to-Cloud + no backend) and asserting the onboarding modal renders instead of the Manage Backends modal. Co-authored-by: openhands <openhands@all-hands.dev> * fix(onboarding): don't skip onboarding modal for launcher-seeded backend PR #1389 generalized the OnboardingHost "returning user with a configured LLM" skip from Cloud-only to all backends (commit 78254e1b). That broke the mock-LLM E2E fresh-install / onboarding-happy-path / onboarding-regressions specs: tests/e2e/mock-llm/backends/mock-llm-auth-modes.spec.ts:57 "auth mode: fresh install with runtime-injected key › reaches the onboarding modal without pre-seeded localStorage" The mock-LLM E2E stack runs every spec serially against a single shared agent-server. Earlier specs configure an LLM profile that persists in the server's settings, so by the time the fresh-install spec runs (with a clean browser context, no `openhands-onboarded` flag, and a launcher-seeded default-local backend), the server reports `llm_api_key_is_set: true` + a non-empty model. `OnboardingHost.isBackendLlmReady` then returned true, so the host marked onboarding complete and returned null — the first-run modal never mounted and the test timed out waiting for `onboarding-step-choose-agent`. Main is green on the same test because main's skip was Cloud-only. The settings-based LLM-ready signal is unreliable for the launcher-seeded default-local backend: the agent-server can be started with an env-injected LLM key, and shared-server deployments retain configured LLMs across browser sessions. Keying first-run onboarding off the server's LLM state would suppress the modal for a genuinely fresh browser install. Fix: keep the skip for Cloud backends and for Local backends the user explicitly added via "Add Backend" (which carry a non-default id), but suppress it for the launcher-seeded default-local backend (`SEEDED_DEFAULT_BACKEND_ID`). First-run detection for that backend stays driven by the `openhands-onboarded` localStorage flag, matching main's behavior and restoring the E2E fresh-install contract. The PR's core intent (suppress the Add Backend recovery modal in locked-to-Cloud mode, commit 47619f11) is unchanged. Tests: * Updated "skips the modal for a Local backend..." to seed a user-added Local backend (non-default id) so the skip still fires for the Add-Backend scenario. * Added "still shows the modal for a launcher-seeded default-local backend even when the agent-server reports a configured LLM" to lock in the fresh-install regression. * All 3332 vitest tests pass; typecheck + lint + build clean. Co-authored-by: openhands <openhands@all-hands.dev> * fix(onboarding): don't auto-complete onboarding for launcher-seeded backend Commit 9029e036 fixed OnboardingHost so the first-run onboarding modal shows for the launcher-seeded default-local backend even when the shared mock-LLM agent-server reports a configured LLM. But the same over-suppression existed in src/root.tsx: a separate `isBackendLlmReady` check (no default-local exclusion) fed a `markCompleted()` effect that persisted `openhands-onboarded=1` whenever the active backend reported a ready LLM — including the launcher-seeded default-local backend. That root-level effect was the remaining cause of the mock-llm-onboarding-regressions.spec.ts:16 failure ("keeps the modal open on backdrop click and Escape"): * The OnboardingModal already renders with no `onClose` on its ModalBackdrop, so backdrop clicks and Escape are no-ops — the modal itself was never closeable that way. * The test failure was actually the `expect.poll` asserting `openhands-onboarded` stays null: root.tsx's `markCompleted` effect fired (agent-server had a configured LLM from earlier serial specs) and persisted completion, even though the modal stayed mounted. Fix: apply the same `SEEDED_DEFAULT_BACKEND_ID` exclusion to root.tsx's `isBackendLlmReady` that OnboardingHost already uses. The settings-based LLM-ready signal is unreliable for the launcher-seeded default backend (env-injected keys, shared-server LLM persistence across browser sessions), so first-run detection there stays driven by the `openhands-onboarded` localStorage flag. The skip still fires for Cloud backends and for Local backends the user explicitly added via "Add Backend" (non-default id). The OnboardingModal's non-dismissible backdrop/Escape behavior is unchanged and already correct (ModalBackdrop receives no `onClose`, so `closeOnEscape`/`closeOnBackdropClick` default-true handlers call `onClose?.()` which is a no-op). Tests: * Added root.test.tsx case "does not mark onboarding complete for the launcher-seeded default-local backend even when the agent-server reports a configured LLM" — verified it fails without the root.tsx fix and passes with it. * All 3333 vitest tests pass; typecheck + lint + build clean. Co-authored-by: openhands <openhands@all-hands.dev> * fix: force Cloud replacement for stale Local backend in locked mode Critical fixes for the locked-to-Cloud flow (PR #1389 review): 1. root.tsx: the ready-backend fast-path in locked mode now requires the active backend to match the locked Cloud host (normalized via the new isSameCloudHost helper), not just . A reachable stale Local backend (or a Cloud backend on a different host) that reports a configured LLM no longer bypasses the Cloud login/replacement flow. The markCompleted effect is also guarded so it only persists completion for the legitimate locked Cloud host. 2. onboarding-modal.tsx: in locked mode, CheckBackendStep is only skipped when the active backend IS the locked Cloud host. A reachable stale Local backend keeps the backend slide visible so Cloud login can replace it. Also addresses minor review suggestions: - LOCK_TO_CLOUD_WINDOW_KEY is now module-private (only getLockedCloudHost reads it; static-server.mjs/tests use the literal string). - Extract shared isBackendLlmReady helper into its own module (is-backend-llm-ready.ts) so root.tsx and OnboardingHost stay in sync without duplicating the rule and without pulling the onboarding modal graph into root's eager bundle. - Inline the no-op initialValueOverrides intermediate in setup-llm-step. Adds regression tests for the stale-Local-backend and other-Cloud-host scenarios in both root.test.tsx and onboarding-modal.test.tsx. Co-authored-by: openhands <openhands@all-hands.dev> * fix(onboarding): close stale-backend lock-to-Cloud bypass in CheckBackendStep (#1389) PR-review bot pointed out (HEAD 55d382be) that keeping the backend slide visible for a non-matching backend in locked mode is insufficient: CheckBackendStep itself still hits its connected-backend shortcut for a reachable stale Local backend, hiding the Cloud login UI and showing a Next button that lets the user continue as Local. Apply the same host-match guard inside CheckBackendStep. A new local `treatAsNoBackend` (= noBackendSelected || lockedCloudHostMismatch) drives: - title: ONBOARDING$LOGIN_TO_CLOUD_TITLE (not BACKEND_TITLE) - render: BackendConnectionOptions (Cloud login UI), no ConnectionBanner - no "Show configuration" toggle and no Next-shortcut action row `noBackendSelected` still governs whether handleConnected calls `addBackend` or `updateBackend`, so the stale backend is replaced rather than duplicated. Strengthen the regression test the bot flagged: it now asserts the Cloud login title and login button are visible, and that the `onboarding-backend-show-configuration` toggle, `onboarding-backend-next` button, and the (misleading) Connected subtitle are all absent. All 3,249 unit tests pass; lint and typecheck are clean. Co-authored-by: openhands <openhands@all-hands.dev> * fix(onboarding): clear stale active org_id when replacing a Cloud backend host (#1389) PR-review bot raised one remaining state carry-over: replacing a mismatched Cloud backend updates its host/apiKey via `updateBackend`, but the persisted `active.orgId` (X-Org-Id) is keyed to the OLD host's org list. The newly-locked Cloud backend would keep sending an invalid `X-Org-Id` until the user manually re-picked an org. Fix in CheckBackendStep.handleConnected: when the submitted payload's host differs from the previously-active backend's host, call `setActive(backend.id, null)` to drop the now-invalid org selection. The user re-picks an org on the new host via the usual org switcher. Local-only edits are unaffected because Local backends always carry `active.orgId === null`, so the conditional is a no-op there. New regression test seeds a Cloud backend at other-cloud.example.com with `orgId="stale-org-from-other-host"`, drives the Cloud login button, and asserts `getActiveSelection().orgId === null` while the backend row is updated in place (same id). All 3,250 unit tests pass; lint and typecheck are clean. Co-authored-by: openhands <openhands@all-hands.dev> * fix(onboarding): dismiss modal immediately after Cloud login in locked mode (#1389) Resolves the flicker hieptl reported on PR #1389: after logging into OpenHands Cloud in locked-to-Cloud mode, the onboarding modal advanced to the Choose Agent slide (the "next window"), then got torn down by the root first-run gate, then briefly remounted via OnboardingHost — appearing to flash in and out. Cloud login IS the onboarding completion in locked mode, so: - CheckBackendStep now calls onClose (dismiss) instead of onNext when a Cloud login succeeds in locked-to-Cloud mode, so the next slide never shows. Standard (non-locked) mode still walks the user through agent/LLM setup via onNext. - root.tsx's locked-mode first-run gate now treats onboardingCompleted as authoritative once the active backend IS the locked Cloud host, so the first-run screen hides immediately on login (without waiting for the Cloud settings probe to confirm a configured LLM). The flag is still ignored when the active backend is not the locked Cloud host, preserving the stale-flag bypass protection. Added failing tests (now passing) reproducing both halves of the flicker: - onboarding-modal: Cloud login in locked mode calls onClose, not onNext. - root: the first-run screen hides immediately after Cloud login completes (post-login state with no configured LLM), instead of reopening via OnboardingHost. Co-authored-by: openhands <openhands@all-hands.dev> * chore: Remove PR-only artifacts * ci: revert docker.yml pull_request branch filter change Reverts the removal of `branches: [main]` from the `pull_request` trigger in .github/workflows/docker.yml (introduced in 5bb8049f). That change is unrelated to the locked-to-Cloud onboarding work on this PR and is out of scope. Restores the file to match main exactly so the Docker workflow again only runs on PRs targeting `main`. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Graham Neubig <gneubig@users.noreply.github.com> Co-authored-by: neubig <398875+neubig@users.noreply.github.com> Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> Co-authored-by: FraterCCCLXIII <panentheum@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
df93ca36a7 |
Add Windows portability guards for workspace flows (#1311)
* Add Windows portability guards for workspace flows * Increase snapshot workflow timeout * Trigger CI after timeout update * Fix windows portability PR after main merge --------- Co-authored-by: neubig <398875+neubig@users.noreply.github.com> |
||
|
|
910b19ae76 |
chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 (#1319)
* chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 Co-authored-by: openhands <openhands@all-hands.dev> * Test fixes * fix: inject proxy base_url for litellm_proxy/* when server omits it (agent-server ≥1.28) Agent-server ≥1.28 may return base_url:null when fetching a litellm_proxy/* profile config, even when the profile was saved with the All-Hands proxy URL. This caused the Basic-tab re-save flow in LlmSettingsLocalView.handleSave to call isOpenHandsProxyModel(model, null) → false, hitting the else-branch that deletes base_url and stranding the profile (issue #1146). Fix: add a secondary check — litellm_proxy/* with a missing base_url is treated the same as litellm_proxy/* with the proxy URL already set, and OPENHANDS_LLM_PROXY_BASE_URL is injected before the save request is sent. Also updates the mock-LLM E2E test to accept both storage representations: - litellm_proxy/* + proxyBaseUrl (pre-1.28, guards issue #1146 regression) - openhands/* + null (1.28+, server-managed routing) And adds a unit test exercising the base_url:null path. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
8071edf72a |
Revert "chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 (#1315)" (#1318)
This reverts commit 1917b5d39fbf09dc51213b4b484698fe394314c7. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
15a52fea75 |
chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 (#1315)
* chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 Co-authored-by: openhands <openhands@all-hands.dev> * chore: update doc examples to reference agent-server 1.28.1 Update version references in AGENTS.md, scripts/dev-safe.mjs, and scripts/check-sdk-version-sync.mjs from 1.27.0 → 1.28.1 to stay in sync with the agentServer pin in config/defaults.json. Fixes: docs-version-sync.test.ts failures Co-authored-by: openhands <openhands@all-hands.dev> * fix: inject proxy base_url for litellm_proxy/* when server omits it (agent-server ≥1.28) Agent-server ≥1.28 may return base_url:null when fetching a litellm_proxy/* profile config, even when the profile was saved with the All-Hands proxy URL. This caused the Basic-tab re-save flow in LlmSettingsLocalView.handleSave to call isOpenHandsProxyModel(model, '') → false, hitting the else-branch that deletes base_url and stranding the profile (issue #1146). Fix: add a secondary check for litellm_proxy/* models with a missing base_url (null/undefined/empty), treating them the same as a stored proxy URL and injecting OPENHANDS_LLM_PROXY_BASE_URL before the save request is sent. Also adds a unit test exercising the base_url:null path. Co-authored-by: openhands <openhands@all-hands.dev> * test(e2e): accept agent-server 1.28 model rewrite in proxy profile test Agent-server 1.28 normalises litellm_proxy/* → openhands/* on storage and manages the proxy URL internally (returning base_url:null). The old assertions hard-coded the pre-1.28 storage format (litellm_proxy/* + explicit proxy URL), causing the test to fail on every 1.28 run. Extract assertProxyProfileConfig() helper that accepts both storage representations: - litellm_proxy/* + proxyBaseUrl (pre-1.28, guards issue #1146 regression) - openhands/* + null (1.28+, server-managed routing) The issue #1146 guard is preserved: a litellm_proxy/* profile without a proxy URL is still flagged as a stranded profile. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
f93cb3c9ee |
settings: persist app preferences and disabled_skills on the agent-server (#1191)
* settings: persist app preferences and disabled_skills on the agent-server The local agent-server now exposes app_preferences on the persisted settings (OpenHands/software-agent-sdk#3539): language, sound notifications, analytics consent, git identity, and disabled_skills are returned on GET /api/settings under app_preferences and updated via a new app_preferences_diff field on PATCH /api/settings. This brings the local agent-server to parity with the cloud, which has always accepted the same keys at the top level. Drops the localStorage workaround that mirrored these fields in two keys (openhands-agent-server-app-preferences and openhands-agent-server-disabled-skills), along with the app-preferences-store.ts module and the DISABLED_SKILLS_STORAGE_KEY helpers it depended on. - SettingsService.transformApiResponse reads app_preferences from the server response and hoists each field onto the flat Settings shape so consumers (settings.language, settings.disabled_skills, …) keep working unchanged. - SettingsService.saveSettings routes the same set of fields through the new app_preferences_diff for local backends and through the existing app_preferences flat-spread path for cloud backends. - New legacy-app-preferences-migration.ts runs once on first getSettings() after upgrade: when the server reports an app_preferences block AND legacy localStorage values are still present, it pushes them up via app_preferences_diff and clears the legacy keys. Pre-1.27 servers (which omit app_preferences entirely) cause the migration to no-op so existing data isn't dropped before the server can accept it. - Updated MSW handlers to round-trip app_preferences and app_preferences_diff so the mock backend matches production. - Test coverage: 5 new tests in __tests__/api/settings-service.test.ts for the local round-trip, the mixed diff routing, the legacy migration, and the pre-1.27 skip path. Closes the localStorage workaround called out in the recent audit of agent-canvas localStorage usage (items 3 and 4: disabled_skills and app-preferences fields). Depends on agent-server 1.27 / SDK PR #3539. Co-authored-by: openhands <openhands@all-hands.dev> * settings: read/write app preferences via misc_settings container Follow-up to the localStorage cleanup in this PR + SDK refactor in openhands/software-agent-sdk#3543. The agent-server now exposes frontend-owned settings under a generic misc_settings container instead of a top-level app_preferences field. Wire shape changes: Before: After: GET /api/settings GET /api/settings -> { app_preferences: {...} } -> { misc_settings: { app_preferences: {...} } } PATCH /api/settings PATCH /api/settings body.app_preferences_diff (shallow body.misc_settings_diff (deep-merged, overlay, replaces named fields) same semantics as agent_settings_diff) Why the rename to misc_settings: the previous name pinned the API to a single 'frontend-owned' namespace. Adding a future category like ui_preferences (sidebar layout / view modes) would have required either yet another top-level field or shoehorning unrelated UI state into AppPreferences. With misc_settings as a container, new categories drop in as nested fields without churning the top-level shape. Changes: - settings-service.api.ts * SettingsApiResponse.app_preferences -> .misc_settings (typed) * SettingsUpdateRequest.app_preferences_diff -> .misc_settings_diff * Add MiscSettings interface * transformApiResponse reads response.misc_settings?.app_preferences * saveSettings emits { misc_settings_diff: { app_preferences } } * Local 'has any diffs' check tracks misc_settings_diff * Doc comments updated; semantics noted as deep-merge - legacy-app-preferences-migration.ts * Gate on serverResponse.misc_settings, not .app_preferences * pushDiff callback now wraps the diff in { app_preferences: ... } - src/mocks/settings-handlers.ts * GET handler returns misc_settings.app_preferences * PATCH handler accepts misc_settings_diff; deep-merges nested app_preferences into the persisted block * Internal mock state stores under misc_settings to match wire shape - __tests__/api/settings-service.test.ts * Four tests updated to assert the new wire shape (local PATCH body, GET round-trip, mixed-diff routing, legacy localStorage migration) * Pre-1.27 detection test now keys off missing misc_settings - AGENTS.md * App-preferences note rewritten for the misc_settings container, explains deep-merge semantics, and documents the in-flight rename (flat shape introduced in #3539 never shipped to users) Cloud path is unchanged: cloud /api/v1/settings still accepts the fields as flat top-level keys, mirrored by saveCloudSettings. Verification: $ npm run typecheck exit 0 $ npm test -- __tests__/api/settings-service.test.ts \ __tests__/api/mock-settings-handlers.test.ts 23 tests passed $ npm test 3009 passed | 12 skipped | 9 todo $ npm run lint All matched files use Prettier code style! $ npm run build built in 1.50s Co-authored-by: openhands <openhands@all-hands.dev> * Bump agent-server default to 1.27.0 Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
cca79d096e |
chore: bump software-agent-sdk to 1.26.0 (#1186)
Update agentServer version pin in config/defaults.json from 1.25.0 to 1.26.0. This drives all four packages (openhands-agent-server, openhands-sdk, openhands-tools, openhands-workspace) which are released in lockstep. Also update matching test expectations and example version strings in dev-safe.mjs, check-sdk-version-sync.mjs, and AGENTS.md. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
a1158f1e77 |
docs: sync README Docker tags with config/defaults.json (#1113)
* docs: sync README Docker tags with config/defaults.json * docs: bump Docker pin to rc.2 and harden release checks Co-authored-by: Cursor <cursoragent@cursor.com> * fix: version --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
dda46c10bd |
feat: default EXTENSIONS_REF from pinned @openhands/extensions SHA in package.json (#1060)
* feat: default EXTENSIONS_REF from pinned @openhands/extensions SHA in package.json
The agent-server polls the OpenHands extensions repo using EXTENSIONS_REF
(defaulting to 'main'), while the frontend bundles the MCP catalog and
automations data from a pinned commit SHA in package.json. This caused the
two to silently diverge — skills loaded at runtime were from latest main
while the UI showed catalog data from an older commit.
Fix: derive a DEFAULT_EXTENSIONS_REF from the '#<sha>' fragment in the
@openhands/extensions git URL in package.json and inject it as the agent-
server's EXTENSIONS_REF when the caller hasn't set it explicitly.
- scripts/dev-safe.mjs: getExtensionsRef() helper reads package.json at
module init; buildAgentServerEnv() spreads EXTENSIONS_REF into the
child-process env unless the caller already set it.
- docker/Dockerfile (config-gen stage): reads package.json alongside
config/defaults.json and emits CONFIG_EXTENSIONS_REF=<sha> into
defaults.env when the dependency uses a pinned git SHA.
- docker/entrypoint.sh: applies CONFIG_EXTENSIONS_REF as the default via
EXTENSIONS_REF="${EXTENSIONS_REF:-${CONFIG_EXTENSIONS_REF:-}}".
User-supplied EXTENSIONS_REF always takes precedence in both paths.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: address review bot comments on EXTENSIONS_REF sync
- entrypoint.sh: guard EXTENSIONS_REF export so an absent CONFIG_EXTENSIONS_REF
never sets the variable to an empty string (which would defeat the
agent-server's own 'main' default in os.environ.get('EXTENSIONS_REF','main'))
- scripts/dev-safe.mjs: getExtensionsRef() now checks devDependencies as
fallback when @openhands/extensions is not in dependencies
- docker/Dockerfile config-gen stage: same devDependencies fallback
- AGENTS.md: replace internal constant name DEFAULT_EXTENSIONS_REF with the
actual env var EXTENSIONS_REF; also note the empty-string guard
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: pre-seed extensions cache when EXTENSIONS_REF is a commit SHA
The SDK's git clone --depth 1 --branch <sha> fails because --branch only
accepts branch/tag names, not raw 40-char commit SHAs (GitHub returns
'fatal: Remote branch <sha> not found').
When EXTENSIONS_REF is a commit SHA, pre-seed the public-skills cache with
a plain full git clone + checkout before starting the agent-server:
- docker/entrypoint.sh: pre-seeds after EXTENSIONS_REF is exported; is a
no-op when the cache already exists or EXTENSIONS_REF is a branch/tag name
- scripts/dev-safe.mjs: exports preseedExtensionsCache() and
DEFAULT_EXTENSIONS_REF; calls pre-seed in main() before spawning the
agent-server; guards with the same SHA regex as entrypoint.sh
- scripts/dev-with-automation.mjs: imports preseedExtensionsCache and
DEFAULT_EXTENSIONS_REF from dev-safe.mjs; calls pre-seed in
startAgentServer() before spawnService()
The SDK's update path (fetch + git checkout) can handle SHAs once the
repo exists, so this is sufficient without any SDK changes.
Co-authored-by: openhands <openhands@all-hands.dev>
* refactor(docker): bake extensions clone into image, cp at runtime
Move the git clone for the pinned extensions SHA from the entrypoint
into a dedicated Dockerfile build stage (extensions-cache).
Why: the clone is deterministic (same SHA for a given image), so doing
it once at build time is cheaper than doing it on first container start.
It also avoids network access at runtime entirely for Docker users.
How:
- New extensions-cache stage (node:24-slim + git): reads package.json,
clones OpenHands/extensions, checks out the pinned SHA into
/tmp/public-skills. When the dependency is not SHA-pinned the stage
exits early with an empty directory (graceful no-op).
- Final stage: COPY --from=extensions-cache stores the result at
/opt/agent-canvas/extensions-cache/ — outside the
/home/openhands/.openhands VOLUME so it is not hidden by bind-mounts.
chown passes ownership to openhands.
- entrypoint.sh: replaces git clone + checkout with cp -r from the
pre-baked path. The guard checks [ -d "${_ext_baked}/.git" ] so
the block is a no-op when the stage produced an empty directory.
The npm-dev preseedExtensionsCache() path in dev-safThe npm-dev preseedExtensionsCache() path in d — that still clones over the
network on first run (build-time baking does not apply to npm dev).
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(extensions-preseed): handle existing shallow clone missing the pinned SHA
When `npm run dev` has been run previously without the PR, the agent-server
SDK creates a shallow clone of the extensions repo via:
git clone --depth 1 --branch main
After PR #1060 is applied, `EXTENSIONS_REF` is set to the 40-char pinned
SHA. The SDK then tries `git checkout <sha>` against that shallow clone,
which fails because the specific commit is not in its shallow history.
`preseedExtensionsCache` had a blind early-return when `.git` already
existed, trusting the "SDK update path" to handle it. But the SDK's update
path also does a plain `git checkout <sha>` and cannot succeed on a shallow
clone that lacks the commit.
Fix: before returning early, verify the SHA is accessible with
git cat-file -t <sha>
If it returns anything other than "commit", the cache is a shallow clone
that pre-dates the pinned commit. Unshallow the existing clone with
git fetch --unshallow
so the full history is available, then checkout the SHA.
Co-authored-by: openhands <openhands@all-hands.dev>
* refactor(extensions): copy from node_modules on npm, always overwrite cache
Implement the intended architecture for extensions cache seeding:
npm path (dev-safe.mjs / dev-with-automation.mjs):
- Replace preseedExtensionsCache (git clone approach) with
copyExtensionsToSkillsCache, which copies the already-installed
node_modules/@openhands/extensions directly into the skills cache.
No network call required — npm already installed the package at
the pinned SHA.
- Always overwrite the cache (rm -rf + cpSync) so stale files from a
prior run that used a different version are never left behind.
- Set EXTENSIONS_REF unconditionally in buildAgentServerEnv so it
always matches the pre-seeded cache content.
Docker path (docker/entrypoint.sh):
- Always copy /opt/agent-canvas/extensions-cache into the skills cache
(rm -rf + cp -r), removing the prior guard that skipped the copy when
.git already existed.
- Set EXTENSIONS_REF unconditionally from CONFIG_EXTENSIONS_REF.
Both paths accept that the SDK will warn 'Using cached version' for a
raw commit SHA — that warning is the expected fallback until SDK polling
is disabled in a follow-up PR.
Note: the npm package only publishes integrations/ and automations/.
Docker's baked full clone also includes marketplaces/, plugins/, skills/.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(extensions): delete stale cache on npm startup instead of copying files
The copy-from-node_modules approach was still broken: the npm package has no
.git directory, so the SDK tried a fresh 'git clone --depth 1 --branch <sha>'
on top of the already-populated directory and got confused.
Simpler fix: just delete ~/.openhands/cache/skills/public-skills on startup.
The SDK starts with a clean slate and attempts its normal clone; for a raw SHA
ref it will warn 'Using cached version' which is the accepted fallback until
SDK polling is disabled in a follow-up PR.
- Replace copyExtensionsToSkillsCache (copy from node_modules) with
clearExtensionsCache (delete the directory)
- Remove now-unused cpSync import
- Docker path unchanged (baked clone always copied by entrypoint.sh)
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(extensions): always delete cache; only set EXTENSIONS_REF if not in env
Three simple rules:
1. Always delete ~/.openhands/cache/skills/public-skills on startup so the
SDK clones fresh — no conditional on DEFAULT_EXTENSIONS_REF.
2. Only inject EXTENSIONS_REF into the agent-server env when the caller has
not already set it (restore the !process.env.EXTENSIONS_REF guard).
3. Let the agent-server SDK do its own cloning from there.
Co-authored-by: openhands <openhands@all-hands.dev>
* feat(extensions): use OH_PUBLIC_SKILLS_PATH to bypass git polling
Switch from EXTENSIONS_REF (which still went through the SDK's broken
git clone --branch <sha> path) to OH_PUBLIC_SKILLS_PATH, introduced in
software-agent-sdk PR #3513. When set, the SDK skips all git operations
and loads public skills directly from the given directory.
npm path (dev-safe.mjs / dev-with-automation.mjs):
- Add cloneExtensionsForSkillsCache(sha, cacheDir): does a targeted
git fetch --depth=1 origin <sha> into ~/.openhands/cache/skills/public-skills.
Reuses the existing directory when it already contains the right commit.
- buildAgentServerEnv now sets OH_PUBLIC_SKILLS_PATH (not EXTENSIONS_REF)
pointing at that directory when DEFAULT_EXTENSIONS_REF is known and
OH_PUBLIC_SKILLS_PATH is not already in the environment.
Docker path (docker/entrypoint.sh):
- Removes EXTENSIONS_REF export entirely.
- After copying the baked clone to the skills cache, exports
OH_PUBLIC_SKILLS_PATH pointing at that directory.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(dev): include openhands-sdk in git-ref uvx args
When OH_AGENT_SERVER_GIT_REF is set, openhands-sdk was missing from the
--with args, so uv fell back to the released PyPI version. That version
lacks the public_skills_path parameter added to update_skills_repository
in SDK PR #3513, so the agent-server's skills_service called the old SDK
path and did a plain git clone of 'main' — overwriting the SHA-pinned
clone our cloneExtensionsForSkillsCache had just created.
Fix: add --with git+<repo>@<ref>#subdirectory=openhands-sdk alongside the
existing openhands-tools and openhands-workspace entries, matching what
the PyPI and local paths already do for openhands-sdk.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(dev): add --reinstall to git-ref uvx command
When OH_AGENT_SERVER_GIT_REF points at a branch whose version string
matches the current PyPI release (e.g. both are 1.25.0), uv silently
reuses the cached PyPI wheels and the git ref is never used.
--reinstall forces uv to build a fresh environment from the specified
git sources regardless of what is already cached. The git clone itself
is still cached in ~/.cache/uv/git-v0/, so only the first run after a
ref change pays the full network cost.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: rename OH_PUBLIC_SKILLS_PATH to PUBLIC_SKILLS_PATH
Aligns with the env var name used by the SDK PR.
Co-authored-by: openhands <openhands@all-hands.dev>
* test: update dev-safe buildAgentServerCommand tests for --reinstall and openhands-sdk
The git-ref path now adds --reinstall (so uv doesn't silently reuse cached
PyPI wheels) and includes openhands-sdk as a --with package so inter-package
APIs stay in sync. Update the two affected toEqual assertions to match.
Co-authored-by: openhands <openhands@all-hands.dev>
* refactor: use EXTENSIONS_REF instead of PUBLIC_SKILLS_PATH for extensions pinning
The PUBLIC_SKILLS_PATH / cloneExtensionsForSkillsCache approach was considered
but not chosen. EXTENSIONS_REF is the variable the agent-server SDK uses, and
it now skips network polling when the requested SHA is already in its cache.
- scripts/dev-safe.mjs: replace PUBLIC_SKILLS_PATH spread in buildAgentServerEnv
with a simple EXTENSIONS_REF injection; remove cloneExtensionsForSkillsCache
function, EXTENSIONS_REPO const, and the main() clone call; remove the
spawnSync and rmSync imports that were only needed for cloning
- scripts/dev-with-automation.mjs: remove cloneExtensionsForSkillsCache import
and the clone call from startAgentServer()
- docker/Dockerfile: remove the extensions-cache build stage entirely; keep the
CONFIG_EXTENSIONS_REF line in config-gen (still needed for entrypoint.sh)
- docker/entrypoint.sh: replace the cp-based seeding block with a single
export EXTENSIONS_REF="${EXTENSIONS_REF:-${CONFIG_EXTENSIONS_REF:-}}"
- AGENTS.md: replace inaccurate OH_PUBLIC_SKILLS_PATH bullet with an accurate
description of the EXTENSIONS_REF flow
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
|
||
|
|
b847dd296f |
Bump agent-server default to 1.25.0 (#1161)
Merged by request after CI passed on the rebased branch. |
||
|
|
7dbe8fd7de |
fix: populate <RUNTIME_SERVICES> in static builds so automations work (#1125)
* fix: populate <RUNTIME_SERVICES> in static builds so automations work The agent's <RUNTIME_SERVICES> system-prompt block is built from VITE_RUNTIME_SERVICES_INFO, which the dev launchers set at build time. Static builds (the Docker image and the published binary) run `npm run build` without it, so the block is dropped and the agent does not know how to reach the local automation backend — it falls back to the cloud API (app.all-hands.dev) and automation creation fails. Inject the info at serve time, mirroring the existing --session-api-key path: - scripts/static-server.mjs: new --runtime-services-info flag injects the JSON as window.__AGENT_CANVAS_RUNTIME_SERVICES_INFO__ - src/api/agent-server-adapter.ts: parseRuntimeServicesInfo() falls back to that window global when the env var is empty - docker/entrypoint.sh: builds the JSON from the sandbox-facing service URLs (AGENT_SERVER_URL, AUTOMATION_BASE_URL) and passes it to the static server(s) Refs #1098 * refactor: extract runtime-services-info into a shared module Replace the inline JSON building in docker/entrypoint.sh with a single source of truth for the <RUNTIME_SERVICES> shape. buildRuntimeServicesInfo now lives in scripts/runtime-services-info.mjs (moved out of dev-safe.mjs, which re-exports it for back-compat) and gains: - optional full-URL overrides (agentServerUrl, automation.url) so the container can pass its runtime-resolved AGENT_SERVER_URL / AUTOMATION_BASE_URL and use 127.0.0.1 (avoiding IPv6 loopback) instead of build-time ports, and - a CLI entrypoint so docker/entrypoint.sh emits the JSON by running the same builder the dev stack uses, rather than a hand-rolled node -e blob. The Dockerfile ships the new dependency-free module into the image. * fix: pass --runtime-services-info in static mode and verify in automation e2e - startStaticFrontend() in dev-with-automation.mjs now builds the runtime-services info JSON via buildAutomationRuntimeServicesInfo() and passes it to static-server.mjs via --runtime-services-info. Without this, the npm binary / dev:static path served pre-built frontends that never populated the agent's <RUNTIME_SERVICES> system-prompt block (the Docker entrypoint already did this). - The mock-LLM automation e2e test now verifies that the <RUNTIME_SERVICES> block is present in the system messages sent to the LLM, and that it includes the expected service entries (Agent Server, Automation backend, /api/automation). Co-authored-by: openhands <openhands@all-hands.dev> * docs: update AGENTS.md with runtime-services-info plumbing changes Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: Rohit Malhotra <rohitvinodmalhotra@gmail.com> Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
9e8b068850 |
fix(dev): keep tmux sockets under ~/.openhands so macOS won't reap them (#1107)
dev-safe.mjs put the tmux socket dir at os.tmpdir(). On macOS that is the per-user $TMPDIR (/var/folders/.../T), which the OS periodically reaps (com.apple.bsd.dirhelper deletes entries untouched for a few days). The reaper deletes the live tmux socket while the tmux server keeps running, orphaning it, so every later new-window fails with: LibTmuxException: new-window: error connecting to .../openhands-agent-canvas-tmux/tmux-501/openhands (No such file or directory) #325 moved the socket dir to os.tmpdir() to dodge a Unix-domain-socket failure on mounted volumes, but its intent (per the commit message) was literally /tmp -- os.tmpdir() != /tmp on macOS, and the per-user temp dir is exactly what gets reaped. Default to <stateDir>/tmux (~/.openhands/agent-canvas/tmux) instead, matching where the rest of dev state already lives: persistent, on local disk, and never reaped mid-session. dev-safe.mjs only ever runs on the host (the container launches openhands-agent-server directly, never this script), so the socket dir is always under a normal local home there. For the rare host whose $HOME is on a socket-incompatible mount (devcontainers, NFS/CIFS homes) -- the case #325 cared about -- honor the standard TMUX_TMPDIR env var (which we already pass through to the agent-server's tmux) so it can be pointed at a local path like /tmp. No new project-specific env var needed. Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4fa822d1f7 |
chore: bump openhands-automation to 1.0.0a6 (#1097)
* chore: bump openhands-automation to 1.0.0a6 * Remove fragile automation version assertion test The test hardcoded an exact version string that breaks on every version bump. It provides no utility — the version is already covered by integration/config tests; pinning it in a unit test just means a manual edit is required on every release. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
578589af7f |
fix(static-server): expose runtime session key via window global (#1091)
The published `agent-canvas` binary is built with no `VITE_SESSION_API_KEY` baked in. At runtime, `scripts/static-server.mjs --session-api-key <key>` injects the key into `index.html`, but only as a write to `localStorage["openhands-agent-server-config"].sessionApiKey`. PR #1046 ("Simplify backend registry selection") removed the legacy localStorage fallback from `getAgentServerSessionApiKey()`, leaving it reading only `import.meta.env.VITE_SESSION_API_KEY`. The combination broke the first-launch experience for users who installed the npm package globally: 1. Fresh browser → `localStorage["openhands-backends"]` is null. 2. `readLegacyBackend()` requires `baseUrl` AND `sessionApiKey` in `openhands-agent-server-config`; the injection only writes the key, so it returns null. 3. `makeDefaultLocalBackend()` reads `VITE_SESSION_API_KEY` → null → returns null. 4. Registry seeds empty → `MissingAgentServerScreen` renders the Manage Backends modal ("No extra backends added yet.") with no way out. The fix mirrors how `__AGENT_CANVAS_AUTH_REQUIRED__` already passes the public-mode flag from `static-server.mjs` to the bundle: - `static-server.mjs` now also injects `window.__AGENT_CANVAS_SESSION_API_KEY__ = <key>` in the `<head>` script. The window global is set first so it is available even if the localStorage write throws (private mode, etc.). - `getBakedSessionApiKey()` in `src/api/agent-server-config.ts` falls back to the window global when `VITE_SESSION_API_KEY` is empty. Tests: - Unit tests in `__tests__/api/agent-server-config.test.ts` cover the window-global fallback, env-takes-precedence ordering, whitespace, and non-string injected values. - `__tests__/scripts/static-server.test.ts` asserts the new window assignment lands before the localStorage write. - A new mock-LLM E2E spec in `tests/e2e/mock-llm/mock-llm-auth-modes.spec.ts` exercises the exact user scenario: a fresh browser context with no pre-seeded localStorage must reach the onboarding modal (not the Manage Backends trap modal) when launched against the prebuilt stack. - Updated docstrings in the auth-modes spec and helper to remove references to the deleted `syncBakedSessionApiKey()` function. AGENTS.md updated to document the window-global path and the current key-rotation reconciliation (`syncLauncherDefaultLocalBackend()` in `storage.ts`). Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
b9d78d3116 |
Simplify backend registry selection (#1046)
* Add partial stack modes to agent-canvas Co-authored-by: openhands <openhands@all-hands.dev> * Simplify backend registry selection Co-authored-by: openhands <openhands@all-hands.dev> * Fix backend selection CI regressions * Seed local proxy backend in cloud tests * Stabilize onboarding snapshot navigation * Stabilize ingress tests on Windows * Remove local backend fallback for cloud calls * Fix frontend-only backend proxy target * Test backend-only launch without build * Add pending workflow and status updates * Use active LLM profile for setup banner * Show backend connection errors before saving * Validate backend keys in health checks * Update backend selector health test mock * Fix frontend-only workspace path * Preserve OpenHands proxy base URL * Address backend review comments * Preserve profile config when switching models * Fail fast on profile export errors * Throw AgentServerUnavailableError when backend registry is empty When no backend is configured (empty registry / NO_BACKEND sentinel), loadAgentServerInfo() was returning null without throwing, causing OptionService.getConfig() to succeed silently. root.tsx then rendered the home page instead of the MissingAgentServerScreen with the manage backends modal. Now loadAgentServerInfo() checks for the NO_BACKEND sentinel when getEffectiveLocalBackend() returns null and throws AgentServerUnavailableError, which root.tsx already handles by showing the manage backends modal. The cloud-backend path (also null from getEffectiveLocalBackend) is preserved — it still returns null. Co-authored-by: openhands <openhands@all-hands.dev> * chore: Remove PR-only artifacts --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com> |
||
|
|
8bb2b8c518 |
Add frontend-only and backend-only agent-canvas modes (#1040)
* Add partial stack modes to agent-canvas Co-authored-by: openhands <openhands@all-hands.dev> * fix: address PR review feedback (#1040) - Replace padEnd(75) with ANSI-aware ansiPadEnd helper in printBanner; String.padEnd counts invisible escape bytes as visible chars, causing the box border to misalign in colour terminals - Deduplicate storage directory creation in ensureDirectories; was being pushed once per launchAgentServer block and once per launchAutomation block (always both true together) — move to a shared unconditional slot - Add explanatory comment to the checkNpm two-clause OR condition Co-authored-by: openhands <openhands@all-hands.dev> * test: add e2e tests for --frontend-only, --backend-only, and port conflicts Add mock-llm-partial-stack.spec.ts with three test groups: 1. --frontend-only: verifies static frontend is served (200 on /), backend routes return 503 (/server_info, /api/settings, /api/automation/v1), and the browser shows the manage-backends modal. 2. --backend-only: verifies /server_info returns 200, /api/settings is reachable, automation endpoint works, and root/asset requests return 503 (no frontend configured). 3. Port conflict: verifies the process exits non-zero with a clear error when the ingress port is occupied, then starts successfully on a free port. Unlike the other mock-llm specs, these tests spawn their own bin/agent-canvas.mjs child processes with isolated state dirs and high port numbers (18310+ range) to avoid collisions. Co-authored-by: openhands <openhands@all-hands.dev> * fix: adjust frontend-only test to expect SPA fallback instead of 503 In frontend-only mode the ingress has no backend routes — all requests (including /server_info, /api/*) fall through to the static server's default backend, which returns index.html via SPA fallback (200 with HTML). The test now verifies that /server_info returns HTML (not JSON) and that the browser detects the missing backend and shows the manage-backends modal. Co-authored-by: openhands <openhands@all-hands.dev> * feat: add --reject-prefix to static server for clean 503 on missing backends In --frontend-only mode the static server was SPA-fallbacking API paths (/server_info, /api/*, /sockets, etc.) to index.html, returning 200 with HTML content instead of a clear failure. This made the frontend's /server_info probe ambiguous. Add a --reject-prefix flag to static-server.mjs: any matching request returns 503 ('Service Unavailable') before the SPA fallback runs. Wire it through dev-with-automation.mjs: getRejectPrefixes(config) computes which API prefixes have no backend configured (e.g. all of them in frontend-only mode) and buildRejectPrefixArgs() passes them as --reject-prefix flags to the static server. Restore the e2e test to assert 503 for /server_info, /api/settings, and /api/automation/v1 in frontend-only mode. Co-authored-by: openhands <openhands@all-hands.dev> * fix: treat non-401 HTTP errors from /server_info as unavailable loadAgentServerInfo was re-throwing all SDK HttpErrors (including 503) as-is, but root.tsx only checks for AgentServerUnavailableError. A 503 from the static server in --frontend-only mode (or any non-401 error) would fall through to the Outlet instead of showing the manage-backends modal. Narrow the re-throw to only preserve 401 (needed for the auth screen in public mode). All other HTTP errors are now wrapped as AgentServerUnavailableError so the app shows the correct recovery UI. Co-authored-by: openhands <openhands@all-hands.dev> * fix: poll automation readiness in backend-only test; retry on 5xx The automation backend starts independently from the agent-server and may not be ready when /server_info first returns 200. pollUrl now treats 5xx responses as 'not ready yet' and keeps retrying. The backend-only test also polls /api/automation/v1 before asserting on it. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Rohit Malhotra <rohitvinodmalhotra@gmail.com> |
||
|
|
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> |
||
|
|
eb3ae22b23 |
fix: fast-fail dev scripts when ports are already in use (#939)
* fix: fast-fail dev scripts when ports are already in use Add `assertPortsFree` to `scripts/dev-safe.mjs` that checks each preferred port and throws a descriptive error if any are already bound, rather than silently binding to an alternative port. - `buildSafeDevConfigAsync` now calls `assertPortsFree` before returning config, so `npm run dev:minimal` exits immediately with a clear message when the agent-server port is occupied. - `dev-with-automation.mjs`'s `buildConfig` does the same for all four service ports (ingress, agent-server, automation, vite), covering `npm run dev` and `npm run dev:static`. - `buildConfig` gains env-var overrides for internal service ports (`OH_CANVAS_SAFE_BACKEND_PORT`, `OH_CANVAS_SAFE_AUTOMATION_PORT`, `OH_CANVAS_SAFE_VITE_PORT`) so tests and advanced users can redirect ports without touching production defaults. Tests updated accordingly: - Old "falls back when port is busy" tests replaced with "throws when port is busy" equivalents. - New `assertPortsFree` describe block with three targeted cases. - `envWithIsolatedKeyPath` in `dev-with-automation.test.ts` now seeds high free ports so the pre-flight check passes when a real dev stack is running during local test execution. Closes #934 Co-authored-by: openhands <openhands@all-hands.dev> * refactor: address review bot suggestions on assertPortsFree - Check ports in parallel with Promise.all instead of sequentially (each check is independent I/O, so parallel is faster and more idiomatic) - Include vscode port in the buildSafeDevConfigAsync pre-flight check alongside the agent-server port, so a conflict on that internal port is also caught with a helpful message rather than a cryptic spawn error Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
665a258b80 |
feat(acp): inline live model picker for ACP conversations (#769) (#832)
* feat(acp): inline live model picker for ACP conversations (#769) Converge ACP model selection onto the native LLM-profile inline picker UX with live mid-conversation switching, replacing the display-only popover. - Bump @openhands/typescript-client 1.23.3 -> 1.24.0 (adds switchAcpModel). - AgentServerConversationService.switchAcpModel(conversationId, model): POST /switch_acp_model via ConversationClient, with switchProfile's local-only guard. - useSwitchAcpModel hook: live switch for a running ACP session; for the home/no-session case, persist the choice as the agent-settings default (agent_settings_diff { acp_model }) so the next conversation inherits it. - ChatInputModel popover becomes a picker over the provider's available_models (check on the effective model), local backend only; cloud / custom-provider / native surfaces keep the display + Settings link. - New i18n key MODEL$AVAILABLE_MODELS. - Tests for the hook (live vs settings-default branches) and the picker. Local backend only (matches native switching); custom/unknown providers and any app_server route remain out of scope per #769. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(acp): open the model picker on click (don't self-close via click-outside) The inline picker's trigger button sits outside the popover element, so the document click-outside handler (useClickOutsideElement) treated the opening click as an "outside" click and closed the popover in the same interaction — clicking the chip appeared to do nothing. (A programmatic el.click() worked by fluke: the popover isn't rendered yet when that click bubbles, so the ref is null and the close is skipped.) Pass the trigger button as the hook's ignoreOutsideClickRef so a click on the chip toggles the popover instead of being treated as an outside click. Validated end-to-end against a local agent-server 1.24.0: the picker opens and lists the provider's available_models, and selecting one writes the default via PATCH /settings (home case), with the chip updating to the new model. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(acp): drop disableToast in useSwitchAcpModel so switch errors surface useSwitchLlmProfile sets meta.disableToast because it's wrapped by useSwitchLlmProfileAndLog, which re-surfaces errors via its own onError. useSwitchAcpModel is called directly (no such wrapper / no onError), so disableToast was silently swallowing failed switches and settings writes (e.g. a 409 before the first message, network errors, the cloud guard). Remove it and let the global mutation error toast report failures — simpler and gives the user feedback when a switch doesn't take. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(acp): share chat input model picker state * chore: address PR review feedback (#832) - Add unit test for useChatInputModelState pinning its branching contract, incl. the active-ACP getAcpProvider lookup (was home-only in old component). - Document why the overflow model submenu uses overflow-y-auto (scroll long model lists) rather than overflow-visible — no floating children to clip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: address PR review feedback (#832) - Wrap the 'Available models' section label in a presentational <li> so it is a valid child of the ContextMenu <ul> (was a bare <div>). - Drop unnecessary 'as never' casts in use-switch-acp-model tests now that the real return types (Promise<void>, Promise<boolean>) are honored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(acp): bump agent-server pin to 1.24.0 for /switch_acp_model The inline ACP model picker POSTs to /api/conversations/{id}/switch_acp_model, which is new in openhands-agent-server 1.24.0. The PR description already lists agent-server:1.24.0 as a dependency, but config/defaults.json was left at 1.23.1, so local dev (npm run dev) and Docker installs would 404 on every model switch attempt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(settings): always land on /settings/agent from /settings The fallback order in ``getFirstAvailablePath`` put ``/settings/llm`` first whenever ``hide_llm_settings`` was off, so clicking Settings sent the user to the LLM page. For ACP users that page is disabled and ``redirectIfAcpActive`` only catches them when the *personal* settings already say ``agent_kind === "acp"`` — being in an ACP conversation with non-ACP personal settings (the common case during the inline picker flow) bypassed the guard and dumped them on /settings/llm. Make ``/settings/agent`` the unconditional first fallback. It is always available (no feature flag hides it), houses the agent-kind picker, and the left nav still gets OpenHands users to LLM in one click — so one extra click for non-ACP users buys a much simpler routing surface and kills the ACP misroute. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(settings/agent): clear command when switching to Custom preset Selecting "Custom" in the agent preset dropdown reset ``acpModel`` and flipped ``isCustomAcpModel`` but left ``commandText`` untouched. On the next render, ``detectPreset(commandText, ACP_PROVIDERS)`` still matched the previous provider's ``default_command`` and snapped the dropdown back off "Custom" — the toggle never stayed on Custom. Clear ``commandText`` in the Custom branch so ``detectPreset`` falls through to ``ACP_CUSTOM_PRESET_KEY`` on the next render and the dropdown stays where the user put it. Empty command also matches the intended "user supplies their own" semantics of the preset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(settings): mark Verification page as disabledByAcp The Verification page writes ``confirmation_mode`` and ``security_analyzer`` into ``conversation_settings_diff``. The ACP agent loop never reads either: ``openhands/sdk/agent/acp_agent.py`` has zero references to ``confirmation_policy`` or ``security_analyzer``, and the only runtime readers (``openhands/sdk/agent/agent.py:844,855``) live on the native ``Agent`` class — not on ``ACPAgent``. The backend accepts the values and stores them on conversation state, but the ACP subprocess never consults them. So the page presents real-looking knobs that silently do nothing for ACP users. Mark it ``disabledByAcp: true`` — same pattern as ``/settings/llm`` and ``/settings/condenser`` — so it greys out in the nav and the existing route guard at ``src/routes/settings.tsx:47-51`` bounces direct visits to ``/settings/agent``. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ci): bump doc/script SDK version examples to 1.24.0 The docs-version-sync test enforces that every documented agent-server version example matches ``config/defaults.json:versions.agentServer``. The previous commit bumped that pin from 1.23.1 to 1.24.0 for the ``/switch_acp_model`` route, but left the example references in AGENTS.md, ``scripts/dev-safe.mjs``, and ``scripts/check-sdk-version-sync.mjs`` behind — the drift-detector caught it as ``test-and-build`` failure. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ci): bump remaining hard-coded 1.23.1 to 1.24.0 ``__tests__/scripts/dev-safe.test.ts`` asserts ``buildAgentServerCommand``'s default ``uvx`` args literally include ``openhands-agent-server==1.23.1`` and matching ``openhands-{sdk,tools,workspace}==1.23.1``. The CI fix in the prior commit only updated docs and example references; the central pin bump in ``config/defaults.json`` flowed through to this test's runtime expectation but the literal expectations were never updated. Bump them. Also bump the ``MOCK_AGENT_SERVER_VERSION`` placeholder in ``src/mocks/settings-handlers.ts`` for consistency with the central pin — no test asserts on it, but leaving the mock at 1.23.1 invites future drift confusion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Debug Agent <debug@example.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b570b4a2f6 |
fix: buildNpmScriptCommand always uses cmd.exe on Windows (#734)
* fix: buildNpmScriptCommand always uses cmd.exe on Windows On Windows, npm sets npm_execpath to a path like C:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js which contains spaces. buildNpmScriptCommand was returning that path as a spawn argument with the full node.exe path as the command. spawnService uses shell:true on Windows, so Node.js passes the unquoted command to cmd.exe: cmd.exe /d /s /c C:\Program Files\nodejs\node.exe ... cmd.exe splits on the space and fails with 'C:\Program' is not recognized as an internal or external command causing Vite to exit with code 1 immediately after npm run dev. Fix: check platform === 'win32' BEFORE checking npm_execpath so Windows always uses the safe cmd.exe /d /s /c npm run <script> form. Co-authored-by: openhands <openhands@all-hands.dev> * fix: buildNpmScriptCommand always uses cmd.exe on Windows On Windows, npm sets npm_execpath to a path like C:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js which contains spaces. buildNpmScriptCommand was returning that path as a spawn argument with the full node.exe path as the command. spawnService uses shell:true on Windows, so Node.js passes the unquoted command to cmd.exe: cmd.exe /d /s /c C:\Program Files\nodejs\node.exe ... cmd.exe splits on the space and fails with 'C:\Program' is not recognized as an internal or external command causing Vite to exit with code 1 immediately after npm run dev. Fix: check platform === 'win32' BEFORE checking npm_execpath so Windows always uses the safe cmd.exe /d /s /c npm run <script> form. Co-authored-by: openhands <openhands@all-hands.dev> * fix: AnimatePresence mode=wait expects one child, not two ChatStatusIndicator had two separately-keyed motion.span children inside <AnimatePresence mode="wait">. framer-motion's wait mode expects exactly ONE child to exit before the next enters; two children trigger the repeated warning: "attempting to animate multiple children within AnimatePresence, but its mode is set to 'wait'" Fix: wrap both elements in a single motion.span with unified key={status} and className="contents" (CSS display:contents preserves flex layout). Co-authored-by: openhands <openhands@all-hands.dev> * fix: set PYTHONUTF8=1 in agent-server/automation env on Windows Python on Windows defaults to the system ANSI codepage (cp1252). The agent-server writes metadata JSON containing emoji (e.g. U+2705 ✅) that cp1252 cannot encode, producing UnicodeEncodeError → POST /api/conversations 500. Old UTF-8 conversation files also fail to load at startup (UnicodeDecodeError). PYTHONUTF8=1 enables Python's UTF-8 mode (PEP 540) for the process, matching Linux/macOS behaviour. Co-authored-by: openhands <openhands@all-hands.dev> * fix: set PYTHONUTF8=1 in agent-server/automation env on Windows Python on Windows defaults to the system ANSI codepage (cp1252). The agent-server writes metadata JSON containing emoji (e.g. U+2705 ✅) that cp1252 cannot encode, producing UnicodeEncodeError → POST /api/conversations 500. Old UTF-8 conversation files also fail to load at startup (UnicodeDecodeError). PYTHONUTF8=1 enables Python's UTF-8 mode (PEP 540) for the process, matching Linux/macOS behaviour. Co-authored-by: openhands <openhands@all-hands.dev> * refactor: remove unrelated file --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
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>
|