mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 15:58:03 +08:00
2e1502f39d8f7357fca35c6c18cc2c0dadcf0da3
152
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
52fb583107 |
feat(conversations): add tag chips, overflow, and hovercard labels (#16346)
Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
4d3d9d197c |
feat(settings): polish Agent Canvas version update UI (#16343)
Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
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> |
||
|
|
8e5dc43004 |
feat: add a domain-neutral extension-manifest host (#16127)
Co-authored-by: Graham Neubig <neubig@gmail.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> |
||
|
|
c86824bafd |
fix: update release repository metadata (#16186)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
1afddcdf67 | chore: bump software-agent-sdk to 1.38.0 and automation to 1.4.1 (#16129) | ||
|
|
4c5bcdcc12 |
feat(settings): add title generation profile preference (#1910)
* feat(settings): add title generation profile preference * test: wait for profile rename completion |
||
|
|
7b1e07d6f8 |
feat: add windows desktop installer build, docs, and win32 fixes (#1897)
* feat: add Windows desktop installer build, docs, and win32 fixes * fix: failing tests * refactor: update the code based on feedback |
||
|
|
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> |
||
|
|
d814348263 |
feat: add macos build workflow and document the install path (#1911)
* feat: add macOS DMG build workflow and document the install path * fix: failing workflow * refactor: update the code based on feedback |
||
|
|
1450d21308 | chore: bump software-agent-sdk to 1.37.0 and automation to 1.2.0 (#1906) | ||
|
|
5f77656b76 | feat: add Agent Canvas update-availability card to settings (#1898) | ||
|
|
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> |
||
|
|
2b7ceea667 |
refactor: define canvas UI as an SDK client tool (#1797)
* refactor: define canvas UI as an SDK client tool Send a JSON-defined canvas_ui_client tool on new, profile-based, and resumed conversation requests while retaining the legacy Python registration for persisted conversations. Normalize the new SDK event kinds to the existing Canvas UI rendering. Co-authored-by: smolpaws <engel@enyst.org> Co-authored-by: openhands <openhands@all-hands.dev> * fix: omit canvas client tool from ACP launches * refactor: rename canvas client tool Use the semantic canvas_ui_control name and contain the SDK-generated action discriminator behind exported constants. Co-authored-by: Engel Nyst <engel.nyst@gmail.com> --------- Co-authored-by: Engel Nyst <engel.nyst@gmail.com> Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Debug Agent <157206163+simonrosenberg@users.noreply.github.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 |
||
|
|
74f06866ec |
Use libraries for local proxy and static serving (#1543)
* Use libraries for local proxy and static serving * Fix CI for proxy library refactor * Fix static server CI failures Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: Codex <codex@openai.com> Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
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 |
||
|
|
37b8bc6e41 |
Add command-k menu (#1206)
* Add command menu Co-authored-by: openhands <openhands@all-hands.dev> * Add command menu keyboard coverage * Translate command menu strings * Stabilize command menu sidebar action * Stabilize command menu item definitions * chore: add live evidence for PR #1206 under .pr/ Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: neubig <398875+neubig@users.noreply.github.com> Co-authored-by: Graham Neubig <gneubig@users.noreply.github.com> |
||
|
|
3f52df2e39 |
feat(settings): add Cloud link to settings sidebar for cloud backends (#1453)
* feat(settings): add Cloud link to settings sidebar for cloud backends
Add a "Cloud" external link at the bottom of the Settings sidebar that
appears only when the active backend is a Cloud backend. It links to
`{cloudHost}/settings` (opens in a new tab) with an external-link icon,
giving users a quick path to their hosted account/settings page. Local
backends and the no-backend state render nothing.
- New `CloudSettingsLink` component reads the active backend and renders
only for cloud backends, normalizing the host (trailing slash) before
appending `/settings`.
- Added to the desktop sidebar, mobile hub, and mobile drawer (next to
the existing backend-synced badge).
- New i18n key `SETTINGS$CLOUD_SETTINGS_LINK` (allowlisted as a brand
name since "Cloud" is identical across locales).
- Added unit tests covering cloud/local/no-backend cases and URL building.
Screenshot of the new Cloud button in the Settings window: .pr/settings-cloud-button.png
Co-authored-by: openhands <openhands@all-hands.dev>
* docs(pr): replace screenshot with correct Settings sidebar view
The previous screenshot captured the manage-backends overlay that
appears for a logged-out cloud backend, not the Settings sidebar.
Re-captured against a connected cloud backend so the Cloud link is
visible at the bottom of the Settings sidebar alongside the nav items.
Co-authored-by: openhands <openhands@all-hands.dev>
* feat(settings): add cloud glyph to Cloud settings sidebar link
Render the Cloud settings link with a leading cloud icon (lucide `Cloud`)
next to the "Cloud" label, keeping the trailing external-link icon so
users can tell it opens the hosted page in a new tab. Matches the other
iconified rows in the Settings sidebar.
Update the PR screenshot to show the new two-icon layout.
* feat(settings): place Cloud link below Secrets with nav-row styling
Move the Cloud settings link out of the sidebar footer into the nav
list, directly below the Secrets entry and above the synced-settings
badge, so it sits where users expect a settings sub-page to be.
Restyle the link to match the other sidebar nav rows: reuse the shared
sidebar-layout classes (sidebarNavRowClassName + SIDEBAR_ROW_INTERACTIVE
idle, SIDEBAR_ICON_SLOT_CLASS, sidebarNavLabelClassName) so it has the
same height, padding, border-radius, transparent idle background, and
hover background as Secrets/LLM/etc. Drop the bespoke bordered card.
Still renders a leading cloud glyph, the "Cloud" label, and a trailing
external-link icon.
Apply the same placement in the mobile hub and mobile drawer.
* chore: Remove PR-only artifacts
* docs(settings): simplify CloudSettingsLink JSDoc; add PR screenshots
- Apply reviewer suggestion to trim verbose JSDoc (keep only the
non-obvious note about why local backends are excluded).
- Add the three evidence screenshots referenced in the PR description
to .pr/ so the Video/Screenshots links resolve on the branch.
---------
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: neubig <398875+neubig@users.noreply.github.com>
Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com>
|
||
|
|
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> |
||
|
|
c7a00b14ab |
fix: seed AUTOMATION_KV_SECRET for local dev stack (#1414)
* fix: seed AUTOMATION_KV_SECRET for local dev The KV store (automation PR#69) requires AUTOMATION_KV_SECRET to be set or every KV endpoint returns 503. In a local dev stack the secret is never configured, so the store is silently unavailable. Fall back to sessionApiKey when the env var is not set explicitly — the same zero-config pattern already used for AUTOMATION_AGENT_SERVER_URL, AUTOMATION_BASE_URL, and AUTOMATION_WORKSPACE_BASE. Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump openhands-automation to 1.0.0a10 Update to the latest released version of openhands-automation on PyPI. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
e1c9e6ab33 |
chore: remove redundant automationSdk version — derive from agentServer (#1333)
The automationSdk version was always intended to equal agentServer. Having a separate field creates a maintenance foothole where the two values can silently drift. Remove automationSdk from defaults.json and have all consumers (check-sdk-version-sync, dev-with-automation, agent-canvas CLI --version output) read versions.agentServer directly. The sync check still catches any mismatch between the released openhands-automation package and the expected SDK version. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
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> |
||
|
|
82ea2b609a | feat: save hosted MCP credentials as secrets (#1331) | ||
|
|
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> |
||
|
|
66394db18a | fix: translate English-copied keys and guard against untranslated values (#1305) | ||
|
|
10f622a430 | chore: remove one-off migration codemods, demo recorder, and unused beep asset (#1232) | ||
|
|
dcf469855a |
UI polish: drawer tabs, empty states, and browser chrome (#1288)
* chore: bump version to 1.0.0-beta.1 * chore: publish beta and rc versions as 'latest' dist-tag * chore: bump version to 1.0.0-beta.2 * fix: use X-Session-API-Key for local automation auth in prompts and RUNTIME_SERVICES (#999) Fixes #980 The agent prompt in recommended-automations-launcher and the RUNTIME_SERVICES block in agent-server-adapter both advertised X-API-Key as the auth header for the local automation backend. The automation service (openhands-automation) does not accept X-API-Key — it accepts Authorization: Bearer and X-Session-API-Key. X-Session-API-Key is the established local convention: the agent server uses it, the frontend automation API client uses it (with an explicit comment that both backends share the same header), and auth.py describes it as matching that convention. Update both call sites and the corresponding test assertion to use X-Session-API-Key. Co-authored-by: openhands <openhands@all-hands.dev> * feat: reuse mock-LLM E2E tests for Docker image validation (#992) * feat: reuse mock-LLM E2E tests for Docker image validation Add a Docker-specific Playwright config (playwright.mock-llm-docker.config.ts) that runs the exact same test specs and helpers against the agent-canvas Docker image instead of the npm build path (bin/agent-canvas.mjs + uvx). Key changes: - Split MOCK_LLM_BASE_URL into two constants in mock-llm-helpers.ts: - MOCK_LLM_BASE_URL: always host-local, used by tests for admin API - MOCK_LLM_AGENT_URL: env-overridable, used when configuring the LLM profile (the URL the agent-server uses for inference). Defaults to MOCK_LLM_BASE_URL for backward compatibility with the npm path. - New playwright.mock-llm-docker.config.ts: - Starts the mock LLM server on the host (same as npm path) - Runs the Docker container with --network host (Linux CI) - Points to the same testDir (tests/e2e/mock-llm/) and specs - Separate output dirs to avoid collision with npm path results - New CI workflow (.github/workflows/mock-llm-docker-e2e.yml): - Builds the Docker image from current code (or uses a pre-built image) - Runs the same specs against the container - Posts PR comment with differentiated report title - render-mock-llm-report.mjs: accept --title flag for Docker vs npm reports - npm run test:e2e:mock-llm:docker script added - .gitignore updated for docker test output dirs The npm path (test:e2e:mock-llm) is fully backward-compatible — no env var override needed since MOCK_LLM_AGENT_URL defaults to MOCK_LLM_BASE_URL. Co-authored-by: openhands <openhands@all-hands.dev> * refactor: chain Docker E2E off existing Docker CI via workflow_run Instead of rebuilding the Docker image in the E2E workflow (duplicating ~10-15 min of Docker build time), use workflow_run to trigger automatically after the existing 'Docker' workflow completes successfully. The workflow now: - Triggers on: workflow_run (Docker completed) + workflow_dispatch (manual) - Derives the image tag from the Docker build's commit SHA (ghcr.io/openhands/agent-canvas:sha-<short>-amd64) - Pulls the already-built image from GHCR — no rebuild needed - Checks out code at the same SHA as the Docker build - Extracts PR number from workflow_run.pull_requests[] for comments Removed: Docker build steps, Buildx setup, build-arg resolution. All image building stays in docker.yml where it belongs. Co-authored-by: openhands <openhands@all-hands.dev> * fix: replace flaky 1s timeout with polling for Active badge assertion The 'Active badge' check in step 2 used a hardcoded 1-second waitForTimeout before reloading. On a loaded CI runner the profile activation mutation may not persist in time, causing the reload to show stale state. This is a pre-existing flake (identical test code passed on the first push and failed on the second). Replace with expect.poll() that retries the reload+check cycle with increasing intervals (1s, 2s, 3s) up to 15 seconds total. Co-authored-by: openhands <openhands@all-hands.dev> * fix: add pull_request trigger for Docker E2E (workflow_run bootstrap) workflow_run only fires when the workflow file exists on the default branch (main). Since mock-llm-docker-e2e.yml is new and only on the PR branch, GitHub doesn't recognize it as a workflow_run listener yet. Add pull_request trigger (gated by 'e2e-tests' label, skip forks) that polls the Docker workflow via gh API until it completes for the PR's head SHA, then pulls the already-built image from GHCR and runs tests. After merge, workflow_run takes over as the primary automatic trigger. The pull_request path remains as a fallback for label-gated runs. Co-authored-by: openhands <openhands@all-hands.dev> * fix: add FILE_STORE, AUTOMATION_BASE_URL, AUTOMATION_WORKSPACE_BASE to Docker entrypoint The Docker entrypoint was missing several environment variables that the npm path (dev-with-automation.mjs) sets for the automation backend: - FILE_STORE=local — without this, the automation backend may fall back to cloud storage (S3/GCS) which fails without credentials, causing tarball- based presets (preset/prompt, preset/plugin) to silently error - LOCAL_STORAGE_PATH — where to store files on the local filesystem - AUTOMATION_BASE_URL — publicly-reachable base URL for callback URLs - AUTOMATION_WORKSPACE_BASE — where automation runs unpack tarballs This explains the Docker E2E failure: the agent's curl to create an automation via /api/automation/v1/preset/prompt returned an error (likely 500 from missing storage config), but the mock LLM doesn't care about terminal output and proceeded to return the scripted final reply. The test then found 0 automations. Co-authored-by: openhands <openhands@all-hands.dev> * fix: exclude auth-modes spec from Docker E2E tests The mock-llm-auth-modes.spec.ts tests npm-binary-specific --auth-required behaviour (a second static-server instance on port 18301). The Docker image doesn't provide this second server — it has its own auth handling. Exclude the spec from the Docker test run via testIgnore. Co-authored-by: openhands <openhands@all-hands.dev> * feat: run auth-modes tests inside Docker via PUBLIC_MODE_PORT Instead of excluding the auth-modes spec from the Docker E2E run or spinning up a host-side static server with a duplicate build/ directory, the Docker entrypoint now supports an optional PUBLIC_MODE_PORT env var. When set, entrypoint.sh starts a second static-server instance from the same baked-in frontend assets with --auth-required (no session key injected). This tests the actual Docker image's auth gate behaviour — not a host-side approximation. The Playwright Docker config passes -e PUBLIC_MODE_PORT=18301 to the container and exports MOCK_LLM_PUBLIC_MODE_URL so the auth-modes spec can reach it. With --network host the port is accessible from the host. Co-authored-by: openhands <openhands@all-hands.dev> * address review feedback: drop unlabeled trigger, improve error messages, document env vars - Drop 'unlabeled' from pull_request trigger types to avoid wasted workflow runs when any label is removed (the job-level if: condition would skip immediately anyway) - Distinguish 'no Docker run found' vs 'didn't complete in time' in the polling loop's final error message - Add comment explaining /api/automation/v1 probe returns 200 without auth so the readiness check won't spin for 180s - Document FILE_STORE, LOCAL_STORAGE_PATH, AUTOMATION_BASE_URL, and AUTOMATION_WORKSPACE_BASE in the entrypoint header — these affect production deployments, not just E2E tests Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump version to 1.0.0-beta.3 * ci: trigger CI on rel-* branch pushes for tag protection rule (#1004) The Release Tag ruleset requires test-and-build (ubuntu) to pass before v* tags can be pushed, but CI previously only ran on main and pull_request events. This caused rel-* version bump commits to fail the tag protection check unless a workaround PR was opened. Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump version to 1.0.0-beta.4 * chore: auto-graduate npm dist-tag from latest to per-tier once first stable release ships (#1028) * chore: always publish to npm with --tag latest until first stable release All alpha/beta/rc versions now get the 'latest' dist-tag so plain 'npm install @openhands/agent-canvas' always resolves to the newest published release. The per-tier dist-tags (alpha/beta/rc) can be re-introduced once the first full stable version is ready to ship. Co-authored-by: openhands <openhands@all-hands.dev> * chore: auto-graduate npm dist-tag when first stable release ships At publish time, query npm for any published version without a pre-release suffix. If none exists, all releases (alpha/beta/rc/stable) use --tag latest so plain 'npm install' always resolves to the newest build. Once a stable version has been published, pre-release versions revert to their own dist-tags (alpha/beta/rc) automatically — no workflow change required. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump version to 1.0.0-beta.5 * feat(mcp): render markdown links in helperText; update Slack catalog pin (#1012) * feat(mcp): render markdown links in helperText; bump extensions to slack field-order PR commit - Add renderHelperText() to install-server-modal.tsx that converts [text](url) patterns into <a> elements with target=_blank, so the Slack workspace-ID helper text (and any future catalog entries) can embed clickable docs links inline. - Bump @openhands/extensions to commit 2d43e9c (branch slack-catalog-field-order-and-helper-links, PR #285) which: • moves SLACK_TEAM_ID before SLACK_BOT_TOKEN in the install modal • replaces the plain SLACK_TEAM_ID helper text with linked copy: 'First visit [here](...#find-your-url) to get your Slack URL and then visit [here](...#find-your-workspace-or-org-id) to get your workspace ID.' - Removes stale integrity hash from package-lock.json for the @openhands/extensions entry; npm install will recompute it. Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump @openhands/extensions to d186872 (SLACK_BOT_TOKEN helperText) Add inline linked helperText for SLACK_BOT_TOKEN in slack.json (PR #285, commit d186872): 'You'll need to create or update a Slack App as shown [here](https://github.com/zencoderai/slack-mcp-server#slack-bot-setup).' Drops the now-redundant helperLink field. Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump @openhands/extensions to b45d3a1 (SLACK_TEAM_ID helperText rewrite) Update SLACK_TEAM_ID helperText to named links: 'First get your [Slack URL](...). Then use that to get your [Workspace ID](...).' Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump @openhands/extensions to 84a0a6e (SLACK_BOT_TOKEN named link) Update SLACK_BOT_TOKEN helperText to: "You'll need to create or update a [Slack App](...#slack-bot-setup)." Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump @openhands/extensions to e07f427 (SLACK_BOT_TOKEN helperText) Update SLACK_BOT_TOKEN helperText to: "You'll need to create or update a [Slack App](...) to get a Bot token" Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump @openhands/extensions to 5efd1b8 Sync to latest commit on slack-catalog-field-order-and-helper-links (PR #285). Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump @openhands/extensions to 952c759 Sync to latest commit on slack-catalog-field-order-and-helper-links (PR #285). Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump @openhands/extensions to f30dbfb Sync to latest commit on slack-catalog-field-order-and-helper-links (PR #285). Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump @openhands/extensions to 02715f4 Sync to latest commit on slack-catalog-field-order-and-helper-links (PR #285). Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump @openhands/extensions to cb092c8 Sync to latest commit on slack-catalog-field-order-and-helper-links (PR #285). Co-authored-by: openhands <openhands@all-hands.dev> * fix(mcp): validate URL scheme in renderHelperText; use matchAll - Guard href against javascript:/data: XSS via /^https?:\/\//i test - Replace exec-in-while with matchAll to drop the eslint-disable comment Addresses review bot feedback on PR #1012. Co-authored-by: openhands <openhands@all-hands.dev> * fix(mcp): use double quotes for fallback href to satisfy Prettier Co-authored-by: openhands <openhands@all-hands.dev> * chore: update @openhands/extensions to latest main (62594156) Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> * chore: bump version to 1.0.0-beta.6 * chore: bump version to 1.0.0-beta.7 * fix(mcp): drop duplicate renderHelperText after main merge * chore: bump version to 1.0.0-beta.8 * docs: update README version to 1.0.0-beta.8 * fix: default LLM setup to Anthropic Claude Opus 4.8 (#1089) * chore: bump version to 1.0.0-beta.9 * docs: update README version to 1.0.0-beta.9 * docs: update README.windows.md version to 1.0.0-beta.9 * fix(dev): align Vite dev origin with ingress and add chat footer padding Route modules loaded from :3001 while the app opened on :8000, causing blank screens on npm run dev. Point Vite server.origin/HMR at the ingress URL and add bottom spacing under the archived conversation banner footer. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): polish spinners, settings empty states, and archived conversation UX Remove grey track rings from all loading spinners so only the animated arc remains visible. Wrap bare settings empty/error messages (SDK schema unavailable, profile load failures, empty profiles/skills/secrets/MCP) in the shared bordered empty-state container for visual consistency. Canonicalize 127.0.0.1 backend URLs to localhost so health probes reach the ingress proxy instead of Vite HMR on macOS dual-stack dev stacks, and sync stored default-local backend host alongside the session key. Disable conversation controls for archived sandboxes (MISSING/ERROR) with tooltips explaining unavailability, using shared archive-status helpers. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): restore light foreground on conversation tab loading state Use the semantic text-foreground token for the spinner and label so loading copy stays readable on the dark surface background. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): polish conversation tab loading and automations empty state Conversation tab loading: - Use TextShimmer on the loading label (same treatment as message sending) with block w-full text-center so the sweep flows across the word, not per character - Keep the spinner on text-tertiary-light for readable secondary grey - Add ConversationTabContentCrossfade to cross-fade between loading and loaded content (agent init and lazy tab chunks); content preloads underneath at opacity 0 while the overlay fades out over 350ms; reduced-motion falls back to an instant swap Automations empty state: - Add a top border above the create-instructions section to separate it from the hint copy Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): unify drawer empty/loading states and polish browser/files tabs Align Changes, VS Code, and runtime waiting states with shared drawer patterns, add browser chrome bar with inactive nav when empty, and improve Files tab empty state and tree toggle icon. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): remove browser screenshot rounding and improve panel fill Drop rounded corners on the screenshot viewer and use min-h-0 flex layout so the browser tab fills the drawer edge-to-edge and collapses correctly. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): polish protip banner, browser chrome, and tab crossfade Hide non-functional browser nav controls, restyle the changes-tab protip with icon and muted subtext, drop Customize label colons, and fix Suspense fallback setState during render. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): move VS Code to files toolbar and refresh drawer icons Relocate editor access from the drawer Code tab into a bordered Files toolbar button, swap tab icons to Lucide, add a terminal empty state, and update the VS Code logo asset. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): animate drawer tab label reveal and icon shifts Use Framer Motion layout transitions so the active tab label expands in and sibling icons slide smoothly when switching drawer tabs. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): pin VS Code in drawer tab row and fix tab drag animation Move VS Code to the drawer header, portal the overflow menu so it is not clipped, and disable tab layout animations while resizing the panel so icons only animate on click. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): shrink drawer tab icons to match standard chrome size Use h-4 w-4 for drawer tab icons so they align with the ellipsis and other inline controls in the top row. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(home): allow changing repo, branch, or workspace before launch Replace static git-control-bar link chips on the home screen with the same dropdowns used in the open-workspace and open-repository dialogs so users can revise their selection until they send the first message. Co-authored-by: Cursor <cursoragent@cursor.com> * Revert "feat(home): allow changing repo, branch, or workspace before launch" This reverts commit 569bf18bd18dbbe2bd2eaec5747737a162079e0e. * refactor: remove unrelated files * refactor: remove unrelated files * refactor: remove unrelated files * refactor: remove unrelated files * refactor: remove unrelated files * refactor: vscode tab --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Tim O'Farrell <tofarr@gmail.com> Co-authored-by: Rohit Malhotra <rohitvinodmalhotra@gmail.com> Co-authored-by: chuckbutkus <chuck@openhands.dev> Co-authored-by: Hiep Le <69354317+hieptl@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
b969162027 |
test(mock-llm): add E2E coverage for Files tab, Git control bar, and Browser tab (#1029)
* test(mock-llm): add E2E coverage for Files tab, Git control bar, and Browser tab Add mock-LLM E2E tests exercising conversation panel tabs and git integration against the real agent-server: - Files tab defaults to diff view when a workspace is attached (selected_workspace seeded in conversation metadata localStorage) - Files tab defaults to file-tree view when NO workspace is attached - Git control bar shows workspace-name pill for folder-attached conversations - Browser tab renders empty state when no page has been browsed All tests run serial in a single describe block, sharing one conversation for the workspace-attached cases (steps 3-5) and creating a fresh conversation for the no-attachment case (step 6). Issue #511 Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): reset mock LLM trajectory before each conversation creation The mock-LLM E2E test failed because the default 2-turn trajectory was exhausted by preceding test suites (automation, conversation). After exhaustion every /chat/completions returns 500, so the agent never produces REPLY_TOKEN and waitForNonUserMessageText times out. Fix: call resetMockLLM(request) at the top of step 2 and step 6 (before each conversation creation), matching the pattern used by mock-llm-conversation.spec.ts step 3. Co-authored-by: openhands <openhands@all-hands.dev> * fix(ci): report timeout instead of '0/0 passed' when test suite is killed When the CI wrapper kills Playwright after the 5-minute deadline (exit code 124), no results.json or marker files exist. Previously the PR comment showed '0/0 passed' with an empty table, which was misleading. Now the render script accepts --exit-code from the workflow. When exit code is 124 and no results exist, it renders a clear timeout entry: '⏱️ (test suite timed out before completing)' with a note pointing to workflow logs. Both mock-llm-e2e.yml and mock-llm-docker-e2e.yml pass the exit code through. Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): re-seed workspace metadata in each test step Each Playwright test() gets a fresh browser context, so localStorage from step 2 is gone when steps 3-5 run. Extract seedWorkspaceMetadata() helper and call it in steps 3 and 4 (which assert on workspace-dependent UI: git control bar name pill and files tab diff-view default). Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): assert git control bar buttons instead of workspace name The agent-server creates conversation worktrees inside the agent-canvas repo, so git detection always finds the real repo ('OpenHands/agent-canvas') and the workspace-name fallback ('my-app') never renders. Assert that Pull/Push buttons are visible instead — these only appear when the git control bar has successfully detected a repository. Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): retry ensureMockLLMProfile on transient socket failures The automation spec's step 1 intermittently fails with 'socket hang up' on GET /api/settings because the agent-server briefly drops connections between test suites (while processing cleanup from the previous spec's afterAll). Add retryOnTransient() helper that retries up to 5 times (1s delay) on socket hang up, ECONNRESET, ECONNREFUSED, 502, and 503. Apply it to both the GET and PATCH calls in ensureMockLLMProfile. Co-authored-by: openhands <openhands@all-hands.dev> * fix(static-server): handle WebSocket proxy socket errors The static-server's proxyWebSocket function was missing error handlers on the piped client/backend sockets. When a WebSocket connection tears down abruptly during test cleanup (ECONNRESET, EPIPE), the unhandled 'error' event crashes the Node.js process, killing the Docker container and causing ECONNREFUSED for all subsequent tests. Add .on('error') handlers to both proxySocket and socket, matching the pattern already used in ingress.mjs (lines 273-278). Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): make git control bar assertion work in npm and Docker In the npm path the agent-server creates worktrees inside the host repo so git detection finds 'OpenHands/agent-canvas' and shows Pull/Push buttons. In the Docker path there's no git repo inside the container, so the git control bar only shows the workspace name pill. Use Playwright's locator.or() to assert on whichever indicator appears: Pull button (npm) or workspace basename text (Docker). Re-add seedWorkspaceMetadata so the Docker path has a workspace name to show. Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): use git-init trajectory for cross-environment git detection Instead of making the test assertion fuzzy, ensure the conversation workspace is always a proper git repo. Register a custom trajectory that runs 'git init && git commit' when no repo exists (Docker path) and skips init when already inside a git worktree (npm path). This lets the git control bar consistently show Pull/Push buttons in both environments, making the assertion deterministic. Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): add git remote in trajectory for Pull/Push button detection The git control bar shows Pull/Push only when it can parse a provider+repository from 'git remote get-url origin'. A bare git init without a remote means the buttons never appear. Update the trajectory to add a fake GitHub remote when bootstrapping a new repo (Docker path). Skip when the workspace already has an origin remote (npm path — inherits the host repo). Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): fix shell syntax in git bootstrap trajectory The if/then/else joined with spaces produced invalid bash: 'then true else' (missing semicolons). Rewrite using || operator which avoids the issue entirely. Also increase Pull button timeout to 25s since useLocalGitInfo polls every 10s. Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): assert workspace pill as primary gate, soft-check Pull/Push The useLocalGitInfo probe requires a connected bash WebSocket that may not be available in Docker after agent completion. The workspace pill ('my-app') is the primary user-facing behavior for folder-attached conversations and renders reliably from localStorage. Make the workspace pill the hard assertion (primary gate). Treat Pull/Push buttons as a soft check that logs a message instead of failing when the git probe hasn't completed in time. Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): increase diff toggle assertion timeout for Docker API latency The toHaveAttribute('aria-checked', 'true') assertion had only a 5s timeout. useHasAttachedSource depends on useActiveConversation fetching the conversation API first — in Docker the round-trip can be slower. Increase to 15s so the React Query response has time to arrive and trigger the re-render that flips the toggle. Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): configure git user in Docker trajectory for commit to work git commit --allow-empty fails in Docker containers without user.name and user.email configured. Add git config commands to the bootstrap trajectory so the initial commit actually creates a HEAD ref. Without a valid commit, useHasGitCommits returns false and the diff toggle defaults to off — matching the design ('no commits means no diff base') but not the test expectation. Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): make diff toggle test environment-agnostic In Docker, useHasGitCommits may not fire (workspace.working_dir may be absent or the bash probe may not execute for finished conversations). This causes the diff toggle to default to 'off' instead of 'on'. Rather than asserting a specific default, verify: 1. Both toggle options render (diff on / diff off) 2. Clicking 'on' switches the toggle to checked state This still exercises the full Files tab rendering pipeline and toggle interactivity without being fragile to the git probe's environment dependencies. Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): add animation waits before panel/tab interactions The right panel uses a 300ms CSS transition. Clicking the diff toggle immediately after opening the panel causes click interception by the animation overlay in Docker. Add explicit waits after panel open and tab switch clicks. Co-authored-by: openhands <openhands@all-hands.dev> * fix(test): robust panel/tab/toggle waits + force click in step 4 - Wait for tab bar visibility (proves panel animation completed) - Wait for diff toggle itself (not the files-tab container which may be 'hidden' during CSS transition) - Use force click to bypass residual animation overlay - Simplify into a single test.step Co-authored-by: openhands <openhands@all-hands.dev> * fix(e2e): use parent toggle container instead of .or() to avoid strict mode violation The SegmentedToggle renders both option buttons simultaneously as a radio group. Using .or() on two always-visible elements triggers Playwright's strict mode ('resolved to 2 elements'). Wait for the parent radiogroup container (files-tab-diff-toggle) instead. Co-authored-by: openhands <openhands@all-hands.dev> * fix(e2e): wait for diff toggle instead of files-tab container in step 6 The files-tab main container reports 'hidden' during the right-panel drawer animation. Wait for the inner diff toggle radio group (same approach as step 4) which is visible once the tab content renders. Co-authored-by: openhands <openhands@all-hands.dev> * chore: address review feedback — trim verbose comments, fix dead code - Trim seedWorkspaceMetadata JSDoc to keep only the addInitScript timing note - Remove self-evident 're-seed' comments in steps 3 and 4 - Trim step 1 trajectory block comment to two lines - Remove step 2 seed rationale comment (function name is sufficient) - Remove box-header section dividers added in this PR - Fix unreachable throw in retryOnTransient via lastError pattern - Tighten retryOnTransient JSDoc to just list the retried conditions Co-authored-by: openhands <openhands@all-hands.dev> * ci: increase Docker E2E timeout from 15 to 25 minutes The 15-minute job timeout is too tight for PR-triggered runs that must first wait for the Docker workflow to complete (up to ~5 min) and then pull the image (up to ~12 min with a cold runner cache), leaving no room for setup and test execution. Successful PR runs already take 12-13 minutes typically. With an unlucky cold Docker cache (observed on the 04:11 UTC run for PR 1029), the image pull alone took 11+ minutes, causing the job to hit the 15-minute timeout before tests even started. Increasing to 25 minutes provides sufficient headroom for: - Docker workflow wait: ~3-5 min typical - Docker image pull (cold cache): up to ~12 min - Test infrastructure setup: ~2 min - Playwright test execution: ~6-7 min Co-authored-by: openhands <openhands@all-hands.dev> * Apply suggestion from @malhotra5 --------- 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> |
||
|
|
8c2cc3997d |
fix: GitHub MCP server works in Docker without Docker-in-Docker (#1282)
* fix: GitHub MCP server works in Docker without Docker-in-Docker
The GitHub MCP catalog entry uses `docker run` as its transport command,
which fails inside the agent-canvas Docker container because Docker is not
available (no daemon, no CLI). This is the only MCP integration affected —
all others use `npx` or `uvx`.
Fix:
- Pre-install the `github-mcp-server` Go binary in the Docker image via a
new multi-arch download stage (supports amd64/arm64)
- Export `getDeploymentMode()` from agent-server-adapter to expose the
runtime services info mode ("docker", "dev:automation", etc.)
- Add `patchGitHubEntry()` in mcp-marketplace-utils.ts that rewrites the
catalog entry from `docker run … ghcr.io/github/github-mcp-server` to
`github-mcp-server stdio` when deployment mode is "docker"
- The patch follows the existing `patchLinearEntry` pattern: immutable
spread, conditional on entry id, wired into `getMcpMarketplaceCatalog()`
Closes #1190
* docs: document GitHub MCP catalog patching in AGENTS.md
Co-authored-by: openhands <openhands@all-hands.dev>
* test: add E2E test for GitHub MCP install flow via marketplace UI
Exercises the full MCP page UI flow:
- Navigate to /mcp, verify GitHub marketplace card is visible
- Open install modal, verify fields (command, PAT input)
- Validate empty PAT shows error
- Fill PAT, submit with mocked /api/mcp/test success, verify installed
- Delete installed server via toggle + confirmation modal
Intercepts POST /api/mcp/test to return mock success since the real
github-mcp-server binary is not available in the test environment.
Co-authored-by: openhands <openhands@all-hands.dev>
* chore: track github-mcp-server version in config/defaults.json
Move the hardcoded GITHUB_MCP_SERVER_VERSION=1.2.0 from the Dockerfile
default into config/defaults.json (versions.githubMcpServer) alongside
the other external dependency pins.
- Dockerfile: ARG no longer has a default; CI and local builds must
pass it explicitly
- docker.yml: reads the version from config and passes it as a build-arg
- docker-build.mjs: reads the version from config and passes it too
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: correct GitHub MCP binary download URL and remove flaky validation test
- Fix Dockerfile: release assets use github-mcp-server_Linux_{arch}.tar.gz
(no version in the filename), not github-mcp-server_{version}_Linux_{arch}.tar.gz
- Remove step 3 (empty PAT validation test) which relied on CSS class
selector that doesn't work reliably in Playwright with compiled Tailwind
- Renumber remaining steps (4→3, 5→4)
Co-authored-by: openhands <openhands@all-hands.dev>
* docs: address review comments — document docker command assumption and arch fallback
- mcp-marketplace-utils.ts: explain why we match on command === 'docker'
and what happens if upstream changes the catalog entry
- Dockerfile: document the *) arch fallback and when to update it
Co-authored-by: openhands <openhands@all-hands.dev>
* test: assert Docker-specific command patching in GitHub MCP E2E test
The test now asserts the command field value based on the deployment mode:
- Docker E2E: expects 'github-mcp-server stdio' (native binary)
- npm E2E: expects 'docker' (original catalog transport)
Uses MOCK_LLM_DOCKER_IMAGE env var presence (set only by the Docker
Playwright config) to determine which assertion to make. This ensures
the patchGitHubEntry runtime rewrite is exercised in Docker E2E.
Co-authored-by: openhands <openhands@all-hands.dev>
* test: add unit tests for patchGitHubEntry Docker command rewrite
Addresses review feedback to add unit test coverage for the runtime
catalog patching. Three new tests via getMcpMarketplaceCatalog:
- Non-Docker mode: GitHub entry keeps original 'docker run' command
- Docker mode: command rewritten to 'github-mcp-server stdio'
- Docker mode: other entries (Tavily) unaffected
Uses vi.mock to control getDeploymentMode return value.
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
|