mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 16:38:34 +08:00
4e7d1ec475cf024246db4e6e64bf38f9b7818fc5
35
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f2dd330905 |
feat: add LLM provider-connections UI (local agent-server) (#16616)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
78068152d0 |
feat(sidebar): add getting started checklist with settings toggle (#16182)
Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
8989bf3bb5 |
feat: set Canvas default model to Kimi K3 and tag it free (#16657)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
692b14706c |
feat: add LLM pre-flight validation to prevent saving misconfigured profiles (#16417)
Co-authored-by: neubig <neubig@users.noreply.github.com> Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com> |
||
|
|
b7e325e998 |
fix: make overflow menus usable on touch devices (#16101)
Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
7d897d7669 |
feat: clarify free OpenHands model endpoints (#16281)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
49b5fd479f | fix(settings): use "Default" label for Agent Profile badge (#16206) | ||
|
|
4c5bcdcc12 |
feat(settings): add title generation profile preference (#1910)
* feat(settings): add title generation profile preference * test: wait for profile rename completion |
||
|
|
54d718ad4e |
feat(agent-profiles): Agent Profiles — Settings → Agent as the profile library (local + cloud) (#1571)
* feat(agent-profiles): minimal local Agent Profiles library reusing the Agent settings form Adds a Settings → Agent profiles library (local backends only) that mirrors the LLM-profiles UX: a list of named profiles with a create/edit view that reuses the existing Agent settings form as the editor — you just add a name (and, for OpenHands agents, pick an LLM profile). Deliberately minimal vs the full Phase-4 UX: no chat-input picker, no live switch, no Settings information-architecture rework. Condenser / verification / MCP stay global, exactly as on main. - Data layer: AgentProfilesService + list/save/delete/rename/activate hooks wrapping the ts-client AgentProfilesClient (endpoints shipped in agent-server v1.29.0). - Editor: AgentSettingsScreen gains an opt-in `embedded` mode (hides its header + global Save, seeds from an override, and reports state via a save control) — mirroring how LlmSettingsScreen is embedded in the LLM-profiles view. The global Agent settings page is unchanged. - Library: AgentProfilesLocalView (list/create/edit) + manager/body/row/menu + delete modal, at the additive route /settings/agents, gated to local backends (cloud has no /api/agent-profiles surface yet, epic #3730). - Maps the form to AgentProfileSaveInput: OpenHands requires an llm_profile_ref (via a picker); ACP stores acp_server/acp_model and the command as a shell string. Validated end-to-end against a real agent-server. Part of OpenHands/software-agent-sdk#3713 (Phase 4). An alternative to the larger #1550. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(agent-profiles): add chat-input agent-profile picker + live in-conversation switch Adds the full chat integration for Agent Profiles (epic #3713, #3727), keeping the simplified library/editor from the previous commit: - New-conversation picker (home): an agent-profile toggle replaces the LLM- profile toggle. Selecting activates the profile so the next conversation launches from it; conversations start via `agent_profile_id` (resolved server-side) instead of an inline agent_settings dump. - Mid-conversation switch, capability-gated by the running agent: - OpenHands conversation → live LLM-profile switch (`/switch_profile`). - ACP conversation → live model switch (`set_session_model`, existing ChatInputModel). - Home / cloud fall back to the agent-profile picker / model picker. - Threads `agent_profile_id` through the conversation-start path (buildStartConversationRequest: agent_profile_id XOR agent_settings; skip the ACP tag / encrypted-settings / subscription check on the profile path) and reads the server's `launched_agent_profile` provenance to mark the current profile without settings-matching. - Replaces the old SwitchProfileButton/context-menu with the new pickers. Validated end-to-end against a real agent-server (SDK main): starting a conversation with `agent_profile_id` returns 201 and stamps `launched_agent_profile { agent_profile_id, revision }`. Ported from #1550's chat implementation. Gates green: typecheck, eslint, prettier, i18n (15 langs), vitest (3496 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(agent-profiles): extract + unit-test buildAgentProfileFields mapping Addresses the code-review feedback that the profile-fields builder — the ACP "built-in default command → null vs verbatim shell string" branch plus the schema-driven tool_concurrency_limit coercion — was the most novel logic in the PR yet had no automated coverage (every test mocked the embedded form away). - Extracts the closure into a pure exported `buildAgentProfileFields()` in agent-settings.tsx; the embedded control now just snapshots state into it. - Adds 8 unit tests locking the round-trip: ACP built-in-default → null, custom command → shell string, custom preset, blank-model → null, OpenHands enable_sub_agents passthrough, concurrency coercion (valid / empty / throws). - Clarifies the service header (client ships in ts-client 1.28.0; the server endpoints it targets shipped in agent-server v1.29.0) per the version-doc nit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): address review feedback + fix e2e regression - Fix mock-LLM E2E regression: the profile-identity spec still targeted the removed `switch-profile-button`; point it at the new `chat-input-llm-profile` picker (mirrors #1550's e2e update). - Use the `useRenameAgentProfile` hook in the editor instead of calling the service directly (the hook was otherwise dead code; now the rename gets list invalidation for free). - Drop the unreachable in-conversation branch from the home AgentProfile picker: the picker only renders on home (a running conversation shows the LLM/model picker), so `useChatInputProfileState` is now home-only (activate as launch default), and the "start new with profile" hint + its CHAT$START_NEW_WITH_PROFILE_HINT key (15 langs) are removed. - Update the two affected tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): correctness fixes from #1571 code review - switch-llm-profile: run the inline "Switched to" message, #1082 metadata persist, and error reporting in mutation-level callbacks so they survive the switcher menu unmounting on select - agent-server-adapter: derive acp_server from agent.acp_server when the acpserver tag is absent, so a profile-launched ACP conversation keeps its model picker and provider chip - use-create-conversation: await the LLM-profile list before the dangling-llm_profile_ref launch guard so a mid-load send can't launch blind - chat-input pickers: read switch/activate pending state via useIsMutating so the pill button actually disables during an in-flight switch - use-activate-agent-profile: surface activation errors (drop disableToast) and optimistically flip active_agent_profile_id with rollback - chat-input-actions: fall back to the LLM picker on the home page when the backend has no /api/agent-profiles surface Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): pass embedded props to reused Agent settings form The profile editor reused AgentSettingsScreen via the route module's default export. React Router's Vite plugin wraps a route default with withComponentProps, which invokes it with route props and drops any props a parent passes — so `embedded`/`onSaveControlChange` never reached it, `saveControl` stayed null, and the Save button was permanently disabled (couldn't create or edit a profile at all). Split the route into a named `AgentSettingsScreen` export (the reusable component embedded consumers import) plus a thin default `AgentSettingsRoute` wrapper, mirroring `LlmSettingsRoute`. The local-view now imports the named export. Updated the unit-test mock to provide the named export (the old mock only stubbed `default`, which is exactly what masked this at unit level). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(agent-profiles): un-gate Agent Profiles on cloud backends The cloud enterprise app-server now exposes the same /api/agent-profiles contract as the local agent-server (OpenHands #15060, epic #3730), so lift the local-only gating and route cloud calls through the cloud proxy. Transport: - cloud/agent-profiles-service.api.ts: CRUD via callCloudProxy (bearer + X-Org-Id) against the identical /api/agent-profiles paths; org resolved server-side from the session, so no {org_id} segment. - cloud/org-profiles-service.api.ts: list org LLM profiles at /api/organizations/{org_id}/profiles so the editor's llm_profile_ref picker works on cloud. Only listing is cloud-routed. - AgentProfilesService + ProfilesService.listProfiles branch to the cloud transport when the active backend is cloud (mirrors SettingsService). Surfaces un-gated: - Settings → Agent profiles nav item + route (no more redirect to /settings/agent). - Home chat-input agent-profile picker (fetch + pickerKind) on cloud. - Launch-from-profile: cloud AppConversationStartRequest now carries agent_profile_id (added to the type + the cloud create request), which the backend resolves and stamps as launched_agent_profile. In-conversation live switch on cloud is intentionally left on the model picker for now: the cloud backend has no per-conversation profile-switch endpoint yet and org LLM-profile detail masks the api_key, so a client-side switch isn't possible — tracked as a follow-up for full parity. Tests updated for the new nav behavior (agent-profiles shown on both). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(agent-profiles): update useAgentProfiles docstring for cloud support Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(agent-profiles): clarify cloud in-conversation switch is intentionally local-only Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): preserve acp_server through the wire normalizer An ACP conversation launched from an agent profile (agent_profile_id) showed a generic chip and an empty in-conversation model picker: the provider identity never reached the UI. Root cause: #1571 taught the conversation adapter to source acp_server from `agent.acp_server` (SDK #3692) when the `acpserver` tag is absent — which is exactly the profile-launch case, since that path doesn't stamp the tag. But `normalizeAgent` (the wire parser feeding the adapter) projected only `{kind, acp_model, llm}` and dropped `acp_server`, so the adapter's fallback always saw undefined → acp_server null → no ACP provider → generic chip + no model list. Add `acp_server` to the normalizeAgent projection (the type already declared it). Regression test covers the no-tag / agent-sourced path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(agent-profiles): make Settings → Agent the profile library Collapse the two Settings sections ("Agent" global form + "Agent profiles" library) into a single "Agent" entry that IS the Agent Profile library: it lists the user's profiles and its create/edit view is the reused Agent settings form plus a name (the embedded AgentSettingsScreen). The active profile is the current agent. - settings-nav: one "Agent" item → /settings/agents (the library). - /settings/agent redirects to /settings/agents; default settings path + ACP route-guard target updated accordingly. Also derive the ACP-enabled state from the ACTIVE AGENT PROFILE rather than settings.agent_settings.agent_kind. Activate is pointer-only and never writes agent_settings, so the global settings are stale when an ACP profile is active; the nav-disable, home ACP context, useLlmConfigured, and the ACP route guard now read the active profile (new useActiveAgentProfile hook) and fall back to settings only while the profile list is loading. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): gate the LLM-setup banner on the active agent profile's LLM useLlmConfigured decided "is the LLM ready" from the standalone active LLM profile, but conversations now launch from the active AGENT profile. For an OpenHands profile the relevant LLM is the one it references via llm_profile_ref — not whichever LLM profile happens to be "active". So the "Your LLM isn't set up" banner could be wrong in both directions (e.g. the active LLM profile has a key but the agent profile references a keyless one). Resolve the LLM profile to check from the active agent profile's llm_profile_ref (openhands), falling back to the active LLM profile only when there's no ref yet. ACP agent profiles stay always-configured (subprocess owns its LLM). New unit test covers the discriminating case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(agent-profiles): relabel LLM profile "Active" → "Default" The active LLM profile no longer drives new conversations (the active AGENT profile does) — it's just the default llm_profile_ref seeded into new agent profiles. Relabel the LLM-profile badge "Active" → "Default" and the row action "Set as active" → "Set as default" to stop implying it launches conversations. New i18n keys (SETTINGS$PROFILE_DEFAULT / _SET_DEFAULT, 15 langs). The agent-profile "Active" badge is unchanged — that one IS active. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(onboarding): land the user's choice on the active agent profile Onboarding configured global agent_settings + an LLM profile (OpenHands) or ACP secrets, but never touched an AGENT profile — so the active agent profile stayed the seeded `default` (openhands → ref `default`), disconnected from what onboarding set up. Result: an OpenHands user who entered a key still hit "LLM isn't set up" (the active agent profile referenced a keyless profile), and ACP users never got an ACP agent profile at all. Add useApplyOnboardingAgentProfile: upsert + activate the well-known `default` agent profile from the onboarding choice. The OpenHands LLM step now points it at the LLM profile it just created; the ACP secrets step makes it an ACP profile for the chosen provider (opus[1m]/valid default, no LLM key needed). Verified e2e: OpenHands onboarding → default agent profile refs the configured LLM + banner clears; Claude Code onboarding → default agent profile is acp/claude-code, active, no LLM required. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: drop unused eslint-disable in onboarding agent-profile hook Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(agent-profiles): gate mutate controls for cloud view-only members Reuse #1532's org-permission gating for the Agent Profiles UI. Agent profiles are org-scoped on cloud (bearer + X-Org-Id, edit_org_settings), so a cloud member previously saw Add/Edit/Delete/Set-active controls that would 403 server-side — the same flash-then-403 problem #1532 fixed for LLM profiles. - Generalize useCanManageLlmProfiles -> useCanManageOrgProfiles (it reads the generic edit_org_settings permission; local users always true). - Thread canManage through AgentProfilesManager -> Body -> Row, mirroring LlmProfilesManager: hide the Add button and the row actions menu for view-only members. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: fix stale comments surfaced by PR review Comment-only. No behavior change. - chat-input-actions.tsx: the pickerKind summary claimed "cloud → model picker (cloud has no profile surface)", contradicting the code, which uses the AgentProfile picker on cloud home too (#15060). Rewrite to match the actual cases; trim the duplicated render-site recap. - acp-route-guard.ts / settings-nav.tsx / settings.tsx: the ACP redirect target moved to /settings/agents (plural) in this PR, but three docstrings still said /settings/agent. Update them. * test(mock-llm-e2e): wire the active agent profile to the mock LLM Fixes the mock-LLM e2e regression where the home composer stayed blocked (submit disabled / launcher never ready) so conversation-launching specs timed out. Conversations now launch from the active AGENT profile (#1571), and `useLlmConfigured` follows that profile's `llm_profile_ref` — not the active LLM profile. The specs seed `openhands-onboarded` and configure an LLM profile the old way, so the seeded "default" agent profile still pointed at a keyless LLM and the composer never unblocked. Mirror what onboarding does for a real user: after activating the mock LLM profile, upsert + activate the "default" agent profile referencing it. Add a shared `ensureMockLLMAgentProfile` helper (called from ensureMockLLMProfile and from the conversation spec, which sets up inline). Verified locally: the full mock-llm-conversation spec passes 4/4 (real conversation runs against the mock LLM) with this change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): address PR #1571 review feedback Human review (VascoSch92): - useLlmConfigured: fall back to the active LLM profile when the active agent profile's llm_profile_ref is stale/absent, mirroring the launch-time fallback in useCreateConversation. Without this the two contradicted each other: launch succeeded via the fallback but the hook reported unconfigured and spuriously disabled the composer + banner (even inside a running conversation). Adds a regression test for the stale-ref scenario. - Drop the dead launched_profile plumbing (wire parse + types + adapter map): it had zero readers (the home picker keys off active_agent_profile_id and the in-conversation picker is LLM/model by design), so the "Consumed by the picker" comments were misleading. - Point the remaining /settings/agent links at /settings/agents (ACP model context, chat-input model state, chat error re-auth, command menu) so the route rename doesn't cost an extra redirect hop. /codereview-roasted: - Extract the triple-nested pickerKind ternary into a pure, unit-tested resolvePickerKind() helper. - Document why cloud OpenHands onboarding intentionally does not repoint the active agent profile (persistAsProfile is local-only; cloud resolves the agent-profile/LLM wiring server-side). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(agent-profiles): scope the profile-launch enrichment gap (#1571 review) - Document at buildStartConversationRequest that the profile path relies on the server/SDK to restore exec tools + public skills (software-agent-sdk#3967), and that canvas_ui + the RUNTIME_SERVICES suffix are intentionally canvas-only. - Point the createConversation positional-args TODO at the tracked issue (#1587). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): preserve unmodeled fields on edit-save + await profiles at launch Two fixes from the #1571 review: Edit-save wiped every profile field the minimal editor doesn't model (condenser, verification, system_message_suffix, skill/MCP refs, embedded skills, ACP session mode/timeout): the save endpoint is a whole-profile overwrite, and the editor posted only its own fields. The save payload now spreads the stored profile under the edited fields via a pure, kind-aware mergeAgentProfileSaveInput — a kind switch stays a clean variant replacement (the server's extra="forbid" union rejects mongrel payloads), and server-managed identity (id/name/revision) is stripped. The edit fetch now uses X-Expose-Secrets: encrypted so any skills[].mcp_tools values round-trip as Fernet tokens instead of persisting the mask literally (same pattern as the LLM-profile editor). Launch raced the agent-profiles query: useCreateConversation read the hook's maybe-unresolved data, so a send fired before the list loaded fell through to the stale global agent_settings path — which activation (pointer-only) never updates — and silently launched the wrong agent. The launch now awaits the list via queryClient.ensureQueryData on the shared query key (mirroring the LLM-profile ref validation below it), with retry: false so backends without the surface degrade to the legacy launch immediately. The dangling-llm-ref downgrade also logs a console.warn so the silent fallback is diagnosable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(acp): drive ACP spec through the Agent Profile editor, not the retired route Settings → Agent is now the Agent Profile library (#1571): the standalone /settings/agent form redirects to /settings/agents, whose editor reuses the same embedded agent-settings-screen form. The ACP mock-llm spec and the resetToOpenHandsAgentViaUI cleanup helper still navigated the old route and waited on the retired agent-save-button, so they timed out — and the cleanup helper's failure (swallowed by afterAll's try/catch) left the "default" agent profile stuck in ACP mode, poisoning downstream specs that share the backend. - Add openAgentProfileEditor(page, name): navigate /settings/agents, open the named profile's editor via its row action menu (row located by the profile-name span[title], mirroring activateProfileViaUI). - Rewrite resetToOpenHandsAgentViaUI to drive the new editor (switch kind → OpenHands, pick an LLM profile, save via save-agent-profile-btn). - Point ACP spec steps 1 & 2 at the editor; swap agent-save-button → save-agent-profile-btn. - Verify step 1 against GET /api/agent-profiles/default (the new source of truth) instead of legacy /api/settings; acp_command is a shell string there (ts-client AgentProfile.acp_command: string | null), not a token array. * fix(build): keep the styling core in one chunk to avoid a tv() init-order crash This PR's new imports grew/shifted the auto-split `vendor` chunk enough that Rolldown's size-based splitter (`maxSize`) sliced the styling core apart — separating a HeroUI component's top-level `tv()` recipe from tailwind-variants' core within the emitted init order. The recipe then evaluated before tailwind-variants initialized, throwing `TypeError: s is not a function` at module load. React Router reported "Error loading route module root-layout, reloading page", looped, and rendered a blank page — deterministically crashing the whole app and failing 13 mock-llm-e2e specs (npm + docker) that load the shell. Give the styling core (@heroui/react + tailwind-variants + tailwind-merge + clsx) its own group that is never size-split, so it initializes as a coherent unit before any consumer's top-level `tv()` call. Verified locally: the home route renders (was a blank page) with zero console errors. * test(mock-llm): make LLM-profile setup idempotent and fix stale ACP launch assertion With the crash fixed, the app renders and a second class of failure surfaced: specs that call `ensureMockLLMProfile` after the first one deadlocked on a stuck "Delete Profile" modal, and the ACP spec's payload assertion checked the old launch shape. - ensureMockLLMProfile: create the mock LLM profile only when absent instead of delete-then-recreate. Once the active agent profile references it (wired right after, via ensureMockLLMAgentProfile — #1571), the LLMProfile FK guard rejects deletion; the delete-confirm modal then silently stays open and its backdrop blocks every later click (`add-llm-profile` timed out across files, home, automations, mcp, model-switch, preset-automation). The mock config is deterministic, so reusing an existing same-named profile is correct. deleteProfileIfExists is unchanged — it still works for the non-referenced profiles that other specs delete. - mock-llm-acp-agent step 3: conversations now launch from the active AgentProfile (#1571), so the POST /api/conversations payload carries `agent_profile_id` and omits `agent_settings` (mutually exclusive, per agent-server-adapter). Assert that shape instead of the retired `agent_settings.agent_kind`; the ACP reply-token check still proves the ACP agent ran. * ci: degrade gracefully when the linked SDK reference isn't a PR "Resolve linked SDK PR" (mock-llm-docker-e2e.yml) greps the PR description for OpenHands/software-agent-sdk#NNNN or .../pull/NNNN and tries to build against that PR's branch. GitHub's "#NNNN" shorthand looks identical for issues and PRs, so a description that links a tracking issue (e.g. #3713) matches the same regex — and /pulls/{number} 404s for an issue number, failing the whole job under `bash -e` instead of falling back to the released SDK version like the "no match" branch already does. Treat a failed PR lookup the same as "no linked PR found": log and exit 0, leaving git_ref unset so the job falls through to the released version. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(test): make ensureMockLLMProfile/AgentProfile converge, not skip Two real e2e failures traced to test-helper bugs surfaced only once #3968 (SDK 1.32.0) let profile-launched conversations actually run: - ensureMockLLMProfile: the earlier idempotent-reuse fix (deadlock guard against the LLMProfile FK constraint) skipped writing the profile's config entirely whenever a same-named profile already existed — correct for repeat calls with the SAME config, but silently ignored a DIFFERENT one. mock-llm-image-upload requests a vision-capable model ("openai/gpt-4o") to get past the mock LLM's default; when an earlier spec in the same CI run had already created "mock-llm" with the default model, the override never applied and the agent replied "the currently selected model does not support image understanding" — confirmed via the CI screenshot. Fixed by editing the existing profile in place (via the LLM settings UI's Edit flow, never deleting it) so every call converges on the requested model/apiKey/baseUrl regardless of what an earlier test left behind. - ensureMockLLMAgentProfile: OpenHandsAgentProfile.skill_refs defaults to `[]` (none discovered) when omitted from the save payload. Workspace- scoped project skills are discovered independently of this and keep working, but a profile-launched conversation's agent never sees any public/preset skill (e.g. an installed automation's bundled skill) without an explicit skill_refs. Set it to `null` (all discovered), matching what a real onboarding-seeded profile effectively gets. mock-llm-model-switch step 2's post-switch reply timeout is left unaddressed: its trajectory hard-codes one padding turn for "the agent-server's internal condenser/skill-analysis call before the main loop" (a documented, historically-fragile assumption per the test's own comment) — plausibly now off by one now that #3968 lets the agent make additional real tool-use calls around a /model switch. Fixing this requires an empirical trajectory-turn count from a real 1.32.0 conversation trace, which needs a CI cycle to observe correctly rather than guessing blind. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * revert(test): drop skill_refs=null from ensureMockLLMAgentProfile CI showed this regressed mock-llm-skills.spec.ts (project skill in workspace/.agents/skills/), which passed before this change: fixed the narrow preset-automation slash-command skill-activation case at the cost of breaking a more fundamental, previously-solid #3968 validation — a net-negative trade, not a clean win. The shared "default" agent profile backs every spec in the suite; widening its skill_refs to "all discovered" has global blast radius across unrelated tests, evidently including some interaction with project-skill discovery/activation tracking that isn't understood yet. A fix for preset-automation's specific skill needs to be scoped to that one profile/test, not applied to the profile every other spec shares. Keeps the ensureMockLLMProfile edit-in-place fix (proven, isolated, fixes mock-llm-image-upload with no observed side effects). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): default new profiles' skill_refs to "all discovered" OpenHandsAgentProfile.skill_refs defaults to `[]` (none) server-side when omitted from a save payload. Neither onboarding's profile seed nor the Settings "Add Agent Profile" editor exposes a skill_refs control, so every newly-created profile silently gets zero public/user/project skills — a profile-launched conversation's agent can't activate any of them (#1571 launches conversations from the active agent profile). This is exactly the mock-llm-preset-automation regression: a slash-command-triggered skill never activates because the "default" test profile has no skill_refs, matching what a real user's fresh profile would also hit. useSaveAgentProfile is the single choke point for every profile save (onboarding seed + Settings create/edit), so default skill_refs to `null` ("all discovered") there whenever the caller hasn't set it explicitly — matches what users actually expect (a new agent has access to their skills unless deliberately scoped down) and requires no SDK change. The pinned typescript-client doesn't type skill_refs on AgentProfileSaveInput yet (SDK/wire drift), so this reaches it via an untyped merge; `in` checks the runtime object since mergeAgentProfileSaveInput's edit-preserve spread can carry it at runtime despite the missing type. Re-applies the equivalent default to ensureMockLLMAgentProfile (the e2e test helper bypasses this hook via a raw fetch) so the test suite mirrors real behavior. Verified: full unit suite green (3618 passed), typecheck clean, agent-profiles-local-view.test.tsx passes unaffected (it mocks useSaveAgentProfile at the hook boundary, so this change is invisible to it). Locally reproduced the fix: mock-llm-preset-automation's slash- command skill-activation test now passes; mock-llm-image-upload (previously fixed) still passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): self-heal LLM profile stream=true for profile-launched conversations A profile-launched conversation (agent_profile_id) never sends agent_settings, so PR #1474's `llm.stream = true` (buildConfiguredOpenHands AgentSettings, agent-server-adapter.ts) never reaches it — the referenced LLM profile's stored `stream` field (SDK default: false) is used as-is by resolve_agent_profile/_build_openhands_settings, with no override, unlike the legacy path. The agent-server decides once, at conversation construction, whether to wire the `on_token` streaming callback — based on whether any of the agent's LLMs has stream=True at that moment — and never re-evaluates it afterward (confirmed by reading LocalConversation.switch_llm: it swaps the LLM but never touches _on_token). So a profile-launched conversation whose LLM profile was never saved with stream=true gets on_token=None for its entire lifetime. switchProfile's switch_llm call (unconditionally sending stream: true, unchanged by this fix) then crashes the next completion with "Streaming requires an on_token callback", since on_token can never be (re-)wired post-construction. Confirmed via real agent-server tracebacks in both mock-llm-e2e and mock-llm-docker-e2e CI runs. Streaming is a pre-existing, independently-shipped feature (PR #1474) that must not regress for legacy-launched conversations — ruling out simply dropping switch_llm's stream:true (would silently disable streaming after a switch for the one case that works today). And since existing users' LLM profiles predate this fix, defaulting stream:true only at future profile-save time (mirroring the skill_refs fix) would still crash on their first profile-launched conversation post-deploy. ensureLlmProfileStreams is a migration shim: at the one call site guaranteed to run for every profile-launched conversation (already fetching the LLM-profiles list to validate llm_profile_ref exists), check the referenced LLM profile's full config and, if stream isn't already true, save it with stream:true — self-healing both new and existing profiles on first use, memoized per profile name for the session so it's a no-op read on every subsequent launch. Mirrors the profile-duplicate flow's exact pattern for round-tripping the encrypted secret (getProfile(name, "encrypted") + saveProfile(..., include_secrets: true)) so the stored api_key is never clobbered. Touches neither the legacy agent_settings path nor switch_llm — both keep working exactly as before. Safe to delete once virtually all users are migrated, or once resolve_agent_profile forces stream=true for OpenHands profiles upstream (same category of fix as the skill_refs default — likely the same #3967 umbrella), whichever comes first. Verified: full unit suite green (3620 passed, +2 new tests exercising this exact self-heal/no-op branching), typecheck clean. Locally reproduced the fix: mock-llm-model-switch's on_token crash no longer occurs; preset-automation and image-upload remain passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(agent-profiles): link the skill_refs/streaming shims to their tracking issue References OpenHands/agent-canvas#1619 (the cleanup-tracking issue for both workarounds) and the specific upstream SDK issues, so the removal criteria is discoverable from the code itself, not just the PR description. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): drop skill_refs/streaming migration shims (SDK #4017 landed) software-agent-sdk#4017 (PR #4018) fixes both gaps these shims worked around: OpenHandsAgentProfile.skill_refs now defaults to null (all discovered) server-side, and the agent-server forces llm.stream=true for profile-launched conversations. Both shims are now dead code. Validated end-to-end against the SDK branch (OH_AGENT_SERVER_LOCAL_PATH) before removing: real HTTP round-trips confirmed skill_refs defaults to null and the launched agent's LLM streams even though the underlying LLM profile is stored with stream=false; the full mock-llm-skills.spec.ts and mock-llm-profile-management.spec.ts suites pass unchanged. Removes: - withDefaultSkillRefs (src/hooks/mutation/use-save-agent-profile.ts) - ensureLlmProfileStreams + its two dedicated tests (src/hooks/mutation/use-create-conversation.ts) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(agent-profiles): correct comments for the disabled_skills deny-list SDK #4017 replaced the profile's skill_refs allow-list (and embedded skills) with a disabled_skills deny-list. Canvas is already deny-list-native — the user-level disabled_skills UI exists and the generic profile merge carries the field automatically — so only two stale comments referencing embedded skills / skill refs needed correcting. No functional change; the per-profile skill picker stays out of scope for the minimal editor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(agent-profiles): drop stale skill_refs from fixtures for the deny-list SDK #4017 replaced the profile's skill_refs allow-list (and embedded skills) with a disabled_skills deny-list. Update the fixtures/comments that still referenced the removed fields (they ride untyped through `as unknown` casts / raw POST bodies, so the generic merge round-trips them regardless): - merge-agent-profile-save-input.test.ts + agent-profiles-local-view.test.tsx: skill_refs -> disabled_skills, drop embedded `skills`, schema_version 3, ACP fixtures drop the skill field (ACP has none). Correct the stale exposeSecrets/mcp_tools comment (profiles are secret-free now). - mock-llm-helpers.ts: the omitted-field comment now describes the deny-list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(agent-profiles): profile fixtures use the v1 baseline schema_version SDK #4017 collapsed the pre-ship AgentProfile schema history to a clean v1 baseline (no v2/v3, no migrations). Update the two profile fixtures to schema_version: 1 to match the shipped model. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): launch the `default` profile via agent_settings; stamp the launched LLM ref The seeded `default` agent profile is the enriched baseline that mirrors global agent_settings, not a deliberate profile pick. Launching it via `agent_profile_id` made the server rebuild the agent purely from the profile, dropping the canvas-only enrichments the profile-resolution path can't carry — the `<RUNTIME_SERVICES>` system-message suffix, the `canvas_ui` tool, and project-skill loading. Route the well-known `default` profile through the agent_settings launch instead; named profiles are deliberate custom configs and keep the profile path. Fixes the mock-llm-docker-e2e automation RUNTIME_SERVICES failure. Also from #1571 review: - Stamp the launched OpenHands profile's `llm_profile_ref` into conversation metadata (not the standalone active LLM profile) so the switcher pill names the exact profile the conversation runs when the two differ (#1082). - Add `retry: false` to the LLM-ref validation fetch, matching the sibling agent-profiles fetch, so a slow/erroring /api/profiles falls back promptly. Hoist the well-known name to `WELL_KNOWN_DEFAULT_AGENT_PROFILE_NAME` (shared by the launch path and onboarding). Re-onboarding intentionally overwrites `default`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): gate the LLM-setup banner on the active agent-profile load `useLlmConfigured` derives `isAcpAgent` and the referenced LLM from the active agent profile but omitted that query's loading state from `isLoading`. On a cold cache an ACP agent (which needs no key) briefly read as an unconfigured OpenHands agent, flashing the "LLM not set up" banner until the profiles query resolved. Thread the `useActiveAgentProfile` loading signal into the indeterminate state so consumers render nothing until the active agent profile is known (#1571 review). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): scope the default→agent_settings launch to OpenHands profiles The `default`→agent_settings shortcut (which preserves <RUNTIME_SERVICES>/canvas_ui) must not apply to an ACP `default` profile: activation is pointer-only, so global agent_settings is stale (still OpenHands) when an ACP profile is active — routing it via agent_settings launched the wrong agent (mock-llm-acp-agent.spec.ts step 3 expected agent_profile_id, got OpenHands agent_settings). ACP also carries no <RUNTIME_SERVICES>/canvas_ui enrichment, so there's nothing to preserve. Gate the shortcut on agent_kind === "openhands"; ACP defaults keep the profile path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agent-profiles): address PR #1571 review findings (VascoSch92) - Gate the default-profile agent_settings downgrade to local backends only; cloud always launches from the resolved agent_profile_id. - Emit an explicit schema-default (not an omitted key) when tool_concurrency_limit is cleared, so edit-save actually resets it. - Restore the tailored "Switched to {name} failed" toast via meta.disableToast + a dedicated onError. - Share one AGENT_PROFILES_RETRY_OPTIONS constant across the launch path, redirectIfAcpActive, and useAgentProfiles so retry policy can't drift between call sites. - Fix a stale comment on optimisticActiveProfile's write path. - Self-heal a dangling llm_profile_ref in the agent-profile editor by validating it against the live LLM-profiles list on load. * fix(agent-profiles): restore cloud in-conversation LLM-profile switching resolvePickerKind hard-coded cloud conversations to the read-only model picker, on the premise that cloud has no per-conversation switch endpoint. That's not true: POST /api/v1/app-conversations/{id}/switch_profile has existed since OpenHands#14288 (2026-05-05), predating this PR, and the frontend plumbing to call it (AgentServerConversationService.switchProfile's cloud branch) was already implemented and just unreachable. Cloud OpenHands conversations now resolve to the LLM-profile picker, same as local, matching how ACP already behaves identically on both backends. main's old SwitchProfileButton had no cloud gate either, so this restores previously-working behavior rather than adding new scope. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9e787557fd |
feat: support LLM profiles on cloud backends (#1532)
* feat: support LLM profiles on cloud backends
Cloud backends had no access to LLM profiles: the LLM was configured through the flat cloud settings form and the chat composer showed a plain model picker. The cloud app-server already exposes the full profile machinery under /api/v1/settings/profiles, so wire agent-canvas to it.
- ProfilesService branches to a new cloud service (src/api/cloud/profiles-service.api.ts) when the active backend is cloud, mirroring how SettingsService delegates to fetchCloudSettings; the profile hooks and the settings manager UI then work transparently.
- The LLM settings route renders the profile manager for both backends.
- Chat-level switching on cloud: the composer shows the profile switcher, /model lists/switches profiles, and per-conversation switching routes through the app-server's server-resolved /app-conversations/{id}/switch_profile endpoint.
* fix: gate cloud LLM profile management on org role (owner/admin)
Cloud org members (role=member) have VIEW_ORG_SETTINGS only: they may view but not create/edit/rename/delete/activate LLM profiles, which the app-server reserves for owner/admin (EDIT_ORG_SETTINGS). The cloud profile settings page exposed every mutating control to all members — reported in PR review.
Surface the caller's role from the existing GET /api/organizations/{orgId}/me call and add useCanManageLlmProfiles() (local backends always true; cloud only for owner/admin, reusing the /me query so no extra request). The settings profile manager hides Add and the per-row actions menu (edit/rename/duplicate/delete/activate) for members, rendering a read-only list.
Per-conversation profile switching in chat stays available to members: the app-server's /app-conversations/{id}/switch_profile route is not org-permission-gated, so switching one's own conversation is a permitted usage action, distinct from managing the org's profiles.
* fix: read profile-manage permission from the server, with role fallback
Review follow-up: instead of hardcoding the role->permission mapping on the client (role === owner||admin), useCanManageLlmProfiles now reads the server-defined `permissions` from GET /api/organizations/{orgId}/me and gates on `edit_org_settings`. Falls back to the previous role check when an older app-server doesn't return `permissions`, so it keeps working against either backend version.
Backend companion (adds `permissions` to /me): OpenHands/OpenHands#15048.
* fix: enforce LLM-profile permissions server-side via the org-gated routes
Route cloud profile CRUD/activate through /api/organizations/{orgId}/profiles, which require EDIT_ORG_SETTINGS server-side — so a member's mutation is rejected with 403 even on a direct API call, not just hidden by the client gate. Falls back to the ungated per-user /api/v1/settings/profiles route only when no org is bound (legacy keys).
A shared cloudProfilesTarget() picks the base path; get/activate normalize the org shapes (llm -> config / llm_applied). Completes the 'validate on both client and server' review point alongside the client gate (companion: OpenHands/OpenHands#15048 exposes the permission on /me).
---------
Co-authored-by: hieptl <hieptl.developer@gmail.com>
|
||
|
|
998b673f10 |
feat: add duplicate button to LLM profile menu (#1372)
- Add 'Duplicate' menu item in profile actions menu (positioned after Rename, before Set as Active)
- Duplicate creates new profile with same settings but name '{old-name}-copy'
- Auto-increment suffix if name exists: '{old-name}-copy-1', '{old-name}-copy-2', etc.
- Preserve encrypted API keys when duplicating profiles
- Add i18n translations for BUTTON$DUPLICATE and SETTINGS$PROFILE_DUPLICATED in 15 languages
- Update component tests to include onDuplicate prop and test coverage
Co-authored-by: openhands <openhands@all-hands.dev>
|
||
|
|
2888a5ca4e |
fix: preserve hidden LLM base URL on basic saves (#1347)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
af9c653f3c |
settings: trust public OpenHands provider models (#1280)
* settings: trust public OpenHands provider models * test: update OpenHands profile E2E expectation * test: allow transitional OpenHands model shape in E2E * test: update OpenHands provider profile E2E * ci: pin mock e2e to SDK provider PR Temporarily run mock-LLM E2E against OpenHands/software-agent-sdk#3548 so the provider settings refactor is validated with the matching SDK end state. * test: stabilize automation trajectory for SDK pin Use non-empty safety replies for the automation-run conversation so the mock trajectory completes whether the pinned SDK does or does not consume an internal LLM turn first. * ci: isolate SDK PR pin to smoke test Keep the full mock-LLM and Docker E2E suites on the released agent-server/automation stack, and add a targeted profile-management E2E job that runs against the SDK PR git ref. * ci: remove temporary SDK PR pin Revert the temporary OpenHands/software-agent-sdk#3548 git ref before switching the PR to the release-branch SDK pin. * ci: pin SDK smoke to release PR Point the temporary SDK smoke-test pin at OpenHands/software-agent-sdk#3638 (Release v1.28.0) so PR #1280 validates against the release branch hash. Co-authored-by: openhands <openhands@all-hands.dev> Remove the temporary SDK release-branch pin now that software-agent-sdk v1.28.0 is published, and run the mock E2E workflow against the released agent-server package. * test: align agent server version references Update docs and drift-detection expectations for the 1.28.0 agent-server pin. --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
197588cc8e |
Add ChatGPT subscription LLM support (#744)
* Add ChatGPT subscription LLM support Co-authored-by: openhands <openhands@all-hands.dev> * Use typed LLM subscription client Co-authored-by: openhands <openhands@all-hands.dev> * Fix subscription model list and device code UX - Merge /api/llm/subscription/openai/models with chatgpt/ provider models from /api/llm/models so the subscription dropdown stays in sync with the full LiteLLM registry (e.g. includes gpt-5.5) - Add gpt-5.3-codex to the hardcoded fallback constant - Replace hardcoded OPENAI_SUBSCRIPTION_MODELS array in the dropdown with a dynamic useOpenAISubscriptionModels hook (falls back to constant when endpoints are unavailable) - Device code block: remove 'Code: ' prefix, fix black-on-dark contrast by using --oh-surface-primary / --oh-text-primary tokens, add CopyToClipboardButton for one-click copy Co-authored-by: openhands <openhands@all-hands.dev> * Add gpt-5.5 (and gpt-5.4/5.4-pro) to subscription model fallback list LiteLLM's chatgpt/ provider registry does not yet include gpt-5.5, but it is available via ChatGPT Plus/Pro subscription. Add it to the hardcoded fallback so it appears in the dropdown regardless of whether the backend's chatgpt/ entries have been updated. Co-authored-by: openhands <openhands@all-hands.dev> * Simplify subscription model source to chatgpt/ provider only - Drop /api/llm/subscription/openai/models as a model source; it was added in this PR set and is redundant — LiteLLM's chatgpt/ provider is the single authoritative list - Remove the OPENAI_SUBSCRIPTION_MODELS hardcoded fallback array, OpenAISubscriptionModel type, DEFAULT_OPENAI_SUBSCRIPTION_MODEL, and isOpenAISubscriptionModel type guard — all replaced by the live subscriptionModels from the hook, or a simple pass-through in non-hook contexts (adapter, profile builder) - Update agent-server-adapter test to reflect pass-through behavior Co-authored-by: openhands <openhands@all-hands.dev> * test: link tracking issue #917 to typescript-client git-pin exemption Co-authored-by: openhands <openhands@all-hands.dev> * Address subscription settings review feedback Co-authored-by: openhands <openhands@all-hands.dev> * Add workspace mock handlers for snapshots Co-authored-by: openhands <openhands@all-hands.dev> * Mock MCP test endpoint in snapshot tests Co-authored-by: openhands <openhands@all-hands.dev> * Stabilize home snapshot waits Co-authored-by: openhands <openhands@all-hands.dev> * Fix subscription settings dark theme contrast Co-authored-by: openhands <openhands@all-hands.dev> * Format subscription service * Fix subscription settings CI coverage * Guard subscription model loading * Auto-poll subscription device login * Format subscription auth auto-poll changes --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: neubig <398875+neubig@users.noreply.github.com> |
||
|
|
270ef6a876 |
Remove obsolete OpenHands proxy base URL handling (#1321)
Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Engel Nyst <engel.nyst@gmail.com> |
||
|
|
910b19ae76 |
chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 (#1319)
* chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 Co-authored-by: openhands <openhands@all-hands.dev> * Test fixes * fix: inject proxy base_url for litellm_proxy/* when server omits it (agent-server ≥1.28) Agent-server ≥1.28 may return base_url:null when fetching a litellm_proxy/* profile config, even when the profile was saved with the All-Hands proxy URL. This caused the Basic-tab re-save flow in LlmSettingsLocalView.handleSave to call isOpenHandsProxyModel(model, null) → false, hitting the else-branch that deletes base_url and stranding the profile (issue #1146). Fix: add a secondary check — litellm_proxy/* with a missing base_url is treated the same as litellm_proxy/* with the proxy URL already set, and OPENHANDS_LLM_PROXY_BASE_URL is injected before the save request is sent. Also updates the mock-LLM E2E test to accept both storage representations: - litellm_proxy/* + proxyBaseUrl (pre-1.28, guards issue #1146 regression) - openhands/* + null (1.28+, server-managed routing) And adds a unit test exercising the base_url:null path. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
8071edf72a |
Revert "chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 (#1315)" (#1318)
This reverts commit 1917b5d39fbf09dc51213b4b484698fe394314c7. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
15a52fea75 |
chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 (#1315)
* chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 Co-authored-by: openhands <openhands@all-hands.dev> * chore: update doc examples to reference agent-server 1.28.1 Update version references in AGENTS.md, scripts/dev-safe.mjs, and scripts/check-sdk-version-sync.mjs from 1.27.0 → 1.28.1 to stay in sync with the agentServer pin in config/defaults.json. Fixes: docs-version-sync.test.ts failures Co-authored-by: openhands <openhands@all-hands.dev> * fix: inject proxy base_url for litellm_proxy/* when server omits it (agent-server ≥1.28) Agent-server ≥1.28 may return base_url:null when fetching a litellm_proxy/* profile config, even when the profile was saved with the All-Hands proxy URL. This caused the Basic-tab re-save flow in LlmSettingsLocalView.handleSave to call isOpenHandsProxyModel(model, '') → false, hitting the else-branch that deletes base_url and stranding the profile (issue #1146). Fix: add a secondary check for litellm_proxy/* models with a missing base_url (null/undefined/empty), treating them the same as a stored proxy URL and injecting OPENHANDS_LLM_PROXY_BASE_URL before the save request is sent. Also adds a unit test exercising the base_url:null path. Co-authored-by: openhands <openhands@all-hands.dev> * test(e2e): accept agent-server 1.28 model rewrite in proxy profile test Agent-server 1.28 normalises litellm_proxy/* → openhands/* on storage and manages the proxy URL internally (returning base_url:null). The old assertions hard-coded the pre-1.28 storage format (litellm_proxy/* + explicit proxy URL), causing the test to fail on every 1.28 run. Extract assertProxyProfileConfig() helper that accepts both storage representations: - litellm_proxy/* + proxyBaseUrl (pre-1.28, guards issue #1146 regression) - openhands/* + null (1.28+, server-managed routing) The issue #1146 guard is preserved: a litellm_proxy/* profile without a proxy URL is still flagged as a stranded profile. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
098af964a8 |
fix(acp): single Save + auth banner + one toast on Settings → Agent (#1251)
* fix(acp): single Save + auth banner + one toast on Settings -> Agent (#988) Three follow-up UX fixes to #1102 on the Settings -> Agent ACP surface: - Single Save. The credentials section rendered its own "Save Changes" right above the page-level one — two identical buttons doing different things (secrets vs agent spec). Consolidate to one Save that persists both; the section is now presentational (the page owns the credential form via a lifted useAcpCredentialForm). A credentials-only edit saves just the secret and skips the redundant settings write. - Auth banner. Surface the onboarding "already signed in to {provider}" banner in the credentials section too, via a new shared AcpAuthStatusBanner that onboarding now also uses. Local-backend login probe only (silent on cloud/unknown), same as onboarding. - One toast. When a single Save persists both the agent spec and a credential, the credential save is silenced so the user sees one "Saved" instead of two (errors still surface). Typecheck + lint green; 50 tests across the agent-settings / credentials-section / onboarding suites pass, incl. new coverage for the single-save flow, the auth-banner states, and the single-toast assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: drop redundant isAcp guard on credential dirty check (#1251) acpCredentialForm.isDirty is already false off the ACP path (no credential fields), so the isAcp && guard is redundant. Per review feedback. --------- Co-authored-by: Debug Agent <debug@example.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ec4616c1c7 |
feat(acp): containerized + cloud ACP — onboarding, secrets, and recycled-sandbox resume (#1013/#1014/#988) (#1102)
* feat(acp): containerized ACP — credential onboarding + inline secrets (#1013/#1014) Wire the canvas halves of agent-canvas#1014 (Docker) and #1013 (credential onboarding) so a user can run an ACP agent (Codex / Claude Code / Gemini) against a containerized agent-server through Canvas, with credentials supplied in the UI. Credential onboarding UX (#1013): - Extend the ACP secrets step beyond the API key to the per-provider reserved credentials a fresh container needs: Codex CODEX_AUTH_JSON, Claude CLAUDE_CODE_OAUTH_TOKEN, Gemini GOOGLE_APPLICATION_CREDENTIALS_JSON + GOOGLE_CLOUD_PROJECT/LOCATION + GOOGLE_GENAI_USE_VERTEXAI. File-content blobs render as multiline fields. - Make the step capability-driven: required on a backend with no host login (cloud, or a logged-out local/Docker backend per the auth probe), optional when a login is detected or the probe can't classify (native dev). - Fix the orphaned-secret bug: warn instead of toasting "Saved" when the active backend can't consume the credential (cloud can't yet read file secrets). Send secrets + model (start request): - buildStartConversationRequest emits reserved ACP credentials inline as StaticSecrets (overriding any same-named LookupSecret) and mirrors them onto agent_context.secrets, so the SDK's acp_file_secrets defaults materialise the *_JSON blobs before the CLI spawns. The orchestrator reads back the saved reserved values for the active provider (local backends only). - Preselect a Vertex-safe acp_model for Gemini (gemini-2.5-flash) so a fresh container doesn't hit gemini-cli's preview default that 404s on Vertex. - Never auto-promote *_BASE_URL to an inline secret (an inherited base URL breaks the Claude OAuth token's bearer auth). Docker setup + docs: - examples/acp-docker/ docker-compose (persistent volume + canvas_ui tool mount + credential notes); .env.sample + docs point VITE_BACKEND_BASE_URL at it. - docs/ACP_AGENTS.md gains a "Running ACP agents in a Docker container" section. Per-conversation isolation (acp_isolate_data_dir) left as a documented TODO — the field isn't exposed on ACPAgentSettings in the released typescript-client. Tests + e2e: - Unit tests for the StaticSecret emission, reserved-credential sets, Vertex model default, getSecretValues read-back, and the required-credentials matrix. - tests/e2e/live-acp/: a vite-node harness that builds each provider's request via buildStartConversationRequest and POSTs it to a real container. Validated with REAL API calls against agent-server c950fdb-python: Codex ✅, Claude ✅, Gemini ✅ (materialise ADC -> vertex-ai -> real reply). Gemini's default-config init is blocked by an SDK/gemini-cli set_session_mode("yolo") issue (documented caveat, not a credential problem). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(acp): make containerized credentials survive the real conversation-start path Validating end-to-end through the application's own orchestrator (buildStartConversationRequestWithEncryptedSettings) against a live container — rather than the request builder in isolation — surfaced two real bugs that would have broken the feature in the product: 1. secrets_encrypted mangled the plaintext reserved StaticSecrets. The app always fetches settings in encrypted mode, so the start request carried secrets_encrypted=true. The agent-server then runs every secret value through cipher.decrypt() during validation — including our reserved ACP creds, which are read back as PLAINTEXT. Result: the credential was silently dropped (decrypt fails → None) on a cipher backend, or a hard 500 ("cipher not configured") on a fresh container with no OH_SECRET_KEY. Fix: don't set secrets_encrypted for ACP conversations — an ACP agent has no encrypted agent secret (no LLM api_key), and its provider creds ride as plaintext StaticSecrets. 2. A different provider's leftover file-content secret broke the active provider. A CODEX_AUTH_JSON saved while onboarding Codex leaks into a later Claude conversation via the global-secrets → LookupSecret path. The SDK materialises file secrets eagerly at spawn by resolving the secret source, and a LookupSecret resolution stalls → ReadTimeout → "Failed to start ACP server: timed out". Fix: reserved file-content blobs (the multiline *_JSON creds) never travel as LookupSecrets — the active provider's is sent inline as a StaticSecret, any other provider's is dropped (getAllReservedAcpFileSecretNames). Re-validated through the app orchestrator against agent-server c950fdb-python (onboarding createSecret → buildAcpAgentSettingsDiff PATCH → orchestrator read-back → real reply): Codex ✅, Claude ✅ (leftover CODEX_AUTH_JSON correctly dropped). Gemini's app path is correct (StaticSecrets emitted, vertex-ai auth reached); this run hit the documented invalid_rapt stale-ADC caveat (host ADC expired since the prior fresh-ADC pass) — an environment issue, not code. Adds regression tests (secrets_encrypted suppressed for ACP / kept for non-ACP; leftover file blob dropped not LookupSecret'd; getAllReservedAcpFileSecretNames) and the app-path e2e harness (tests/e2e/live-acp/acp-docker-app-e2e.mts). Notes OH_SECRET_KEY as optional (secret persistence) in the compose example. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: address PR review feedback (#1102) - retag acp_isolate_data_dir TODO #1014 (this PR) -> #1019 (the per-conversation isolation follow-up the knob serves) - note the Gemini Vertex scalars (PROJECT/LOCATION/USE_VERTEXAI) are plain config / a routing flag, not secrets Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(acp): order subscription credential before API key in onboarding Show each provider's reserved subscription/Vertex credential first (Claude CLAUDE_CODE_OAUTH_TOKEN, Codex CODEX_AUTH_JSON, Gemini Vertex SA), then the API key, then the base URL — the subscription token is the primary auth path for ACP providers, with the API key as the fallback. Display order only; getAcpProviderSecrets consumers are order-independent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(i18n): disable i18next value escaping so React handles it i18next's default escapeValue double-escapes interpolated values on top of React's own escaping, rendering paths like ~/.codex/auth.json as ~/.codex/auth.json. Set interpolation.escapeValue=false (the standard react-i18next config); React still escapes at render time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(acp): unify secret wire-delivery; keep "reserved" as onboarding-only Drop the reserved-vs-custom split in how secrets reach the agent-server. Previously, provider credentials ("reserved") rode inline as StaticSecrets while user secrets rode as loopback LookupSecrets — a fork introduced only to dodge a deadlock: the SDK resolved an ACP agent's secrets synchronously on its event loop at CLI spawn, so a loopback LookupSecret self-deadlocked. That deadlock is fixed at the source in software-agent-sdk#3510 (ACP cold-start runs off the event loop), so the workaround is no longer needed. Now every secret — env-var credential, file-content blob, or user secret — ships uniformly as a LookupSecret, for ACP and non-ACP alike. The SDK resolves and (for file blobs) materialises them off the loop, so the loopback fetch is safe. "Reserved" survives only as an onboarding/validation concept (which fields to prompt for per provider, capability-driven required steps) — it no longer affects the wire. Removed: StaticSecret type, acpStaticSecrets option + the inline path, the file-blob lookupSkip, SecretsService.getSecretValues, and the reserved-name value read-back. Kept: secrets_encrypted suppression for ACP (an ACP request carries no encrypted payload, and a fresh ACP container may have no OH_SECRET_KEY cipher). Note: getReservedAcpSecretNames / getAllReservedAcpFileSecretNames in constants/acp-providers.ts are now unused by the wire; the former is still useful for validation, the latter can be pruned. Depends on software-agent-sdk#3510. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(acp): prune now-dead reserved-secret wire helpers Follow-up to the wire-delivery unification: getReservedAcpSecretNames and getAllReservedAcpFileSecretNames were only ever consumed by the inline StaticSecret / file-blob-skip path, which is gone. They have no remaining production callers, so remove them (and their tests). The reserved-credential field definitions (ACP_RESERVED_CREDENTIALS, getAcpProviderSecrets) and the ``reserved`` / ``multiline`` flags stay — onboarding still reads them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(acp): re-point containerized ACP at SDK 1.25.0 (#3510) + fix e2e harnesses The unified LookupSecret delivery (e076e9bb) depends on software-agent-sdk#3510 (ACP cold-start off the event loop), which first ships in v1.25.0. The example compose/docs/e2e all still defaulted to agent-server:c950fdb-python, which predates #3510 and deadlocks the first ACP turn ("Failed to start ACP server: timed out"). Bump every default to 1.25.0-python and document it as the minimum. Also realign the live-acp e2e harnesses, which still encoded the removed StaticSecret API (the PR's headline evidence predated the unification): - acp-docker-e2e.mts: store each credential via SecretsService.createSecret, send name-only customSecrets, assert every emitted secret is a LookupSecret. - acp-docker-app-e2e.mts: flip the assertion StaticSecret -> LookupSecret; drop the stale getSecretValues reference. - Both: fix a polling bug where "idle" (the transient pre-run state) was treated as terminal, so the loop bailed before the agent ran and read an empty reply. Terminal is now {finished, error, stuck, stopped}. Correct the stale StaticSecret doc comments in constants/acp-providers.ts (reserved is now an onboarding/validation marker, not a wire distinction). Re-validated in-container against agent-server:1.25.0-python: Codex and Claude pass end-to-end on both harnesses (LookupSecret resolves off-loop, no deadlock, even with leftover cross-provider file-secrets present). Gemini's credential path is proven (vertex-ai auth reached) but the turn is blocked by gemini-cli 0.45.x ignoring the requested acp_model and running gemini-3-flash — an SDK model-selection concern tracked in software-agent-sdk#3532, not a Canvas bug; the docs/e2e notes are corrected accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(acp): improve credential hint text with fetch commands Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(acp): show provider credentials in Settings → Agent Adds a Credentials section to /settings/agent when an ACP provider is selected, so users can set or rotate tokens/keys after onboarding without hunting through Settings → Secrets. Mirrors the onboarding fields exactly (same hints, same already-saved placeholders, Optional tag on multiline fields) with its own Save button that writes directly to the secret store. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(acp): drop the agent_context.secrets mirror — request.secrets is the sole channel The mirror's justification ("ACPAgent's spawn-time env loop reads from agent_context.secrets, not the registry") predates the pinned minimum agent-server: 1.25.0 already injects the ACP spawn env from secret_registry, seeded from request.secrets (sdk#3299/#3464), and sdk#3528 removes the agent_context drain entirely. Keeping the mirror preserved a second, dead credential channel — the exact coupling agent-canvas#1039 is eliminating. Canvas now sends every credential in top-level request.secrets only. Tests inverted to pin the single-channel contract; adapter/type comments updated to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(acp): non-flash Gemini default, shared credential form, review cleanups - ACP_VERTEX_SAFE_MODEL → gemini-2.5-pro: gemini-cli 0.45.x re-resolves any *-flash id at generation time to its current default flash (sdk#3532), so a flash pin is never honored; docs + e2e defaults updated to match - extract AcpSecretField + useSaveAcpSecrets and move AcpCredentialsSection to components/ — onboarding and Settings → Agent share one field renderer and one save flow (incl. the orphaned-file-credential warning on cloud) - a required credentials step is only satisfied by an actual credential (a masked `secret` field) — a base URL or GCP scalar alone no longer unblocks - warn inline when CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_BASE_URL are both set (typed or saved) — the pair silently breaks bearer auth - drop the near-dead `reserved` field flag; collapse the leftover two-block secrets scaffolding in buildStartConversationRequest - sync 14 stale locales on the OAuth/file-blob hints; fix issue refs (TODO #1019→#1014 — #1019 is closed; OpenHands#1016→agent-canvas#1016) - tests: settings credentials-section coverage, non-flash pin, conflict matrix, tightened-gate cases Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(acp): unify default-model surfaces + dedupe credential forms and e2e harness Review-pass cleanups: - Route ALL three default-model surfaces (onboarding diff builder, Settings -> Agent seeding, start-request null fallback, + chat-input display) through getAcpPreferredDefaultModel, so the Vertex-safe Gemini override can't diverge between surfaces. New regression tests pin the diff-builder and start-request fallbacks to it. - Extract useAcpCredentialForm + AcpConflictWarnings: the onboarding step and the Settings credentials section now share the values state, existing-secret lookups, conflict pairs, and save flow. - Extract tests/e2e/live-acp/harness.mts: provider plans, host credential collectors, and HTTP/poll helpers shared by both live scripts (a model default can no longer drift between them). - Restore the TODO(#1019) retag (accidentally reverted to the self-referencing #1014 in the last cleanup commit); same fix in docs/ACP_AGENTS.md. - Drop the tautological ACP_VERTEX_SAFE_MODEL literal assertion, fix a dead key-ternary in getAcpProviderSecrets, TODO(#1016) on the cloud file-credential capability check, and document that baked .env creds don't satisfy the onboarding login probe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: address PR review feedback (#1102) - Restore package-lock.json to main — the npm-install churn (29 dropped "dev": true flags) was never meant to ship with this PR - Note why global escapeValue:false is safe (React escapes at render; no translated string hits dangerouslySetInnerHTML) - Note the non-macOS skip path in the e2e claudeOAuthToken collector Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(acp): tighten the credential gate + clarify base-URL docs (#1102 review) - A file blob no longer satisfies the required credential step on a backend that can't materialise it (cloud, #1016) — the save flow already warned it was orphaned, so it can't be what opens the gate. consumesFileCredentials moves into useAcpCredentialForm so the gate and the save warning share one capability check. - Next stays disabled while the login probe is still classifying a local backend, so a fast click can't slip past a gate about to come up "unauthenticated". A probe that completes as "unknown" stays permissive. - Docs: a saved *_BASE_URL secret does ride along on every start request like any other saved secret; Canvas only never derives one from LLM settings. Reword the two claims that suggested otherwise. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(e2e): record 2026-06-07 re-validation — all three providers pass Fresh 1.25.0-python container + fresh volume at the branch tip: Codex and Claude pass both scripts; Gemini's full turn now passes too (fresh ADC + gemini-2.5-pro + session-mode override), upgrading the previous "blocked on model selection" row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(acp): resume a recycled cloud ACP conversation via bootstrap prompt (#988) A cloud ACP conversation whose sandbox was recycled (STOPPED/MISSING, e.g. the runtime idle-stopped or hit its TTL) was a read-only dead end: the chat input was replaced by the archived banner, and cloud createConversation never re-provisions an existing conversation_id. The backend already supports resuming such a conversation — re-issuing the start with the same conversation_id rebuilds it and, for ACP, replays the durable event store as a bootstrap prompt (OpenHands#14640) — but nothing in canvas triggered it. Surface it: - AppConversationStartRequest.conversation_id so the cloud start path can target an existing conversation. - wakeRecycledCloudConversation(id, repoSelection): re-POST /api/v1/app-conversations with the conversation_id (and repo selection, so the rebuilt working dir matches the original cwd an ACP resume keys off). - useWakeConversation mutation: wakes + invalidates the conversation queries so the active-conversation poll reconnects once the fresh sandbox is RUNNING. - A Resume button in the archived banner for an ACP conversation whose sandbox is MISSING (ERROR stays read-only). Validated e2e against a local SaaS-equivalent stack (OpenHands main app_server + a main-built agent-server image, Docker sandboxes): create an ACP conversation, docker rm -f the sandbox, wake → fresh sandbox + bootstrap-prompt resume, the agent recalls prior context (codeword) and the <<RESUMED CONVERSATION>> marker is present. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(acp): consume file-content credentials on cloud too (#988) Cloud now materialises reserved file-content credentials (Codex auth.json, Gemini Vertex SA) from the per-user encrypted secret store via agent_context.secrets at conversation start (the cloud backend pins an SDK that materialises reserved file secrets), so a pasted blob is consumable on every supported backend — not just local. Drop the local-only gate on consumesFileCredentials: a Codex/Gemini file blob now satisfies the onboarding credential gate on cloud and saving it toasts success instead of the orphaned-credential warning. Folds the remaining cloud-enablement piece in from the native-resume canvas branch (the wake/bootstrap-resume path landed separately); native session/load is a backend-only concern (SDK + OpenHands), so canvas needs nothing further. 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> |
||
|
|
5d50a3aafb | fix: profiles as source of truth + keep one active (local mode) (#1128) | ||
|
|
fae56f520b |
fix: preserve OpenHands proxy base_url for rewritten litellm_proxy profiles (#1148)
The OpenHands-model check only matched the `openhands/*` id the GUI submits, but the agent-server rewrites curated OpenHands models to `litellm_proxy/*` on save. Any later Basic-tab save then misclassified the stored profile as a generic provider and dropped its proxy `base_url`, stranding it as `litellm_proxy/* + base_url:null`. LiteLLM then routes the OpenHands key to the default OpenAI endpoint and the conversation fails with an auth error. Broaden the detector (`isOpenHandsProxyModel`) to also recognize the rewritten `litellm_proxy/*` form when paired with the All-Hands proxy base URL, and apply it in both Basic-save paths (local profile editor and the standalone settings screen). The shared base-URL check is reused by `normalizeDisplayModel` instead of being duplicated. Fixes #1146 Co-authored-by: Engel Nyst <engel.nyst@gmail.com> |
||
|
|
a89aac89f6 |
fix: prevent deletion of the active LLM profile (#1127)
* fix: prevent deletion of the active LLM profile Hide the Delete option in the profile actions menu when the profile is the currently active one, so users cannot accidentally remove it. Co-authored-by: openhands <openhands@all-hands.dev> * fix: add tooltip to disabled Delete menu item for active profile When the Delete action is disabled because the profile is active, show a StyledTooltip ('Cannot delete the active profile') over the item so users understand why it is not interactive. - Add SETTINGS$PROFILE_CANNOT_DELETE_ACTIVE i18n key + all translations - Wrap the disabled Delete MenuItem in StyledTooltip with a span so pointer events reach the trigger on a disabled button - Add complementary test confirming Delete is enabled when isActive=false - Extend i18n mock with the new key Co-authored-by: openhands <openhands@all-hands.dev> * test: fix delete-modal tests to use inactive profile The two delete-modal tests were clicking Delete on the active profile, which is now correctly disabled. Switch both tests to the second (inactive) profile so they exercise the enabled path. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
b9d78d3116 |
Simplify backend registry selection (#1046)
* Add partial stack modes to agent-canvas Co-authored-by: openhands <openhands@all-hands.dev> * Simplify backend registry selection Co-authored-by: openhands <openhands@all-hands.dev> * Fix backend selection CI regressions * Seed local proxy backend in cloud tests * Stabilize onboarding snapshot navigation * Stabilize ingress tests on Windows * Remove local backend fallback for cloud calls * Fix frontend-only backend proxy target * Test backend-only launch without build * Add pending workflow and status updates * Use active LLM profile for setup banner * Show backend connection errors before saving * Validate backend keys in health checks * Update backend selector health test mock * Fix frontend-only workspace path * Preserve OpenHands proxy base URL * Address backend review comments * Preserve profile config when switching models * Fail fast on profile export errors * Throw AgentServerUnavailableError when backend registry is empty When no backend is configured (empty registry / NO_BACKEND sentinel), loadAgentServerInfo() was returning null without throwing, causing OptionService.getConfig() to succeed silently. root.tsx then rendered the home page instead of the MissingAgentServerScreen with the manage backends modal. Now loadAgentServerInfo() checks for the NO_BACKEND sentinel when getEffectiveLocalBackend() returns null and throws AgentServerUnavailableError, which root.tsx already handles by showing the manage backends modal. The cloud-backend path (also null from getEffectiveLocalBackend) is preserved — it still returns null. Co-authored-by: openhands <openhands@all-hands.dev> * chore: Remove PR-only artifacts --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com> |
||
|
|
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> |
||
|
|
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 |
||
|
|
27fe2469f2 |
UI polish: Neo theme, form system, sidebar, and conversation list (#733)
* feat(theme): add OpenHands-Neo theme with white primary buttons Introduce a neutral-based palette variant that overrides brand tokens at runtime, stop inlining primary colors on AgentServerUIRoot, and tie loaders to --oh-color-primary. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): standardize form controls to 36px rounded-lg styling Extract shared form-control class helpers and apply them across buttons, inputs, dropdowns, and settings fields; fix loading text contrast and backend retry button styling. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): indent switch helper text under labels Align description copy with toggle labels using shared pl-14 offset across agent, verification, and schema boolean fields. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): unify toggle switch and polish settings navigation Share an animated white-active toggle between settings and automations, align sidebar sub-nav rows to 36px, and match git settings helper copy to section subtitles. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): use font-medium for page and modal headings Standardize Typography H1–H3, modal titles, and route section headers on medium weight instead of semibold or bold. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): unify connected status green with sidebar indicator Route server and backend status dots through --oh-status-success so the conversation header and backend dropdown match the left nav. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): round toast corners to match button radius Use --oh-radius on all react-hot-toast surfaces and wire shared TOAST_OPTIONS into the root Toaster. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): unify combobox carets and polish error toasts Replace filled chevrons on dropdown triggers with a shared HeroUI-style caret, remove backend selector focus glow, and show CircleX on error toasts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): remove hover delay on nav icons and dropdown carets Snap sidebar, settings, extensions, and dropdown hover colors instantly by excluding color from form-control transitions and using transition-none on nav rows. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): unify settings list styling and secrets header layout Extract shared list row/table classes for secrets and LLM profiles, use h-10 rows, and move the add-secret action to the top-right header. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): polish settings lists, profile menu, and error indicators Use h-12 list rows with subtler hover, align the LLM profile actions menu with shared context-menu styling, and unify error banner/toast icons on --oh-status-error. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): tighten conversation list row spacing to 2px Remove stacked link padding so folder rows and conversation cards use the same 2px vertical gap as grouped list sections. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): cap grouped conversation folders at five rows Show five conversations per workspace/repo folder with a grey More/Less toggle, keeping the active thread visible when it falls outside preview. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): polish conversation card metadata and launcher buttons Align card footer text with titles, drop the workspace folder icon, place branch beside repo name, match open launcher styling to secondary buttons, and darken grouped folder More/Less link color. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): size collapsed sidebar nav rows to 36px height Use a full-width 36px-tall hit target for collapsed rail icons instead of a fixed 40×40 square so nav rows match expanded row height. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): prevent sidebar logo flicker when collapsing Keep a single mounted logo in the header and briefly suppress the collapsed hover expand overlay so collapse does not flash the logo away. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): refine LLM profile editor header layout Hide the settings section title while adding or editing a profile, show a labeled Back control, and place the Add/Edit title below it using standard page heading styles. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): refine secrets add/edit form header layout Hide the list page title while adding or editing a secret, and show a Back control with Add/Edit a Secret headings using standard page styles. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): normalize button font weight to regular across the app Align buttons and button-like controls with the updated theme by defaulting shared form/BrandButton styles to font-normal and removing medium/semibold overrides from individual call sites. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): align error toast styling with chat error banner Top-align the error icon and use text-sm muted copy so toast notifications match the inline error message above the chat input. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): improve error toast icon alignment with message text Render the icon inside ErrorToastContent so it lines up with the message, center on single-line toasts, and top-align when the text wraps. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): shrink sidebar collapse toggle to match filter button Use a 28px hit target and 14px chevron so the collapse control matches the conversations filter icon button. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): polish conversation empty state, status menu, and logo Hide the no-conversations message until the first fetch completes, align the server status dropdown with other context menus, and remove the sidebar logo hover tooltip. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: failing tests --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
8e90704a51 | chore: remove redundant styling assertions and assert semantic attrs instead (#566) | ||
|
|
40032f97c8 |
fix: clarify local llm profiles ui (#534)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
b4d26635b0 | fix: prevent LLM profile actions menu from being clipped by scroll container (#523) | ||
|
|
0e94c5a369 | fix: right-align action buttons in modals/dialogs (#520) | ||
|
|
e1f5a6662f |
design: cool-grey palette, runtime theme switcher, and token-system cleanup (#458)
* fix(conversation): cap chat column width at 800px Replace responsive max-w-4xl / max-w-6xl with max-w-[800px] so the middle column stays narrower on large viewports. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(chat): Connect Repo CTA and hide empty branch pill - Use COMMON$CONNECT_REPO with FolderOpen when no repo/workspace is linked - Show branch control only when selectedBranch is set (drop No Branch) - Cap chat interface wrapper at max-w-[800px] without right-panel width coupling Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): refine input controls and local auth fallback Improve chat input pills and model dropdown interactions while ensuring local agent-server auth uses the configured session key for default-local and cloud-proxy calls to avoid stale-key 401s. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): align attachment and placeholder control styling Move the file-attach trigger into the chat action controls so it sits before Tools, and restyle it as a grey plus button with a circular hover state to match adjacent controls. Also align the chat input placeholder color with the same neutral control tone for visual consistency. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): simplify agent status labels and tone Shorten English agent-status messages for the chat pill and align the status text color with the other grey controls for a more consistent compact UI. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): align model popover settings row styling Add an LLM Settings action to the model popover and normalize its layout, spacing, and divider treatment to match existing dropdown menu patterns while keeping left-aligned positioning. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): restyle status controls and move send action Make the agent-status control transparent by default with gray-to-white icon hover behavior, and move the submit button to the bottom-right controls area beside agent status. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): tighten spacing above git control bar Reduce the top margin before the git control bar so it better matches the bottom spacing around the chat action controls. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): gate submit button on input content Keep the send button inactive until the input has non-whitespace text, and align the revised button sizing/positioning with the bottom action row layout. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): streamline overlays and remove legacy event rails Unify chat control styling and overlay behavior so status/typing/scroll controls float above the thread without adding layout bars, and remove left-rail/checkmark affordances from grouped and generic event cards for a cleaner stream. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): tighten status indicator spacing Reduce status indicator pill padding and icon size, and add right text padding to balance the compact layout in the chat control overlay. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): prioritize centered scroll control over loader Keep the scroll-to-bottom control centered and visible whenever the user is away from the bottom, and use solid base/hover fills so it matches the updated chat surface styling. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): soften conversation event header styling Use the lighter gray chat tone for conversation event header labels/icons and switch those labels to normal weight so grouped event rows match the updated control styling. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): refine markdown spacing and divider styling Tighten markdown vertical rhythm in chat content, add a shared grey horizontal-rule renderer, and tune heading hierarchy to medium/compact styles for clearer structure without heavy emphasis. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): tighten vertical spacing in action event rows Reduce stacked margins and paddings across grouped action rows, generic event cards, and collapsible thinking blocks so adjacent conversation entries read as a denser, more consistent stream. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(design): add app gray palette reference artifacts Capture the current gray color usage in dedicated SVG references, including both a curated palette and a strict exhaustive inventory for design and UI consistency work. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): align compact input overflow menus with menu conventions Keep add-file pinned inline, collapse controls only when width truly runs out, and switch overflow entries to standard context-menu row/submenu patterns while preserving the send button layout at tight widths. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(conversation-panel): use list filter icon for older filters Swap the older-conversations summary toggle icon to ListFilter so it matches the intended sidebar filter affordance. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): complete local workspace launch flow in git controls Switch the local git control CTA from repository connection to workspace launching, including an above-button workspace menu and automatic add-workspace modal when none exist. This also captures the pending chat action/menu styling and test updates in the current working tree. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): relocate desktop vertical padding to input controls Remove desktop top/bottom padding from the main chat panel and apply equivalent bottom spacing to the chat control area so the open repo/workspace controls and input footer keep consistent breathing room. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(conversation): remove bottom margin from chat pane header Drop the chat header bottom margin so the conversation title row sits flush with the content below. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): refresh git control bar immediately after Connect Repo The "Connect Repo" empty-state in the chat input footer kept rendering even after the Open Repository modal had successfully launched a clone and the agent had reported the repository as ready. The bar would only heal after a hard refresh (or never, on cloud backends). Three independent bugs were stacking: 1. Optimistic update was writing to the wrong React Query cache key. `useUpdateConversationRepository.onMutate` called `setQueryData` with `["user", "conversation", id]` (3 elements), but `useUserConversation` reads from `["user", "conversation", id, backendId, orgId]` (5 elements). `setQueryData` requires an *exact* key match, so the update landed on an orphan cache entry that no observer ever read. Switched to `setQueriesData`/`getQueriesData` with the 3-element prefix so the optimistic write actually reaches the active query (Tanstack v5 prefix-matches `setQueriesData` filters). Also normalized `branch`/`gitProvider` to `null` to match the shape produced by the server-side refetch and prevent identity-flicker between the two updates. 2. Cloud `batchGetCloudConversations` / `searchCloudConversations` ignored the local repo selection entirely. For local backends `toAppConversation` overlays `selected_repository`/`selected_branch`/ `git_provider` from `localStorage`, but the cloud path returned the raw SaaS payload — and the SaaS often returns `null` for those fields until its own background hydration finishes. So every refetch (mutation invalidation, 30s poll, panel mount) overwrote the optimistic value with `null` and the bar snapped back to "Connect Repo". Added `overlayStoredRepoSelection` which fills only the `null` slots from local storage; populated server values still win, so we don't shadow real backend changes. 3. `updateConversationRepository` overwrote the entire metadata blob. `setStoredConversationMetadata` is replace-not-merge, so calling it with just `{selected_repository, selected_branch, git_provider}` silently dropped `selected_workspace` (the local-folder attach marker used by the Files tab to default to diff view, see the "Files tab diff-view default logic" note in `AGENTS.md`). Now reads the existing entry first and spreads it under the new repo fields. Defense-in-depth changes: - `useLocalGitInfo` now stays enabled until the conversation reports a *complete* repo tuple (`selected_repository` + `git_provider` + `selected_branch`), not just `selected_repository`. This lets the bar recover from partial-metadata cases (e.g. cloud hydration populates only the repo name first, or the user clones into a subdirectory of `working_dir`). The probe also gained a nested `find . -mindepth 2 -maxdepth 4 -name .git` fallback so a clone into `<workingDir>/<repo>/` is still detected after the direct `git remote get-url origin` in `<workingDir>` returns "no such remote 'origin'" (the agent-server pre-initialises every workspace as a worktree, so the parent directory always has a `.git` folder with no remote). - `useUpdateConversationRepository.onSettled` invalidates `["local-git-info", conversationId]` so the bar re-probes immediately after a connect rather than waiting on the next 10s refetch tick. - `git-control-bar.tsx`'s `hasRepository` predicate now keys off the *resolved* `selectedRepository` + `gitProvider` (which include the local-git probe's findings), not just the conversation field. This lets pull/push/PR buttons light up for local-workspace conversations whose repo metadata was inferred from `git remote`, matching what the repo + branch chips already showed. Verification I traced the failure mode by hitting the live agent-server directly: $ curl -s -X POST .../api/bash/execute_bash_command \\ -H "X-Session-API-Key: \$KEY" \\ -d '{"command":"git remote get-url origin", "cwd":"<workingDir>"}' git remote: error: No such remote 'origin' git rev-parse HEAD: ambiguous argument 'HEAD': unknown revision confirming the worktree-without-remote shape that broke the direct probe and forced the nested-find fallback. Tests __tests__/hooks/mutation/use-update-conversation-repository.test.tsx - optimistically updates the cached conversation under the prefix-extended key used by useUserConversation - rolls back the prefix-keyed cache entry when the mutation rejects __tests__/api/cloud-conversation-service.test.ts (new) - overlays locally-stored repo selection onto batchGetCloudConversations results when the server returns nulls - prefers the cloud server values over locally-stored selections when present - leaves null entries untouched when the cloud server returns null for a missing conversation - returns an empty array without calling the proxy when no ids are provided - overlays repo selection on each item returned from searchCloudConversations Wider sweep: npx vitest run __tests__/hooks/mutation \\ __tests__/api/cloud-conversation-service.test.ts \\ __tests__/api/conversation-metadata-store.test.ts \\ __tests__/api/agent-server-adapter.test.ts \\ __tests__/components/features/chat -> 22 files, 152 tests passed. User-visible behavior after this change: 1. Clicking Launch in the Connect Repo modal flips the bar to repo + branch chips immediately (optimistic update now reaches the active query). 2. The bar stays flipped through the next refetch on cloud backends (overlay keeps the local selection visible until the SaaS catches up). 3. Bar picks up nested clones within ~1s on local backends (local-git-info invalidation forces a re-probe instead of waiting on the 10s poll), and the nested-find fallback handles 'clone into <workingDir>/<repo>/' flows. 4. Pull/push/PR buttons now light up for local-workspace conversations whose remote was inferred from git remote. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(conversation): make right-panel drawer state session-only The right-side drawer's open/closed state (`isRightPanelShown` / `hasRightPanelToggled`) was persisted in localStorage, which made the panel feel sticky in a way users didn't expect — it would still be open after reloads or revisits even though they wanted a clean, focused chat view. Move drawer state fully into the in-memory Zustand store so it: - always starts closed on app load (or on opening a conversation after a restart), - survives in-app navigation because Zustand stays alive across React Router transitions, - only persists tab selection (`selectedTab`), which is the part users do want to come back to. The legacy `rightPanelShown` field is silently stripped from older persisted blobs by `sanitizeStoredState`, so old localStorage data doesn't churn or leak into the new schema. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(ui): standardize three-dots ellipsis trigger across the app Different surfaces had drifted to slightly different "more options" buttons: - conversation header used a 24x24 icon with a hardcoded fill color, - conversation cards in the side panel used a separate square `ellipsis.svg` glyph, - the conversation tab bar used a 20x20 icon with bespoke colors, - LLM profile rows wrapped the icon in a bordered button with yet another color. Promote `EllipsisButton` to be the canonical trigger and route every inline variant through it so size (w-4 h-4 / 16x16), color (`text-[#9299AA]`), and hover treatment (`hover:text-white hover:bg-white/10`) stay consistent everywhere. Layout-only overrides (e.g. translate, opacity-when-paused) flow through `className`, and a `testId` escape hatch keeps the existing `profile-menu-trigger` selector working. The chat-input overflow button intentionally keeps its pill-shaped custom variant; a doc comment on `EllipsisButton` calls that out so future contributors don't replace it. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(design): add 15-stop cool grey palette and complete migration plan Documents migration of ~98 scattered grey values across agent-canvas to a unified 15-stop cool blue-grey family (hue ≈ 220–224°), with all existing hex values, Tailwind utilities, CSS variables, and alpha variants mapped to the nearest new token by RGB + lightness proximity. Artifacts: - cool-grey-palette.svg: visual palette strip + per-shade migration map - cool-grey-migration.md: CSS/Tailwind definitions, per-file migration tables, alpha variant equivalents, and a 6-phase implementation plan Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): remove circular pill and ripple effect from autocomplete caret buttons Replace the default HeroUI selector button styling (rounded-full, fixed dimensions, hover fill) with a flat transparent icon and disable the press ripple via selectorButtonProps={{ disableRipple: true }} on all Autocomplete instances. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(design): enforce single border color token across all UI elements - Unify --oh-border-input to cool-grey-700 (same as --oh-border), eliminating the 3-way border split across inputs, cards, and dividers - Replace border-neutral-600 in .button-base with border-[var(--oh-border)] - Replace all border-tertiary usages (27 files) with --oh-border for outer borders and --oh-border-subtle for within-panel dividers - Fix border-tertiary-light on toggle switch OFF state → --oh-border - Fix border-t-tertiary on app-settings Git section divider → --oh-border-subtle - Fix divide-tertiary in profiles-body → divide-[var(--oh-border-subtle)] - Fix secrets table row dividers: --oh-border-subtle → --oh-border - Fix files-tab toolbar and file-quick-row header lines → --oh-border - Fix repo-connector and new-conversation card borders → --oh-border - Remove bg-surface from automations-list and automation-detail routes so they inherit bg-base from the root layout, matching all other pages Co-authored-by: Cursor <cursoragent@cursor.com> * fix(design): migrate automations to shared tokens; fix secondary button, card, and hover strokes - Replace all legacy tailwind.config.js color tokens in automations/ (27 files): bg-surface-card → bg-[var(--oh-surface)], bg-surface-elevated → bg-surface-raised, border-border → border-[var(--oh-border)], text-content-muted → text-muted, status/toggle/badge tokens → --oh-success/--oh-danger/--oh-muted variants - Fix active-status-badge and status-badge inactive fills from bg-border → bg-surface-raised - BrandButton secondary variant: yellow outline+text → border-[var(--oh-border)] text-white hover:bg-surface-raised; move hover:opacity-80 off base onto primary/tertiary only - BrandButton primary: replace text-base (font-size conflict) with text-[var(--oh-color-base)] so all variants share the base text-sm font size - Card primitive default/outlined themes: --oh-border-input → --oh-border (fixes visible mismatch between repo-connector and Start from Scratch cards on home screen) - marketplace-card, skill-card, skills-toolbar: replace hover:border-white/40 with hover:border-[var(--cool-grey-500)] and focus:ring-primary/60 with focus:ring-[var(--oh-border)] - automations routes: remove explicit bg-surface so pages inherit bg-base from root layout Co-authored-by: Cursor <cursoragent@cursor.com> * fix(design): normalize spinners, borders, and accent colors to design tokens Replace hardcoded `border-primary`, `border-blue-500`, and `text-primary` with cool-grey-aligned tokens (`border-white`, `border-white/20`, `var(--oh-border)`, `var(--oh-muted)`) across loading spinners, modals, dropdowns, and link styles. Switch dropdown selected-item highlight from `--oh-interactive-active` to `--oh-interactive-selected`. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(theme): add runtime color theme switcher with OpenHands-Neutral palette - Add src/themes/color-themes.ts: two themes (OpenHands-DeepSea / OpenHands-Neutral) with --cool-grey-* scale overrides and matching --heroui-* HSL channel overrides (default, content, background, foreground families) so HeroUI components and portalled popovers both respond to theme changes. - Inject overrides via a <style> tag on [data-agent-server-ui] AND [data-theme=dark] so portal content rendered to document.body picks up the new palette alongside inline components. - Add ThemeInput (SettingsDropdownInput) to Application Settings; applies the theme immediately on selection and persists to localStorage under openhands-color-theme. - Add ColorThemeApplier to root.tsx so the persisted theme is re-applied on every page load with no flash. Additional token fixes found during theme testing: - Define --color-tertiary-alt → --oh-text-dim in tailwind.css so the ~25 placeholder:text-tertiary-alt / text-tertiary-alt usages (API key input, helper text, badges) resolve correctly. - Fix environment-switch-overlay: replace bg-card / border-border / text-foreground with --oh-surface / --oh-border / --oh-foreground. - Fix AutocompleteSection headings in model-selector: add classNames={{ heading: "text-[var(--oh-muted)]" }} so Verified / Other Models labels are readable against the dropdown background. - Unify structural panel dividers: sidebar right edge + footer separator + files-tab tree divider all changed from --oh-border-subtle to --oh-border, matching the right panel. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ui): improve suggestion card hover to match secondary button style Co-authored-by: Cursor <cursoragent@cursor.com> * fix(theme): set OpenHands-Neutral as the default color theme Co-authored-by: Cursor <cursoragent@cursor.com> * fix: failing tests --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
c371afec55 |
feat: LLM profiles route integration (PR C) (#393)
* feat: integrate LLM profiles into settings route (PR C) - Add LlmSettingsLocalView component for integrated profile management - Extend SdkSectionSaveControl to expose form values for custom save flows - Update LLM settings route to render profile list with create/edit views - Add i18n keys for profile create/edit UI (CREATE_PROFILE, EDIT_PROFILE, PROFILE_CREATED, PROFILE_UPDATED, MODEL_REQUIRED, STATUS, BUTTON) - Add test coverage for LlmSettingsLocalView The integrated view shows: - Profile list with active badge and action menu - Add Profile button that opens create form - Edit button that loads profile config and opens edit form - Back/Cancel buttons to return to list view Co-authored-by: openhands <openhands@all-hands.dev> * chore: address PR review feedback (#393) - Improve mock typing with properly typed helper functions that provide all required React Query fields, eliminating incomplete 'as unknown as' casts - Add integration test that verifies the save flow (fills in profile name, clicks save, verifies UI state transitions) - Add component documentation noting future refactoring opportunity (extract useProfileForm, useProfileSave hooks for better testability) - Document API key preservation behavior: currently preserves existing encrypted key in edit mode with no new key; note about potential 'Clear API Key' UX enhancement for future - Document auto-derive name race condition: client-side uniqueness check uses render-time state, so concurrent profile creation by another client would result in server conflict error (handled gracefully) - Document default export change in route file: LlmSettingsLocalView is now the default export; named export LlmSettingsScreen remains for embedded use Co-authored-by: openhands <openhands@all-hands.dev> * fix: update llm-settings test to use named export The default export of llm-settings.tsx changed to render LlmSettingsLocalView (the profiles manager). The test needs to import the named export LlmSettingsScreen to test the form component directly. Co-authored-by: openhands <openhands@all-hands.dev> * Fix LLM profile button/badge sizing and update typescript-client to v0.6.0 - BrandButton: Change padding from p-2 to px-3 py-2 for better text display - ProfileRow: Increase active badge vertical padding from py-0.5 to py-1 - Update @openhands/typescript-client from commit SHA to v0.6.0 tag Co-authored-by: openhands <openhands@all-hands.dev> * Fix LLM settings to show regular form in cloud mode and empty form in create mode - LlmSettingsRoute: Render LlmSettingsScreen (standard form) for cloud backends and LlmSettingsLocalView (profile manager) for local backends only - LlmSettingsLocalView: Pass empty initial values in create mode to ensure fresh form fields, add key prop to force form remount between profiles - Add unit tests for cloud vs local backend rendering - Add unit tests for create mode empty form initialization Co-authored-by: openhands <openhands@all-hands.dev> * Fix edit mode form initialization to display profile values - Fix initialValueOverrides logic to properly check for edit mode AND existing initialValues before using them - Add prefix to edit mode key for clearer remount semantics - Add unit tests verifying edit mode populates profile name correctly - Add unit tests verifying getProfile is called with encrypted mode Co-authored-by: openhands <openhands@all-hands.dev> * Add debug logging to trace edit profile data flow Co-authored-by: openhands <openhands@all-hands.dev> * Fix edit profile config parsing - read from config directly not config.llm The API returns profile config with llm settings at the top level (config.model, config.api_key, config.base_url), not nested under config.llm. Fixed the parsing to read directly from detail.config. Co-authored-by: openhands <openhands@all-hands.dev> * Handle profile rename during edit and update active profile When editing a profile and changing its name: 1. Rename the profile first using ProfilesService.renameProfile 2. Then save the profile config to the new name 3. If the renamed profile was the active profile, re-activate it after the rename (since rename doesn't update active_profile) This prevents creating duplicate profiles when just changing the name. Co-authored-by: openhands <openhands@all-hands.dev> * Fix package-lock.json to use https protocol for typescript-client The lock file was using git+ssh:// protocol which causes Vercel build failures since Vercel doesn't have SSH keys configured. Changed to git+https:// and removed the integrity hash (git deps don't have one). Co-authored-by: openhands <openhands@all-hands.dev> * chore(profiles): UI polish, shared validation, onboarding integration (#417) * fix(profiles): Available Profiles heading translation * fix(profiles): use brand badge for active profile indicator * feat(profiles): replace form heading with "Back to LLM profiles list" * fix(profiles): unify profile-name validation and reject any whitespace * fix(onboarding): persist onboarding LLM choice as an active profile * refactor(profiles): drop redundant trim/wrapper after validator change * Fix LLM profile route mocks and warnings * chore: update baseline snapshots [skip ci] --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com> Co-authored-by: Graham Neubig <neubig@gmail.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
8fc9a8b50c |
feat: LLM profiles UI components, modals, and tests (PR B) (#389)
* feat: add LLM profiles API layer and React Query hooks This PR adds the foundational data layer for the LLM profiles feature: ## API Layer - ProfilesService: Thin wrapper around SDK ProfilesClient with methods for list, get, save, delete, rename, and activate profile operations - Re-exports SDK types for consumer convenience ## React Query Hooks - useLlmProfiles: Query hook for listing all profiles - useSaveLlmProfile: Mutation hook for creating/updating profiles - useDeleteLlmProfile: Mutation hook for deleting profiles - useRenameLlmProfile: Mutation hook for renaming profiles - useActivateLlmProfile: Mutation hook for activating a profile All mutation hooks properly invalidate both profile list and settings caches on success, and disable global toasts (consumers handle errors). ## Utilities - deriveProfileNameFromModel: Derives a clean profile name from model strings (e.g., 'openai/gpt-4' -> 'gpt-4') - PROFILE_NAME_PATTERN: Validation regex for profile names ## Tests - 47 tests covering all new functionality - API service method tests - Hook behavior tests (success, error handling, cache invalidation) - Utility function tests Part 1 of LLM Profiles feature (PR A from split plan). No UI changes - this is purely a data layer addition. Co-authored-by: openhands <openhands@all-hands.dev> * fix: address PR review feedback - Remove client.close() calls for consistency with other services (SettingsService, SecretsService don't call close()) - Use SETTINGS_QUERY_KEYS.personal() instead of .all for precision - Add ActiveBackendProvider wrapper in useLlmProfiles tests - Add test for query key including backend.id and orgId - Add test for backend-switch cache isolation - Fix truncation test to actually exercise trailing-dash removal - Add test for model names that sanitize to empty string Addresses review feedback from all-hands-bot. Co-authored-by: openhands <openhands@all-hands.dev> * chore: Remove PR-only artifacts * feat: add LLM profiles UI components, modals, and tests This is part 2 (PR B) of the LLM profiles feature split: Components added: - LlmProfilesManager: Main container managing list/edit views - LlmProfilesListView: List view with profile rows - ProfileRow/ProfileListRow: Display individual profiles - ProfileActionsMenu/ProfileListActionsMenu: Dropdown menus - ProfileNameInput: Editable name field with validation - RenameProfileModal: Modal for renaming profiles - DeleteProfileModal: Confirmation modal for deletion - ProfilesBody: Container for profile list content Supporting components: - api-key-modal-base.tsx: Base modal for API key inputs - brand-button.tsx: Styled button component - settings-input.tsx: Form input component Tests: 77 component tests covering all new components Builds on PR A (API layer + hooks) from feat/llm-profiles-api-hooks Co-authored-by: openhands <openhands@all-hands.dev> * docs: add component screenshots for PR B Screenshots showing: - Profile name input and profile rows (active/inactive) - Profiles body list and LlmProfilesListView - Profile actions dropdown menu (Edit/Rename/Delete) Co-authored-by: openhands <openhands@all-hands.dev> * refactor: update LLM profiles components to match reference implementation - Update ProfileRow to accept isActive, onActivate, isActivating props - Update ProfileActionsMenu with new interface including Set Active option - Update ProfilesBody to pass active state and callbacks to ProfileRow - Update LlmProfilesManager with activation functionality - Add SETTINGS$PROFILE_SET_ACTIVE i18n key - Remove duplicate components (ProfileListRow, ProfileListActionsMenu, LlmProfilesListView) - Update all related tests to match new component interfaces - Active badge now uses primary color (gold) with dark text Co-authored-by: openhands <openhands@all-hands.dev> * style: update LLM profiles UI to match reference implementation - Active badge: use green success color (bg-success) instead of gold primary - Add LLM Profile button: use secondary variant (border style) instead of primary - Cancel buttons in modals: add tertiary variant to BrandButton (solid gray bg) - Update delete and rename modals to use tertiary variant for Cancel buttons Co-authored-by: openhands <openhands@all-hands.dev> * fix: address PR review feedback for LLM profiles - Add aria-labelledby to ApiKeyModalBase for dialog accessibility - Extract MenuItem component in ProfileActionsMenu to reduce duplication - Add accessible loading state with sr-only text in DeleteProfileModal - Trim whitespace on input change in RenameProfileModal - Add ariaLabel and aria-busy props to BrandButton - Add console.error logging for activation failures - Add keyboard navigation tests for ProfileActionsMenu - Add isPending state tests for DeleteProfileModal - Add boundary condition tests for ProfileNameInput Co-authored-by: openhands <openhands@all-hands.dev> * chore: Remove PR-only artifacts * docs: add component screenshots for PR #389 Screenshots showing: - Profile list with Active badge (green) - Profile actions menu (Edit, Rename, Set Active, Delete) - Rename profile modal with validation - Delete profile confirmation modal Co-authored-by: openhands <openhands@all-hands.dev> * chore: Remove PR-only artifacts * docs: re-add component screenshots for PR #389 Screenshots showing: - Profile list with Active badge (green) - Profile actions menu (Edit, Rename, Set Active, Delete) - Rename profile modal with validation - Delete profile confirmation modal Co-authored-by: openhands <openhands@all-hands.dev> * chore: remove PR screenshots per user request Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com> |
||
|
|
1c239d73a9 |
Port OpenHands frontend to direct agent_server integration
Co-authored-by: openhands <openhands@all-hands.dev> |