mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 16:08:23 +08:00
43e6db69191afe6ff8d08ec107e4d6428857978d
7377
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
43e6db6919 |
docs: add API access rules to AGENTS.md and code review skill (#506)
Document the two mandatory API access conventions that are enforced by the CI test src/api/no-direct-agent-server-calls.test.ts: 1. All agent-server calls must use typed @openhands/typescript-client classes (ConversationClient, FileClient, VSCodeClient, ServerClient, RemoteWorkspace, RemoteEventsList) instantiated via getAgentServerClientOptions() -- never raw axios/fetch. 2. All cloud SaaS and runtime-sandbox calls must go through callCloudProxy() in src/api/cloud/proxy.ts to avoid CORS, using hostOverride for runtime-sandbox URLs and authMode='session-api-key' for those endpoints. AGENTS.md gets a full '## API Access Rules' section with client listings, option helper references, CORRECT/WRONG code examples, and the allowed-exceptions list. The custom-codereview-guide.md skill gets a '## Frontend API Access Conventions' section with DO NOT APPROVE triggers, forbidden pattern lists, correct examples, and a note about the silent hostOverride bug. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
acb04fd9ba |
perf(snapshots): skip retries on comparison pass, suppress consent modal (#505)
- Add --retries=0 to test:e2e:snapshots: snapshot pixel-diff failures are deterministic — retrying cannot fix them. On a PR that changes 36/60 snapshots this tripled execution count and added ~2 min to the comparison pass. - Extract seedLocalStorage() helper (tests/e2e/snapshots/support/) that seeds openhands-onboarded and openhands-telemetry-consent in a single addInitScript call. All 13 snapshot specs now use it instead of duplicated inline addInitScript blocks. - Pre-seeding openhands-telemetry-consent='denied' eliminates the race condition where changes-tab timed out at 60 s (x3 retries = 3 min) because dismissConsentModal fired before the modal rendered with domcontentloaded. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
14d2e9454b |
test(snapshot): changes tab diff viewer + backend management UI (6 tests) (#450)
* test(snapshot): changes tab diff viewer + backend management UI (6 tests) Pre-seed MOCK_GIT_CHANGES with M/A/D entries (using AgentServerGitChangeStatus values: UPDATED/ADDED/DELETED) so changes-tab tests can exercise the file list, Monaco diff viewer, and deleted-file placeholder without per-test MSW manipulation. Expose window.__setMockGitChanges__ so the empty-state test can clear the list after boot and trigger a React Query refetch via __TEST_INVALIDATE_QUERIES__, avoiding a full page reload that would reinitialise module state. Backend management tests exercise the selector dropdown, add-backend modal, and manage-backends modal — all driven by localStorage seeding via addInitScript. Co-authored-by: openhands <openhands@all-hands.dev> * chore: update baseline snapshots [skip ci] * ci: trigger re-run against CI-generated baselines * fix(snapshot-tests): mask Monaco editor for stable CI screenshots; fix unit test - changes-tab spec: mask data-testid=editor-container so Monaco's sub-pixel font hinting (which varies per OS) doesn't cause false pixel-diff failures - mock-conversation-handlers test: update assertion to match the new pre-seeded MOCK_GIT_CHANGES (3 M/A/D entries) instead of the previous empty array Co-authored-by: openhands <openhands@all-hands.dev> * chore: update baseline snapshots [skip ci] * ci: trigger re-run against CI-regenerated baselines (Monaco mask + unit test fix) * fix(snapshot-tests): normalize RandomTip height via addStyleTag for stable empty-state screenshot RandomTip renders a randomly-chosen tip whose line-count varies, causing the flex-1 container above it to have different heights across runs. Fix by injecting a CSS rule via page.addStyleTag() that pins .text-m.bg-tertiary.p-4 to 80px (visibility:hidden so the variable text is invisible) — layout is now deterministic. Switch back to screenshotting the full files-tab panel since dimensions are stable. Co-authored-by: openhands <openhands@all-hands.dev> * chore: update baseline snapshots [skip ci] * ci: trigger re-run against baselines (empty-state RandomTip height fix) * fix(snapshot-tests): use inner content div for empty-state screenshot to avoid left-strip artefact Screenshot files-tab's last direct div child (the flex-1 content wrapper) instead of the outer main element. During CI baseline generation the outer main's bounding box occasionally captured a ~30px left-panel overlay artefact that made the baseline permanently diverge from subsequent verification runs. Targeting the inner wrapper excludes the outer-element overflow while still showing the full empty-state (icon + 'no changes yet' text + hidden tip area). Co-authored-by: openhands <openhands@all-hands.dev> * chore: update baseline snapshots [skip ci] * ci: validate against fresh inner-div empty-state baseline * test(snapshot): extended backend UI flows — 12 tests, 19 screenshots Add backends-extended.snapshot.spec.ts covering 8 behaviour flows with iterative screenshot captures at each state transition: Flow 1a Blank add form — Save disabled until name+host filled Flow 1b Local backend — Save enabled with name+host, no API key needed Flow 1c Cloud backend — Save disabled without API key, enabled with it Flow 2a Host auto-infers Local kind; OAuth section disappears Flow 2b Cloud-domain URL keeps Cloud kind; OAuth section shows Flow 2c Manual kind selection locks type (touchedKind=true) even when a cloud URL is later typed into the Host field Flow 3 OAuth Login button disabled while host is empty; enabled once filled Flow 4 Remove backend: shows ConfirmationModal → Cancel keeps row → Confirm removes it from the list (4 screenshots) Flow 5 Edit modal pre-populates name/host/key from stored backend Flow 6 Switch active backend: environment-switch overlay captured via page-level screenshot + animation override so the card is opaque at frame-0; after-switch state verified via selector label Flow 7 Whitespace-only host keeps Save disabled; syntactically invalid URL is accepted by the frontend (no URL-format validation) Flow 8 Cancel add form: dismisses modal, Manage Backends confirms no phantom entry was saved Notable decisions: - Uses body[data-environment-switching="true"] as the early DOM signal before React paints the portal div for the switch overlay - Adds inline style-tag override before the overlay screenshot because .environment-switch-overlay > div has opacity:0 at animation frame 0; Playwright's animations:"disabled" pauses there, making the card invisible without the override - Backends seeded via page.addInitScript localStorage injection so tests are fully self-contained with no MSW state dependency Co-authored-by: openhands <openhands@all-hands.dev> * chore: update baseline snapshots [skip ci] * ci: validate extended backend snapshot tests against CI baselines * ci: always post snapshot PR comment even when test generation step fails The 'Post snapshot report to PR' step was skipped whenever 'Generate current PR snapshots' exited non-zero (e.g. a test crash like a hidden element, not just a snapshot diff). GitHub Actions skips steps without an always() guard when a prior step fails. Add always() so the comment is posted regardless — showing diffs or the test failure output — which was the intended behaviour. Co-authored-by: openhands <openhands@all-hands.dev> * ci: fix snapshot comment - remove tracked screenshots, add crash reporting Three fixes: 1. Remove 28 git-tracked snapshot PNGs from this branch. These were committed by the old baseline-in-git workflow before #482 migrated to artifact storage. Because they stayed tracked (gitignore doesn't untrack already-indexed files), every CI checkout put them in tests/e2e/__snapshots__/ BEFORE the baseline artifact was downloaded. The Save step then copied them into /tmp/main-baselines, making the new tests appear as 'Unchanged' instead of 'New' in the PR comment. 2. Add 'Clear snapshot directory before downloading baselines' step. Wipes tests/e2e/__snapshots__/ before the artifact download so any future accidentally-tracked files can never contaminate the baseline. 3. Surface test crashes in the PR comment. - Generate step gets continue-on-error + an id so subsequent steps can read its outcome. - GENERATE_OUTCOME is passed to the comment script. - If outcome == 'failure', a GitHub-flavoured WARNING callout is prepended to the comment with a direct link to the CI run logs. - A dedicated 'Fail if snapshot generation had test crashes' step restores the job failure that continue-on-error absorbed. Co-authored-by: openhands <openhands@all-hands.dev> * ci: use PR number in snapshot concurrency group for cleaner cancellation The previous group used github.ref which resolves to refs/pull/{N}/merge for PR events — correct but opaque. Using github.event.pull_request.number makes the grouping explicit and human-readable (snapshot-tests-450), and falls back to github.ref for main pushes and workflow_dispatch. cancel-in-progress: true was already set, so new commits already cancelled prior runs. This just makes the intent clearer. Co-authored-by: openhands <openhands@all-hands.dev> * fix: syntax error in post-snapshot-comment.mjs (] vs ) in lines.push) lines.push(...) was accidentally closed with ]; instead of ); after splitting the original lines = [...] array literal into a push call. Caused a SyntaxError at startup, preventing any comment from being posted. Co-authored-by: openhands <openhands@all-hands.dev> * fix: snapshot test disabled states, changes-tab crash, and CI false-failures Three fixes: 1. BrandButton disabled visual styling (brand-button.tsx) disabled:opacity-30 pseudo-class was not applying in Vite dev mode (Tailwind v4 + postcss-prefix-selector interaction), making disabled and enabled buttons visually identical in snapshot screenshots. Fix: add isDisabled conditional class directly ('opacity-30 cursor-not-allowed pointer-events-none') so the disabled appearance is applied regardless of whether :disabled pseudo-class works. 2. changes-tab test crash (changes-tab.snapshot.spec.ts) Test waited for data-testid='files-tab' but the right panel always starts CLOSED (isRightPanelShown = false is session-only Zustand state; sanitizeStoredState strips any persisted rightPanelShown key). Fix: click data-testid='right-panel-toggle' after navigation to open the panel before waiting for files-tab. Also remove the no-op rightPanelShown: true from the localStorage seed. 3. CI false-failures for new snapshot tests (snapshot-tests.yml + post-snapshot-comment.mjs) The 'Fail if comparison found differences' step fired on 'missing baseline' failures (expected for new tests in a PR) as well as actual pixel-diff failures. Fix: - post-snapshot-comment.mjs outputs has_changes=true/false to GITHUB_OUTPUT (true only when changed.length > 0, i.e. real diffs) - 'Fail if' step now checks steps.post-comment.outputs.has_changes == 'true' instead of compare.outcome == 'failure', so PRs that only add new snapshot tests pass CI cleanly. Co-authored-by: openhands <openhands@all-hands.dev> * fix: reject invalid host URLs in backend form; use http for local addresses Two related fixes to backend host validation / normalisation: 1. isValidHostUrl() — reject invalid host strings canSubmit previously only checked host.trim().length > 0, so garbage like 'not://:::a valid url!!!' passed through and enabled the Save button. isValidHostUrl() adds two checks before the URL constructor: (a) the trimmed value must be non-empty, (b) it must contain no whitespace. This catches the test-case input whose spaces are the tell-tale sign of a malformed value. 2. normalizeHost() — http:// for local addresses Bare hostnames (no explicit scheme) were unconditionally prepended with https://, but local servers almost never have TLS certificates. The new isLocalAddress() helper detects localhost, 127.x, RFC-1918 private ranges (10.x, 192.168.x, 172.16-31.x), .local / mDNS names, and single-label hostnames — all get http:// instead of https://. Hostnames with dots that are not in those ranges (e.g. app.all-hands.dev) still default to https://. Explicit http:// or https:// prefixes are always preserved as-is. Test update: the 'backend-add-invalid-url-accepted' snapshot is renamed to 'backend-add-invalid-url-disabled' and the assertion flips from not.toBeDisabled() → toBeDisabled(), reflecting the new behaviour. Co-authored-by: openhands <openhands@all-hands.dev> * feat: inline error feedback on Name and Host fields in BackendForm Three parts: 1. SettingsInput gains error / showRequiredTag / onBlur props - error?: string — red border on the input plus a small red alert paragraph below it (role=alert, data-testid=${testId}-error, linked via aria-describedby). - showRequiredTag?: boolean — renders a red * after the label to signal that the field is mandatory, consistent with OptionalTag. - onBlur?: () => void — forwarded directly to the <input>. - aria-invalid is set automatically when error is truthy. 2. BackendForm wires touched state → errors → inputs - nameTouched / hostTouched (both false on open, set on blur) - nameError: 'Name is required' when touched + empty - hostError: 'Host is required' when touched + blank/whitespace; 'Enter a valid URL (e.g. http://localhost:8080)' when touched + non-empty but fails isValidHostUrl() - Both name and host SettingsInputs get showRequiredTag, the computed error, and onBlur={() => setXTouched(true)}. Errors are intentionally suppressed until blur so the form does not scold the user before they have had a chance to type anything. 3. Three snapshot tests call .blur() after .fill() to reveal errors - backend-add-name-only-disabled: focus+blur empty host → 'Host is required' appears below the Host field. - backend-add-whitespace-host-disabled: blur after fill(' ') → same 'Host is required' (whitespace counts as empty). - backend-add-invalid-url-disabled: blur after invalid URL fill → 'Enter a valid URL...' appears below the Host field. The backend-add-blank-disabled snapshot is unchanged (neither field touched, no errors yet — correct for the fresh-open state). New i18n keys: BACKEND$NAME_REQUIRED, BACKEND$HOST_REQUIRED, BACKEND$HOST_INVALID (English only; other locales fall back to en). Co-authored-by: openhands <openhands@all-hands.dev> * fix: prettier formatting on nameError / hostError ternaries Co-authored-by: openhands <openhands@all-hands.dev> * fix: disable OAuth Login button until name and host are both valid Previously the 'Login with OpenHands' button was enabled as soon as a non-empty host was typed, even when the Name field was still blank. This let users go through the full OAuth device-flow only to find they still couldn't save because the name was missing. Gate isDisabled on !name.trim() || !isValidHostUrl(host) so the button stays disabled until the form is actually ready to save (modulo the API key that OAuth itself will provide). Update Flow 3 snapshot test to fill the name before asserting the button becomes enabled, and update the test description accordingly. Co-authored-by: openhands <openhands@all-hands.dev> * chore: address PR review feedback (#450) IPv6 parsing fixes (normalizeHost / isLocalAddress): - normalizeHost: handle bracket notation [::1]:8080 (extract ::1), bare IPv6 addresses with multiple colons (use whole string as hostname), and regular host:port as before — prevents split(':')[0] from grabbing only the first segment of a multi-colon IPv6 address - isLocalAddress: strip brackets before comparison; add :: (any-addr), ::ffff:127.x.x.x (IPv4-mapped loopback), fe80::/10 (link-local), fc00::/7 (unique local); tighten single-label check to exclude addresses that contain colons (bare IPv6 non-local addresses) Mark fields touched on submit attempt: - handleSubmit sets nameTouched + hostTouched when !canSubmit so inline errors appear for keyboard users who press Enter on an incomplete form Snapshot workflow comparison-crash detection: - Pass COMPARE_OUTCOME=${{ steps.compare.outcome }} to post-comment - post-snapshot-comment.mjs reads COMPARE_OUTCOME and prepends a '[!WARNING]' block when the comparison step itself crashed (timeout/OOM) so the comment accurately reflects the run state instead of silently showing an incomplete/empty diff table Remove unnecessary serial mode from backends-extended snapshot suite: - Each test calls setupPage() with fresh state on its own Playwright page; no shared mutable state exists between tests, so serial is unnecessary and slows the suite Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
b2b71855c6 |
feat(dev): surface dev-stack runtime services in agent system prompt (#503)
* feat(dev): surface dev-stack runtime services in agent system prompt Add a structured 'runtime services' info object that the dev launchers (`dev:safe`, `dev:automation`, `dev:docker`, and the published `agent-canvas` binary) propagate to the frontend via `VITE_RUNTIME_SERVICES_INFO`. The frontend renders it into a `<RUNTIME_SERVICES>` markdown block and attaches it as `AgentContext.system_message_suffix` on every `POST /api/conversations`. This means agents start each conversation knowing exactly what services exist in the current dev stack (ingress URL, automation backend URL + `/api/automation` prefix, auth header, etc.), instead of having to probe or — worse — assume `localhost:8000` is the automation server when it is actually the Agent Server they are running inside of. URLs are written from the agent's point of view: dockerless modes use `localhost`, `dev:docker` uses `host.docker.internal`. When automation isn't running in the current mode (e.g. `dev:safe`), the block says so explicitly so agents know to skip `/api/automation` calls. Co-authored-by: openhands <openhands@all-hands.dev> * fix(runtime-services): address review feedback on PR #503 - Validate required `agentServerPort` in `buildRuntimeServicesInfo`; previously a missing port baked `http://localhost:undefined` into the agent's system prompt. - Skip the automation entry when the supplied `automation` object has no `port` (e.g. a bare `{}` from a misconfigured launcher). - Rename the JSON service key from `vite` to `frontend` and add a `kind: "vite" | "static"` discriminator + mode-aware description, so static-build dev stacks (`dev:docker`, the published binary, ...) no longer surface a misleading "Vite dev server" line in the agent system prompt. The renderer still accepts the legacy `vite` key. - Anchor the "don't guess" warning to the actual agent-server URL from runtime info instead of hardcoded `localhost:8000`, since the agent-server uses different ports across dev modes (18000 in dev:safe, 8000 in dev:docker, ...). - Plumb `frontendKind` through `buildAutomationRuntimeServicesInfo` and stamp `config.frontendKind` in `dev-with-automation.mjs::main` so both Vite spawn and static-build paths describe the frontend correctly. - Expand AGENTS.md with the JSON schema of `VITE_RUNTIME_SERVICES_INFO` and a concrete example of the rendered `<RUNTIME_SERVICES>` block. - Tests: assert the new URL-in-warning behavior, the new `frontend` / legacy `vite` rendering, the `agentServerPort`-required guard, the `automation: {}` skip, and the legacy `vitePort` alias. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
8db469487b |
docs(readme): mention OpenAPI docs alongside UI URL (#502)
Both the agent server's FastAPI docs and the automation backend's docs are reachable through the local ingress proxy on port 8000, so point users at them right next to the existing UI URL in both the Docker and non-Docker quickstart sections. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
4db59b8b94 |
Proxy agent-server FastAPI docs (/docs, /redoc, /openapi.json) through the ingress (#501)
* feat(ingress): route /docs to the agent server Add /docs to the list of prefixes proxied to the agent-server in: - vite.config.ts (Vite dev server proxy) - scripts/dev-with-automation.mjs (ingress + static-server fallback) - scripts/dev-static.mjs (ingress + static-server fallback) Update the explanatory comment in scripts/static-server.mjs to match. This exposes the agent-server's FastAPI Swagger UI at `/docs` on the ingress port, alongside the automation backend's existing `/api/automation/docs`. Co-authored-by: openhands <openhands@all-hands.dev> * feat(ingress): also route /redoc and /openapi.json to the agent server Without /openapi.json, the Swagger UI page served at /docs (added in the previous commit) renders but fails to load any spec. /redoc is the FastAPI-served ReDoc alternative and benefits from the same fix. Routes are added everywhere /docs already is: - vite.config.ts (Vite dev server proxy) - scripts/dev-with-automation.mjs (ingress + static-server fallback) - scripts/dev-static.mjs (ingress + static-server fallback) Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
9203a72d64 |
feat(snapshot-ci): group PR comment snapshots by spec file (#499)
Previously every changed/new snapshot got its own ### heading and table, making it hard to see which snapshots belong to the same feature or flow (e.g. the four-step MCP Slack install flow, the five-step secrets lifecycle, or the skills search/filter sequence). Group all three sections (🔴 Changed, 🆕 New, ✅ Unchanged) by spec file: - Replaced formatRelPath() with specFromRelPath() + groupBySpec() helpers. - Changed / New: one ### heading per spec (with count when > 1 snapshot), then **bold name** + the 3-column expected|actual|diff table per snapshot. - Unchanged: compact grouped bullet list — bold spec heading, then one bullet per snapshot name, no superfluous intro sentence. No workflow changes required; the grouping is purely in the comment script. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
63704f65e5 |
test: rename sidebar nav label "New" → "Chats" to verify snapshot CI diff comment (#497)
* test: rename sidebar nav label New → Chats to trigger snapshot diff Intentional one-line change to verify that the snapshot CI workflow correctly posts a PR comment showing the expected/actual/diff images. Co-authored-by: openhands <openhands@all-hands.dev> * fix(snapshot-ci): save comparison test-results before update step clears them Root cause: Playwright wipes its output directory (test-results/) at the start of each new run. The workflow runs the tests twice: 1. npm run test:e2e:snapshots → comparison, writes *-diff.png files 2. npm run test:e2e:snapshots:update → regenerates baselines, clears test-results/ first, no diffs written By the time post-snapshot-comment.mjs runs, all diff files are gone. diffBySnapshotName is always empty, so every snapshot is classified as "unchanged" even when Playwright reported 16 failures. Fix: - Add a "Save comparison test-results" step immediately after the comparison run that copies test-results/ to /tmp/comparison-results before the update pass can delete them. - Pass COMPARISON_RESULTS_DIR=/tmp/comparison-results to the comment script. - In post-snapshot-comment.mjs, read TEST_RESULTS_DIR from COMPARISON_RESULTS_DIR env var (falls back to "test-results" for local use). Co-authored-by: openhands <openhands@all-hands.dev> * docs: document snapshot CI comparison-results ordering in AGENTS.md Co-authored-by: openhands <openhands@all-hands.dev> * revert: restore sidebar nav label to New Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
c3de18580d |
ci: increase Playwright CI workers from 1 to 2 (#495)
ubuntu-24.04 runners have 2 vCPUs. Each Playwright worker runs in its own browser context so tests are fully isolated (MSW state, localStorage, and React Query cache are all page-level). Doubling from 1→2 workers should roughly halve wall-clock test time on CI with no risk of interference. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
d886a68a43 |
fix: prevent snapshot CI runs from sending PostHog analytics events (#490)
* fix: separate PostHog keys for production and dev builds
The telemetry service (canvas_install events) was using a single
hardcoded PostHog key as a fallback in every build, so CI snapshot
tests sent events to the same project as real users, inflating the
unique-persons count and making install metrics unreliable.
Changes:
src/services/telemetry.ts
- POSTHOG_API_KEY is now string | null keyed off import.meta.env.PROD:
- Production: VITE_POSTHOG_API_KEY || 'phc_REPLACE_WITH_PRODUCTION_KEY'
(placeholder — swap in the real key before deploying)
- Dev/test: VITE_POSTHOG_API_KEY || null
PostHog never initialises when the key is null, so no events reach
any PostHog project from dev servers or CI snapshot runs.
- initializePostHog() now returns null immediately when POSTHOG_API_KEY
is null, before loading the posthog-js module at all.
src/mocks/analytics-handlers.ts
- Added https://z.openhands.dev/* intercept alongside the existing
https://us.i.posthog.com/e handler. The library telemetry service
routes through z.openhands.dev (the OpenHands reverse proxy), which
MSW previously never saw.
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: hardcode PostHog keys per deployment environment
Select the PostHog key based on hostname rather than a single constant or
env var override:
- app.all-hands.dev → POSTHOG_PROD_KEY
- staging.app.all-hands.dev → POSTHOG_STAGING_KEY (shares prod key for
now; swap to a dedicated project key when one is provisioned)
- all other origins → null (no tracking)
Both the app-level PostHog (option-service / posthog-wrapper) and the
library telemetry service (telemetry.ts) follow the same pattern.
VITE_POSTHOG_API_KEY still works as an escape hatch for library consumers.
Drop VITE_DO_NOT_TRACK=1 from dev:mock: hostname detection already
returns a null key for localhost, so PostHog never initialises there.
Removing the flag also restores the TelemetryConsentBanner in snapshot
tests (the banner can be snapshotted correctly again).
Also adds PRODUCT_URL.STAGING to constants and a null guard in
initializePostHog for the key-null case.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: use prod PostHog project for now, drop placeholder staging URL
Remove the speculative staging.app.all-hands.dev hostname that doesn't
exist yet. Both POSTHOG_STAGING_KEY constants now alias POSTHOG_PROD_KEY
so staging events flow to the same project once the staging hostname is
wired up. The TODO comments mark exactly where to add the real staging
hostname and swap in a dedicated key.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: scope telemetry key to prod builds only, revert app-level PostHog changes
telemetry.ts: switch from hostname detection to import.meta.env.PROD as
the gate — this tracks local installs everywhere they are deployed, not
just on a specific hostname. Dev/test builds get null so no events reach
PostHog. Both POSTHOG_PROD_KEY and POSTHOG_STAGING_KEY are named
constants pointing at the same project for now; swap POSTHOG_STAGING_KEY
once a dedicated staging project exists.
option-service.api.ts: revert to posthog_client_key null. The app-level
PostHog wrapper belongs to the SaaS analytics path and is not relevant
for local install tracking.
constants.ts: drop the speculative STAGING URL addition.
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: use VITE_APP_ENV to select staging vs prod PostHog key at bundle time
Both POSTHOG_PROD_KEY and POSTHOG_STAGING_KEY are now referenced in the
assignment. VITE_APP_ENV is a build-time constant: Vite replaces it with
a literal in the static bundle so the correct key is compiled in with no
runtime branching.
VITE_APP_ENV=staging npm run build → POSTHOG_STAGING_KEY
(any other prod build) → POSTHOG_PROD_KEY
dev build (PROD=false) → null
Set VITE_APP_ENV=staging in the staging deployment build config (Vercel
env vars, CI, etc.) to activate the staging key once it is provisioned.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: gate PostHog key on VITE_APP_ENV, not PROD
import.meta.env.PROD is true for any vite build output including
npm run dev (which runs a production static build via dev-docker.mjs),
so local developers would inadvertently compile in POSTHOG_PROD_KEY.
Switch to requiring VITE_APP_ENV to be explicitly set at bundle time:
VITE_APP_ENV=production → POSTHOG_PROD_KEY
VITE_APP_ENV=staging → POSTHOG_STAGING_KEY
unset (local dev, CI) → null, PostHog never initialises
Document VITE_APP_ENV in .env.sample so it is visible to developers.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: bake VITE_APP_ENV=production into build:lib for npm releases
Without this, the published library bundle has POSTHOG_API_KEY=null
and library telemetry never fires for any consumer of the package.
A released npm package is a production artifact, so it should always
get POSTHOG_PROD_KEY compiled in.
Co-authored-by: openhands <openhands@all-hands.dev>
* refactor: simplify PostHog key to staging default, prod only when explicit
Drop the null/three-way branch. The key is now always a string:
- VITE_APP_ENV=production (hardcoded in build:lib and production CI) → POSTHOG_PROD_KEY
- everything else (local dev, CI, staging builds) → POSTHOG_STAGING_KEY
Since the key is never null, the null guard in initializePostHog is also removed.
Co-authored-by: openhands <openhands@all-hands.dev>
* Apply suggestions from code review
Co-authored-by: Rohit Malhotra <rohitvinodmalhotra@gmail.com>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
|
||
|
|
a998f88b72 |
fix(skills): populate slash menu and skills modal (#362)
* fix(skills): populate slash menu and skills modal on cloud backends * fix(skills): always load public skills * fix(deps): use HTTPS instead of SSH for typescript-client git dep The package-lock.json was resolving @openhands/typescript-client as git+ssh://... which requires SSH authentication. Vercel deployments don't have SSH access configured for GitHub, causing deployment failures. Changed to git+https://... which allows Vercel to fetch the dependency using standard HTTPS authentication. Co-authored-by: openhands <openhands@all-hands.dev> * chore: retrigger Vercel deployment --------- Co-authored-by: hieptl <hieptl.developer@gmail.com> Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
2d5a52d4be |
chore: bump agent-server default to 1.22.1 (#477)
* chore: bump agent-server default to 1.22.1 * chore: update OH_AUTOMATION_VERSION * chore: update DEFAULT_AUTOMATION_SDK_VERSION |
||
|
|
77b7012993 |
ci: add resolution guidance to failing snapshot PR comment (#489)
* ci: add resolution guidance to failing snapshot PR comment When snapshots differ from the main baseline, the comment now includes a short blockquote explaining both resolution paths: - merge the latest main (in case upstream baselines have moved) - add the update-snapshots label to acknowledge intentional changes * ci: wait for main baseline workflow before downloading artifact Before downloading the snapshot-baselines artifact on PR runs, resolve main's current HEAD SHA and check if the snapshot-tests.yml run for that exact commit is still in-progress or queued. If so, poll every 10 s (up to 10 min) until it completes, then proceed. This eliminates the race condition where a PR job starts while main's baseline upload is still in-flight, causing it to pull the previous (stale) artifact and produce false snapshot failures. The wait targets only the run for the current HEAD SHA — not an older in-progress run from a different commit — so two rapid commits to main can't trick the check into waiting for the wrong run. Also bumps job timeout-minutes from 20 → 30 to accommodate the wait. --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
38121aaf2f |
fix: normalize path separators in no-direct-agent-server-calls test for Windows (#488)
On Windows, path.relative() returns backslash-separated paths, but ALLOWED_AD_HOC_HTTP_FILES uses forward slashes. The Set.has() check therefore never matches on Windows, causing false positive violations. Normalize relPath to forward slashes before the check so the test passes correctly on all platforms. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
e9b5e07293 |
ci: store snapshot baselines as GitHub Actions artifacts instead of git (#482)
* ci: store snapshot baselines as GitHub Actions artifacts, not in git Move baseline PNG storage from git to a 90-day GitHub Actions artifact named 'snapshot-baselines', uploaded on every push to main. - PRs download the latest main-branch artifact and run Playwright comparison against it; no more checked-in PNGs causing merge conflicts. - New 'post-snapshot-comment.mjs' script classifies each snapshot as Changed/New/Unchanged, commits images to .pr/snapshots/<run_id>/ and posts a PR comment with collapsed <details> sections showing side-by-side expected/actual/diff images via raw.githubusercontent.com. - For fork PRs or if push fails, falls back to a workflow run link for downloading the 'snapshot-test-results' artifact. - Force-refresh baselines any time via workflow_dispatch force_update=true (replaces the old update_snapshots=true flow that committed PNGs to git). - Remove 44 baseline PNGs from git; gitignore tests/e2e/__snapshots__/. - Update AGENTS.md with the new workflow model. Co-authored-by: openhands <openhands@all-hands.dev> * ci: fix bootstrap case — pass CI when no baseline artifact exists yet When no main-branch 'snapshot-baselines' artifact has been uploaded yet (e.g. this very first run after merging from an old baseline-in-git flow), the comparison step fails because Playwright has nothing to compare against. Gate the 'Fail if differences' step on has_baselines==true so the bootstrap PR passes with all snapshots shown as new. Once it merges to main the artifact is created and subsequent PRs compare normally. Also derive the PR comment status from the classification (changed.length > 0) rather than from TEST_OUTCOME, which is 'failure' in the bootstrap case despite zero actual regressions. Co-authored-by: openhands <openhands@all-hands.dev> * ci: delete stale comment and re-post on each push; always embed new snapshot images - Replace PATCH-in-place with DELETE + POST so every push posts a fresh comment whose image URLs reference the current run's .pr/snapshots/<run_id>/. Editing in-place would leave raw.githubusercontent.com URLs pointing at the previous run's images once new images are committed under a new run_id. - Embed new snapshot images inside the collapsed <details> section when commitSha is available; add a fallback artifact-download link when the push fails (e.g. fork PRs). - Handle 204 No Content returned by DELETE in githubFetch. Co-authored-by: openhands <openhands@all-hands.dev> * ci: fix find-run to query artifacts API by name, not workflow runs by status The previous approach (find latest successful run of snapshot-tests.yml on main) would match old runs that predate the artifact upload step, causing actions/download-artifact to hard-fail with 'Artifact not found' before the comparison or comment steps could run. Fix: query the artifacts REST API directly for name=snapshot-baselines, filtering to non-expired artifacts from the main branch. This guarantees we only match runs that actually uploaded the baseline artifact. Also add continue-on-error: true to the download step as a safety net against the artifact expiring between the API lookup and the download. Co-authored-by: openhands <openhands@all-hands.dev> * chore: snapshot images for run 25929925639 [skip ci] * ci: replace .pr/snapshots on each run instead of accumulating per-run dirs Previously each CI run committed images under .pr/snapshots/<run_id>/, so reruns would accumulate multiple directories on the PR branch. The PR comment always pointed to the current run's images (via SHA in the raw.githubusercontent URL), but old directories silently piled up. Fix: use a fixed .pr/snapshots/ path and git rm -rf --ignore-unmatch it before staging new images. Each run completely replaces the previous images rather than appending alongside them. Raw URLs still use the commit SHA so they remain stable per push. Co-authored-by: openhands <openhands@all-hands.dev> * chore: snapshot images for run 25930435218 [skip ci] * ci: allow review_requested on draft same-repo PRs to trigger pr-review GitHub does not fire pull_request events for review_requested on draft PRs — only pull_request_target fires. The existing if condition rejected pull_request_target for same-repo PRs via the fork check, so requesting all-hands-bot or openhands-agent on a draft PR was always silently skipped. Add a carve-out: pull_request_target is also accepted for same-repo PRs when draft==true AND action==review_requested. Non-draft same-repo PRs are unaffected — they continue to be handled by the pull_request event, and the draft==true guard prevents pull_request_target from also running (no duplicate). Co-authored-by: openhands <openhands@all-hands.dev> * ci: add update-snapshots label bypass for intentional snapshot changes When snapshot diffs are expected (UI redesign, intentional change, etc.) the author now adds the 'update-snapshots' label to the PR to acknowledge them: - Fail step gains a !contains(labels, 'update-snapshots') guard so CI passes even when Playwright reports differences. - The PR comment status adjusts: ❌ 'N snapshots differ — add label to acknowledge' when unapproved, ✅ 'N snapshots changed — acknowledged via label' when approved. - The snapshot workflow now triggers on labeled/unlabeled events so that adding or removing the label immediately re-runs CI with the current label state in scope (no manual re-run or empty commit needed). - New baselines are uploaded automatically when the PR merges to main, so no separate 'regenerate on main' step is needed. Co-authored-by: openhands <openhands@all-hands.dev> * docs: update snapshot testing section in AGENTS.md Add details on: artifact lookup by name, delete-then-post comment behavior, fixed .pr/snapshots/ path (no accumulation), update-snapshots label bypass for intentional changes, labeled/unlabeled triggers, bootstrap behavior. Co-authored-by: openhands <openhands@all-hands.dev> * fix: git rm must run before copyFile, not after On the second CI run the branch already has .pr/snapshots/ tracked from the previous run. The old order was: copyFile → git rm → git add. git rm removes tracked files from disk, which deleted the freshly written images, leaving the directory empty and causing 'fatal: pathspec did not match any files'. Fix: run git rm --ignore-unmatch before copyFile so the tracked files are cleared from disk first; then copyFile writes clean new files with nothing to conflict; then git add finds them as expected. Co-authored-by: openhands <openhands@all-hands.dev> * chore: snapshot images for run 25931876588 [skip ci] * fix: push snapshot images to orphan branch, not PR branch Pushing to the PR branch with [skip ci] caused required checks to never run on the HEAD commit, permanently blocking the PR. New approach: - publishImages() creates a fresh git repo in a temp directory, adds the images, and force-pushes to snapshot-artifacts/pr-<N> — a dedicated ephemeral branch that no CI workflow watches. - The PR branch is never touched by CI, so required checks always run on the actual code commits. - [skip ci] is removed; no loop prevention is needed because nothing triggers snapshot CI on the artifacts branch. - Images in the orphan commit live at changed/<relPath>-{actual,expected,diff}.png and new/<relPath>.png (no .pr/snapshots/ prefix). - raw.githubusercontent.com/<owner>/<repo>/<sha>/changed/... URLs are stable because they pin the orphan commit SHA. - pr-artifacts.yml gains a closed trigger + cleanup-snapshot-artifacts job that deletes snapshot-artifacts/pr-<N> when the PR merges or is abandoned. - Stale .pr/snapshots/ files from previous CI runs removed from this branch. Co-authored-by: openhands <openhands@all-hands.dev> * docs: update AGENTS.md — orphan branch image storage, branch cleanup * docs: tighten AGENTS.md snapshot section and pr-artifacts description - Remove duplicate gitignore mention (already stated in baseline-storage line) - Update pr-artifacts.yml description to cover both cleanup responsibilities: .pr/live-e2e/ (on approval) and snapshot-artifacts/pr-<N> (on close) Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
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> |
||
|
|
2899c7b383 |
Fix static automation auth and switch LLM tool (#474)
* Fix static automation auth and switch LLM tool * Allow automation SDK release lag in sync check * chore: update baseline snapshots [skip ci] * chore: trigger CI after snapshot update --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
e5711e74b2 |
Clean up dev stack process trees on shutdown (#475)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
c50fe2ace3 |
fix(frontend): show full LLM model name on button and popover (#479)
* fix: show full LLM model name on button and popover * chore: update baseline snapshots [skip ci] * ci: trigger build --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
94890db862 |
fix: refresh stale visual snapshot baselines (#473)
* fix: refresh stale visual snapshot baselines * chore: update baseline snapshots [skip ci] * ci: retrigger workflows --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
f26ed89ea7 | refactor: remove unused Recent Conversations section (#471) | ||
|
|
b6df038ff1 | fix: add cursor-pointer to sidebar Settings and onboarding Skip buttons (#469) | ||
|
|
c4ae12863e | fix: align sidebar conversation list loading skeleton with loaded card (#466) | ||
|
|
00f35cd8b1 |
fix(frontend): hide repo button on local backend in GitControlBar (#464)
* fix: hide repo button on local backend in GitControlBar * refactor: update the code based on feedback |
||
|
|
818b788191 | fix: localize ChatAddFileButton aria-label (#462) | ||
|
|
a3d90fa4b1 | fix: sync status-label translations with shortened English copy (#460) | ||
|
|
bcd50c9832 |
chat: conversation UX polish (input controls, drawer state, ellipsis, git controls) (#455)
* 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> * fix: lint * fix: failing tests * refactor: package-lock.json * refactor: package-lock.json * refactor: remove artifacts * refactor: update the code based on feedback --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
5b05321483 | Fix Windows static asset serving (#456) | ||
|
|
ed216c38aa |
feat(chat): add /model slash command for LLM profiles (#418)
* 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> * 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 * feat(chat): add /model slash command for LLM profiles * Add model profile slash completions * chore: address model command review feedback (#418) Co-authored-by: openhands <openhands@all-hands.dev> * chore: address model command follow-up review (#418) Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Graham Neubig <neubig@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> |
||
|
|
e20fd0c105 |
test(snapshot): onboarding modal steps + sidebar new-conversation popover (#449)
* test(snapshot): onboarding modal 4 steps + sidebar new-conversation popover 4 onboarding-step snapshots (choose-agent, check-backend, setup-llm, say-hello) and 1 sidebar new-conversation popover snapshot spec. The sidebar popover test is marked test.fixme pending re-wiring of NewConversationButton in sidebar.tsx (temporarily commented out at lines 241-244 with 'Temporarily hide the dedicated New Conversation button' comment). The component and its testids exist in new-conversation-button-local.tsx. Co-authored-by: openhands <openhands@all-hands.dev> * chore: update baseline snapshots [skip ci] * ci: trigger re-run against CI-generated baselines --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
b06ac9114b |
Add Docker projects workspace shortcut (#397)
* Add Docker projects workspace shortcut * Address Docker projects review feedback --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
a654d54ae9 | Use ws for HTTP websocket targets (#453) | ||
|
|
863f479f07 |
test(snapshot): skills page loaded/search/filter states (#447)
* test(snapshot): skills page loaded / search / filter states Expose window.__OH_QUERY_CLIENT__ in dev/mock mode so Playwright tests can seed the React Query cache and bypass MSW's empty skills response. Adds 4 new tests: loaded grid, search-filtered, no-match, type-filter. Co-authored-by: openhands <openhands@all-hands.dev> * chore: update baseline snapshots [skip ci] * ci: trigger re-run against CI-generated baselines --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
0fc85df818 |
Run Docker dev server with host user and tmpfs home (#443)
* Run Docker dev server as host user * Use tmpfs for Docker agent home * Document Docker tmpfs home rationale |
||
|
|
0f6bfe7b42 |
test(snapshot): batch 2 — secrets, verification/condenser, sidebar (12 tests, 12 baselines) (#441)
* test(snapshot): batch 2 — secrets, verification/condenser, sidebar (12 tests)
Add three new Playwright visual snapshot spec files with 12 tests total,
all passing against local baselines:
**settings-secrets.snapshot.spec.ts** (5 tests)
- secrets list with two pre-seeded rows
- add-new-secret form open (empty)
- add-new-secret form filled with name + value
- after saving — list shows three rows
- delete confirmation modal open
**settings-verification.snapshot.spec.ts** (4 tests)
- verification settings page (confirmation mode OFF, default)
- verification settings with confirmation mode ON — reveals Security
Analyzer combobox
- verification settings dirty — Save Changes button enabled after toggle
- condenser settings page renders schema-driven form
**sidebar.snapshot.spec.ts** (3 tests)
- conversation panel with three conversations + status dots
- sidebar collapsed to icon rail
- conversations filter menu opened from toggle button
Implementation notes:
- SettingsSwitch renders the checkbox as `<input hidden>`; click the
enclosing `<label>` via `label:has([data-testid])` selector instead of
trying to click the hidden input directly.
- HeroUI Autocomplete does not forward `data-testid` to the DOM; use
`getByRole("combobox", { name: /.../ })` for the Security Analyzer field.
- Added condenser section to MOCK_AGENT_SETTINGS_SCHEMA so
/settings/condenser renders the schema-driven form (not the
"SDK settings schema unavailable" fallback).
- Added condenser default values to MOCK_DEFAULT_USER_SETTINGS.agent_settings.
- Local baselines committed; CI must regenerate via the
"Update baseline snapshots" workflow dispatch before merging.
Co-authored-by: openhands <openhands@all-hands.dev>
* docs: update AGENTS.md with snapshot testing lessons from batch 2
Add three key patterns discovered while implementing batch 2 snapshot tests:
- Hidden checkbox pattern (SettingsSwitch label-click workaround)
- HeroUI Autocomplete testId non-forwarding (use role selector)
- SdkSectionPage early return when schema section is missing
Co-authored-by: openhands <openhands@all-hands.dev>
* chore: update baseline snapshots [skip ci]
* ci: trigger re-run against CI-generated baselines
Co-authored-by: openhands <openhands@all-hands.dev>
* test(snapshot): drop redundant verification-settings-dirty test
The dirty test was pixel-for-pixel identical to the ON test: both
navigate to /settings/verification (confirmation_mode defaults to false),
click the toggle ON, then screenshot. There is no visual distinction
between 'form is now ON' and 'form is dirty after being toggled ON' because
clicking the toggle is both the action that makes the form dirty and the
action that reveals the Security Analyzer field.
The 'ON' snapshot already captures the dirty/enabled state implicitly:
- toggle gold (ON)
- Security Analyzer dropdown visible
- Save Changes button bright/enabled (vs. dimmed in the OFF snapshot)
Remove the test and its baseline. The spec now has 3 tests:
1. verification OFF — toggle gray, no analyzer, Save dimmed
2. verification ON — toggle gold, analyzer visible, Save enabled
3. condenser — schema-driven form
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||
|
|
3f28f2d625 |
Fix static automation agent-server auth (#442)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
a4089e0e4a |
Default user launchers to static frontend (#434)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
15363865b7 |
test: add snapshot coverage for Automations, MCP, and Skills pages (#436) (#438)
* test: add snapshot tests for Automations, MCP, and Skills pages
Add 7 new visual regression snapshot tests covering three previously
untested pages:
automations.snapshot.spec.ts (3 tests)
- automations-list-active-inactive: full list with active/inactive groups
- automations-search-no-results: search filter that matches nothing
- automations-delete-modal: delete confirmation modal overlay
mcp-page.snapshot.spec.ts (3 tests)
- mcp-empty-installed: empty installed section + full marketplace
- mcp-custom-server-editor: "Add custom MCP server" modal form
- mcp-search-filtered: marketplace filtered by "slack" query
skills-page.snapshot.spec.ts (1 test)
- skills-empty: empty skills state (MSW returns { skills: [] })
Supporting changes:
- src/mocks/automation-handlers.ts: add GET /api/automation/health MSW
handler (returns { status: "ok" }) so the automations list page can
render without a real backend in mock mode
- tests/e2e/snapshots/COVERAGE_PLAN.md: 16-spec snapshot coverage plan
with TODOs for backend-not-configured and loaded-skills states that
require exposing window.__MSW_WORKER__ for per-test handler overrides
Key design decisions documented in spec file headers:
- Automation health/list: MSW intercepts all same-origin requests before
page.route() (service worker takes precedence); the new MSW health
handler is required for the list page to load in mock mode
- MCP "with installed": settings mock cannot be injected via page.route()
since MSW wins for GET /api/settings; replaced with the custom server
editor modal which is state-independent
- Skills loaded/search: POST /api/skills is intercepted by MSW (returns
[]) before page.route() can override; deferred with TODO comment
Co-authored-by: openhands <openhands@all-hands.dev>
* test: add empty-automations + iterative MCP install snapshot tests
Merge latest main (2 commits), then add 3 new snapshot tests:
automations-no-automations (1 new PNG)
- Deletes all 5 seed automations via MSW DELETE REST calls, then
triggers a React Query refetch (without page.reload) to show the
EmptyState component with the "How to create an automation" cards.
- Key insight: the MSW automations Map is PAGE-level JS state, not
service-worker state. page.reload() reinitialises it to 5 items;
using window.__TEST_INVALIDATE_QUERIES__() avoids this.
mcp-slack-install-{1-4} (4 new PNGs) – iterative marketplace install
step 1: marketplace before install (Slack card visible)
step 2: Slack install modal open (empty SLACK_BOT_TOKEN / SLACK_TEAM_ID)
step 3: install modal with filled credentials (token masked, T01ABC123)
step 4: after clicking Install — Slack in Installed section, success
toast "MCP server saved.", Slack card shows "INSTALLED" badge
mcp-custom-server-{1-4} (4 new PNGs) – iterative custom SSE server add
step 1: custom server editor open (SSE type selected, empty URL)
step 2: URL filled in (https://api.example-mcp.com/sse)
step 3: API key also filled (optional field)
step 4: after Add Server — custom SSE card in Installed section
(toast dismissed, clean installed view)
All flow tests use real simulated clicks + form fills via data-testid
selectors; no page.route() overrides needed because the MSW settings
PATCH/GET cycle persists mcp_config across the refetch.
Supporting changes:
- src/entry.client.tsx: expose window.__TEST_INVALIDATE_QUERIES__ in
mock mode so specs can trigger React Query refetches without reload.
Documented why reload-based approaches fail (MSW handler state is
PAGE-level JS, reset on every full page load).
All 10 snapshot tests pass (34s).
Co-authored-by: openhands <openhands@all-hands.dev>
* docs: document MSW page-level JS state and __TEST_INVALIDATE_QUERIES__ helper
Co-authored-by: openhands <openhands@all-hands.dev>
* chore: update baseline snapshots [skip ci]
* ci: trigger snapshot validation against CI-regenerated baselines [skip ci]
Co-authored-by: openhands <openhands@all-hands.dev>
* ci: validate snapshots against CI-regenerated baselines
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||
|
|
4ed58e2379 |
fix: restore data-testid="chat-interface" removed by #340 (#437)
* fix: restore data-testid="chat-interface" removed by #340 PR #340 added left padding to the ChatInterface wrapper div but accidentally dropped the data-testid attribute in the same edit. This broke both the collapsible-thinking snapshot tests and the live e2e test, which both use getByTestId('chat-interface') as the load signal and screenshot target. Co-authored-by: openhands <openhands@all-hands.dev> * chore: update baseline snapshots [skip ci] * ci: trigger re-run after snapshot baseline update --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
ba3ab46251 | fix: allow chat column to shrink below content min-content (#435) | ||
|
|
5ccc8e627c |
Fail fast when frontend deps are missing (#404)
Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
b1b3a37e24 | fix: stop skeleton from flashing on 10s background refetch (#431) | ||
|
|
301273dae1 | fix: prevent add custom server button from being shrunk by flex header (#429) | ||
|
|
da1eb5318a | fix: remove stale MCP item from Settings sidebar (#427) | ||
|
|
0205ad89c8 |
feat(extensions): Extensions nav, unified card design, and UX polish across Skills & MCP (#386)
* feat(extensions): add Extensions left-nav with Skills/Plugins/MCP tabs - Add ExtensionsNavigation sidebar component with Skills, Plugins, and MCP Servers tabs - Add /skills/plugins route and page scaffold - Refactor skills-settings page to use original switch-list style with auto-save - Rename sidebar Skills nav link to "Extensions" with Boxes icon Co-authored-by: Cursor <cursoragent@cursor.com> * feat(extensions): remove Integrations from main sidebar, add nav to MCP page - Remove the standalone Integrations/MCP link from the main sidebar nav - Add ExtensionsNavigation to the MCP page so all three extensions pages (Skills, Plugins, MCP Servers) share the same left-nav two-column layout - Drop unused PuzzleIcon import from sidebar Co-authored-by: Cursor <cursoragent@cursor.com> * fix(extensions): normalize ExtensionsNavigation position across all pages Align outer container on skills-plugins and mcp pages to match skills-settings: remove redundant px-[14px] from wrappers (the aside already carries md:pl-[14px]), add pr-[14px] to right scroll columns, and drop overflow-hidden from the plugins wrapper so sticky works. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(extensions): move plugins route from /skills/plugins to /plugins Co-authored-by: Cursor <cursoragent@cursor.com> * feat(sidebar): highlight Extensions link when on /mcp or /plugins Co-authored-by: Cursor <cursoragent@cursor.com> * fix(extensions): reorder nav — Skills, MCP Servers, Plugins Co-authored-by: Cursor <cursoragent@cursor.com> * feat(extensions): use custom stacked-boxes icon for Skills nav item Co-authored-by: Cursor <cursoragent@cursor.com> * feat(extensions): use hexagon icon for Plugins nav item Co-authored-by: Cursor <cursoragent@cursor.com> * feat(extensions): update Plugins icon to box with cross-section lines Co-authored-by: Cursor <cursoragent@cursor.com> * fix(extensions): unify page header styles across Skills, MCP, and Plugins Co-authored-by: Cursor <cursoragent@cursor.com> * feat(skills): move toggle to right side of skill rows Co-authored-by: Cursor <cursoragent@cursor.com> * fix(extensions): use text-muted for grey description text on all extension pages Co-authored-by: Cursor <cursoragent@cursor.com> * fix(extensions): use text-tertiary-light for page subtitle text Co-authored-by: Cursor <cursoragent@cursor.com> * fix(skills): add max-w-5xl content wrapper to match MCP page width Co-authored-by: Cursor <cursoragent@cursor.com> * feat(skills): wrap each skill in a card matching MCP marketplace style Co-authored-by: Cursor <cursoragent@cursor.com> * feat(skills): two-column grid layout and white hover border on cards Co-authored-by: Cursor <cursoragent@cursor.com> * fix(mcp): use white hover border on marketplace cards Co-authored-by: Cursor <cursoragent@cursor.com> * fix(mcp): move backend synced badge below description text Co-authored-by: Cursor <cursoragent@cursor.com> * feat(skills): bring in full SkillCard UI with description, badges, triggers, and details Co-authored-by: Cursor <cursoragent@cursor.com> * feat(skills): move toggle to right side of skill card header Co-authored-by: Cursor <cursoragent@cursor.com> * feat(extensions): move backend synced badge from MCP page to extensions nav Co-authored-by: Cursor <cursoragent@cursor.com> * fix(mcp): change marketplace grid from 3 columns to 2 columns Co-authored-by: Cursor <cursoragent@cursor.com> * fix(badge): remove icon, border, background, and rounded pill from backend synced badge Co-authored-by: Cursor <cursoragent@cursor.com> * fix(skills): align skill card colors with MCP marketplace card tokens Co-authored-by: Cursor <cursoragent@cursor.com> * fix(mcp): align top padding and header spacing with other extensions pages Co-authored-by: Cursor <cursoragent@cursor.com> * fix(mcp): use items-center on header row to stop title being pushed down Co-authored-by: Cursor <cursoragent@cursor.com> * fix(mcp): group title+description together so button doesn't inflate the gap Co-authored-by: Cursor <cursoragent@cursor.com> * fix(mcp): remove orphaned BackendSyncedSettingsBadge JSX reference causing crash Co-authored-by: Cursor <cursoragent@cursor.com> * fix(skills): remove monospace font from trigger chip pills Co-authored-by: Cursor <cursoragent@cursor.com> * fix(skills): hide empty description placeholder when no description provided Co-authored-by: Cursor <cursoragent@cursor.com> * Match skills and MCP search input style and width to one grid column Replace skills toolbar search with same rounded-lg/border-tertiary/bg-base-secondary style as the MCP MarketplaceSearch, and constrain both to w-1/2 so they span exactly one column of the two-column grid layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Increase skills toolbar gap from gap-3 to gap-6 Co-authored-by: Cursor <cursoragent@cursor.com> * Make extensions page grids and search inputs responsive at smaller sizes - Skills and MCP grids: grid-cols-1 on mobile, md:grid-cols-2 on larger screens - Skills and MCP search inputs: w-full on mobile, md:w-1/2 on larger screens Co-authored-by: Cursor <cursoragent@cursor.com> * Remove Details collapsible section from skill cards Co-authored-by: Cursor <cursoragent@cursor.com> * Remove phantom gap from labelless SettingsSwitch by rendering label div only when needed Co-authored-by: Cursor <cursoragent@cursor.com> * Change search focus ring from yellow/primary to white on both Skills and MCP Co-authored-by: Cursor <cursoragent@cursor.com> * Change active filter pill highlight from yellow/primary to white Co-authored-by: Cursor <cursoragent@cursor.com> * Add 'Soon' badge to Plugins nav item in Extensions navigation Co-authored-by: Cursor <cursoragent@cursor.com> * Change badge text from 'Soon' to 'Coming Soon' Co-authored-by: Cursor <cursoragent@cursor.com> * Add 'Installed' section label above skills grid to match MCP page Co-authored-by: Cursor <cursoragent@cursor.com> * Replace Marketplace heading and description with plain 'Library' label Co-authored-by: Cursor <cursoragent@cursor.com> * Add text-foreground to section labels to fix black text rendering Co-authored-by: Cursor <cursoragent@cursor.com> * Fix MCP delete hang: close modal on error, reduce save retry count to 1 Co-authored-by: Cursor <cursoragent@cursor.com> * Remove 'Installed' section label from skills grid Co-authored-by: Cursor <cursoragent@cursor.com> * Remove icons from skill type badges Co-authored-by: Cursor <cursoragent@cursor.com> * Restore Details dropdown on skill cards, remove only the top border line Co-authored-by: Cursor <cursoragent@cursor.com> * fix(skills): stabilize settings tests and no-match testid - Add data-testid hooks for the intro copy and zero-result message. - Align skills route tests with Extensions nav (/plugins) and the current description block (no separate badge). - Use fireEvent.change for the search no-match case; user.type hit the default test timeout in isolation. PR note: Empty Skills filter chips and search placeholder in dev were from stale generated i18n (keys exist in translation.json but not in ignored public/locales until make-i18n). Run npm run make-i18n after editing translations; npm run build already runs it via pre-build. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
607ddae7fb |
fix: refine sidebar and home onboarding layout (#340)
* fix: simplify sidebar nav and add settings-focused rails Remove the legacy settings submenu from the left sidebar, move key actions into sticky footer controls, and add a dedicated settings-page left rail so navigation matches the new layout and interaction flow. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: improve collapsed sidebar expand affordance Make the collapsed rail open reliably from empty-space clicks and hover by swapping the logo area to an explicit expand control. Also update the OpenHands logo asset to use white/transparent fills for the requested visual treatment. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: refresh conversation panel filtering and sidebar visual separation This aligns the conversation list behavior with the new older-conversation filter/menu flow and adds a clear desktop divider for the left navigation rail to improve layout clarity. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: prevent conversation list horizontal overflow This makes repo/branch metadata rows shrink and truncate correctly and enforces horizontal clipping in the conversation list scroller so long labels never introduce sideways scrolling. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: refine sidebar conversation list layout and interactions This updates the left-nav conversation list styling/spacing and keeps the older-conversations controls fixed above the scroll region, while tightening dropdown/timestamp behavior to match the intended hover and layering UX. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: simplify sidebar new conversation entry This temporarily hides the standalone New Conversation button and reuses the first sidebar nav item as a compact "New" entry with a plus icon to streamline the left rail. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: align sidebar conversation header and new-link state This tightens the conversation summary indent to align with the nav icon column and makes the + New sidebar link active only on the base conversations route. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: tighten sidebar conversation list spacing Unify sidebar and conversation-list horizontal padding at 2px offsets so nav items and rows align consistently, and reduce extra empty row spacing by only rendering metadata footers when shown. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: align collapsed backend switcher with shared dropdown Reuse the existing dropdown menu implementation for the collapsed sidebar backend switcher so it matches the primary backend selector behavior and opens upward with the expected menu layout. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: show backend connection dot on collapsed server control Surface the active backend health indicator on the collapsed server button so connectivity status remains visible even when the full backend selector is hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: hide conversation rail items in collapsed sidebar Stop rendering the compact conversation row list when the sidebar is collapsed so the left rail only shows the intended navigation controls. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: align older-conversations menu with dropdown styles Apply the shared dropdown menu treatment to the older-conversations filter and add a checkmark indicator for repo/branch metadata visibility. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: standardize conversation and backend dropdown color tokens Align the older-conversations and backend selector menus with the shared context-menu visual tokens so dropdown surfaces and separators match the conversation view styling. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: make backend footer controls transparent at rest Remove default border and fill from the backend selector footer controls so they match sidebar button behavior with hover-only emphasis. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: refine sidebar settings state and load-more styling Restore an explicit active style for the backend settings button and remove the default underline from the conversation list load-more action. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: remove extra indentation from settings sidebar tabs Stop using the indented sidebar link variant in desktop settings navigation so tab items align with the rest of the sidebar. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: add matching left padding to chat layout container Apply the same responsive left inset as the right side in the chat interface wrapper so content alignment is balanced on desktop. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: streamline and center home onboarding layout Move the guide banner to the top, center the main onboarding content, and remove the recent conversations/tasks section from the home screen to simplify first-run focus. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: restore collapsed settings navigation action Wire the collapsed settings control to navigation context and add coverage to prevent regressions when the sidebar is collapsed. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(sidebar): open backend dropdown on hover in both collapsed and expanded states Co-authored-by: Cursor <cursoragent@cursor.com> * fix(sidebar): add close delay so cursor can reach dropdown before it dismisses Co-authored-by: Cursor <cursoragent@cursor.com> * fix(sidebar): keep backend popover open on mouse-leave so modal clicks don't get swallowed Co-authored-by: Cursor <cursoragent@cursor.com> * fix(sidebar): lift modal state to Sidebar so Add/Manage Backend work from collapsed popover Co-authored-by: Cursor <cursoragent@cursor.com> * fix(sidebar): restore missing AutomationsIcon import Co-authored-by: Cursor <cursoragent@cursor.com> * fix(sidebar): make collapsed backend popover dropdown actions work Two distinct bugs prevented users from interacting with the backend popover when the sidebar was collapsed: 1. Selecting a backend option silently expanded the sidebar instead of switching backends. The aside's rail-collapse handler only bails on `a, button, input, [role='button'], [role='link']`, but dropdown options are `<li role='option'>`. The click bubbled up and `setCollapsed(false)` ran before the backend switch completed, unmounting the popover mid-flight. Fix: stop click propagation at the popover wrapper so the rail handler never sees clicks from inside it. 2. Clicking Add Backend / Manage Backends opened the modal but it immediately disappeared. The modal state lived inside `BackendSelector`, which was mounted inside the popover. Once the cursor moved toward the centred modal, the popover's onMouseLeave timer fired -> the popover closed -> `BackendSelector` unmounted -> the modal state was destroyed. Fix: lift the modal state into `Sidebar` and render the modals there. `BackendSelector` accepts new optional `onOpenAddBackend` and `onOpenManageBackends` callbacks; when provided it skips its internal modal rendering. Also restores the expanded sidebar's hover-close behavior on the shared Dropdown (had been left as `onMouseLeave={undefined}`). Adds focused regression tests covering all three behaviors. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(dropdown): wire hidden combobox trigger for collapsed nav Co-authored-by: Cursor <cursoragent@cursor.com> * fix: lint * fix: failing tests --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
a1befdd89d |
Fix stale backend health errors after recovery (#424)
Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: hieptl <hieptl.developer@gmail.com> |
||
|
|
3a0eb9a9b1 |
feat: collapse thinking and extended reasoning into collapsible sections (#409)
* feat: collapse thinking and extended reasoning into collapsible sections Render ThinkAction events and LLM reasoning content (reasoning_content / thinking_blocks) inside collapsible sections instead of inline chat messages. This keeps the chat compact, especially when the thinking language (usually English) differs from the conversation language. Changes: - Add CollapsibleThinking component with lightbulb icon and expand/collapse toggle - Update EventMessage to render ThinkAction as CollapsibleThinking - Show reasoning_content/thinking_blocks from action events as collapsible - Add i18n translations for Thinking title/expand/collapse (15 languages) - Add getReasoningContent helper to extract extended thinking from events - Update tests for new collapsible behavior Co-authored-by: openhands <openhands@all-hands.dev> * docs: add collapsible thinking notes to AGENTS.md Co-authored-by: openhands <openhands@all-hands.dev> * fix: resolve prettier/eslint lint errors in collapsible thinking - Reformat multiline ternary to single line in collapsible-thinking.tsx - Remove unnecessary JSX wrapping around <CollapsibleThinking /> in event-message.tsx - Remove unused eslint-disable directive for react/jsx-props-no-spreading Co-authored-by: openhands <openhands@all-hands.dev> * test: add Playwright visual snapshot tests for collapsible thinking Add 4 visual snapshot tests covering: - ThinkAction collapsed state (lightbulb icon + 'Thinking' label) - ThinkAction expanded state (full thinking content visible) - Reasoning content (reasoning_content field) collapsed with bash action - Reasoning content expanded alongside bash action Uses MSW mock dev server for API handling and injects events directly into the Zustand event store via __OH_EVENT_STORE__. Co-authored-by: openhands <openhands@all-hands.dev> * chore: update baseline snapshots [skip ci] * ci: trigger CI with updated baseline snapshots Co-authored-by: openhands <openhands@all-hands.dev> * docs: update AGENTS.md with conversation snapshot testing patterns Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
ebe6b392d4 |
fix: pre-bundle all dependencies to prevent Safari 504 errors (#414)
* fix: pre-bundle all dependencies to prevent Safari 504 errors Safari gets stuck in 504 Gateway Timeout errors when Vite discovers new dependencies at runtime and triggers 'optimized dependencies changed. reloading'. Chrome handles this gracefully, but Safari cannot recover. The fix is to pre-bundle all dependencies that were being discovered at runtime: - @openhands/typescript-client and its subpath exports - posthog-js/react, class-variance-authority, downshift, framer-motion - rehype-raw, rehype-sanitize, unist-util-visit - uuid, zustand, zustand/middleware - All react-syntax-highlighter language definitions (60+ languages) Co-authored-by: openhands <openhands@all-hands.dev> * fix: add no-store header to prevent Safari caching stale modules After a fresh npm install, Vite generates new hashes for pre-bundled deps. Safari's aggressive caching holds references to old URLs that no longer exist, causing blank screens that require manually clearing cache. Adding Cache-Control: no-store ensures Safari always fetches fresh module references in dev mode. * fix: force dep re-bundling on every server start for Safari Adding optimizeDeps.force=true ensures Vite re-bundles all dependencies on every server start, generating fresh hashes. This prevents Safari from trying to load stale cached module URLs after npm install. * fix: use noDiscovery instead of force to prevent Safari 504s The force: true option was causing Vite to rebuild ALL deps on every server start, creating a race condition where Safari requests deps during optimization and gets 504s. Chrome silently retries through this, Safari doesn't. Switching to noDiscovery: true instead - this tells Vite to only pre-bundle deps listed in 'include' and never discover new ones at runtime. This avoids the runtime optimization that causes 504s, while still using cached deps when available (faster startup). * chore: remove build step from README - no longer needed The vite.config.ts fix (noDiscovery + comprehensive include list) makes the build step unnecessary for Safari compatibility. * chore: remove Cache-Control header - not needed with noDiscovery The no-store header was a workaround for Safari caching stale module URLs after 504 errors. With noDiscovery: true, 504s can't happen, so the header is unnecessary. * docs: add dev:static as alternative for non-development use dev:static serves pre-built static files instead of running Vite dev server. Faster loads, better for slow networks, no hot reload needed if you're just running agent-canvas rather than developing on it. --------- Co-authored-by: John-Mason Shackelford <john-mason@openhands.dev> Co-authored-by: openhands <openhands@all-hands.dev> |