diff --git a/.env.example b/.env.example index dab70218..702dc04e 100644 --- a/.env.example +++ b/.env.example @@ -21,8 +21,16 @@ INTELLIGENCE_API_KEY= OPENAI_API_KEY= OPENAI_BASE_URL=https://api.openai.com/v1 OPENAI_MODEL= -# Optional opt-out of SDK usage telemetry (separate from conversation persistence). +# Keep the CLI-generated CPK_TELEMETRY_ID with its matching project key. +# The SDK uses it to connect OpenDots usage to the Intelligence account. +# CPK_TELEMETRY_ID= +# SDK usage telemetry is enabled by default (separate from conversations). +# See docs/TELEMETRY.md for signup reporting and validation. +# Optional sampling override (default 1): +# COPILOTKIT_TELEMETRY_SAMPLE_RATE=1 +# Either flag below opts out: # COPILOTKIT_TELEMETRY_DISABLED=true +# DO_NOT_TRACK=1 # Optional isolated read-only public-page browser for WEB_SEARCH_PROVIDER=browser. BROWSER_URL=http://127.0.0.1:4311 diff --git a/README.md b/README.md index 25ad8f0b..027f60d7 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ npx copilotkit@latest login npx copilotkit@latest project select ``` -`project select` writes a project key to `.env` as `CPK_INTELLIGENCE_API_KEY`. Then add `OPENAI_API_KEY` and `OPENAI_MODEL` to `.env` and restart `npm run dev`. +`project select` writes a project key to `.env` as `CPK_INTELLIGENCE_API_KEY`. Keep the generated `CPK_TELEMETRY_ID` with that key so SDK usage can resolve to your Intelligence account. Then add `OPENAI_API_KEY` and `OPENAI_MODEL` to `.env` and restart `npm run dev`. Do not run `copilotkit onboard` in this folder. OpenDots already contains its CopilotKit integration, and onboarding adds a second, generic one. @@ -183,7 +183,7 @@ Conversation messages, tool calls, and run events are persisted in the configure The configured model provider receives conversation context, including authorized page content and tool results. Local Intelligence does not make a remote model local: configure the app's model and the Automatic Learning model separately. Public-web research sends queries and selected URLs to Parallel by default; set `WEB_SEARCH_PROVIDER=disabled` to disable those tools. Speech and messaging integrations send data to their configured providers when used. -CopilotKit SDK telemetry collects usage metadata separately from conversation persistence. Set `COPILOTKIT_TELEMETRY_DISABLED=true` to opt out; see [CopilotKit telemetry](https://docs.copilotkit.ai/telemetry). Review [CopilotKit's privacy policy](https://www.copilotkit.ai/privacy-policy) and the policies and retention settings of each service you configure. Installing or renewing local Intelligence still requires CopilotKit sign-in and internet access. +CopilotKit SDK telemetry collects usage metadata separately from conversation persistence. OpenDots also records bounded browser setup stages and first successful assistant activation with a random installation ID; the same opt-out flags disable this tracking and purge pending setup events. See [browser setup telemetry](docs/SETUP-TELEMETRY.md). Runtime events are tagged `OpenDots`; see [signup and usage tracking](docs/TELEMETRY.md). Set `COPILOTKIT_TELEMETRY_DISABLED=true` or `DO_NOT_TRACK=1` to opt out; see [CopilotKit telemetry](https://docs.copilotkit.ai/telemetry). Review [CopilotKit's privacy policy](https://www.copilotkit.ai/privacy-policy) and the policies and retention settings of each service you configure. Installing or renewing local Intelligence still requires CopilotKit sign-in and internet access. ## Features diff --git a/compose.yml b/compose.yml index dca11473..be199b67 100644 --- a/compose.yml +++ b/compose.yml @@ -11,9 +11,12 @@ services: OWNER_ID: ${OWNER_ID:-opendots-owner} INTELLIGENCE_API_KEY: ${INTELLIGENCE_API_KEY:-} CPK_INTELLIGENCE_API_KEY: ${CPK_INTELLIGENCE_API_KEY:-} + CPK_TELEMETRY_ID: ${CPK_TELEMETRY_ID:-} INTELLIGENCE_API_URL: ${INTELLIGENCE_API_URL:-} INTELLIGENCE_GATEWAY_WS_URL: ${INTELLIGENCE_GATEWAY_WS_URL:-} INTELLIGENCE_WS_URL: ${INTELLIGENCE_WS_URL:-} + DO_NOT_TRACK: ${DO_NOT_TRACK:-0} + COPILOTKIT_TELEMETRY_SAMPLE_RATE: ${COPILOTKIT_TELEMETRY_SAMPLE_RATE:-1} COPILOTKIT_TELEMETRY_DISABLED: ${COPILOTKIT_TELEMETRY_DISABLED:-false} VOICE_API_KEY: ${VOICE_API_KEY:-} VOICE_MODEL: ${VOICE_MODEL:-} diff --git a/docs/SETUP-TELEMETRY.md b/docs/SETUP-TELEMETRY.md new file mode 100644 index 00000000..bde0a266 --- /dev/null +++ b/docs/SETUP-TELEMETRY.md @@ -0,0 +1,33 @@ +# Browser setup telemetry + +OpenDots tracks its existing browser setup flow through the server's CopilotKit ingest contract. This is the web distribution; OpenDots has no native installer, engine installation, Windows prerequisite, image-pull wizard, or model/harness picker. Those desktop events are not applicable. + +Telemetry is enabled by default. Either `DO_NOT_TRACK=true|1` or `COPILOTKIT_TELEMETRY_DISABLED=true|1` disables the setup emitter and runtime. Restart after changing the server environment. Opt-out deletes the setup tracking row, including its pending queue and fallback installation identity. It does not change the owner, conversations, or application settings. Sampling rate zero also suppresses setup events. + +| Event | Closed properties | +| ------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| `oss.runtime.setup_step_viewed` | `kind=step_viewed`, step: `setup_required`, `ready`, `settings` | +| `oss.runtime.setup_failed` | `kind=setup_failed`, step enum, error class: `configuration_missing`, `assistant_run_failed`, `channel_start_failed` | +| `oss.runtime.setup_activated` | `kind=activated` | +| `oss.runtime.setup_abandoned` | `kind=setup_abandoned`, last step enum | + +`ready` means required server configuration exists. Activation requires an assistant message with nonempty streamed text and a successful AG-UI `RUN_FINISHED`, with no run error, abort, or timeout. It is recorded once per installation and remains deduplicated across server restarts. User messages, partial answers, tool-only runs, and configuration readiness do not activate the installation. Server-managed conversations, including Slack when configured, share this installation activation. + +The authenticated `/api/setup-telemetry` route accepts only strict step-view and abandonment objects. It rejects unknown keys, invalid steps, arbitrary properties, client-forged activation/failure, and readiness inconsistent with server state. Existing owner-token, origin, cross-site, content-type, and body-size guards apply. Settings visibility does not expose environment configuration values. + +Metadata names OpenDots, `opendots_distribution=web`, numeric release `opendots_version=0.1.0`, `runtime_env=node`, server platform (`macos|windows|linux|other`) and architecture (`aarch64|x86_64|other`), and a random persistent `installation_id`. Server platform identifies the server, including its container; it does not infer the browser OS. There are no prompts, answers, raw errors, keys, URLs, file paths, model names, emails, owner IDs, browser user agents, or machine-derived identifiers in setup events. + +The current nonblank `CPK_TELEMETRY_ID` takes precedence as the ingest header and runtime `telemetryId`, preserving the CLI project/account identity. If absent, both use the persistent random installation UUID. The installation UUID remains separate metadata even when the CLI identity is present. Removing the CLI variable returns to the installation fallback. The runtime receives matching metadata through `telemetryProperties`; no PostHog SDK, public capture key, or separate signup event is added. + +Events persist in the existing SQLite database before delivery. The queue holds at most 256 events, dropping oldest entries on overflow. UUID event IDs and Unix-second timestamps survive replay and retries. Delivery starts with server startup, retries every 15 seconds, and preserves events after a network or HTTP failure. Each pass has a two-second budget with a 1.5-second per-request timeout; shutdown delivery therefore finishes within approximately 3.5 seconds and leaves remaining events for restart. Diagnostics use bounded categories and never include transport exception details. + +Browser `pagehide` sends a best-effort authenticated keepalive abandonment event. Accepted abandonment clears the tracked step; shutdown and restart recover abandonment from an unfinished persisted step when needed. A closed tab, killed browser, inaccessible server, or failed browser-to-server request may lose an event before it reaches SQLite. Recovered abandonment describes the last observed installation setup step; it is not a definitive measure of tab exits, especially with concurrent tabs. HTTP ingest acknowledgement alone is not evidence of downstream PostHog acceptance. + +Local regression coverage includes offline replay across reopened databases, stable event IDs/identity, timestamp units, queue bounds, both opt-out variables and values, fallback/CLI precedence, recovered abandonment, activation deduplication, agent success/startup failures, assistant-role gating, timeout/abort suppression, bounded shutdown, schema rejection, and authenticated origin-protected routing. Tests intercept transports or inject a local recorder and do not send validation events to production: + +```sh +DO_NOT_TRACK=1 npm exec vitest run tests/setup-telemetry.test.ts tests/dot-agent-channel.test.ts tests/telemetry.test.ts tests/app.test.ts +npm run typecheck +``` + +Production delivery and PostHog attribution are separate from these local checks; see [TELEMETRY.md](TELEMETRY.md) for live validation and reporting. diff --git a/docs/SETUP.md b/docs/SETUP.md index 803f9c8e..274b3151 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -38,7 +38,7 @@ npx copilotkit@latest login npx copilotkit@latest project select ``` -`login` opens the browser to sign in or create an account. `project select` lets you select or create a project, and writes a project-scoped key to `.env` as `CPK_INTELLIGENCE_API_KEY`. It removes an `INTELLIGENCE_API_KEY` line, so that the file holds one credential. Use the same project for Slack and Automatic Learning below. +`login` opens the browser to sign in or create an account. `project select` lets you select or create a project, and writes a project-scoped key to `.env` as `CPK_INTELLIGENCE_API_KEY`. Keep the generated `CPK_TELEMETRY_ID` together with the project key when deploying, including Docker. It connects SDK telemetry to the selected Intelligence account; see [signup and usage tracking](TELEMETRY.md). It removes an `INTELLIGENCE_API_KEY` line, so that the file holds one credential. Use the same project for Slack and Automatic Learning below. Do not run `copilotkit onboard` in this folder. Onboarding is for apps that do not have CopilotKit yet. OpenDots already has its integration. @@ -89,7 +89,7 @@ The model environment variable names follow the configured provider adapter. Pro Select a Space to open its page library. Search for a document, switch between grid and list views, or create a new page. The visual editor supports formatting, headings, lists, checklists, tables, and slash commands. Use `/` to insert a block and Cmd/Ctrl+S to save immediately. Pages autosave after editing pauses; the save status tells you whether changes reached the server. -Page actions include creating subpages, moving a page within its Space, and editing Markdown source. Existing documents with unsupported visual-editor syntax stay in source mode to preserve their content. Manual editing works without conversation credentials. +Page actions include creating subpages, moving a page within its Space, deleting a page (its subpages move up to the deleted page's parent), and editing Markdown source. Existing documents with unsupported visual-editor syntax stay in source mode to preserve their content. Manual editing works without conversation credentials. Open a page's chat and choose a specialist with access to that Space. Grant access from the Dot’s settings in the sidebar. The server creates or reuses a CopilotKit Thread for that page and specialist. The Dot receives the current saved page as context and can read, create, and edit pages in its authorized Spaces. The page conversation uses that page’s Space by default; other chats use the Dot’s default page destination. Save your manual edits before asking it to revise the document. Revision checks reject stale writes; a conflict keeps your local draft available for recovery. Failed saves stop automatic retries until you retry or resolve the conflict, so a disconnected session does not silently replace newer content. diff --git a/docs/TELEMETRY.md b/docs/TELEMETRY.md new file mode 100644 index 00000000..009d435e --- /dev/null +++ b/docs/TELEMETRY.md @@ -0,0 +1,162 @@ +# Intelligence signup and OpenDots usage tracking + +OpenDots uses the CopilotKit SDK's existing telemetry transport. There is no +separate PostHog SDK or PostHog key to configure in this application. + +## Configure the server + +Run these commands from the OpenDots root: + +```sh +npx copilotkit@latest login +npx copilotkit@latest project select +``` + +Keep both generated values in the server environment: + +| Variable | Purpose | +| -------------------------- | ------------------------------------------------------------------------------------------ | +| `CPK_INTELLIGENCE_API_KEY` | Server-only project key for persistent conversations. | +| `CPK_TELEMETRY_ID` | CLI-issued project binding that the telemetry sink can resolve to an Intelligence account. | + +Deploy the values for the selected project together. Do not generate your own +telemetry ID, reuse another project's ID, or put either value in client-side +variables. An API key alone does not give runtime telemetry a resolvable identity. +If the CLI reports an identity provisioning failure, resolve that failure and +run project selection again before claiming account attribution works. + +Docker Compose forwards `CPK_TELEMETRY_ID` from `.env` into the app container. +For other deployment environments, copy the CLI-generated identity and its +matching project key into the server environment, then restart the server. +Keep the identity server-side. Leave `OWNER_ID` and conversation/thread IDs +unchanged; they scope app data and are not the signup account identity. + +The SDK sends existing runtime events to +`https://telemetry.copilotkit.ai/ingest`. OpenDots adds +`accessibility_title: "OpenDots"`. The current SDK defaults to full sampling +(`1`), and Docker Compose uses the same default. An explicit +`COPILOTKIT_TELEMETRY_SAMPLE_RATE` override is respected. + +Remove old deployment settings that disable telemetry if tracking is desired. +To opt out, set either `COPILOTKIT_TELEMETRY_DISABLED=true` or `DO_NOT_TRACK=1` +and restart the API. Both variables accept `true` or `1`. The `npm test` command disables telemetry for ordinary automated fixtures. + +## What the events measure + +- `intelligence_signup`: an Intelligence account created in Clerk, emitted by + the existing backend sync with the Clerk user ID as its PostHog `distinct_id`. +- `oss.runtime.instance_created`: the OpenDots runtime handler was created. +- `oss.runtime.copilot_request_created`: a request reached the runtime; this is + usage evidence, not proof of a successful model answer. + +The sink enriches runtime events with `telemetry_id`, and with `clerk_user_id` +when that identity resolves. Runtime and signup events can have different +PostHog `distinct_id` values: join the runtime `clerk_user_id` property to the +signup `distinct_id` rather than assuming a standard person funnel joins them. + +This connects completed signups to OpenDots usage. It does not prove OpenDots +was the acquisition source: the account might predate OpenDots. Measuring +acquisition requires carrying a source through the Intelligence signup flow. +OpenDots does not emit a duplicate signup event on startup, login or key entry. + +## Validate in PostHog + +After deploying, restart the API and make one chat request. In +[PostHog project 26816](https://eu.posthog.com/project/26816), run: + +```sql +SELECT event, timestamp, distinct_id, + properties.accessibility_title AS app, + properties.telemetry_id AS telemetry_id, + properties.clerk_user_id AS clerk_user_id +FROM events +WHERE event IN ( + 'oss.runtime.instance_created', + 'oss.runtime.copilot_request_created' + ) + AND properties.accessibility_title = 'OpenDots' + AND timestamp >= now() - INTERVAL 1 DAY +ORDER BY timestamp DESC +LIMIT 20 +``` + +Confirm the tag, the expected telemetry identity and the resolved Clerk subject. +Then find that subject's `intelligence_signup` event. To count accounts signed +up in the last 30 days that also used OpenDots in that period: + +Run the saved [signup-account query](telemetry/opendots-signups.hogql.sql). +It joins requests to signup accounts by Clerk ID. If a request lacks Clerk +enrichment, it uses the latest resolved startup/runtime event with the same +telemetry identity in the reporting window. This avoids assuming every request +has a resolved account, and avoids joining unrelated installations by IP or +PostHog person ID. The [daily usage query](telemetry/opendots-usage.hogql.sql) +uses the same identity lookup. Both queries exclude marked validation events +from requests and identity lookups. + +An HTTP `202` from ingest alone does not prove PostHog delivery. If the event +or identity enrichment is missing, check the deployed server environment and +the `TelemetrySinkIngest` / `TelemetrySinkFanout` CloudWatch logs. The canonical +reporting surface is [dashboard 664553](https://eu.posthog.com/project/26816/dashboard/664553). + +## Local verification + +`npm test` disables production telemetry for ordinary fixtures. The telemetry +tests start fresh processes, intercept SDK fetch calls before importing it, +and check the OpenDots tag, CLI-issued identity header, full sampling, explicit +sampling override, both opt-outs and absence of the project key in the payload. +These checks prove the local SDK contract; production delivery and identity +resolution require the PostHog checks above. Internal `@copilotkit.ai` accounts +are excluded from signup sync, so validate a new external signup separately. + +Ask the reporting owner to add the OpenDots filter and Clerk-ID join to +its reporting rule. The query counts signup accounts associated with usage, +not signups caused by the template. Local Intelligence evaluation also requires +CopilotKit sign-in, but a runtime event by itself does not establish a new signup. + +## Reporting queries and validation evidence + +The saved [signup-account query](telemetry/opendots-signups.hogql.sql) and +[daily usage query](telemetry/opendots-usage.hogql.sql) exclude marked validation +probes. The [setup-event query](telemetry/opendots-setup.hogql.sql) and +[installation conversion query](telemetry/opendots-setup-conversion.hogql.sql) +report the [browser setup flow](SETUP-TELEMETRY.md). All four queries executed +successfully against the production PostHog query API. Use these as the OpenDots +reporting rule in PostHog. Request counts are +attempts, including requests rejected by the SDK; they are not successful answers. + +On October 5, 2026, a live SDK probe using the CLI-selected `opendots` project +sent `oss.runtime.instance_created` and `oss.runtime.copilot_request_created` +to the production sink. Both returned HTTP `202` with marker +`opendots-validation-be790d6a-ff3e-4ce9-be30-5e3741ffc707`. The request was stopped +at SDK body validation before an agent ran. It made no model call and created +no signup or customer conversation. + +Authenticated PostHog queries returned one event of each type, both with a +telemetry identity and resolved Clerk user ID. Both events resolved to a staff +account (`@copilotkit.ai`), which signup sync excludes; the matching signup +count was therefore zero. This proves live SDK delivery and account resolution, +not a new external signup. The saved reporting queries both executed successfully: +zero signup accounts and no non-validation daily usage rows at validation time. + +The validation key is permitted to execute HogQL but cannot read dashboard +664553 (`403`, missing `dashboard:read`). The queries are prepared and executed, +but were not saved as dashboard charts. Saving the reporting rule requires +dashboard and insight read/write scopes, or the reporting owner's intervention. + +A marked `oss.runtime.setup_step_viewed` from the production setup emitter also +reached PostHog with its installation UUID: +`opendots-setup-validation-1ddf8b49-4d6a-434e-b345-4982d48cd9a5`. Setup events +do not receive Clerk account enrichment from the shared fanout; only the two +exact runtime event types above do. Join setup and runtime by their shared +`installation_id` to associate setup with account-resolved usage. The runtime +continues to use the CLI identity as its transport identity. Activation and +abandonment edge cases are validated locally, not fabricated in production. + +The combined updated probe +`opendots-validation-70c8067c-aa54-48c5-8d37-725baca262cf` delivered a startup, +request, and setup-view event with one shared installation UUID. Startup +resolved to the Clerk account; the request retained telemetry identity but +lacked Clerk enrichment. This is why reporting uses the same-identity startup +lookup. A missing request Clerk ID alone is not evidence that the template +lost its identity. The shared sink's dependency resolution is separate from +this template's transport wiring. diff --git a/docs/telemetry/opendots-setup-conversion.hogql.sql b/docs/telemetry/opendots-setup-conversion.hogql.sql new file mode 100644 index 00000000..6d93c3fb --- /dev/null +++ b/docs/telemetry/opendots-setup-conversion.hogql.sql @@ -0,0 +1,29 @@ +-- Installation funnel. Ready configuration alone is not activation. +SELECT count() AS installations_seen, + countIf(saw_setup_required = 1) AS setup_required, + countIf(saw_ready = 1) AS ready, + countIf(activated = 1) AS first_successful_answer, + countIf(failed = 1) AS had_setup_failure, + countIf(abandoned = 1) AS reported_abandonment +FROM ( + SELECT properties.installation_id AS installation, + max(event = 'oss.runtime.setup_step_viewed' + AND properties.step = 'setup_required') AS saw_setup_required, + max(event = 'oss.runtime.setup_step_viewed' + AND properties.step = 'ready') AS saw_ready, + max(event = 'oss.runtime.setup_activated') AS activated, + max(event = 'oss.runtime.setup_failed') AS failed, + max(event = 'oss.runtime.setup_abandoned') AS abandoned + FROM events + WHERE event IN ( + 'oss.runtime.setup_step_viewed', + 'oss.runtime.setup_failed', + 'oss.runtime.setup_activated', + 'oss.runtime.setup_abandoned' + ) + AND properties.accessibility_title = 'OpenDots' + AND timestamp >= now() - INTERVAL 30 DAY + AND empty(toString(properties.validation_run_id)) + AND notEmpty(toString(properties.installation_id)) + GROUP BY installation +) diff --git a/docs/telemetry/opendots-setup.hogql.sql b/docs/telemetry/opendots-setup.hogql.sql new file mode 100644 index 00000000..92461c9a --- /dev/null +++ b/docs/telemetry/opendots-setup.hogql.sql @@ -0,0 +1,19 @@ +-- Setup events are separate from Intelligence signup events. +-- Browser abandonment is best-effort; activation means a successful answer. +SELECT event, + toString(properties.step) AS step, + toString(properties.error_class) AS error_class, + count() AS events, + uniqExact(toString(properties.installation_id)) AS installations +FROM events +WHERE event IN ( + 'oss.runtime.setup_step_viewed', + 'oss.runtime.setup_failed', + 'oss.runtime.setup_activated', + 'oss.runtime.setup_abandoned' + ) + AND properties.accessibility_title = 'OpenDots' + AND timestamp >= now() - INTERVAL 30 DAY + AND empty(toString(properties.validation_run_id)) +GROUP BY event, step, error_class +ORDER BY event, step, error_class diff --git a/docs/telemetry/opendots-signups.hogql.sql b/docs/telemetry/opendots-signups.hogql.sql new file mode 100644 index 00000000..554581fe --- /dev/null +++ b/docs/telemetry/opendots-signups.hogql.sql @@ -0,0 +1,35 @@ +-- Signup accounts with OpenDots requests in the same 30-day window. +-- Resolve a missing request Clerk ID from boot/runtime events with the same +-- telemetry identity. Validation probes are excluded from both sides. +SELECT count(DISTINCT signup.distinct_id) AS signups_with_opendots_usage +FROM events AS signup +INNER JOIN ( + SELECT DISTINCT clerk_user_id FROM ( + SELECT request.timestamp AS timestamp, + if(notEmpty(request.clerk_user_id), request.clerk_user_id, + identity.clerk_user_id) AS clerk_user_id + FROM ( + SELECT timestamp, toString(properties.telemetry_id) AS telemetry_id, + toString(properties.clerk_user_id) AS clerk_user_id + FROM events + WHERE event = 'oss.runtime.copilot_request_created' + AND properties.accessibility_title = 'OpenDots' + AND timestamp >= now() - INTERVAL 30 DAY + AND empty(toString(properties.validation_run_id)) + ) AS request + LEFT JOIN ( + SELECT toString(properties.telemetry_id) AS telemetry_id, + argMax(toString(properties.clerk_user_id), timestamp) AS clerk_user_id + FROM events + WHERE event IN ('oss.runtime.instance_created', 'oss.runtime.copilot_request_created') + AND properties.accessibility_title = 'OpenDots' + AND timestamp >= now() - INTERVAL 30 DAY + AND empty(toString(properties.validation_run_id)) + AND notEmpty(toString(properties.telemetry_id)) + AND notEmpty(toString(properties.clerk_user_id)) + GROUP BY telemetry_id + ) AS identity ON request.telemetry_id = identity.telemetry_id + ) WHERE notEmpty(clerk_user_id) +) AS usage ON signup.distinct_id = usage.clerk_user_id +WHERE signup.event = 'intelligence_signup' + AND signup.timestamp >= now() - INTERVAL 30 DAY diff --git a/docs/telemetry/opendots-usage.hogql.sql b/docs/telemetry/opendots-usage.hogql.sql new file mode 100644 index 00000000..925327ca --- /dev/null +++ b/docs/telemetry/opendots-usage.hogql.sql @@ -0,0 +1,32 @@ +-- Request attempts and resolved accounts, excluding validation probes. +-- Uses same-identity boot events when request enrichment is unavailable. +SELECT toDate(timestamp) AS day, count() AS request_attempts, + uniqExactIf(clerk_user_id, notEmpty(clerk_user_id)) AS accounts +FROM ( + SELECT request.timestamp AS timestamp, + if(notEmpty(request.clerk_user_id), request.clerk_user_id, + identity.clerk_user_id) AS clerk_user_id + FROM ( + SELECT timestamp, toString(properties.telemetry_id) AS telemetry_id, + toString(properties.clerk_user_id) AS clerk_user_id + FROM events + WHERE event = 'oss.runtime.copilot_request_created' + AND properties.accessibility_title = 'OpenDots' + AND timestamp >= now() - INTERVAL 30 DAY + AND empty(toString(properties.validation_run_id)) + ) AS request + LEFT JOIN ( + SELECT toString(properties.telemetry_id) AS telemetry_id, + argMax(toString(properties.clerk_user_id), timestamp) AS clerk_user_id + FROM events + WHERE event IN ('oss.runtime.instance_created', 'oss.runtime.copilot_request_created') + AND properties.accessibility_title = 'OpenDots' + AND timestamp >= now() - INTERVAL 30 DAY + AND empty(toString(properties.validation_run_id)) + AND notEmpty(toString(properties.telemetry_id)) + AND notEmpty(toString(properties.clerk_user_id)) + GROUP BY telemetry_id + ) AS identity ON request.telemetry_id = identity.telemetry_id +) +GROUP BY day +ORDER BY day diff --git a/package.json b/package.json index 0d3e3b76..b8184593 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "start": "node --env-file-if-exists=.env dist/server/server/index.js", "browser": "node --env-file-if-exists=.env --import tsx src/browser/index.ts", "browser:start": "node --env-file-if-exists=.env dist/server/browser/index.js", - "test": "vitest run", + "test": "cross-env DO_NOT_TRACK=1 vitest run", "typecheck": "tsc --noEmit", "lint": "eslint src tests vite.config.ts", "format": "prettier --write src tests *.json *.ts *.js index.html public .github Dockerfile \"compose*.yml\" deployment README.md docs .env.example --ignore-unknown", diff --git a/src/client/App.tsx b/src/client/App.tsx index 8f1861b3..e5cc6466 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -32,6 +32,7 @@ import type { WorkspaceState, } from '../shared/types'; import { api, ApiError, authHeaders, setToken } from './api'; +import { trackSetupStep } from './setup-telemetry'; import { applyCaptureResult, applyRefreshResult, @@ -46,6 +47,7 @@ import { ResultPane } from './ResultPane'; import { TaskRow } from './TaskPresentation'; import { TaskActions } from './TaskActions'; import { WorkspaceDialog, type Dialog } from './WorkspaceDialog'; +import { submitComposerOnEnter } from './chat-composer'; function describeFailure(error: unknown, fallback: string) { return { @@ -202,6 +204,17 @@ export function App() { (item) => item.id === selectedThread && item.dotId === dot?.id, ); const configured = !!workspace && workspace.setup.missing.length === 0; + const setupStep = workspace + ? dialog?.type === 'settings' + ? 'settings' + : configured + ? 'ready' + : 'setup_required' + : undefined; + useEffect(() => { + if (!setupStep) return; + return trackSetupStep(setupStep); + }, [setupStep]); const chooseDot = (next: Dot) => { setSelectedDot(next.id); setSelectedThread( @@ -468,7 +481,7 @@ export function App() { > Scheduled & activity - {state.tasks.length} + {state.tasks.length > 0 && {state.tasks.length}}