mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 16:38:34 +08:00
* feat(telemetry): add posthog events for install, conversation creation, and prebuilt automation - canvas_install already fires pre-consent via telemetry.ts - Add conversation_created to NPM library telemetry (trackEvent) in use-create-conversation.ts onSuccess - Add prebuilt_automation_enabled event in recommended-automations-launcher.tsx when a catalog automation is successfully launched - Add prebuilt_automation_enabled event in automations-list.tsx and automation-detail.tsx handleToggle when enabling (not disabling) - All consent-gated events route through telemetry.ts to PostHog prod via POSTHOG_PROD_KEY baked in by build:lib Co-authored-by: openhands <openhands@all-hands.dev> * refactor: align posthog tracking with existing useTracking hook pattern - Add trackPrebuiltAutomationEnabled to useTracking hook (typed, named, includes commonProperties like all other tracked events) - Remove duplicate conversation_created trackEvent call in use-create-conversation (trackConversationCreated already covers it) - Replace all trackEvent(telemetry.ts) calls in automation routes and recommended-automations-launcher with useTracking hook functions - Add trackPrebuiltAutomationEnabled to useCallback dep array in launcher Co-authored-by: openhands <openhands@all-hands.dev> * refactor: implement dual-system PostHog tracking (Option C) System 1 — telemetry.ts (anonymous, library-level): - canvas_install and canvas_new_session remain in telemetry.ts - trackEvent() stays as the public library API (lib/index.ts export) - Rule: never import trackEvent in app routes/components System 2 — useTracking hook (identified, app-level): - Wire posthog_client_key from VITE_POSTHOG_CLIENT_KEY env var in OptionService.getConfig() so PostHogProvider mounts when key is set - Document VITE_POSTHOG_CLIENT_KEY in .env.sample - Add 6 typed tracking functions to useTracking: trackInitialQuerySubmitted, trackUserMessageSent, trackDownloadVsCodeButtonClicked, trackSettingsSaved, trackMcpConfigUpdated, trackDownloadTrajectoryButtonClicked - All include commonProperties (current_url, user_email) automatically - Migrate all 6 raw usePostHog()/posthog.capture() call sites: chat-interface.tsx, conversation-card.tsx, settings-form.tsx, use-save-settings.ts, use-download-conversation.ts - Rule: never call posthog.capture() directly from components Consent unification: - AnalyticsConsentFormModal now calls setTelemetryConsent() after saving user_consents_to_analytics so both systems honour the same decision Documentation: - AGENTS.md: tracking architecture section with boundary rules, system ownership table, and instructions for adding new events Co-authored-by: openhands <openhands@all-hands.dev> * fix: treat null consent as opted-out in useSyncPostHogConsent PostHog defaults to capturing enabled on init. The previous hook only called opt_out_capturing() when user_consents_to_analytics was explicitly false, leaving a window where events fired while settings were loading (null) or on first visit before the user had made a decision. Changes: - null and false both route to opt_out_capturing() — only an explicit true opts PostHog in - Remove hasSyncedRef: the hook now reacts to every settings change, which correctly handles consent granted in another tab or after the initial load. handleCaptureConsent is idempotent so repeated calls are safe. - Update comment to document the three-state consent model Co-authored-by: openhands <openhands@all-hands.dev> * test: add unit tests for tracking and consent changes New test files: - use-sync-posthog-consent.test.ts (7 tests) Covers the null/false/true consent states and reactive re-sync behaviour. Specifically tests the bug fix: null now opts out. - use-tracking.test.ts (17 tests) One test per tracking function. Verifies correct event names, property mapping (snake_case / SCREAMING_SNAKE_CASE), and that commonProperties (current_url, user_email) are included. Also covers git_user_email fallback and null user_email. - analytics-consent-form-modal.test.tsx (5 tests) Verifies setTelemetryConsent is called with 'granted'/'denied' matching the form checkbox state on submission. - use-save-settings.test.ts (4 tests) Covers trackMcpConfigUpdated: fires on new config, skips on absent config, skips on same object reference, handles zero counts. - use-download-conversation.test.ts (3 tests) Covers trackDownloadTrajectoryButtonClicked: called on download, called before the API request, still called even when API rejects. Updated test files: - option-service.test.ts Two new cases for posthog_client_key: returns key from env var when set, returns null when env var is absent. - chat-interface.test.tsx Added vi.mock('#/hooks/use-tracking') to prevent test noise. New 'Tracking' describe block: verifies trackInitialQuerySubmitted fires on first message and trackUserMessageSent on follow-ups. - conversation-card.test.tsx Added vi.mock('#/hooks/use-tracking') guard. - settings-form.test.tsx Added vi.mock('#/hooks/use-tracking') guard. New test: verifies trackSettingsSaved is called with LLM_API_KEY_SET and SEARCH_API_KEY_SET on form submission. Co-authored-by: openhands <openhands@all-hands.dev> * fix(tests): resolve two CI failures in tracking tests use-tracking.test.ts: COMMON.current_url was hardcoded as 'http://localhost/' but jsdom in CI runs at 'http://localhost:3000/'. Change COMMON from a module-level const to a let populated in beforeEach via window.location.href so it always matches the environment. chat-interface.test.tsx: trackUserMessageSent was never called (0 times) because totalEvents is derived from uiEvents via useFilteredEvents, not from the events array. The test was setting events but leaving uiEvents empty, so totalEvents stayed 0 and the component took the trackInitialQuerySubmitted branch instead. Seed uiEvents with a minimal MessageEvent (has llm_message.role + content, which isMessageEvent and shouldRenderAgentServerEvent both accept) so totalEvents = 1. Co-authored-by: openhands <openhands@all-hands.dev> * style: fix prettier formatting in option-service getConfig ?? null was split onto its own line but the full expression fits within the 80-char printWidth (79 chars), so prettier requires them joined on one line. Co-authored-by: openhands <openhands@all-hands.dev> * fix(tests): resolve 4 TypeScript type errors in test files analytics-consent-form-modal.test.tsx: Spread of unknown[] is not valid — TS requires a tuple type or rest param. Replace (...args: unknown[]) => mock(...args) with a typed single-arg forwarder: (consent: string) => mock(consent). use-save-settings.test.ts: MCPConfig requires shttp_servers (added in a later migration). Add shttp_servers: [] to all three fixture objects (full config, sharedConfig reference-equality test, and zero-count test). Co-authored-by: openhands <openhands@all-hands.dev> * fix(tests): type setTelemetryConsentMock to accept a string arg vi.fn(() => Promise.resolve()) infers zero parameters, so calling it with a string argument fails TS2554. Add _consent: string to the implementation so the inferred mock signature matches the call. Co-authored-by: openhands <openhands@all-hands.dev> * fix(tracking): add explicit consent guard to useTracking All tracking functions now require posthog to be initialized AND user_consents_to_analytics === true before calling posthog.capture(). Previously, events were blocked only via PostHog's opt-out mechanism (set by useSyncPostHogConsent), leaving three gaps: 1. posthog undefined (no VITE_POSTHOG_CLIENT_KEY) - silent no-op 2. Desync between useSyncPostHogConsent and useTracking can cause events to fire or be dropped depending on hook ordering 3. No self-documenting enforcement of the documented requirement that System 2 requires user_consents_to_analytics === true The new internal track() helper short-circuits when either condition is unmet, so each tracking function is self-contained and the consent contract is explicit in the code that enforces it. Tests: beforeEach now includes user_consents_to_analytics: true; posthog mock is made variable so the undefined case can be tested; new 'consent gate' describe block covers all blocked cases (false, null, undefined settings, no posthog). Co-authored-by: openhands <openhands@all-hands.dev> * feat: add usePostHogIdentify hook for cloud-mode PostHog identity Calls posthog.identify(userId, { email }) for cloud users who have granted analytics consent. Required because PostHogProvider is initialized with person_profiles='identified_only', which silently drops all events until identify() has been called. Identity lifecycle: - consent === true + userId present → posthog.identify() - consent === false (explicit denial) → posthog.reset() - userId becomes null after identify (logout) → posthog.reset() - consent === null / settings loading → no-op userId sourced from useCloudCurrentUserId() (cloud mode only). Local mode is skipped — no stable server-issued user ID available. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev>
41 lines
3.1 KiB
Bash
41 lines
3.1 KiB
Bash
# OpenHands Agent Server target
|
|
# These defaults assume you are manually pointing the frontend at a backend on
|
|
# 127.0.0.1:8000. The recommended local workflow is `npm run dev`, which starts
|
|
# an isolated local backend for this checkout. Use `npm run dev:frontend` only
|
|
# when you intentionally want to point at a separately managed backend.
|
|
VITE_BACKEND_HOST="127.0.0.1:8000" # Host:port used by the Vite dev proxy
|
|
VITE_BACKEND_BASE_URL="http://127.0.0.1:8000" # Base URL used by browser-side direct requests
|
|
# VITE_SESSION_API_KEY="" # Set to the same value as backend SESSION_API_KEY or OH_SESSION_API_KEYS_0 when auth is enabled
|
|
# VITE_WORKING_DIR="/workspace/project/agent-canvas" # Base dir for per-conversation working_dirs. Each conversation's working_dir is <VITE_WORKING_DIR>/<id_hex>. Defaults to <OH_CANVAS_SAFE_STATE_DIR>/workspaces, which is the sibling of the agent server's <state_dir>/conversations/ persistence dir — both share the same <id_hex> per conversation.
|
|
# VITE_WORKER_URLS="" # Optional comma-separated worker URLs for the Browser tab
|
|
# VITE_ENABLE_BROWSER_TOOLS="true" # Set to false to omit BrowserToolSet from new conversations
|
|
# VITE_LOAD_PUBLIC_SKILLS="false" # Set to false to disable loading public skills from https://github.com/OpenHands/extensions (on by default)
|
|
|
|
# Frontend dev server
|
|
VITE_FRONTEND_PORT="3001" # Port to run the frontend application
|
|
VITE_USE_TLS="false" # Use HTTPS/WSS for proxied backend connections
|
|
VITE_INSECURE_SKIP_VERIFY="false" # Skip TLS certificate verification for proxied backend requests
|
|
|
|
# App-level PostHog project key — enables useTracking (identified, behaviour analytics).
|
|
# Events route to https://us.i.posthog.com under this project. Omit to disable the
|
|
# PostHogProvider entirely (useTracking calls are silently dropped).
|
|
# VITE_POSTHOG_CLIENT_KEY="phc_..."
|
|
|
|
# Deployment environment — controls which PostHog project key is compiled in.
|
|
# Set to "production" or "staging" only in real deployment build pipelines.
|
|
# Leave unset locally so developers never accidentally send telemetry.
|
|
# VITE_APP_ENV="production"
|
|
|
|
# Mocking / test helpers
|
|
VITE_MOCK_API="false" # Enable/disable API mocking with MSW
|
|
|
|
# `npm run dev` (scripts/dev-safe.mjs) — isolated local backend via uvx
|
|
# OH_CANVAS_SAFE_BACKEND_PORT="18000" # Port the spawned agent-server listens on
|
|
# OH_CANVAS_SAFE_VSCODE_PORT="18001" # Port forwarded to the embedded VS Code (defaults to backend port + 1)
|
|
# OH_CANVAS_SAFE_STATE_DIR="$HOME/.openhands/agent-canvas" # Where conversations and bash events are stored (tmux sockets live under /tmp)
|
|
|
|
# Agent server version selection (uvx) — listed in order of precedence (highest first)
|
|
# OH_AGENT_SERVER_LOCAL_PATH="" # Absolute path to a local software-agent-sdk checkout. Runs the local checkout via uvx with editable openhands-sdk/tools/workspace installs so source edits are picked up on restart. Highest precedence.
|
|
# OH_AGENT_SERVER_GIT_REF="" # Git commit SHA or branch name (e.g., "main", "abc1234"). Takes precedence over version.
|
|
# OH_AGENT_SERVER_VERSION="" # Specific PyPI version (e.g., "1.18.0"). Uses latest release if unset.
|