Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions apps/app/src/app/lib/den.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2120,6 +2121,25 @@ export function createDenClient(options: { baseUrl: string; token?: string | nul
return minted;
},

async reportConnectDiagnosticIncidents(
orgId: string,
batch: ConnectDiagnosticClientBatch,
): Promise<void> {
const result = await requestJsonRaw<unknown>(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<DenWorkerTokens> {
const payload = await requestJson<unknown>(baseUrls, `/v1/workers/${encodeURIComponent(workerId)}/tokens`, {
method: "POST",
Expand Down
2 changes: 2 additions & 0 deletions apps/app/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
Expand Down Expand Up @@ -180,14 +182,18 @@ 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,
config: {
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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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 false;
const parsed = JSON.parse(raw) as unknown;
if (!parsed || typeof parsed !== "object") return false;
return (parsed as { connectionDiagnosticsEnabled?: unknown }).connectionDiagnosticsEnabled === true;
} catch {
return false;
}
}

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;
}
Loading
Loading