* feat(automations): add backend health check before loading automations UI
- Add checkHealth() method to AutomationService that calls /api/automation/health
- Create useAutomationHealth hook for React Query integration
- Create BackendNotConfigured component to display when backend is unavailable
- Update automations-list.tsx and automation-detail.tsx routes to check health
- Show 'Automations Backend Not Configured' UI with host URL and retry button
- Add i18n translations for new UI strings
- Add tests for hook and component
* fix: perform health check for cloud backends too, update message
- Remove assumption that cloud backends are always healthy
- Call /api/automation/health via cloud proxy for cloud backends
- Update message to generic 'Automations Unavailable' / 'not available right now'
- Remove host URL display (not needed for generic message)
- Rename component to BackendUnavailable (keep BackendNotConfigured as alias)
* fix: disable automation API calls when health check fails
- Add enabled option to useAutomations, useAutomationDetail, and useAutomationRuns hooks
- Only fetch automations data when the backend health check passes
- Update hook call sites to pass enabled flag based on isBackendHealthy
- Update tests to use new options-based API
* test: add checkHealth mock to automation-detail test
The test was failing because the health check now gates automation API calls.
Added checkHealth mock returning { status: 'ok' } so the test can proceed.
---------
Co-authored-by: openhands <openhands@all-hands.dev>
* Rework home workspace UX for agent-server backend
- Drop the Repositories/Workspaces tab treatment on the home page. The
agent-canvas build only ever talks to an agent-server backend, so
RepoConnector now renders WorkspaceSelectionForm directly. The
LaunchTabs component is removed; RepositorySelectionForm is left in
place for any future cloud-backend variant.
- Add a 'Manage Workspaces' modal accessible from the workspace
dropdown footer. It lists each saved workspace with a per-row
Remove button (wired to useWorkspacesStore.removeWorkspace) and
hides itself when there are no workspaces yet. Removing the
currently selected workspace clears the form selection.
- Folder browser 'Use this folder' now adds only the chosen
directory as a single workspace (named by its basename), instead
of importing every immediate subdirectory. The button is enabled
whenever a path is selected.
- Add HOME$MANAGE_WORKSPACES, HOME$MANAGE_WORKSPACES_EMPTY,
HOME$REMOVE_WORKSPACE, HOME$DONE i18n keys and rephrase
HOME$ADD_WORKSPACES to the singular '+ Add Workspace'.
- Update tests: remove obsolete LaunchTabs cases, rewrite the
folder-import test to assert single-folder behavior, and add
coverage for Manage Workspaces remove + hidden-when-empty.
Co-authored-by: openhands <openhands@all-hands.dev>
* Workspace parents: dynamically list subdirectories
Adds a 'workspace parent' concept alongside individual workspaces:
- 'Add Workspace' modal footer now has two action buttons:
* 'Add this directory' (primary): saves the navigated folder as a
single workspace, as before.
* 'Add all subdirectories' (secondary): saves the folder as a
LocalWorkspaceParent instead. The parent itself is not selectable;
its immediate subdirectories are listed dynamically wherever
workspaces are shown.
- New useResolvedWorkspaces() hook merges static workspaces with the
live subdirectory listing of every saved parent (via FilesService
.searchSubdirs and useQueries). Static workspaces win on duplicate
paths.
- ManageWorkspacesModal grows a 'Workspace parents' section that lets
users remove parents (their dynamic children disappear with them) and
shows the current resolved children indented underneath.
- WorkspaceDropdown takes an optional showManage prop so the Manage
entry stays visible when only parents (whose children may not have
loaded yet) exist.
Co-authored-by: openhands <openhands@all-hands.dev>
* chore(test): remove launch-tabs.test.tsx orphaned by LaunchTabs deletion
The LaunchTabs component was deleted in 'Rework home workspace UX
for agent-server backend', but the test file that imports it from
`#/components/features/home/launch-tabs` was missed in that change
(it was added on main concurrently and pulled in via merge), leaving
the suite unable to resolve the import.
Co-authored-by: openhands <openhands@all-hands.dev>
* chore(lint): prettier nit in manage-workspaces-modal
Co-authored-by: openhands <openhands@all-hands.dev>
* Address workspace UX review feedback
Co-authored-by: openhands <openhands@all-hands.dev>
* Fix cloud repo launcher on home screen
---------
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: hieptl <hieptl.developer@gmail.com>
* fix(chat): ignore agent-server's raw 'tool_name: {args}' summary fallback
The SDK's _extract_summary emits f'{tool_name}: {json_args}' when the LLM
omits a summary, which rendered as a huge unreadable JSON blob in the chat
title. Detect that pattern and fall through to the existing localized
titles (e.g. 'Editing <path>', 'Running <cmd>') so users see something
like 'edited file foo.txt' or 'ran command echo' instead.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(chat): preserve whitespace in event titles by wrapping in span
The Trans-rendered title (e.g. "Editing <path>foo.txt</path>") is a
fragment of a text node + strong element. When inserted directly into
the flex row in GenericEventMessage, those become separate anonymous
flex items and the trailing space between "Editing" and the path is
collapsed, producing 'Editingfoo.txt'. Wrap the title in a span so the
inline content lays out normally.
Co-authored-by: openhands <openhands@all-hands.dev>
* Allow sending messages after a conversation enters error state
The chat input was force-disabled whenever the conversation's execution
status was ERROR or STUCK, so users had no way to send a follow-up
message and recover. The send pipeline itself doesn't reject in those
states (it sends via WebSocket if open, otherwise queues via REST), so
the UI gate was the only blocker.
Drop the isExecutionErrored check in CustomChatInput and remove the
now-unused executionStatus prop pass-through from InteractiveChatBox.
Co-authored-by: openhands <openhands@all-hands.dev>
* Suppress duplicate toast for v1 AgentErrorEvent
The agent error is already shown inline in the chat log (and via the
error banner). The legacy WS event handler was treating any event with
an 'error' field as a server error and firing a toast, double-notifying
on agent errors.
Co-authored-by: openhands <openhands@all-hands.dev>
* Stabilize flaky test timeouts
---------
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: hieptl <hieptl.developer@gmail.com>
* Make Changes tab sleeker and square the tab container
- Replace heavy bordered/rounded file-diff cards with subtle border-b
dividers matching the tab title separator (#474A54)
- Tighten file row padding and recolor view-mode toggle buttons to
match existing tab-title button hover palette
- Drop rounded corners and side borders from EditorContainer and
markdown preview so editors flow into the next file row
- Remove p-4/gap-3 from changes-tab so rows form a continuous list
- Square the shared TabContainer/TabContentArea/loading shell
Co-authored-by: openhands <openhands@all-hands.dev>
* Fix test stability issues
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
* Show local git remote/branch in conversation control bar
For conversations started from a local workspace (no selected_repository
on the conversation), shell out via the agent server's bash-execute
endpoint to read 'git remote get-url origin' and the current branch from
the conversation's working dir, and use them to populate the repo and
branch chips in the chat git control bar.
- New parseGitRemoteUrl util maps remote URLs to {host, owner/repo,
provider}, recognizing github/gitlab/bitbucket/azure cloud hosts.
- New useLocalGitInfo query hook runs only when the conversation lacks a
selected_repository, has a working_dir, and the runtime is ready.
Failures (non-git checkout, etc.) silently fall back to the existing
'No repo / No branch' display.
- GitControlBar layers the local fallback after conversation/task data
but keeps pull/push/PR gated on conversation/task data only, so we
don't enable provider-specific flows for repos the agent didn't clone.
- Repo/branch buttons now render the detected name even when no provider
is known; the external-link styling is reserved for cases where we can
build a real URL.
Co-authored-by: openhands <openhands@all-hands.dev>
* Bump typescript-client and request HEAD ref for git changes
Update @openhands/typescript-client from 7d20f11 to 6b9603f (latest main)
to pick up the new optional 'ref' option on RemoteWorkspace.gitChanges
and gitDiff. Pass { ref: 'HEAD' } from both the V1 and legacy git-service
adapters (and the cloud-proxy query strings) so the Changes panel shows
working tree + index versus the latest commit (i.e. staged + unstaged)
instead of diffing against the auto-detected upstream/default branch.
Co-authored-by: openhands <openhands@all-hands.dev>
* chore(lint): prettier nits in use-local-git-info
Co-authored-by: openhands <openhands@all-hands.dev>
* Refactor git remote URL normalization
---------
Co-authored-by: openhands <openhands@all-hands.dev>
* Group consecutive action/observation events in V1 chat
Fold runs of consecutive ActionEvent/ObservationEvent cards into a
single collapsible EventGroup so a long sequence of tool calls no
longer dominates the chat scroll. Default state is collapsed; the
header shows '{n} actions completed' with a check once every action
in the group has its observation, or '{completed} of {total} actions'
plus the currently-running action's title while at least one is still
in flight. Expanding renders the original EventMessage children
verbatim, so each card still expands inline the way it did before.
- New: src/components/v1/chat/group-events.ts (folding helper, with
EVENT_GROUP_MIN_SIZE threshold and an isGroupableEvent allow-list
that excludes Finish/Think actions, planning/task-tracker
observations, hooks, errors, and message events)
- New: src/components/v1/chat/event-message-components/event-group.tsx
- Wired into src/components/v1/chat/messages.tsx; no other call sites
changed
- i18n keys EVENT_GROUP$ACTIONS_COMPLETED, EVENT_GROUP$ACTIONS_PROGRESS,
EVENT_GROUP$EXPAND, EVENT_GROUP$COLLAPSE
- Tests: 12 cases for the grouping logic, 4 for the component render
Co-authored-by: openhands <openhands@all-hands.dev>
* Hoist agent thoughts out of action groups
When consecutive ActionEvent/ObservationEvent cards are folded into an
EventGroup, any thought attached to one of those actions used to render
inline inside the (default-collapsed) group, hiding the agent's
reasoning from the message pane.
groupEvents now emits a 'thought' RenderedItem whenever a groupable event
carries (or, for an observation, originates from) a non-empty
ActionEvent.thought. The hoisted thought flushes the current run and
starts a new one, so a thought between actions always shows in the
message pane and breaks the surrounding action group. messages.tsx
renders that item via ThoughtEventMessage and passes a new
suppressThought prop to EventMessage so the inline thought isn't
duplicated when the group is expanded. ThinkAction is excluded because
its thought IS its action body.
Co-authored-by: openhands <openhands@all-hands.dev>
* Group pairs of consecutive actions in the chat stream
Lower EVENT_GROUP_MIN_SIZE from 3 to 2 so two back-to-back groupable
action/observation events also fold into a collapsible EventGroup,
matching the behavior already applied to runs of 3+.
Co-authored-by: openhands <openhands@all-hands.dev>
* Hide inner left bar on events nested inside an EventGroup
Each GenericEventMessage renders its own border-l-2 left bar. When the
message is rendered inside an expanded EventGroup (which already draws
its own outer left bar), the inner bar shows up as a redundant white
bar next to the outer one.
Add an IsInEventGroupContext that EventGroup sets to true around its
expanded children, and have GenericEventMessage drop its left
border/padding when that context is set. Standalone rendering is
unchanged.
Co-authored-by: openhands <openhands@all-hands.dev>
* chore(lint): fix prettier/eslint nits in v1 chat event-grouping files
Co-authored-by: openhands <openhands@all-hands.dev>
* Address PR feedback for event grouping
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
* Render ACP sub-agent tool-call events in chat (#132)
Port of upstream OpenHands frontend PRs:
- OpenHands#13994 — initial ACPToolCallEvent rendering
- OpenHands#14246 — drop the 'ACP · ' prefix from titles
- OpenHands#14247 — suppress in_progress events to avoid empty-args flash
Adds end-to-end support for the new V1 ACPToolCallEvent surfaced by ACP
sub-agents (Claude Code, Codex, Gemini CLI, …). Each tool call is rendered
through the same GenericEventMessage wrapper used for observation events,
so the resulting card shape, success indicator, and markdown formatting
match the rest of the OpenHands chat.
Highlights:
- New ACPToolCallEvent type + isACPToolCallEvent type guard, wired into
the OpenHandsEvent union.
- getACPToolCallContent + getACPToolCallTitleKey helpers — execute calls
render as Command:/Output: blocks just like getTerminalObservationContent;
non-execute calls render their raw_input as a JSON Input: block. Errors
use **Error:**, missing output falls back to OBSERVATION$COMMAND_NO_OUTPUT,
and very long output is truncated to MAX_CONTENT_LENGTH.
- getACPToolCallResult — maps status + is_error to success | error |
undefined, mirroring getObservationResult so in_progress calls render
without a check mark.
- shouldRenderEvent — hides in_progress ACP events to avoid an empty-args
card flashing before the first populated event arrives (matches #14247).
- handleEventForUI — dedupes ACP events by tool_call_id so streaming
in_progress → completed/failed transitions update the card in place
instead of stacking duplicate cards.
- EventMessage dispatch — routes ACP events to GenericEventMessageWrapper
after hook execution events, before generic action handling.
- Five new ACTION_MESSAGE$ACP_* i18n keys (RUN / EDIT / READ / FETCH /
TOOL) localised across all bundled languages, with the title rendered
inline via the existing <cmd> Trans component (no 'ACP · ' prefix per
#14246).
Tests: 39 new/modified vitest cases across get-acp-tool-call-content,
should-render-event, handle-event-for-ui, and event-message-acp-tool-call.
Visual verification + demo GIF: .pr/issue-132/
This commit was created by an AI agent (OpenHands) on behalf of the user.
Co-authored-by: openhands <openhands@all-hands.dev>
* Capture real ACP card rendering for #142 demo
Addresses @neubig's review feedback on PR #142 ('I did not see any
examples of ACP events being rendered in the interface'):
- Updates demo.gif to actually show three ACPToolCallEvent cards
rendering in the live conversation UI (collapsed view + expanded
view with Command/Output/Input blocks).
- Adds 04-acp-cards-collapsed.png + 05-acp-cards-expanded.png as the
individual frames, plus 04-acp-rendering-notes.md explaining what
each frame shows and why a synthetic event injection was needed
(no Claude Code / Codex / Gemini CLI binary in the sandbox).
- Adds capture.mjs (the Playwright script used to record the demo) so
future re-records are reproducible.
- In src/stores/use-event-store.ts, exposes the existing Zustand
store on window.__OH_EVENT_STORE__ when import.meta.env.DEV is
true. Tree-shaken from production builds; no behaviour change for
end users. Useful as a general dev affordance for fixture/preview
tooling beyond this PR.
Functional verification (unchanged from the original PR):
npx vitest run __tests__/components/v1/chat/event-content-helpers/get-acp-tool-call-content.test.ts __tests__/components/v1/chat/event-message-acp-tool-call.test.tsx
Test Files 2 passed (2)
Tests 20 passed (20)
This commit was created by an AI agent (OpenHands) on behalf of the user.
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: seed automation API key into agent-server secrets
- Add seedAutomationSecret() that calls PUT /api/settings/secrets after
agent-server is ready, storing the automation API key as
OPENHANDS_AUTOMATION_API_KEY
- This makes the key available to agents during conversations so they can
authenticate with the automation backend
- Add sessionApiKey to config for optional auth header
- Update help text and documentation
Co-authored-by: openhands <openhands@all-hands.dev>
* test: add tests for seed automation secret and fix CI failure
- Add tests for localApiKey and sessionApiKey config in buildConfig
- Add tests for secrets documentation in help output
- Fix root-layout-refetch.test.tsx unhandled rejection from framer-motion
by adding async cleanup with microtask flush
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: detect SESSION_API_KEY when seeding automation secret
The seedAutomationSecret() function was failing with 401 Unauthorized
because it wasn't detecting the SESSION_API_KEY environment variable
that the agent-server uses by default (V0 config).
The agent-server checks these env vars for session API keys:
- SESSION_API_KEY (V0 config, picked up by default factory)
- OH_SESSION_API_KEYS_0 (V1 config)
The original code only checked OH_SESSION_API_KEY and VITE_SESSION_API_KEY,
missing the actual env vars the server reads. In OpenHands Cloud
environments, SESSION_API_KEY is set automatically, causing the 401.
This fix adds SESSION_API_KEY and OH_SESSION_API_KEYS_0 to the
fallback chain, with SESSION_API_KEY taking highest precedence
since it matches the agent-server's default behavior.
Adds tests verifying:
- SESSION_API_KEY detection
- OH_SESSION_API_KEYS_0 detection
- Precedence order (SESSION_API_KEY > OH_SESSION_API_KEYS_0 > others)
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: add retry logic and longer timeout for secret seeding
On slower systems, the agent-server may take longer to start up,
causing the secret seeding to fail with 'fetch failed' errors.
This fix adds:
1. Increased initial wait timeout from 30s to 60s for agent-server startup
2. Retry logic in seedAutomationSecret (5 retries with 2s delay)
3. Better error logging showing elapsed time and last error
4. AbortSignal.timeout on fetch requests to avoid hanging
5. Skip seeding if server fails to start (with warning message)
The retry logic handles transient failures during server warmup
but immediately fails on 401/403 auth errors (no point retrying).
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: add automations frontend on /automations subpath
Port automations frontend from automation-repo to agent-canvas.
## Changes
### New Routes
- /automations - List view of all automations
- /automations/:automationId - Automation detail view
### New Components
- Automation list components: card, card-skeleton, group, empty-state, error-state
- Automation detail components: header, sections (config, prompt, plugins, activity)
- Shared UI components: toggle-switch, metadata-chip, status-badge, kebab-menu, search-input
### API Integration
- automation-service.api.ts - API client for automation CRUD operations
- Uses existing openHands axios client (shared base URL with agent server)
### MSW Mock Server Handlers
- automation-handlers.ts - Mock handlers for testing
- automations.mock.ts - Sample automation data
- automation-runs.mock.ts - Sample automation run data
### Tests
- API tests: automation-service.test.ts, automation-handlers.test.ts
- Component tests: toggle-switch, metadata-chip, search-input, error-state
- Detail component tests: section-card, run-status-badge, not-found-state
### Hooks
- use-automations.ts - React Query hook for fetching automations list
- use-automation-detail.ts - React Query hook for fetching single automation
- use-has-permission.ts - Permission checking utility hook
### Types
- automation.ts - TypeScript types for automation entities
### Icons
- Added SVG icons: activity, bell, calendar, check-circle, chevron-down,
chevron-left, clock, cog, database, exclamation-circle, git-branch,
kebab-vertical, power, puzzle, search, sparkle, target, trash, x-circle, x-mark
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: add local API key auth for automation backend
- Use VITE_AUTOMATION_API_KEY env var for frontend to authenticate
- Pass AUTOMATION_LOCAL_API_KEY to automation backend in dev mode
- Use dedicated axios instance with Bearer auth interceptor
- Add --refresh to uvx to ensure latest git commits are fetched
- URL-encode automation IDs in API paths
The default local API key is 'openhands-local-api-key' which matches
between the frontend and backend for local development.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: automation service tests and ingress port conflicts
- Fix automation-service.test.ts to mock axios instance correctly
(was mocking openHands but service uses automationAxios)
- Use vi.hoisted() for mock functions available during vi.mock hoisting
- Change ingress test ports from 19000-19003 to 29000-29003 to avoid
conflict with VS Code server on port 19000
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: add CORS origins for automation backend in dev mode
The automation backend defaults CORS origins to app.all-hands.dev,
which blocks localhost requests. Add localhost origins for the
ingress port and Vite dev server port.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: update create-instructions styling and add missing i18n keys
- Use semantic color tokens (text-content, text-basic, bg-base-secondary,
bg-base, border-default) instead of hardcoded neutral-* colors
- Add all AUTOMATIONS$ i18n keys for the automations frontend
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: hieptl <hieptl.developer@gmail.com>
* feat: track install immediately without consent, add proxy support
BREAKING CHANGE: Install event (canvas_install) is now sent immediately
on first use, regardless of consent status. Users can still opt out via
VITE_DO_NOT_TRACK=1 or browser's Do Not Track setting.
Changes:
- trackInstall() sends the install event immediately without waiting for consent
- trackFirstUse() is now deprecated, calls trackInstall() for backward compat
- Session/custom events still require user consent
- Add VITE_POSTHOG_UI_HOST env var for reverse proxy support
- PostHog initialization now includes ui_host configuration
This change allows tracking library adoption even if users haven't made
a consent choice yet, while still respecting hard opt-outs.
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: default PostHog host to z.openhands.dev proxy
Use OpenHands' managed reverse proxy by default to bypass ad blockers.
The proxy at z.openhands.dev routes telemetry to PostHog's US region.
Library consumers can still override with VITE_POSTHOG_HOST if needed.
Co-authored-by: openhands <openhands@all-hands.dev>
* chore: remove deprecated trackFirstUse function
Only trackInstall() is now exported. No backwards compatibility shim needed.
Co-authored-by: openhands <openhands@all-hands.dev>
* docs: add privacy/GDPR compliance notes to install tracking
Address review feedback by documenting the privacy implications and
GDPR legal basis (legitimate interest under Article 6(1)(f)) for
sending the anonymous install event before consent.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: wait for translations before showing consent modal
- Use useTranslation's 'ready' state to wait for translations to load
- Add small delay (50ms) to ensure DOM is fully hydrated
- Prevents translation keys from flashing on first appearance
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: multi-backend support with cloud SaaS proxy routing
* feat: route conversation export through cloud proxy on cloud backends
* fix: route conversation delete through cloud proxy on cloud backends
* fix: forward settings diffs verbatim through cloud proxy save
* fix: surface cloud-aware settings sub-pages and gate local-only routes
* fix: route secrets settings through cloud proxy on cloud backends
* fix: route conversation stop runtime through cloud proxy on cloud backends
* fix: re-expose planning agent UI for cloud backends and route plan file reads through cloud proxy
* fix: route Display Cost runtime fetch through cloud proxy and ungate local metrics without session API key
* fix: handle WAITING_FOR_SANDBOX task status from cloud backends to prevent UI crash
* fix: re-expose Public Share in conversation menu for cloud backends
* fix: redirect to home when switching backends from a conversation page
* fix: hide cloud orgs the API key can't access in backend selector
* feat: support running multiple local agent-servers with shared persistence
* fix: lint
* fix: failing tests
* feat: add first-use telemetry tracking with consent
- Add telemetry service with consent management (src/services/telemetry.ts)
- Add useTelemetry React hook for easy integration (src/hooks/use-telemetry.ts)
- Add TelemetryConsentBanner component with i18n support
- Add local development server for testing (scripts/telemetry-dev-server.mjs)
- Add comprehensive tests for telemetry service and hook
- Export telemetry utilities from library index
- Respect DO_NOT_TRACK environment variable for privacy
- Uses localhost:8080 endpoint for development
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: address PR review feedback
- Make TELEMETRY_ENDPOINT configurable via VITE_TELEMETRY_ENDPOINT env var
- Make POSTHOG_API_KEY configurable via VITE_POSTHOG_API_KEY env var
- Add validation to skip telemetry if API key not configured (except localhost)
- Fix DO_NOT_TRACK to work in browser environments using VITE_DO_NOT_TRACK
- Also respect browser's navigator.doNotTrack standard
- Update consent banner hint text to reference correct env var
- Add documentation comments for all configuration options
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: hardcode PostHog credentials for centralized telemetry
- Use OpenHands PostHog project API key for all library users
- Use PostHog US Cloud endpoint (https://us.i.posthog.com/capture)
- Remove environment variable configuration for endpoint/API key
- Telemetry now automatically sends to centralized project when consent granted
- Users can still opt out via UI, VITE_DO_NOT_TRACK, or browser DNT setting
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: use separate PostHog API keys for dev and production
- Dev environment: phc_kBtz5nKmxVRRQ7HtPwr2QX9eMC5j65zE86QKocVNwb4U
- Production: phc_BgzfxKdgsYMLFTmJqt424ZoyVHvKFfrwttLimzdYTKFK
- Automatically selects key based on import.meta.env.DEV
Co-authored-by: openhands <openhands@all-hands.dev>
* chore: use single production PostHog API key everywhere
Simplify by using the same API key for all environments.
Co-authored-by: openhands <openhands@all-hands.dev>
* chore: rename telemetry events
- library_first_use → canvas_install
- library_session_start → canvas_new_session
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: migrate telemetry to PostHog SDK
Replace raw HTTP requests with PostHog SDK for:
- Automatic event batching
- Built-in retry logic with exponential backoff
- Offline support (queues events, sends when back online)
- Automatic session tracking
- Better device/browser info enrichment
Benefits:
- More reliable event delivery
- Reduced network requests
- Cleaner code with less manual state management
- Future-proof for feature flags, session replay, etc.
Co-authored-by: openhands <openhands@all-hands.dev>
* refactor: remove redundant hasTrackedFirstUse state in hook
The trackFirstUse() function already has built-in deduplication via
localStorage, so the local React state was unnecessary. Simplified
the hook and added a comment explaining the deduplication mechanism.
Co-authored-by: openhands <openhands@all-hands.dev>
* chore: remove obsolete telemetry dev server
The local dev server was used when telemetry used raw HTTP requests
to a configurable endpoint. Now that we use the PostHog SDK with
the real PostHog endpoint, this is no longer needed.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: remove trailing comma in package.json
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: address PR review feedback
- Make POSTHOG_API_KEY configurable via VITE_POSTHOG_API_KEY env var
- Make POSTHOG_HOST configurable via VITE_POSTHOG_HOST env var
- Add session deduplication using sessionStorage to prevent duplicate
canvas_new_session events from multiple hook instances
- Clear sessionStorage in clearTelemetryData()
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: use dynamic imports for PostHog SSR compatibility
- Convert top-level posthog-js import to dynamic import for SSR safety
- Add getPostHog() lazy loader that only imports in browser context
- Make setTelemetryConsent, clearTelemetryData, getPostHogInstance async
- Update documentation to clarify default telemetry destination
- Update tests for async function signatures
This ensures the library works correctly in SSR frameworks (Next.js, Remix,
etc.) that might import this module server-side.
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: add telemetry consent banner to app layout
The consent banner now appears on all pages until the user explicitly
accepts or declines telemetry. This ensures users are always prompted
for consent on their first visit regardless of which page they land on.
Co-authored-by: openhands <openhands@all-hands.dev>
* refactor: update telemetry consent banner to modal style
- Changed from bottom banner to centered modal overlay (matching OpenHands)
- Uses ModalBackdrop, ModalBody, BaseModalTitle, BaseModalDescription
- Single checkbox with 'Confirm preferences' button pattern
- Full-screen overlay blocks interaction until user makes a choice
- Added i18n keys: TELEMETRY$SEND_ANONYMOUS_DATA, TELEMETRY$CONFIRM_PREFERENCES
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: ensure PostHog is initialized before tracking events
- Made grantConsent/denyConsent in useTelemetry hook async to ensure
PostHog initialization completes before state update triggers tracking
- Updated tests for async consent functions
- This fixes a race condition where trackFirstUse() could be called before
PostHog's opt_in_capturing() had been executed
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: add automation backend integration with standalone ingress proxy
- Add scripts/ingress.mjs: standalone HTTP reverse proxy for routing traffic
to multiple backends based on URL path prefix
- Add scripts/dev-with-automation.mjs: orchestrates full stack with
agent-server, automation backend (both via uvx), Vite, and ingress
- Make 'npm run dev' run full stack by default (was dev:safe, now dev:automation)
- Rename 'npm run dev:safe' to 'npm run dev:minimal' for agent-server + Vite only
- Update README with new quickstart showing full stack as default
- Update AGENTS.md with architecture documentation
Architecture:
http://localhost:8000 (Ingress)
├── /api/automation/* → Automation Backend (:18001)
├── /api/*, /sockets → Agent Server (:18000)
└── /* (default) → Vite Dev Server (:3001)
* test: add tests for ingress and dev-with-automation scripts
- Add __tests__/scripts/ingress.test.ts with 14 tests covering:
- CLI argument parsing (--help, --port, --route, --default)
- Route matching (exact, prefix, longest-match-first)
- Proxy functionality (forwarding, query params, error handling)
- 502 response when backend unavailable
- 503 response for unmatched routes with no default
- Add __tests__/scripts/dev-with-automation.test.ts with 19 tests covering:
- buildAutomationCommand() with various git refs/repos
- buildConfig() port and path configuration
- CLI --help output
- Graceful exit when uvx is missing
- Export testable functions from dev-with-automation.mjs
* fix: prevent dev-with-automation from auto-executing when imported
The script was calling main() unconditionally, which caused test failures
when vitest imported the module. Now check if the module is the main entry
point before executing.
---------
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: hieptl <hieptl.developer@gmail.com>
Port of OpenHands/OpenHands#14284. The LLM model badge in the conversation
header was constrained to max-w-[150px] with an inner `truncate`, which
cut off long model identifiers such as `litellm_proxy/claude-sonnet-4-5-20250929`
to `litellm_proxy/cl…`. Drop the width cap and inner truncate, and apply
`whitespace-nowrap` to the outer span so the full name renders inline.
Also adds scripts/record-demo.mjs - a small playwright recorder used to
capture the verification GIF under .pr/issue-135/ - and updates the
existing test to assert the un-truncated structure.
Closes#135.
Co-authored-by: openhands <openhands@all-hands.dev>
The openhands-agent-server package exposes an executable named
'agent-server', not 'openhands-agent-server'. When using PyPI versions
(either specific or latest), we need to use the --from syntax:
uvx --from openhands-agent-server agent-server
This fixes the error:
An executable named 'openhands-agent-server' is not provided by
package 'openhands-agent-server'.
Use 'uvx --from openhands-agent-server agent-server' instead.
Fixes#117
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: use agent server APIs for settings persistence
- Replace localStorage with HTTP API for settings storage
- Use `X-Expose-Secrets: encrypted` header for GET /api/settings
to receive encrypted secrets (not exposing raw values)
- Use `secrets_encrypted: true` in start conversation payload
- Add `getSettingsForConversation()` to build encrypted settings
payload for conversation start endpoint
- Update secrets service to use /api/settings/secrets endpoints
- Add mock handlers for settings and secrets API endpoints
- Update tests for new API-based settings flow
This integrates with software-agent-sdk PR #3060
(feat/encrypted-secrets-in-transit) which adds server-side
encryption support for secrets in transit.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: update test mocks for encrypted settings API and add OH_SECRET_KEY support
- Update use-create-conversation-metadata.test.ts to mock getSettingsForConversation()
which is now called by buildStartConversationRequestWithEncryptedSettings
- Skip flaky onOpen websocket test that times out intermittently in CI
- Add OH_SECRET_KEY environment variable support in dev-safe.mjs:
- Uses default key for local development
- Can be overridden via OH_SECRET_KEY environment variable
- Logs secret key source at startup
Co-authored-by: openhands <openhands@all-hands.dev>
* docs: update AGENTS.md for settings API and OH_SECRET_KEY
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: update secrets service to use agent-server API routes
Changes:
- Update SecretsService to use /api/settings/secrets endpoints instead of /api/v1/secrets
- Simplify secrets-service.types.ts to remove unused pagination types
- Update use-get-secrets hook to do client-side filtering (agent-server doesn't support pagination)
- Update mock handlers to only use agent-server API routes
- Update secrets-settings test to mock getSecrets instead of searchSecrets
- Remove pageSize option from useSearchSecrets since agent-server doesn't paginate
The agent-server API routes (per SDK PR #3060):
- GET /api/settings/secrets - List secrets (names/descriptions only)
- GET /api/settings/secrets/{name} - Get secret value
- PUT /api/settings/secrets - Upsert secret
- DELETE /api/settings/secrets/{name} - Delete secret
Co-authored-by: openhands <openhands@all-hands.dev>
* docs: update AGENTS.md for secrets API routes
- Document the agent-server secrets CRUD routes in MSW handlers list
- Update git provider token persistence note to reflect server-side storage
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: update secret name validation to match agent-server requirements
- Change pattern from '^\S*$' (no whitespace) to '^[a-zA-Z][a-zA-Z0-9_]{0,63}$'
- Add title prop to SettingsInput component for validation error messages
- Secret names must: start with letter, contain only letters/numbers/underscores, be 1-64 chars
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: include custom secrets in conversation requests via LookupSecret
Custom secrets configured in Settings > Secrets are now automatically
included in conversation start requests. Instead of exposing secret values
to the frontend, we use LookupSecret entries that point to the agent-server
endpoint /api/settings/secrets/{name}. The agent-server fetches the actual
values at runtime.
Changes:
- Add LookupSecret interface to agent-server-adapter.ts
- Add customSecrets option to StartConversationOptions
- Build LookupSecret entries for each custom secret in buildStartConversationRequest
- Update buildStartConversationRequestWithEncryptedSettings to fetch and include
custom secrets list from SecretsService.getSecrets()
- Include X-Session-API-Key header in LookupSecret when configured
This ensures secrets never touch the frontend in plaintext while still
making them available to conversations.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: address review comments - no localStorage fallback, retry logic, SDK docs
Review feedback addressed:
1. secrets-service.ts: Server storage MUST succeed before updating localStorage
- addGitProvider now stores to server FIRST, only updates localStorage on success
- createSecret/updateSecret/deleteSecret now throw on failure (no silent returns)
- Added retry logic with exponential backoff for all API calls
2. settings-service.api.ts: No silent fallback for encrypted settings
- getSettingsForConversation now throws if encrypted fetch fails
- Conversations should not start with broken/redacted credentials
- Added retry logic with exponential backoff
3. AGENTS.md: Document SDK dependency
- Settings persistence APIs require SDK PR #3060
- Until released, npm run dev defaults to main branch
- Documented git provider storage design (server + localStorage)
4. dev-safe.mjs: Default to SDK main branch
- Added DEFAULT_GIT_REF='main' constant
- npm run dev now uses main until settings APIs are released
- TODO comment to update once released
Note: Git provider tokens still use localStorage for frontend git API calls
(repo search, branches), but MUST succeed on server first.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: update server secret when only host changes
When updating just the host (empty token), the server secret's description
must also be updated to keep metadata in sync. Previously, only localStorage
was updated, violating the 'server storage must succeed first' principle.
Now the host-only update path also calls createSecret() to update the
server secret's description before updating localStorage.
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
* feat: use uvx for temporary agent-server installation in dev mode
- Replace direct agent-server CLI invocation with uvx temporary install
- Add OH_AGENT_SERVER_VERSION env var for specific PyPI versions
- Add OH_AGENT_SERVER_GIT_REF env var for git commits/branches
- Auto-install uv in .openhands/setup.sh if not present
- Update documentation (README, DEVELOPMENT.md, AGENTS.md)
- Add comprehensive tests for buildAgentServerCommand()
This removes the requirement to permanently install agent-server via
'uv tool install'. Users only need uv installed, and npm run dev will
automatically download and run the appropriate agent-server version.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: use subdirectory syntax for git ref in uvx monorepo
The software-agent-sdk is a uv workspace monorepo with packages in
subdirectories (openhands-agent-server/, openhands-tools/, etc.).
When installing from git, uvx requires the #subdirectory= fragment to
specify which package to install from the workspace.
Tested with: OH_AGENT_SERVER_GIT_REF=main npm run dev
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
Restore the default foreground color on the themed AgentServerUIRoot wrapper so inherited text and currentColor icons match the pre-scoping dark theme again.
Co-authored-by: openhands <openhands@all-hands.dev>
Add actionable dev-safe output when agent-server is missing, including README and uv installation guidance, and update tests plus quickstart docs.
Co-authored-by: openhands <openhands@all-hands.dev>