From f729aff4d0c92cdbed378b54fba84ae5236f47da Mon Sep 17 00:00:00 2001 From: Jalil Date: Fri, 24 Jul 2026 09:07:22 +0200 Subject: [PATCH 1/2] feat(connect): report desktop connection incidents --- apps/app/src/app/lib/den.ts | 20 + apps/app/src/i18n/locales/en.ts | 2 + .../connections/cloud-mcp-reconciler.ts | 8 +- .../connect-diagnostics-preferences.ts | 66 ++++ .../connect-diagnostics-reporter.ts | 357 ++++++++++++++++++ .../use-session-mcp-maintenance.ts | 7 + .../domains/settings/pages/connect-view.tsx | 14 + .../settings/pages/preferences-view.tsx | 17 + .../src/react-app/kernel/local-provider.tsx | 7 + .../src/react-app/shell/settings-route.tsx | 7 + .../connect-diagnostics-reporter.test.ts | 258 +++++++++++++ apps/server/src/cloud-mcp-health.test.ts | 36 +- apps/server/src/cloud-mcp-health.ts | 17 + packages/types/package.json | 5 + packages/types/src/den/connect-diagnostics.ts | 139 +++++++ packages/types/src/index.ts | 1 + packages/types/tsup.config.ts | 1 + 17 files changed, 960 insertions(+), 2 deletions(-) create mode 100644 apps/app/src/react-app/domains/connections/connect-diagnostics-preferences.ts create mode 100644 apps/app/src/react-app/domains/connections/connect-diagnostics-reporter.ts create mode 100644 apps/app/tests/connect-diagnostics-reporter.test.ts create mode 100644 packages/types/src/den/connect-diagnostics.ts diff --git a/apps/app/src/app/lib/den.ts b/apps/app/src/app/lib/den.ts index 81e64baf07..e11ffa077a 100644 --- a/apps/app/src/app/lib/den.ts +++ b/apps/app/src/app/lib/den.ts @@ -2,6 +2,7 @@ import { normalizeDesktopConfig, type DesktopConfig as SharedDesktopConfig, } from "@openwork/types/den/desktop-policies"; +import type { ConnectDiagnosticClientBatch } from "@openwork/types/den/connect-diagnostics"; // Re-export the shared schema under the local alias so React consumers // (e.g. the cloud domain's desktop-config provider) can import it alongside @@ -2120,6 +2121,25 @@ export function createDenClient(options: { baseUrl: string; token?: string | nul return minted; }, + async reportConnectDiagnosticIncidents( + orgId: string, + batch: ConnectDiagnosticClientBatch, + ): Promise { + const result = await requestJsonRaw(baseUrls, "/v1/diagnostics/connect-incidents", { + method: "POST", + token, + organizationId: orgId, + body: batch, + }); + if (!result.ok) { + throw new DenApiError( + result.status, + "connect_diagnostics_delivery_failed", + "OpenWork could not deliver connection diagnostics.", + ); + } + }, + async getWorkerTokens(workerId: string, orgId: string): Promise { const payload = await requestJson(baseUrls, `/v1/workers/${encodeURIComponent(workerId)}/tokens`, { method: "POST", diff --git a/apps/app/src/i18n/locales/en.ts b/apps/app/src/i18n/locales/en.ts index 48638ee059..b7e4eb43ca 100644 --- a/apps/app/src/i18n/locales/en.ts +++ b/apps/app/src/i18n/locales/en.ts @@ -1111,6 +1111,8 @@ export default { "settings.appearance_title": "Appearance", "settings.analytics_toggle": "Share anonymous usage data", "settings.analytics_toggle_desc": "Helps us understand which features matter. Never includes your messages, prompts, code, or file contents.", + "settings.connection_diagnostics_toggle": "Share desktop Connect diagnostics", + "settings.connection_diagnostics_toggle_desc": "Keeps metadata-only OpenWork Connect failures and recoveries for up to 24 hours on this device, then sends pseudonymous reports to OpenWork Diagnostics for seven days. Never includes messages, emails, tokens, URLs, or tool data.", "settings.audit_log_title": "Audit log", "settings.auto_compact": "Auto context compaction", "settings.privacy_section_desc": "Control what OpenWork shares to improve the product.", diff --git a/apps/app/src/react-app/domains/connections/cloud-mcp-reconciler.ts b/apps/app/src/react-app/domains/connections/cloud-mcp-reconciler.ts index ad4fa9f823..66173fecb7 100644 --- a/apps/app/src/react-app/domains/connections/cloud-mcp-reconciler.ts +++ b/apps/app/src/react-app/domains/connections/cloud-mcp-reconciler.ts @@ -3,6 +3,7 @@ import { type DenMcpTokenMintContext, resolveCloudMcpResourceUrl, } from "../../../app/lib/den"; +import { CONNECT_DIAGNOSTIC_CLIENT_HEADER } from "@openwork/types/den/connect-diagnostics"; import type { OpenworkCloudMcpEngineRefresh, OpenworkCloudMcpEngineRefreshResult, @@ -25,6 +26,7 @@ import { type CloudMcpScope, type CloudMcpUserState, } from "./cloud-mcp-user-state"; +import { getConnectDiagnosticsClientId } from "./connect-diagnostics-preferences"; export const OPENWORK_CLOUD_EXPECTED_TOOLS = [ "openwork-cloud_search_capabilities", @@ -180,6 +182,7 @@ export function buildOpenworkCloudMcpReconcilePayload(input: { const url = resolveMcpUrl(input.token, input.context.fallbackUrl); if (!workspaceId || !url) return null; const app = appMetadata(); + const diagnosticsClientId = getConnectDiagnosticsClientId(); return { workspaceId, name: CLOUD_MCP_SERVER_NAME, @@ -187,7 +190,10 @@ export function buildOpenworkCloudMcpReconcilePayload(input: { type: "remote", enabled: true, url, - headers: { Authorization: `Bearer ${input.token.token}` }, + headers: { + Authorization: `Bearer ${input.token.token}`, + ...(diagnosticsClientId ? { [CONNECT_DIAGNOSTIC_CLIENT_HEADER]: diagnosticsClientId } : {}), + }, oauth: false, }, tokenMetadata: tokenMetadata(input.token), diff --git a/apps/app/src/react-app/domains/connections/connect-diagnostics-preferences.ts b/apps/app/src/react-app/domains/connections/connect-diagnostics-preferences.ts new file mode 100644 index 0000000000..cfd15a998b --- /dev/null +++ b/apps/app/src/react-app/domains/connections/connect-diagnostics-preferences.ts @@ -0,0 +1,66 @@ +import { CONNECT_DIAGNOSTIC_CLIENT_RETENTION_MS } from "@openwork/types/den/connect-diagnostics"; + +import { LOCAL_PREFERENCES_KEY } from "@/react-app/kernel/local-preferences-storage"; + +export const CONNECT_DIAGNOSTIC_CLIENT_ID_KEY = "openwork.connectDiagnostics.clientId"; +export const CONNECT_DIAGNOSTIC_QUEUE_KEY = "openwork.connectDiagnostics.queue.v1"; +export const CONNECT_DIAGNOSTIC_FAILURE_STATE_KEY = "openwork.connectDiagnostics.failureState.v1"; + +const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu; + +function localStorageAvailable(): boolean { + return typeof window !== "undefined" && typeof window.localStorage !== "undefined"; +} + +export function isConnectDiagnosticsEnabled(): boolean { + if (!localStorageAvailable()) return false; + try { + const raw = window.localStorage.getItem(LOCAL_PREFERENCES_KEY); + if (!raw) return true; + const parsed = JSON.parse(raw) as unknown; + if (!parsed || typeof parsed !== "object") return true; + return (parsed as { connectionDiagnosticsEnabled?: unknown }).connectionDiagnosticsEnabled !== false; + } catch { + return true; + } +} + +function createUuid(): string | null { + if (typeof globalThis.crypto?.randomUUID === "function") { + return globalThis.crypto.randomUUID(); + } + return null; +} + +/** + * A random per-install identifier. Den hashes it before forwarding and the + * diagnostics service never receives the raw value. + */ +export function getConnectDiagnosticsClientId(): string | null { + if (!isConnectDiagnosticsEnabled()) return null; + try { + const existing = window.localStorage.getItem(CONNECT_DIAGNOSTIC_CLIENT_ID_KEY)?.trim() ?? ""; + if (UUID_V4_PATTERN.test(existing)) return existing; + const created = createUuid(); + if (!created) return null; + window.localStorage.setItem(CONNECT_DIAGNOSTIC_CLIENT_ID_KEY, created); + return created; + } catch { + return null; + } +} + +export function clearConnectDiagnosticLocalData(): void { + if (!localStorageAvailable()) return; + try { + window.localStorage.removeItem(CONNECT_DIAGNOSTIC_CLIENT_ID_KEY); + window.localStorage.removeItem(CONNECT_DIAGNOSTIC_QUEUE_KEY); + window.localStorage.removeItem(CONNECT_DIAGNOSTIC_FAILURE_STATE_KEY); + } catch { + // Best effort: storage may be unavailable or read-only. + } +} + +export function connectDiagnosticClientRetentionMs(): number { + return CONNECT_DIAGNOSTIC_CLIENT_RETENTION_MS; +} diff --git a/apps/app/src/react-app/domains/connections/connect-diagnostics-reporter.ts b/apps/app/src/react-app/domains/connections/connect-diagnostics-reporter.ts new file mode 100644 index 0000000000..97567cb95e --- /dev/null +++ b/apps/app/src/react-app/domains/connections/connect-diagnostics-reporter.ts @@ -0,0 +1,357 @@ +import { + CONNECT_DIAGNOSTIC_CLIENT_RETENTION_MS, + CONNECT_DIAGNOSTIC_NETWORK_CODES, + CONNECT_DIAGNOSTIC_PHASES, + connectDiagnosticClientEventSchema, + type ConnectDiagnosticClientEvent, + type ConnectDiagnosticNetworkCode, + type ConnectDiagnosticPhase, +} from "@openwork/types/den/connect-diagnostics"; + +import { recordInspectorEvent } from "@/app/lib/app-inspector"; +import { createDenClient, readDenSettings, type DenSettings } from "@/app/lib/den"; +import type { + OpenworkCloudMcpFailure, + OpenworkCloudMcpHealth, +} from "@/app/lib/openwork-server"; + +import { + CONNECT_DIAGNOSTIC_FAILURE_STATE_KEY, + CONNECT_DIAGNOSTIC_QUEUE_KEY, + clearConnectDiagnosticLocalData, + getConnectDiagnosticsClientId, + isConnectDiagnosticsEnabled, +} from "./connect-diagnostics-preferences"; + +const CONNECT_DIAGNOSTIC_QUEUE_LIMIT = 500; +const APP_VERSION = String(import.meta.env.VITE_OPENWORK_APP_VERSION ?? "").trim(); + +type QueuedConnectDiagnostic = { + queuedAt: number; + denBaseUrl: string; + organizationId: string; + event: ConnectDiagnosticClientEvent; +}; + +type FailureState = Record; +const flushInFlight = new Map>(); + +export type ConnectDiagnosticAttempt = { + outcome: "ready" | "skipped" | "failed"; + health: OpenworkCloudMcpHealth | null; + issue?: Pick< + OpenworkCloudMcpFailure, + "code" | "stage" | "retryable" | "requestId" | "referenceId" | "details" + > | null; + maintenanceAttempt: number; +}; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function normalizeTarget(value: string): string { + return value.trim().replace(/\/+$/u, ""); +} + +function targetKey(settings: Pick): string { + return JSON.stringify([normalizeTarget(settings.baseUrl), settings.activeOrgId?.trim() ?? ""]); +} + +function parseQueueItem(value: unknown): QueuedConnectDiagnostic | null { + if (!isRecord(value)) return null; + const queuedAt = typeof value.queuedAt === "number" && Number.isFinite(value.queuedAt) + ? value.queuedAt + : null; + const denBaseUrl = typeof value.denBaseUrl === "string" ? normalizeTarget(value.denBaseUrl) : ""; + const organizationId = typeof value.organizationId === "string" ? value.organizationId.trim() : ""; + const event = connectDiagnosticClientEventSchema.safeParse(value.event); + if (queuedAt === null || !denBaseUrl || !organizationId || !event.success) return null; + return { queuedAt, denBaseUrl, organizationId, event: event.data }; +} + +function readQueue(now = Date.now()): QueuedConnectDiagnostic[] { + if (typeof window === "undefined") return []; + try { + const parsed = JSON.parse(window.localStorage.getItem(CONNECT_DIAGNOSTIC_QUEUE_KEY) ?? "[]") as unknown; + if (!Array.isArray(parsed)) return []; + return parsed + .map(parseQueueItem) + .filter((item): item is QueuedConnectDiagnostic => + item !== null && now - item.queuedAt <= CONNECT_DIAGNOSTIC_CLIENT_RETENTION_MS) + .slice(-CONNECT_DIAGNOSTIC_QUEUE_LIMIT); + } catch { + return []; + } +} + +function writeQueue(queue: QueuedConnectDiagnostic[]): void { + if (typeof window === "undefined") return; + try { + window.localStorage.setItem( + CONNECT_DIAGNOSTIC_QUEUE_KEY, + JSON.stringify(queue.slice(-CONNECT_DIAGNOSTIC_QUEUE_LIMIT)), + ); + } catch { + // Best effort. Diagnostics must never interfere with Connect itself. + } +} + +function readFailureState(): FailureState { + if (typeof window === "undefined") return {}; + try { + const parsed = JSON.parse( + window.localStorage.getItem(CONNECT_DIAGNOSTIC_FAILURE_STATE_KEY) ?? "{}", + ) as unknown; + if (!isRecord(parsed)) return {}; + return Object.fromEntries( + Object.entries(parsed) + .filter((entry): entry is [string, number] => + typeof entry[1] === "number" && Number.isInteger(entry[1]) && entry[1] >= 0) + .map(([key, value]) => [key, Math.min(value, 10_000)]), + ); + } catch { + return {}; + } +} + +function writeFailureState(state: FailureState): void { + if (typeof window === "undefined") return; + try { + window.localStorage.setItem(CONNECT_DIAGNOSTIC_FAILURE_STATE_KEY, JSON.stringify(state)); + } catch { + // Best effort. + } +} + +function safePhase(stage: string | null | undefined): ConnectDiagnosticPhase { + return CONNECT_DIAGNOSTIC_PHASES.includes(stage as ConnectDiagnosticPhase) + ? stage as ConnectDiagnosticPhase + : "engine_delivery"; +} + +function safeErrorCode(code: string | null | undefined): string | null { + const value = code?.trim() ?? ""; + if (!value) return null; + return value.length <= 120 && /^[a-z0-9][a-z0-9_.-]*$/iu.test(value) + ? value + : "connect_failure"; +} + +function safeRequestId(issue: ConnectDiagnosticAttempt["issue"]): string | null { + const value = issue?.requestId?.trim() || issue?.referenceId?.trim() || ""; + return /^[a-z0-9][a-z0-9_.:-]{0,127}$/iu.test(value) ? value : null; +} + +function walkDiagnosticMetadata( + value: unknown, + visit: (record: Record) => string | number | null, + seen = new Set(), +): string | number | null { + if (!isRecord(value) || seen.has(value)) return null; + seen.add(value); + const direct = visit(value); + if (direct !== null) return direct; + for (const nested of Object.values(value)) { + if (isRecord(nested)) { + const found = walkDiagnosticMetadata(nested, visit, seen); + if (found !== null) return found; + } + } + return null; +} + +function networkCodeFrom(value: unknown): ConnectDiagnosticNetworkCode | null { + const allowed = new Set(CONNECT_DIAGNOSTIC_NETWORK_CODES); + const found = walkDiagnosticMetadata(value, (record) => { + for (const key of ["code", "networkCode", "cause"]) { + const candidate = typeof record[key] === "string" ? record[key].trim().toUpperCase() : ""; + if (allowed.has(candidate)) return candidate; + } + const message = typeof record.message === "string" + ? record.message.toUpperCase().replace(/[^A-Z0-9]+/gu, "_") + : ""; + for (const candidate of CONNECT_DIAGNOSTIC_NETWORK_CODES) { + if (message.includes(candidate)) return candidate; + } + return null; + }); + return typeof found === "string" ? found as ConnectDiagnosticNetworkCode : null; +} + +function httpStatusFrom(value: unknown): number | null { + const found = walkDiagnosticMetadata(value, (record) => { + for (const key of ["httpStatus", "status", "statusCode"]) { + const candidate = record[key]; + if (typeof candidate === "number" && Number.isInteger(candidate) && candidate >= 100 && candidate <= 599) { + return candidate; + } + } + return null; + }); + return typeof found === "number" ? found : null; +} + +function failedProbeStep(health: OpenworkCloudMcpHealth | null) { + return health?.tools.direct.trace?.steps.find((step) => !step.ok) ?? null; +} + +function compatibilityVersion(value: string | null | undefined): string | null { + const normalized = value?.trim() ?? ""; + const bounded = normalized.slice(0, 80); + return /^[a-z0-9][a-z0-9.+_() -]{0,79}$/iu.test(bounded) ? bounded : null; +} + +function platformCategory(): "macos" | "windows" | "linux" | "other" | null { + if (typeof navigator === "undefined") return null; + const platform = navigator.platform?.toLowerCase() ?? ""; + if (platform.includes("mac")) return "macos"; + if (platform.includes("win")) return "windows"; + if (platform.includes("linux")) return "linux"; + return "other"; +} + +function createEvent( + input: ConnectDiagnosticAttempt, + settings: DenSettings, + clientId: string, +): ConnectDiagnosticClientEvent | null { + if (input.outcome === "skipped") return null; + const failures = readFailureState(); + const key = targetKey(settings); + const previousFailures = failures[key] ?? 0; + const consecutiveFailures = input.outcome === "failed" + ? Math.min(10_000, previousFailures + 1) + : 0; + failures[key] = consecutiveFailures; + writeFailureState(failures); + + // Healthy maintenance is noise. Preserve only the transition that proves a + // previously failing client recovered. + if (input.outcome === "ready" && previousFailures === 0) return null; + + const probeStep = failedProbeStep(input.health); + const diagnosticDetails = input.issue?.details + ?? input.health?.firstFailure?.details + ?? probeStep?.error + ?? input.health?.tools.direct.error; + return connectDiagnosticClientEventSchema.parse({ + schemaVersion: 1, + eventId: crypto.randomUUID(), + attemptId: crypto.randomUUID(), + clientId, + observedAt: new Date().toISOString(), + phase: input.outcome === "failed" + ? safePhase(input.issue?.stage ?? input.health?.firstFailure?.stage) + : "mcp_request", + outcome: input.outcome === "failed" ? "failure" : "recovered", + errorCode: input.outcome === "failed" + ? safeErrorCode(input.issue?.code ?? input.health?.firstFailure?.code) + : null, + networkCode: input.outcome === "failed" ? networkCodeFrom(diagnosticDetails) : null, + httpStatus: input.outcome === "failed" + ? probeStep?.httpStatus ?? httpStatusFrom(diagnosticDetails) + : null, + retryable: input.outcome === "failed" ? input.issue?.retryable ?? null : null, + deviceOnline: typeof navigator === "undefined" || typeof navigator.onLine !== "boolean" + ? null + : navigator.onLine, + durationMs: typeof input.health?.durationMs === "number" + ? Math.max(0, Math.min(10 * 60 * 1_000, Math.round(input.health.durationMs))) + : null, + consecutiveFailures, + maintenanceAttempt: Math.max(1, Math.min(20, Math.round(input.maintenanceAttempt))), + appVersion: APP_VERSION || null, + platform: platformCategory(), + serverVersion: compatibilityVersion(input.health?.compatibility.openwork.serverVersion), + engineVersion: compatibilityVersion(input.health?.compatibility.opencode.actualVersion), + serverRequestId: input.outcome === "failed" + ? safeRequestId(input.issue) ?? safeRequestId(input.health?.firstFailure) + : null, + }); +} + +async function flushConnectDiagnosticQueueOnce(settings: DenSettings): Promise { + if (!isConnectDiagnosticsEnabled()) { + clearConnectDiagnosticLocalData(); + return 0; + } + const orgId = settings.activeOrgId?.trim() ?? ""; + const authToken = settings.authToken?.trim() ?? ""; + const denBaseUrl = normalizeTarget(settings.baseUrl); + if (!orgId || !authToken || !denBaseUrl) return 0; + + const queue = readQueue(); + const deliverable = queue + .filter((item) => item.denBaseUrl === denBaseUrl && item.organizationId === orgId) + .slice(0, 50); + if (deliverable.length === 0) { + writeQueue(queue); + return 0; + } + + await createDenClient({ baseUrl: settings.baseUrl, token: authToken }) + .reportConnectDiagnosticIncidents(orgId, { events: deliverable.map((item) => item.event) }); + const delivered = new Set(deliverable.map((item) => item.event.eventId)); + // Re-read after the request so an event queued while delivery was in flight + // is never overwritten by this older snapshot. + writeQueue(readQueue().filter((item) => !delivered.has(item.event.eventId))); + recordInspectorEvent("connect_diagnostics.delivered", { count: delivered.size }); + return delivered.size; +} + +export function flushConnectDiagnosticQueue(settings = readDenSettings()): Promise { + const key = targetKey(settings); + const existing = flushInFlight.get(key); + if (existing) return existing; + const pending = flushConnectDiagnosticQueueOnce(settings).finally(() => { + if (flushInFlight.get(key) === pending) flushInFlight.delete(key); + }); + flushInFlight.set(key, pending); + return pending; +} + +export function recordConnectDiagnosticAttempt( + input: ConnectDiagnosticAttempt, + settings = readDenSettings(), +): void { + if (!isConnectDiagnosticsEnabled()) { + clearConnectDiagnosticLocalData(); + return; + } + const clientId = getConnectDiagnosticsClientId(); + const orgId = settings.activeOrgId?.trim() ?? ""; + const denBaseUrl = normalizeTarget(settings.baseUrl); + if (!clientId || !orgId || !settings.authToken?.trim() || !denBaseUrl) return; + let event: ConnectDiagnosticClientEvent | null = null; + try { + event = createEvent(input, settings, clientId); + } catch (error) { + recordInspectorEvent("connect_diagnostics.event_rejected", { + errorType: error instanceof Error ? error.name : typeof error, + }); + } + if (event) { + const queue = readQueue(); + queue.push({ queuedAt: Date.now(), denBaseUrl, organizationId: orgId, event }); + writeQueue(queue); + recordInspectorEvent("connect_diagnostics.queued", { + clientId, + eventId: event.eventId, + outcome: event.outcome, + phase: event.phase, + errorCode: event.errorCode, + }); + } + void flushConnectDiagnosticQueue(settings).catch((error: unknown) => { + recordInspectorEvent("connect_diagnostics.delivery_failed", { + errorType: error instanceof Error ? error.name : typeof error, + }); + }); +} + +export const connectDiagnosticsTesting = { + readQueue, + networkCodeFrom, + httpStatusFrom, +}; diff --git a/apps/app/src/react-app/domains/connections/use-session-mcp-maintenance.ts b/apps/app/src/react-app/domains/connections/use-session-mcp-maintenance.ts index e9a19037e7..303e44b3c9 100644 --- a/apps/app/src/react-app/domains/connections/use-session-mcp-maintenance.ts +++ b/apps/app/src/react-app/domains/connections/use-session-mcp-maintenance.ts @@ -25,6 +25,7 @@ import { runOpenworkCloudMcpReconciler, type CloudMcpClient, } from "./cloud-mcp-reconciler"; +import { recordConnectDiagnosticAttempt } from "./connect-diagnostics-reporter"; export const SESSION_MCP_MAINTENANCE_INTERVAL_MS = 5 * 60 * 1000; export const SESSION_MCP_MAINTENANCE_TIMEOUT_MS = 2 * 60 * 1000; @@ -389,6 +390,12 @@ export function useSessionMcpMaintenance(input: { stage: issue?.stage ?? null, retryable: issue?.retryable ?? null, }); + recordConnectDiagnosticAttempt({ + outcome: attemptInput.result.outcome, + health: attemptInput.result.health, + issue, + maintenanceAttempt: attemptInput.attempt, + }, settings); if (cancelled) return; setCloudMcpState({ status: attemptInput.result.outcome === "ready" diff --git a/apps/app/src/react-app/domains/settings/pages/connect-view.tsx b/apps/app/src/react-app/domains/settings/pages/connect-view.tsx index c7fc0df604..78b1cdcea0 100644 --- a/apps/app/src/react-app/domains/settings/pages/connect-view.tsx +++ b/apps/app/src/react-app/domains/settings/pages/connect-view.tsx @@ -63,6 +63,10 @@ import { cloudMcpProbeTraceLines, } from "../../connections/cloud-mcp-diagnostics"; import { readCloudMcpUserState } from "../../connections/cloud-mcp-user-state"; +import { + getConnectDiagnosticsClientId, + isConnectDiagnosticsEnabled, +} from "../../connections/connect-diagnostics-preferences"; export type ConnectViewState = "loading" | "signin" | "active" | "pitch"; @@ -452,6 +456,8 @@ function AgentAccessAdvanced(props: { const rows = cloudMcpAdvancedRows(props.health); const traceLines = cloudMcpProbeTraceLines(props.health?.tools.direct.trace); const refreshLines = cloudMcpEngineRefreshLines(props.engineRefresh); + const diagnosticsEnabled = isConnectDiagnosticsEnabled(); + const diagnosticsClientId = diagnosticsEnabled ? getConnectDiagnosticsClientId() : null; return (