mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 17:08:34 +08:00
4fa822d1f75df98c5507382dc53cac8ea36e48bc
7610
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
098f8893ba |
Polish onboarding modal flow and final-step UX (#1095)
* Restore choose-agent as the first onboarding step. Put agent selection back ahead of backend setup so new users pick an agent before connecting a server, and keep the step-0 Next button right-aligned when Back is hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * Add onboarding step preview via query param. Support `?previewOnboardingStep=0-3` to open a specific slide for design review without marking onboarding complete, mounting from the root layout when the param is present. Co-authored-by: Cursor <cursoragent@cursor.com> * Redesign onboarding Say Hello step input and footer. Use the chat-style send control in a raised input container, replace the launch button with Close, and add trailing punctuation to the default hello message across locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Scroll recommended automations grid in onboarding Say Hello. Keep the section heading fixed while only the workflow cards scroll, and use a lighter raised surface on automation tiles for contrast against the modal panel. Co-authored-by: Cursor <cursoragent@cursor.com> * Make onboarding backend Next save and align the footer. Remove the separate Save button, left-align Back with Next on the right, and submit the backend form through Next so a successful connection test advances the flow. Co-authored-by: Cursor <cursoragent@cursor.com> * Disable backdrop dismiss on onboarding modal only. Add a closeOnBackdropClick option to ModalBackdrop and turn it off for new-user setup so outside clicks no longer skip onboarding while other modals keep the default behavior. Co-authored-by: Cursor <cursoragent@cursor.com> * Collapse onboarding backend fields when connected and refresh copy. Hide the configuration form behind a Show configuration toggle once the health probe succeeds, and explain that users can register multiple agent backends. Co-authored-by: Cursor <cursoragent@cursor.com> * Left-align Back on the onboarding LLM step footer. Use justify-between so Back and Next match the layout on the other onboarding steps. Co-authored-by: Cursor <cursoragent@cursor.com> * Avoid skeleton flicker during onboarding LLM step transition. Keep SDK settings content visible on background refetches so clicking Next from LLM setup no longer flashes the loading skeleton. Co-authored-by: Cursor <cursoragent@cursor.com> * Polish onboarding footer spacing and suppress setup save toast. Match the Say Hello footer top/bottom spacing and disable the generic settings-saved toast while advancing through the startup modal LLM step. Co-authored-by: Cursor <cursoragent@cursor.com> * Refine final onboarding step actions and scrolling. Hide Skip on the final slide, add an OR divider in Say Hello, and keep the recommendation heading/description inside the same scrollable area as the automation cards. Co-authored-by: Cursor <cursoragent@cursor.com> * Remove extra spacing before onboarding recommendations. Drop the top margin above the recommended automations block on the Say Hello step so it aligns tightly with the updated scrollable recommendations layout. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: remove unrelated file --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
24d627b88b |
fix: public mode auth gate bypasses stored backend keys (#1101)
* fix: public mode auth gate bypasses stored backend keys After a user enters their API key via the auth screen in public mode (`--public`), the key is persisted to the backend registry in localStorage. On page reload, `isAuthRequiredAndMissing()` only checked for a *baked-in* session key (env var / window global), which is intentionally absent in public mode. This caused the auth screen to reappear in an infinite loop. The fix adds a secondary check: if auth is required and the baked key is missing, but the backend registry already has a registered backend with an API key, the instant gate is bypassed and the normal `/server_info` probe validates the stored key instead. Also adds: - E2E test for returning-user scenario (valid stored key → skip auth) - E2E test for frontend-only → backend-only cross-connection - E2E test for switching between multiple backend-only instances Fixes #1099 Co-authored-by: openhands <openhands@all-hands.dev> * fix: reload page after adding backend in cross-connect tests The MissingAgentServerScreen's useConfig caches the AgentServerUnavailableError with retries disabled. After adding a backend through the manage-backends modal, a page reload is needed to re-probe with the newly stored backend. Co-authored-by: openhands <openhands@all-hands.dev> * fix: cross-connect test strict mode violation Both home-chat-launcher and onboarding-step-choose-agent can render simultaneously (onboarding overlays the home page). The .or() locator fails Playwright strict mode when both match. Check each individually and also assert the error screens are NOT visible. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
db40530370 | fix: keep modal open on errant outside click (#1100) | ||
|
|
a344117a2b | fix: default LLM setup to Anthropic Claude Opus 4.8 (#1089) | ||
|
|
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> |
||
|
|
547a5eb687 | Update Docker image version in README (#1070) | ||
|
|
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> |
||
|
|
3122fa9a56 |
feat(acp): render running tool-call cards (pairs with SDK source dedup) (#1027)
* feat(acp): render running tool-call cards (pairs with SDK source dedup) The SDK now persists exactly two ACPToolCallEvents per tool_call_id -- an early `started` event (pending/in_progress) and one terminal completed/failed event, the action->observation pair -- instead of one cumulative-output frame per ToolCallProgress. Relax shouldRenderEvent so the `started` event renders the card as "running" (the absent check mark, via getACPToolCallResult) and the terminal event replaces it in place through handleEventForUI, mirroring how an ObservationEvent supersedes its ActionEvent. The old terminal-only gate existed to hide the half-formed cards the SDK's per-progress fan-out flashed mid-stream; that fan-out is gone, so the running card is now a single clean event. Widen ACPToolCallStatus to include `pending` (the started event's status) and refresh the related comments and tests. Implements OpenHands/agent-canvas#1025 (epic #988). Requires the software-agent-sdk PR that dedups ACP tool-call progress at the source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: sync getACPToolCallResult doc with pending status (#1027) Address review nit: the JSDoc mentioned only in_progress, but pending now also falls through to undefined (running card) after the status-type widening. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: address PR review feedback (#1027) - handle-event-for-ui comment: mention pending alongside in_progress as a started-event status (mirrors the widened ACPToolCallStatus type) - add getACPToolCallResult unit tests (pending/in_progress/null -> undefined, failed/is_error -> error, completed -> success) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: reflow handle-event-for-ui comment (#1027) Address review nit: merge the awkwardly-wrapped sentence onto continuous lines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Debug Agent <debug@example.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
37c8987247 | feat: adds translations for "Remote" in multiple languages in translation.json (#1056) | ||
|
|
58b03f3b73 |
fix: surface dynamic_context in Agent Tools & Metadata modal (#932) (#973)
* fix: surface dynamic_context in Agent Tools & Metadata modal (#932) The backend SystemPromptEvent carries three content fields (system_prompt, tools, dynamic_context), but the frontend only read the first two. dynamic_context (datetime, skills catalog, runtime services, custom secrets) is part of the system message the model actually receives and is larger than the static prompt, so the modal showed less than half of the agent's true context. - Add optional dynamic_context to the SystemPromptEvent interface - Surface it via adaptSystemMessage and a new "Dynamic Context" tab - Defensively redact unmasked <CUSTOM_SECRETS> values client-side - Reset to the System tab on open so a now-hidden tab can't leave an empty panel - Add tests and translations for all locales Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor: remove "Dynamic Context" tab and consolidate context in "System" tab * refactor: trim system_prompt text and localize regex instances in redactCustomSecrets --------- Co-authored-by: burak.keles <burak.keles@ode.al> Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com> Co-authored-by: simonrosenberg <157206163+simonrosenberg@users.noreply.github.com> |
||
|
|
d128a3ef0e |
feat: expose event trigger details in automation UI (#1007)
* feat: expose event trigger details in automation UI Add first-class support for displaying event-based automation triggers in the frontend, closing the gap between what the backend returns and what users can see. Changes: - Extend AutomationTrigger type with source, on, filter fields - ConfigurationSection shows event source, event type, and filter expression (with expand/collapse for long filters) - Edit modal displays read-only event trigger info block for event automations while keeping cron schedule editor for cron automations - List/card views show type-specific icons (clock for cron, globe for event) and event trigger pills instead of schedule pills - Add formatEventOn shared utility to avoid duplication - Add i18n translations for 5 new keys across 15 languages - Add mock event-trigger automations and run history for dev/testing - Add 5 new tests for ConfigurationSection covering both trigger types * fix: hide plugins section for event-triggered automations Per review feedback — plugins section is redundant for event automations since the event source is already displayed in the configuration section. * refactor: address review feedback on event trigger details * fix: failing tests --------- Co-authored-by: Jathin Sreenivas <sjathin@amazon.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
5eb519c014 |
refactor(acp): stop forwarding acp_env; route ACP creds via Secrets panel (#1050)
ACP provider credentials already flow through the Secrets panel (request.secrets / agent_context.secrets) — the cipher-protected, wire-native channel that matches the regular agent. acp_env was a parallel, partly-unmasked credential channel that the SDK has since deprecated (software-agent-sdk #3464). Remove acp_env from the forwarded ACP settings so it is no longer sent on the conversation request, and explicitly scrub any legacy persisted acp_env from the OpenHands payload (it previously rode the shared ACP_SETTINGS_KEYS strip-list). Cred delivery is unchanged — the ACP onboarding already provisions provider keys via the Secrets panel. Toward the registry-only end-state in OpenHands/agent-canvas#1039 (remove the acp_env channel). Follows OpenHands/agent-canvas#1022. Co-authored-by: Debug Agent <debug@example.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.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> |
||
|
|
a53bff52d4 |
fix(chat): polish pending user message sending UX (#1053)
* fix(chat): polish pending user message sending and error UX Move sending/error status below the bubble, left-align the sending label, and use theme error colors with a compact retry button. Add hover stop control on in-flight messages so users can dismiss pending sends. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): improve pending send UX and truncate long user messages Clamp sent user bubbles to three lines with a bottom gradient, pill-style View more hint (fast fade on hover), and click-to-expand via an overlay control. Move the in-flight stop control inside the bubble and use the stop icon. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): refine pending send UX and add dev preview helper Polish sending/error pending bubbles: adaptive stop button placement, filled stop icon, full-opacity sending state, Thinking-matched status text, and user message spacing. Add dev-only preview seeding via ?previewPendingChat= for mock review. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): restore cancelled sends and stabilize pending bubble layout Return stopped message text to the input when empty, reserve a fixed stop-button column to prevent text shift, and tighten user bubble vertical padding to balance line-height spacing. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): add flowing gradient to sending status text Reuse the workbench kanban gradient-flow animation on the pending send label for a subtle live-activity shimmer. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): polish pending send shimmer, stop control, and preview Add a reusable TextShimmer for the sending label, overlay the stop button on the bubble with clearer hover feedback, align sending status spacing with Thinking, and seed a multiline sending case in the dev preview. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
1b03dbac43 |
fix(sidebar): scroll conversation filter menu on short viewports (#1052)
The sidebar filter dropdown had no max height, so on small windows its Organize/Sort/Show sections extended past the viewport with no way to reach items at the bottom. Cap height to available viewport space and use overflow-y-auto with the custom scrollbar only when content overflows. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
09af97be17 | fix: stop loading the cloud conversation after switching to a local backend (#1047) | ||
|
|
c0c413f406 |
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> |
||
|
|
667c5162fa |
[codex] Reapply active LLM profile after save (#961)
* Reapply active LLM profile after save * chore: add active profile reapply evidence Simplify the active-profile reapply predicate and cover the recreate-active-profile path from review feedback. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
53ed7757cb |
docs: rewrite self-hosting guide for --public mode (#1038)
* docs: rewrite self-hosting guide for --public mode Replace the outdated npm-run-dev + nginx-basic-auth workflow with the production CLI (npx @openhands/agent-canvas --public). Key changes: - Step 3 now uses a single npx command instead of clone/install/dev - Security model uses LOCAL_BACKEND_API_KEY + --public (built-in API key entry screen) instead of nginx HTTP basic auth as the primary defense - Removed ~80 lines of htpasswd, POSIX ACL, and apache2-utils setup - nginx section is now TLS-only (no auth_basic directives) - Architecture diagram updated: static server replaces Vite dev server, LOCAL_BACKEND_API_KEY replaces SESSION_API_KEY - Section 5 simplified: key comes from LOCAL_BACKEND_API_KEY, no session-api-key.txt or cross-origin basic-auth workaround Co-authored-by: openhands <openhands@all-hands.dev> * docs: rename "Run the server" to "Run Agent Canvas" Co-authored-by: openhands <openhands@all-hands.dev> * docs: add process-persistence guidance (tmux + systemd) to self-hosting guide Add a brief section after the 'npx --public' command explaining how to keep the service alive across SSH sessions, covering: - Option A: tmux one-liner for quick setups - Option B: minimal systemd unit with Restart=on-failure for long-term deployments Co-authored-by: openhands <openhands@all-hands.dev> * docs: use export + openssl rand for key, remove dead anchor link - Replace inline LOCAL_BACKEND_API_KEY=<secret> pattern with export + openssl rand -base64 32 so the key stays out of ps aux and shell history - Add brief explanation of why export is preferred - Update tmux example to use export before tmux new-session - Remove dangling reference to non-existent #advanced-defense-in-depth section Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
e386ba316c |
Harden workflow GitHub context handling (#750)
* Harden workflow GitHub context handling Pass attacker-controllable GitHub context and workflow values through environment variables before shell use. |
||
|
|
ce1401d3cd |
fix(frontend): localize conversation status labels (#1010)
* fix: localize conversation status labels * refactor: update the code based on feedback * refactor: update the code based on feedback |
||
|
|
56545a78c5 |
Add issue duplicate checker workflow (#987)
* Add issue duplicate checker workflow |
||
|
|
e408deea9e |
fix: bind static-server dual-stack to fix Docker E2E ECONNREFUSED on ::1 (#1032)
* fix: bind static-server dual-stack to fix Docker E2E ECONNREFUSED on ::1 The Docker mock-LLM E2E tests were flaky because static-server.mjs bound to 0.0.0.0 (IPv4 only), but Playwright and Chromium often resolve `localhost` to ::1 (IPv6) on Ubuntu CI runners, causing intermittent ECONNREFUSED. The non-Docker tests didn't have this problem because they go through ingress.mjs, which calls server.listen(port) without a host argument — Node.js defaults to :: (dual-stack: IPv4 + IPv6). Changes: - static-server.mjs: default host from "0.0.0.0" to null; when null, call server.listen(port) without host so Node binds to :: - docker/entrypoint.sh: drop --host 0.0.0.0 from both static-server invocations so they use the dual-stack default - playwright.mock-llm.config.ts: drop --host 0.0.0.0 from the public-mode static server (tests hit it directly via localhost) Callers behind ingress.mjs (dev-with-automation, dev-static) still pass --host 0.0.0.0 explicitly, which is fine since the ingress itself is already dual-stack. Co-authored-by: openhands <openhands@all-hands.dev> * fix: use 127.0.0.1 in Docker E2E config to avoid IPv6 ECONNREFUSED The Docker mock-LLM E2E tests fail with ECONNREFUSED ::1:18300 because Playwright/Chromium resolve `localhost` to ::1 (IPv6) on Ubuntu CI runners, but the Docker container's static-server binds to 0.0.0.0 (IPv4 only). The non-Docker tests don't have this issue because they go through ingress.mjs which binds to :: (dual-stack: IPv4 + IPv6). Rather than changing the server binding (which could have side-effects inside the Docker container), this fix changes the Docker Playwright config to use 127.0.0.1 directly for all URLs: INGRESS_URL, MOCK_LLM_BACKEND_URL, MOCK_LLM_PUBLIC_MODE_URL, and the webServer health-check probe. This bypasses DNS resolution entirely and connects via IPv4. Co-authored-by: openhands <openhands@all-hands.dev> * fix: keep Docker container alive when a backend service exits The Docker entrypoint used `wait -n` which exits the entire container when ANY child process exits. After heavy automation tests (which spawn multiple conversations), the agent-server or automation backend could exit, taking down the static-server with it — causing ECONNREFUSED for subsequent tests. In the non-Docker path, each service is an independent host process, so one crashing doesn't affect the others. The ingress proxy returns 502 for the dead backend but stays up. Change the entrypoint to monitor children in a loop: log crashes but keep the container running as long as any service is still alive. Only exit when ALL tracked children are dead. The SIGTERM trap still handles clean shutdown. Co-authored-by: openhands <openhands@all-hands.dev> * fix: simplify entrypoint keep-alive to avoid wait -n interaction issues Replace the complex PID monitoring loop with a simple sleep loop. The previous wait -n based loop regressed automation tests (5/14 vs 8/14 on the simpler IPv4-only commit), likely due to bash wait -n signal handling interacting poorly with child processes. The sleep loop keeps the container alive indefinitely. The existing SIGTERM/SIGINT trap handles clean shutdown. Co-authored-by: openhands <openhands@all-hands.dev> * fix: address review feedback on entrypoint and docs - Wait on STATIC_PID only (not all children): the static-server is the critical ingress process. If it dies the container exits with a meaningful exit code. Backend crashes (agent-server, automation) are tolerated — the proxy returns 502. (Copilot feedback) - Add `exit 0` to cleanup(): ensures the script terminates after a SIGTERM-triggered trap return instead of falling through. The `wait` builtin is signal-interruptible so SIGTERM is processed immediately — no stale sleep blocking delivery. (all-hands-bot) - Update AGENTS.md to match the actual behavior (wait on static-server PID, not a monitoring loop or infinite sleep). (Copilot feedback) Co-authored-by: openhands <openhands@all-hands.dev> * fix: use signal-safe sleep-wait loop with static-server liveness check The bare `wait "$STATIC_PID"` approach failed the same way as the original `wait -n` (8/14 — conversation/model-switch tests get ECONNREFUSED after automation). The `while true; do sleep 86400; done` pattern from the previous commit was the only one that passed 14/14, but had two issues flagged in review: 1. Bare `sleep` as foreground blocks SIGTERM delivery (all-hands-bot) 2. Container stays alive forever even if static-server dies (Copilot) This commit addresses both: - `sleep 10 & wait $!` — `wait` (builtin) is the foreground op, so SIGTERM interrupts it immediately and the trap fires. - `while kill -0 "$STATIC_PID"` — loop exits when the critical ingress process dies; container exits with a meaningful code. - `cleanup()` keeps `exit 0` so the script terminates after a signal-triggered trap return. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
dbc3abfed3 |
docs: clarify README install option requirements (#945)
* Clarify README install option requirements * docs: add Windows PowerShell syntax for Docker sandbox option Split Option 2 into Linux/Mac and Windows (PowerShell) code blocks to address cross-platform shell syntax differences. The original Unix-style export syntax doesn't work on Windows CMD or PowerShell. * docs: refine install commands across platforms * docs: move Windows PowerShell instructions to separate README --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: chuckbutkus <chuck@openhands.dev> Co-authored-by: enyst <engel.nyst@gmail.com> |
||
|
|
8b3ebbedba |
App 2052/optionally sync mcp config with secrets (#1001)
* add toggles to form fields * refactor(mcp-secrets): improve save-as-secret hook, toggle a11y, and add tests - useSaveFieldsAsSecrets: wrap returned fn in useCallback for stable identity, pass field.label as secret description, truncate key lists >3 in toasts, update JSDoc to document upsert behaviour - SaveAsSecretToggle: replace hidden input with sr-only for AT accessibility, move data-testid to label, add aria-hidden to decorative track, replace div with button for keyboard-reachable info trigger with aria-label - InstallServerModal: add stateRef to avoid stale closure in onSuccess callback, add comment explaining why argFields have no secret toggle - translation.json: plural-safe English wording for MCP$SECRETS_SAVED - tests: add use-save-fields-as-secrets.test.ts (9 tests), add save-as-secret-toggle.test.tsx (9 tests), extend install-server-modal with save-as-secret describe block (8 tests); 35/35 pass Co-authored-by: openhands <openhands@all-hands.dev> * refactor: update the code based on feedback --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
936b950dc0 |
Design updates: navigation polish, modal tokens, and git control bar fixes (#884)
* Fix ACP provider registry typing for the pinned typescript-client. Assert model list fields on the upstream registry record so staged typecheck passes until the client export catches up. Co-authored-by: Cursor <cursoragent@cursor.com> * Centralize form-control transitions and chat input pill button classes. Extract shared transition tokens and chatInputPill/Icon class names so hover timing and styling stay consistent across the chat input action row. Co-authored-by: Cursor <cursoragent@cursor.com> * Add shared dropdown classes for instant hover colors. Introduce dropdown-classes utilities so menus and combobox rows can disable transition-colors delays from one place. Co-authored-by: Cursor <cursoragent@cursor.com> * Apply instant hover colors across dropdown and context menu rows. Replace transition-colors on menu triggers and rows with shared dropdown classes so text and background colors snap on hover. Co-authored-by: Cursor <cursoragent@cursor.com> * Add collapsible automation prompt section with gradient fade. Long prompts default to a max height with a bottom dissolve and View More toggle so the detail page stays scannable. Co-authored-by: Cursor <cursoragent@cursor.com> * Add play icon to the automation detail Run now button. Match the grid/list Run now affordance and update the detail test to query the i18n key used in mock mode. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix add automation modal title color on dark surfaces. Apply text-white to the heading so it remains readable on bg-base-secondary. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix switch profile menu row alignment for two-line profiles. Override items-center from shared menu row classes with items-stretch so flex-col profile rows stay left-aligned. Co-authored-by: Cursor <cursoragent@cursor.com> * Standardize dropdown list spacing and tighten profile menu header padding. Add shared 2px row gaps across context menus and dropdown panels, and remove stacked top padding above the switch-profile "Available Profiles" heading. Co-authored-by: Cursor <cursoragent@cursor.com> * Unify dropdown row icon styling, spacing, and alignment. Use shared muted-to-white icon hover tokens, a fixed 16px icon slot for label alignment, and standard row gaps across combobox menus, context menus, and the backend selector footer. Co-authored-by: Cursor <cursoragent@cursor.com> * Use secondary cancel styling on the LLM profile editor footer. Remove the top border divider so the action row matches the rest of the form layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Extract a shared BackNavButton for settings and detail back links. Centralizes muted back navigation styling so automations, LLM profiles, and secrets use the same control. Co-authored-by: Cursor <cursoragent@cursor.com> * Cap switch-profile menu scrolling at four profile rows. Move overflow off the outer context menu so the header and settings link stay pinned while only the profile list scrolls. Max height is sized for four two-line rows (name + model) via switchProfileMenuListScrollClassName. Co-authored-by: Cursor <cursoragent@cursor.com> * Show active mode with a checkmark in the change-agent menu. Pass conversationMode into the context menu and reuse the shared menu row checkmark pattern for Code vs Plan selection. Co-authored-by: Cursor <cursoragent@cursor.com> * Use a blue hover fill on the active Plan agent pill. Replace the grey white/10 overlay with the plan accent border color so hover stays in the Plan palette. Co-authored-by: Cursor <cursoragent@cursor.com> * Add themed modal title tokens and shared class helpers. Introduce --oh-modal-title-foreground plus modalTitle*ClassName helpers so modal headings can stay white on dark surfaces without hardcoded text-white. Co-authored-by: Cursor <cursoragent@cursor.com> * Route BaseModalTitle through the shared modal title token. Default modal headings now use modalTitleClassName instead of text-content-2, and compact workspace modals use modalTitleSmClassName rather than ad hoc white. Co-authored-by: Cursor <cursoragent@cursor.com> * Apply modal title tokens to backend management dialogs. Use modalTitleLgClassName and modalTitleLgMediumClassName for add, edit, and manage backend modal headings. Co-authored-by: Cursor <cursoragent@cursor.com> * Apply modal title tokens to skills dialogs. Use modalTitleLgClassName for skill detail and add-skill modal headings. Co-authored-by: Cursor <cursoragent@cursor.com> * Apply modal title tokens to automation dialogs. Replace hardcoded text-white headings with modalTitleLg*ClassName helpers. Co-authored-by: Cursor <cursoragent@cursor.com> * Apply modal title tokens to MCP install and editor dialogs. Co-authored-by: Cursor <cursoragent@cursor.com> * Apply modal title token to the legacy AI settings modal. Co-authored-by: Cursor <cursoragent@cursor.com> * Restore white plugin launch modal title styling. Drop the text-content-2 override so Typography.H2 uses its default white heading. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix git control bar inactive styling during conversation loading. Wait for history preload before enabling Pull/Push/PR actions and use shared muted theme tokens for the disabled state. Co-authored-by: Cursor <cursoragent@cursor.com> * Remove setup-time pills from recommended automation cards. Drop the clock duration tag so recommended automations only show MCP and connect-state metadata. Co-authored-by: Cursor <cursoragent@cursor.com> * Polish extensions nav label and modal title weight. Rename the extensions sidebar heading to Customize: and use medium weight for large modal titles. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
3c52a10d4b |
test: add mock-LLM E2E test for /model slash command (mid-conversation profile switch) (#998)
* test: add mock-LLM E2E test for /model slash command
Add tests/e2e/mock-llm/mock-llm-model-switch.spec.ts exercising the
full /model mid-conversation profile switch flow against the real
agent-server with a scripted mock LLM backend.
Step 1 — setup:
- ensureMockLLMProfile() configures agent_settings.llm (proven pattern)
- POST /api/profiles/{name} creates profile B as the switch target
- Registers a 3-entry trajectory: padding for the internal LLM call,
INITIAL_REPLY_TOKEN, POST_SWITCH_REPLY_TOKEN
Step 2 — conversation + /model switch:
- Starts conversation from home page, waits for agent reply
- Types '/model model-switch-profile-b' and submits
- Verifies 'Switched to profile' confirmation in data-testid=model-messages
- Verifies POST /api/conversations/{id}/switch_profile was intercepted
- Sends follow-up, verifies agent responds (post-switch continuity)
- Verifies no error banners
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(ci): prevent main-branch Docker E2E from posting comments to PRs
When the Docker workflow completes on main, it triggers
mock-llm-docker-e2e.yml via workflow_run. GitHub's API can populate
workflow_run.pull_requests[] with unrelated open PRs (stale association
from shared commit ancestry). This caused main-branch Docker E2E
failures to post ❌ comments on random PRs.
Fix: skip PR number extraction when the triggering workflow ran on
main/master. Main-branch runs still execute (validating the published
image) but no longer post misleading comments to PRs. PR-specific runs
via the pull_request trigger are unaffected.
Co-authored-by: openhands <openhands@all-hands.dev>
* refactor: address review comments on mock-LLM model-switch test
- Import APIRequestContext from top-level @playwright/test import
instead of inline import() type references (review #1)
- Extract setChatInput to shared mock-llm-helpers.ts and reuse it
in both mock-llm-conversation and mock-llm-model-switch (review #2)
- Add upstream reference for the padding trajectory entry coupling
to CondensationMixin (review #3)
- Use direct field assertion on switchProfileBody.profile_name
instead of loose JSON.stringify substring match (review #4)
- Remove redundant toBeVisible check after waitForNonUserMessageText
already polls model-messages elements (review #5)
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(ci): deduplicate Docker E2E workflow_run triggers + add retry
Two Docker E2E reliability fixes:
1. Concurrency group: workflow_run triggers used the unique
workflow_run.id as the group key, so multiple Docker builds
completing on main each spawned their own E2E run (they never
cancelled each other). Changed to key by the triggering
workflow's head_branch, so main-branch workflow_run triggers
share the group 'wr-main' and only the latest run survives.
pull_request triggers still key by PR number (unchanged).
2. Retry: Docker Playwright config now uses retries:1 in CI to
handle transient container startup ECONNREFUSED failures. The
webServer health-check confirms the stack is up, but occasional
races between container readiness and the first test request
can still cause failures.
Co-authored-by: openhands <openhands@all-hands.dev>
* refactor: address second round of review comments
- Make saveProfile idempotent with best-effort delete-first so stale
profiles from crashed CI runs don't cause persistent 409 failures
- Increase switch-confirmation timeout from 15s to 30s for consistency
with all other waitForNonUserMessageText calls in this test
- Add waitForTestId guard before post-switch setChatInput to handle
potential UI disable during profile switch settling
Co-authored-by: openhands <openhands@all-hands.dev>
* refactor: minor nits — non-null assertion + testId in error message
- Use switchProfileBody!.profile_name after toBeTruthy guard instead
of optional chain (the assertion guarantees non-null)
- Include testId in setChatInput error message for easier diagnosis;
accept optional testId parameter for future reuse
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
|
||
|
|
3e51a68bd4 |
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> |
||
|
|
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> |
||
|
|
e5907df5b4 |
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> |
||
|
|
51c22c22c4 | fix: stop sending agent_context: null in cloud settings saves (#1021) | ||
|
|
779cba5f6e |
docs(acp): add a guide for using ACP agents (Claude Code, Codex, Gemini CLI) (#972)
* docs(acp): add ACP agents onboarding/config guide A guide for onboarding and configuring external ACP agents (Claude Code, Codex, Gemini CLI): where each stores its login, which credentials onboarding collects, and how subscription/login vs API-key precedence works. The credentials-collection code originally in this PR landed via #1002 (registry-derived fields including Gemini), so #972 is now docs-only — rebased onto current main. * docs(acp): state that a subscription login takes priority over API keys Make explicit that a subscription / OAuth login is preferred over an env API key for all three providers — while signed in, a key isn't used (the onboarding fields do nothing). Corrects the earlier claim that Claude Code uses ANTHROPIC_API_KEY instead of the login: with both present, `claude auth status` reports it's using the subscription (claude.ai). The base URL remains the one override that takes effect under a login. * docs(acp): show the conversation-turns edge in the flow diagram Canvas relays each turn through the Agent Server, not just the settings PATCH; add that edge so the diagram doesn't read as config-only (addresses review). --------- Co-authored-by: Debug Agent <debug@example.com> |
||
|
|
6c376bdd57 |
feat(frontend): warn when the LLM is not configured after skipping onboarding (#1011)
* feat: warn when the LLM is not configured after skipping onboarding * refactor: update the code based on feedback |
||
|
|
5a60043fcd |
feat(onboarding): client-side ACP login detection + collect Gemini credentials (no SDK changes) (#1002)
* feat(onboarding): detect ACP login client-side via the bash endpoint
Auto-detect whether the chosen ACP provider (Claude Code / Codex / Gemini) is
already signed in and show a "✓ you're already signed in" banner instead of
unconditionally asking for an API key. Refs OpenHands/agent-canvas#964.
This is a 100% canvas implementation — no SDK endpoint, no SDK release, no
version pin. Detection is gated to local backends (where the provider CLIs and
credential files actually live) and runs the provider's own status command
through the existing agent-server bash endpoint, classifying the output:
- Claude Code → `claude auth status --json` (read `loggedIn`; exits non-zero
when logged out, so we read the JSON, not the exit code).
- Codex → `codex login status` ("Logged in …" / "Not logged in" — on stderr,
so both streams are checked).
- Gemini → has no status command (browser OAuth), so check its credentials
file `~/.gemini/oauth_creds.json`.
Anything that can't be classified — CLI not installed ("command not found"),
unexpected output, or the bash call failing — is reported as `unknown`, so
onboarding falls back to the API-key fields rather than a false banner. Verified
live end-to-end through the Vite proxy → bash endpoint: all three authenticated
on a logged-in machine; a CLI stripped from PATH (exit 127) → `unknown`.
- `AcpService.getAuthStatus(server)` (`BashClient`-based) returns the classified
status; `useAcpAuthStatus` is gated to local backends and no longer keys off
whether the provider has API-key fields, so Gemini is detected too.
- The credentials step renders a login-status screen (banner, no inputs) for
key-less providers like Gemini; the onboarding flow no longer skips slide 2.
- New i18n: ACP_AUTH_DETECTED, ACP_AUTH_CHECKING, ACP_LOGIN_TITLE,
ACP_LOGIN_SUBTITLE, ACP_AUTH_DETECTED_NO_KEY.
Tests: `acp-service.api.test.ts` covers each provider's classifier incl. the
missing-CLI → `unknown` path (the "no available ACP process" case); hook/step/
modal tests cover gating, the Gemini login-only screen, and no-skip routing.
Supersedes the protocol-probe approach (software-agent-sdk#3452 /
typescript-client#196 / the #971 wiring), which can be closed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: address PR review feedback (#1002)
- Move acp-service.api.test.ts under __tests__/ for consistency with the
project's test layout.
- classifyGemini: exact (trimmed) match on present/absent instead of substring,
so stray output falls through to "unknown".
- Document why `retry: false` co-exists with probeAcpAuth's internal catch.
* chore: fix stale GET /api/acp/auth-status comment (#1002)
The endpoint design was replaced by bash-command detection via AcpService;
update the comment in setup-acp-secrets-step to match.
* chore: address PR review feedback (#1002)
- classifyGemini reads only stdout (the echo writes there); avoids a stray
stderr line turning a real result into unknown.
- Shorten probe timeout 30s -> 10s; the detection commands are fast local
checks, so a long onboarding spinner isn't warranted.
* feat(onboarding): collect ACP credentials for all providers incl. Gemini
Onboarding's ACP credentials step now offers an API-key (+ optional base-URL)
field for Gemini too, not just Claude Code and Codex. Refs agent-canvas#972.
- getAcpProviderSecrets now derives fields from the SDK registry's
api_key_env_var / base_url_env_var (via @openhands/typescript-client) instead
of a hand-maintained per-provider map — so all three built-ins (Claude Code,
Codex, Gemini CLI) get their fields and the list tracks the SDK with no
parallel copy to drift.
- The step renders the standard key fields for every ACP provider plus a note
that a subscription / OAuth login (Gemini's Google login, a Claude login)
takes precedence, so the keys are optional. The detection banner still shows
"you're already signed in" on top when a login is found.
- Drops the now-unreachable Gemini-only "login screen" branch and its three
unused i18n keys (ACP_LOGIN_TITLE/SUBTITLE, ACP_AUTH_DETECTED_NO_KEY); adds
ACP_SECRETS_SUBSCRIPTION_NOTE.
Tests updated: Gemini now asserts a GEMINI_API_KEY field renders alongside the
banner (step + modal).
* docs: drop transient-branch-state comments
Remove comments that narrated this (unreleased) branch's evolution rather than
describing the code — e.g. 'the flow no longer skips it', 'now exposes',
'login-status screen (no key fields)'. The modal slide-2 comment now just states
current behavior (every ACP provider shows the credentials form).
* fix(onboarding): clearer "already signed in" banner copy
The banner said "adding an API key below is optional", which read ambiguously.
Reword to "you can leave the fields below blank" — the subscription/login takes
precedence over an env API key for all three providers (verified: Claude reports
authMethod=claude.ai even with ANTHROPIC_API_KEY set; Codex stays on ChatGPT;
Gemini uses its OAuth auth-type), so the key isn't needed while signed in.
Also hide the redundant "leave these blank" subtitle note once authenticated —
the banner already conveys it.
---------
Co-authored-by: Debug Agent <debug@example.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
e6a39f357f |
fix(frontend): show clear preview unavailable message for office documents (#958)
* fix: show clear preview unavailable message for office documents * refactor: update the code based on feedback * refactor: update the code based on feedback |
||
|
|
d0994a6fe1 |
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> |
||
|
|
449d1fc9e5 |
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>
|
||
|
|
1d4fd1525c |
chore: switch to tag-based release workflow with per-tier npm dist-tags (#996)
- create-release.yml now triggers on v* tag push (not PR merge).
The release branch is never merged to main; publishing is triggered
by pushing the tag directly to the rel-X.Y.Z branch.
- npm-publish.yml resolves the dist-tag from the version string:
alpha → alpha, beta → beta, rc → rc, stable → latest
Removes the temporary 'always publish as latest' workaround.
- release.md skill and AGENTS.md updated to document the new model.
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> |
||
|
|
ee94749c70 |
fix: switch conversations by LLM profile name (#978)
Use the agent-server switch_profile endpoint for running conversations so the UI does not need to fetch encrypted profile secrets before switching models. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
cd03d9cf9c |
chore: bump version to 1.0.0-alpha.10 (#990)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
9285c7a827 |
fix: set AUTOMATION_AGENT_SERVER_URL in Docker entrypoint to enable local auth (#977)
Without AUTOMATION_AGENT_SERVER_URL, ServiceSettings.is_local_mode returns False and the automation server tries to validate the session API key against the OpenHands cloud API (app.all-hands.dev/api/v1/users/me), which returns 401 for locally-generated keys. Setting AUTOMATION_AGENT_SERVER_URL activates the local-mode fast path in authenticate_request(), which validates the key directly against AUTOMATION_LOCAL_API_KEY (already set to the session key) without any network call. This matches what dev-with-automation.mjs and dev-static.mjs already do correctly. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
f87167deb6 |
fix: always send frontend's chosen default model to agent-server (#898)
* fix: always send frontend's chosen default model to agent-server (#807) When the agent-server returns an empty model string (e.g. because no settings have been saved yet), the adapter's type guard accepted it as a valid string and forwarded it verbatim. The agent-server would then fall back to its SDK default ('gpt-5.5') rather than using the frontend's chosen default ('openhands/minimax-m2.7'). Fix: strengthen the guard in buildConfiguredOpenHandsAgentSettings to also reject empty/whitespace-only strings, so the frontend's own default is always sent explicitly: llm.model = typeof llm.model === 'string' && llm.model.trim().length > 0 ? llm.model : DEFAULT_SETTINGS.llm_model; While here: align the legacy V0 /api/options/models mock endpoint's default_model with DEFAULT_SETTINGS (was incorrectly set to claude-opus; should be minimax-m2.7 to match the rest of the defaults), and document the canonical default model and the two-location update rule in AGENTS.md. Closes #807 Co-authored-by: openhands <openhands@all-hands.dev> * refactor(tests): parameterize llm.model fallback tests + add spec + AGENTS.md - Extract getModelFrom() helper to eliminate repeated as unknown as ModelPayload casts - Consolidate 7 individual fallback it() blocks into two it.each() groups: (1) agent_settings variants (undefined/empty/whitespace/no-llm-block/empty-settings) (2) encryptedAgentSettings variants (empty model / empty object) - Rename @spec annotation from BM-807 to LLD-001 (not a backend-management spec) - Add specs/llm-defaults.md with LLD-001 checklist - Add AGENTS.md note documenting canonical default model, its location, and the two-location update rule Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
0b98b618c1 |
fix: don't poll git info when no workspace is attached to conversation (#919)
useLocalGitInfo was firing git commands every 10s regardless of whether the active conversation had an attached workspace, because workingDir always fell back to getAgentServerWorkingDir() and the queryEnabled guard had no !!workingDir check. Mirror the pattern already used in useHasGitCommits: read workingDir solely from conversation.workspace.working_dir and gate queryEnabled on !!workingDir. The getAgentServerWorkingDir import is no longer needed and is removed. Fixes #776 Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: chuckbutkus <chuck@openhands.dev> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
efe35446d0 |
Fix browser panel screenshot flow (#963)
Stop clearing browser state when the Browser tab first mounts, and instead reset it only when switching to a different conversation. This preserves the URL and screenshot that were already emitted for the active conversation. Also update the canvas_ui browser guidance so agents capture a screenshot with browser_get_state(include_screenshot=true) before opening the Browser tab. browser_navigate alone only updates the URL, which left the panel blank even though navigation succeeded. Add regression coverage for both behaviors: - preloaded browser screenshots survive first Browser tab mount - browser state still resets on conversation change - canvas_ui browser instructions require screenshot capture before opening the Browser tab Co-authored-by: Tim O'Farrell <tofarr@gmail.com> |
||
|
|
38253e41ba |
fix: proxy to window.location.origin when local hostnames differ (127.0.0.1 vs localhost) (#975)
shouldUseProxyOrigin() only redirected to window.location.origin when the browser was at a non-local hostname. When the configured backend uses 127.0.0.1 and the browser accesses via localhost (Docker default), both are in localHosts so the proxy substitution was skipped — sending the automation health check directly to http://127.0.0.1:8000 and triggering a CORS error from http://localhost:8000. Expand the condition to also proxy when the configured local hostname differs from the browser hostname (e.g. 127.0.0.1 vs localhost). Fixes #974 Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
032b761984 |
fix: persist LLM profile changes from all tabs and fix OpenHands round-trip (#970)
* fix: persist LLM profile changes from all tabs and fix OpenHands round-trip * refactor: update the code based on feedback |
||
|
|
117d391bfa |
test(onboarding): extract shared saved-secret fixture in ACP secrets step tests (#969)
Centralize the "ANTHROPIC_API_KEY already saved + wait for placeholder" setup into a renderWithSavedApiKey() helper shared by the three existing-secret tests, and have renderStep own userEvent.setup() so each test no longer repeats it. Pure test refactor — no production code or behavior change. Co-authored-by: Debug Agent <debug@example.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a46156b231 |
feat(onboarding): collect ACP provider API keys and store as secrets (#967)
* feat(onboarding): collect ACP provider API keys and store as secrets Add a credentials step to ACP onboarding for providers that authenticate via an env-var API key (Claude Code, Codex). The step collects ANTHROPIC_API_KEY/ANTHROPIC_BASE_URL and OPENAI_API_KEY/OPENAI_BASE_URL and upserts each filled field as a global secret of the same name, so they appear under Settings -> Secrets and reach the agent subprocess as env vars. - New per-provider secret-field registry in acp-providers.ts (getAcpProviderSecrets); single source of truth alongside the existing icon and description metadata. - New SetupAcpSecretsStep rendered on slide 2 of the onboarding modal; OpenHands keeps the LLM step, Gemini CLI (OAuth login) skips slide 2. - Step is skippable: empty fields aren't written; an existing secret shows an "already saved" placeholder and is left untouched. - i18n keys across all 15 locales; tests for the step and modal routing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(onboarding): mark ACP API-key fields optional The whole credentials step is skippable (and more so once subscription auto-detection lands), so the API keys shouldn't read as required either. Decouple input masking from optionality: a new `secret` flag controls the password input (API keys) vs plain text (base URLs), and every field now renders the "Optional" tag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(onboarding): assert existing ACP secret is not overwritten when blank Covers the toSave skip path flagged in review: when a credential already exists and the user leaves the field blank, handleNext must take the early-return (no createSecret call) and still advance. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: address PR review feedback (#967) - Narrow SetupAcpSecretsStep's providerKey from string to OnboardingAgentId so a mistyped provider key is a compile error rather than a silently empty form; matches the type the onboarding modal already tracks. - Add a key-rotation test: existing secret + typed replacement -> createSecret is called with the new value (guards the toSave path against an accidental alreadySet skip). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: Remove PR-only artifacts --------- Co-authored-by: Debug Agent <debug@example.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com> |