diff --git a/PRIVACY.md b/PRIVACY.md index bcd9186b707..163d0ab14d9 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -10,19 +10,10 @@ Roo Code respects your privacy and is committed to transparency about how we han - **Commands**: Any commands executed through Roo Code happen on your local environment. However, when you use AI-powered features, the relevant code and context from your commands may be transmitted to your chosen AI model provider (e.g., OpenAI, Anthropic, OpenRouter) to generate responses. We do not have access to or store this data, but AI providers may process it per their privacy policies. - **Prompts & AI Requests**: When you use AI-powered features, your prompts and relevant project context are sent to your chosen AI model provider (e.g., OpenAI, Anthropic, OpenRouter) to generate responses. We do not store or process this data. These AI providers have their own privacy policies and may store data per their terms of service. - **API Keys & Credentials**: If you enter an API key (e.g., to connect an AI model), it is stored locally on your device and never sent to us or any third party, except the provider you have chosen. -- **Telemetry (Usage Data)**: We only collect feature usage and error data if you explicitly opt-in. This telemetry is powered by PostHog and helps us understand feature usage to improve Roo Code. This includes your VS Code machine ID and feature usage patterns and exception reports. We do **not** collect personally identifiable information, your code, or AI prompts. - -### **How We Use Your Data (If Collected)** - -- If you opt-in to telemetry, we use it to understand feature usage and improve Roo Code. -- We do **not** sell or share your data. -- We do **not** train any models on your data. ### **Your Choices & Control** - You can run models locally to prevent data being sent to third-parties. -- By default, telemetry collection is off and if you turn it on, you can opt out of telemetry at any time. -- You can delete Roo Code to stop all data collection. ### **Security & Updates** diff --git a/src/core/Cline.ts b/src/core/Cline.ts index ba171ce3fa1..e595180a034 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -71,7 +71,6 @@ import { McpHub } from "../services/mcp/McpHub" import crypto from "crypto" import { insertGroups } from "./diff/insert-groups" import { OutputBuilder } from "../integrations/terminal/OutputBuilder" -import { telemetryService } from "../services/telemetry/TelemetryService" const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution @@ -191,12 +190,6 @@ export class Cline { this.enableCheckpoints = enableCheckpoints this.checkpointStorage = checkpointStorage - if (historyItem) { - telemetryService.captureTaskRestarted(this.taskId) - } else { - telemetryService.captureTaskCreated(this.taskId) - } - // Initialize diffStrategy based on current state this.updateDiffStrategy( Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.DIFF_STRATEGY), @@ -1468,10 +1461,6 @@ export class Cline { await this.browserSession.closeBrowser() } - if (!block.partial) { - telemetryService.captureToolUsage(this.taskId, block.name) - } - // Validate tool use before execution const { mode, customModes } = (await this.providerRef.deref()?.getState()) ?? {} try { @@ -2956,7 +2945,6 @@ export class Cline { if (lastMessage && lastMessage.ask !== "command") { // havent sent a command message yet so first send completion_result then command await this.say("completion_result", result, undefined, false) - telemetryService.captureTaskCompleted(this.taskId) } // complete command message @@ -2974,7 +2962,6 @@ export class Cline { commandResult = execCommandResult } else { await this.say("completion_result", result, undefined, false) - telemetryService.captureTaskCompleted(this.taskId) } if (this.isSubTask) { @@ -3144,7 +3131,6 @@ export class Cline { userContent.push({ type: "text", text: environmentDetails }) await this.addToApiConversationHistory({ role: "user", content: userContent }) - telemetryService.captureConversationMessage(this.taskId, "user") // since we sent off a placeholder api_req_started message to update the webview while waiting to actually start the API request (to load potential details for example), we need to update the text of that message const lastApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started") @@ -3346,7 +3332,6 @@ export class Cline { role: "assistant", content: [{ type: "text", text: assistantMessage }], }) - telemetryService.captureConversationMessage(this.taskId, "assistant") // NOTE: this comment is here for future reference - this was a workaround for userMessageContent not getting set to true. It was due to it not recursively calling for partial blocks when didRejectTool, so it would get stuck waiting for a partial block to complete before it could continue. // in case the content blocks finished @@ -3798,8 +3783,6 @@ export class Cline { return } - telemetryService.captureCheckpointDiffed(this.taskId) - if (!previousCommitHash && mode === "checkpoint") { const previousCheckpoint = this.clineMessages .filter(({ say }) => say === "checkpoint_saved") @@ -3851,8 +3834,6 @@ export class Cline { return } - telemetryService.captureCheckpointCreated(this.taskId) - // Start the checkpoint process in the background. service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`).catch((err) => { console.error("[Cline#checkpointSave] caught unexpected error, disabling checkpoints", err) @@ -3884,8 +3865,6 @@ export class Cline { try { await service.restoreCheckpoint(commitHash) - telemetryService.captureCheckpointRestored(this.taskId) - await this.providerRef.deref()?.postMessageToWebview({ type: "currentCheckpointUpdated", text: commitHash }) if (mode === "restore") { diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 72cf56d35b8..f450ee46e02 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -53,8 +53,6 @@ import { Cline, ClineOptions } from "../Cline" import { openMention } from "../mentions" import { getNonce } from "./getNonce" import { getUri } from "./getUri" -import { telemetryService } from "../../services/telemetry/TelemetryService" -import { TelemetrySetting } from "../../shared/TelemetrySetting" /** * https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts @@ -85,9 +83,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.contextProxy = new ContextProxy(context) ClineProvider.activeInstances.add(this) - // Register this provider with the telemetry service to enable it to add properties like mode and provider - telemetryService.setProvider(this) - this.workspaceTracker = new WorkspaceTracker(this) this.configManager = new ConfigManager(this.context) this.customModesManager = new CustomModesManager(this.context, async () => { @@ -931,13 +926,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { ), ) - // If user already opted in to telemetry, enable telemetry service - this.getStateToPostToWebview().then((state) => { - const { telemetrySetting } = state - const isOptedIn = telemetrySetting === "enabled" - telemetryService.updateTelemetryState(isOptedIn) - }) - this.isViewLaunched = true break case "newTask": @@ -1799,15 +1787,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { }) } break - - case "telemetrySetting": { - const telemetrySetting = message.text as TelemetrySetting - await this.updateGlobalState("telemetrySetting", telemetrySetting) - const isOptedIn = telemetrySetting === "enabled" - telemetryService.updateTelemetryState(isOptedIn) - await this.postStateToWebview() - break - } } }, null, @@ -1867,12 +1846,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { * @param newMode The mode to switch to */ public async handleModeSwitch(newMode: Mode) { - // Capture mode switch telemetry event - const currentTaskId = this.getCurrentCline()?.taskId - if (currentTaskId) { - telemetryService.captureModeSwitch(currentTaskId, newMode) - } - await this.updateGlobalState("mode", newMode) // Load the saved API config for the new mode if it exists @@ -2211,10 +2184,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { experiments, maxOpenTabsContext, browserToolEnabled, - telemetrySetting, showRooIgnoredFiles, } = await this.getState() - const telemetryKey = process.env.POSTHOG_API_KEY const machineId = vscode.env.machineId const allowedCommands = vscode.workspace.getConfiguration("roo-cline").get("allowedCommands") || [] @@ -2244,8 +2215,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { diffEnabled: diffEnabled ?? true, enableCheckpoints: enableCheckpoints ?? true, checkpointStorage: checkpointStorage ?? "task", - shouldShowAnnouncement: - telemetrySetting !== "unset" && lastShownAnnouncementId !== this.latestAnnouncementId, + shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId, allowedCommands, soundVolume: soundVolume ?? 0.5, browserViewportSize: browserViewportSize ?? "900x600", @@ -2272,8 +2242,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { maxOpenTabsContext: maxOpenTabsContext ?? 20, cwd, browserToolEnabled: browserToolEnabled ?? true, - telemetrySetting, - telemetryKey, machineId, showRooIgnoredFiles: showRooIgnoredFiles ?? true, } @@ -2455,7 +2423,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { maxOpenTabsContext: stateValues.maxOpenTabsContext ?? 20, openRouterUseMiddleOutTransform: stateValues.openRouterUseMiddleOutTransform ?? true, browserToolEnabled: stateValues.browserToolEnabled ?? true, - telemetrySetting: stateValues.telemetrySetting || "unset", showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? true, } } @@ -2535,47 +2502,4 @@ export class ClineProvider implements vscode.WebviewViewProvider { public getMcpHub(): McpHub | undefined { return this.mcpHub } - - /** - * Returns properties to be included in every telemetry event - * This method is called by the telemetry service to get context information - * like the current mode, API provider, etc. - */ - public async getTelemetryProperties(): Promise> { - const { mode, apiConfiguration } = await this.getState() - const appVersion = this.context.extension?.packageJSON?.version - const vscodeVersion = vscode.version - const platform = process.platform - - const properties: Record = { - vscodeVersion, - platform, - } - - // Add extension version - if (appVersion) { - properties.appVersion = appVersion - } - - // Add current mode - if (mode) { - properties.mode = mode - } - - // Add API provider - if (apiConfiguration?.apiProvider) { - properties.apiProvider = apiConfiguration.apiProvider - } - - // Add model ID if available - const currentCline = this.getCurrentCline() - if (currentCline?.api) { - const { id: modelId } = currentCline.api.getModel() - if (modelId) { - properties.modelId = modelId - } - } - - return properties - } } diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index 2e9fcdf3364..e87efadbdb6 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -444,7 +444,6 @@ describe("ClineProvider", () => { experiments: experimentDefault, maxOpenTabsContext: 20, browserToolEnabled: true, - telemetrySetting: "unset", showRooIgnoredFiles: true, } @@ -1652,62 +1651,3 @@ describe("ContextProxy integration", () => { expect(mockContextProxy.setValues).toBeDefined() }) }) - -describe("getTelemetryProperties", () => { - let provider: ClineProvider - let mockContext: vscode.ExtensionContext - let mockOutputChannel: vscode.OutputChannel - let mockCline: any - - beforeEach(() => { - // Reset mocks - jest.clearAllMocks() - - // Setup basic mocks - mockContext = { - globalState: { - get: jest.fn().mockImplementation((key: string) => { - if (key === "mode") return "code" - if (key === "apiProvider") return "anthropic" - return undefined - }), - update: jest.fn(), - keys: jest.fn().mockReturnValue([]), - }, - secrets: { get: jest.fn(), store: jest.fn(), delete: jest.fn() }, - extensionUri: {} as vscode.Uri, - globalStorageUri: { fsPath: "/test/path" }, - extension: { packageJSON: { version: "1.0.0" } }, - } as unknown as vscode.ExtensionContext - - mockOutputChannel = { appendLine: jest.fn() } as unknown as vscode.OutputChannel - provider = new ClineProvider(mockContext, mockOutputChannel) - - // Setup Cline instance with mocked getModel method - const { Cline } = require("../../Cline") - mockCline = new Cline() - mockCline.api = { - getModel: jest.fn().mockReturnValue({ - id: "claude-3-7-sonnet-20250219", - info: { contextWindow: 200000 }, - }), - } - }) - - test("includes basic properties in telemetry", async () => { - const properties = await provider.getTelemetryProperties() - - expect(properties).toHaveProperty("vscodeVersion") - expect(properties).toHaveProperty("platform") - expect(properties).toHaveProperty("appVersion", "1.0.0") - }) - - test("includes model ID from current Cline instance if available", async () => { - // Add mock Cline to stack - await provider.addClineToStack(mockCline) - - const properties = await provider.getTelemetryProperties() - - expect(properties).toHaveProperty("modelId", "claude-3-7-sonnet-20250219") - }) -}) diff --git a/src/extension.ts b/src/extension.ts index d88bf2a251d..deec4b8a729 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,7 +18,6 @@ import { CodeActionProvider } from "./core/CodeActionProvider" import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider" import { handleUri, registerCommands, registerCodeActions } from "./activate" import { McpServerManager } from "./services/mcp/McpServerManager" -import { telemetryService } from "./services/telemetry/TelemetryService" /** * Built using https://github.com/microsoft/vscode-webview-ui-toolkit @@ -51,9 +50,6 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(outputChannel) outputChannel.appendLine("Roo-Code extension activated") - // Initialize telemetry service after environment variables are loaded - telemetryService.initialize() - // Get default commands from configuration. const defaultCommands = vscode.workspace.getConfiguration("roo-cline").get("allowedCommands") || [] @@ -62,7 +58,6 @@ export function activate(context: vscode.ExtensionContext) { context.globalState.update("allowedCommands", defaultCommands) } const sidebarProvider = new ClineProvider(context, outputChannel) - telemetryService.setProvider(sidebarProvider) context.subscriptions.push( vscode.window.registerWebviewViewProvider(ClineProvider.sideBarId, sidebarProvider, { @@ -151,5 +146,4 @@ export async function deactivate() { outputChannel.appendLine("Roo-Code extension deactivated") // Clean up MCP server manager await McpServerManager.cleanup(extensionContext) - telemetryService.shutdown() } diff --git a/src/services/telemetry/TelemetryService.ts b/src/services/telemetry/TelemetryService.ts deleted file mode 100644 index d3ea8bfb5f2..00000000000 --- a/src/services/telemetry/TelemetryService.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { PostHog } from "posthog-node" -import * as vscode from "vscode" -import { logger } from "../../utils/logging" - -// This forward declaration is needed to avoid circular dependencies -interface ClineProviderInterface { - // Gets telemetry properties to attach to every event - getTelemetryProperties(): Promise> -} - -/** - * PostHogClient handles telemetry event tracking for the Roo Code extension - * Uses PostHog analytics to track user interactions and system events - * Respects user privacy settings and VSCode's global telemetry configuration - */ -class PostHogClient { - public static readonly EVENTS = { - TASK: { - CREATED: "Task Created", - RESTARTED: "Task Reopened", - COMPLETED: "Task Completed", - CONVERSATION_MESSAGE: "Conversation Message", - MODE_SWITCH: "Mode Switched", - TOOL_USED: "Tool Used", - CHECKPOINT_CREATED: "Checkpoint Created", - CHECKPOINT_RESTORED: "Checkpoint Restored", - CHECKPOINT_DIFFED: "Checkpoint Diffed", - }, - } - - private static instance: PostHogClient - private client: PostHog - private distinctId: string = vscode.env.machineId - private telemetryEnabled: boolean = false - private providerRef: WeakRef | null = null - - private constructor() { - this.client = new PostHog(process.env.POSTHOG_API_KEY || "", { - host: "https://us.i.posthog.com", - }) - } - - /** - * Updates the telemetry state based on user preferences and VSCode settings - * Only enables telemetry if both VSCode global telemetry is enabled and user has opted in - * @param didUserOptIn Whether the user has explicitly opted into telemetry - */ - public updateTelemetryState(didUserOptIn: boolean): void { - this.telemetryEnabled = false - - // First check global telemetry level - telemetry should only be enabled when level is "all" - const telemetryLevel = vscode.workspace.getConfiguration("telemetry").get("telemetryLevel", "all") - const globalTelemetryEnabled = telemetryLevel === "all" - - // We only enable telemetry if global vscode telemetry is enabled - if (globalTelemetryEnabled) { - this.telemetryEnabled = didUserOptIn - } - - // Update PostHog client state based on telemetry preference - if (this.telemetryEnabled) { - this.client.optIn() - } else { - this.client.optOut() - } - } - - /** - * Gets or creates the singleton instance of PostHogClient - * @returns The PostHogClient instance - */ - public static getInstance(): PostHogClient { - if (!PostHogClient.instance) { - PostHogClient.instance = new PostHogClient() - } - return PostHogClient.instance - } - - /** - * Sets the ClineProvider reference to use for global properties - * @param provider A ClineProvider instance to use - */ - public setProvider(provider: ClineProviderInterface): void { - this.providerRef = new WeakRef(provider) - logger.debug("PostHogClient: ClineProvider reference set") - } - - /** - * Captures a telemetry event if telemetry is enabled - * @param event The event to capture with its properties - */ - public async capture(event: { event: string; properties?: any }): Promise { - // Only send events if telemetry is enabled - if (this.telemetryEnabled) { - // Get global properties from ClineProvider if available - let globalProperties: Record = {} - const provider = this.providerRef?.deref() - - if (provider) { - try { - // Get the telemetry properties directly from the provider - globalProperties = await provider.getTelemetryProperties() - } catch (error) { - // Log error but continue with capturing the event - logger.error( - `Error getting telemetry properties: ${error instanceof Error ? error.message : String(error)}`, - ) - } - } - - // Merge global properties with event-specific properties - // Event properties take precedence in case of conflicts - const mergedProperties = { - ...globalProperties, - ...(event.properties || {}), - } - - this.client.capture({ - distinctId: this.distinctId, - event: event.event, - properties: mergedProperties, - }) - } - } - - /** - * Checks if telemetry is currently enabled - * @returns Whether telemetry is enabled - */ - public isTelemetryEnabled(): boolean { - return this.telemetryEnabled - } - - /** - * Shuts down the PostHog client - */ - public async shutdown(): Promise { - await this.client.shutdown() - } -} - -/** - * TelemetryService wrapper class that defers PostHogClient initialization - * This ensures that we only create the PostHogClient after environment variables are loaded - */ -class TelemetryService { - private client: PostHogClient | null = null - private initialized = false - private providerRef: WeakRef | null = null - - /** - * Initialize the telemetry service with the PostHog client - * This should be called after environment variables are loaded - */ - public initialize(): void { - if (this.initialized) { - return - } - - try { - this.client = PostHogClient.getInstance() - this.initialized = true - } catch (error) { - console.warn("Failed to initialize telemetry service:", error) - } - } - - /** - * Sets the ClineProvider reference to use for global properties - * @param provider A ClineProvider instance to use - */ - public setProvider(provider: ClineProviderInterface): void { - // Keep a weak reference to avoid memory leaks - this.providerRef = new WeakRef(provider) - // If client is initialized, pass the provider reference - if (this.isReady()) { - this.client!.setProvider(provider) - } - logger.debug("TelemetryService: ClineProvider reference set") - } - - /** - * Base method for all telemetry operations - * Checks if the service is initialized before performing any operation - * @returns Whether the service is ready to use - */ - private isReady(): boolean { - return this.initialized && this.client !== null - } - - /** - * Updates the telemetry state based on user preferences and VSCode settings - * @param didUserOptIn Whether the user has explicitly opted into telemetry - */ - public updateTelemetryState(didUserOptIn: boolean): void { - if (!this.isReady()) return - this.client!.updateTelemetryState(didUserOptIn) - } - - /** - * Captures a telemetry event if telemetry is enabled - * @param event The event to capture with its properties - */ - public capture(event: { event: string; properties?: any }): void { - if (!this.isReady()) return - this.client!.capture(event) - } - - /** - * Generic method to capture any type of event with specified properties - * @param eventName The event name to capture - * @param properties The event properties - */ - public captureEvent(eventName: string, properties?: any): void { - this.capture({ event: eventName, properties }) - } - - // Task events convenience methods - public captureTaskCreated(taskId: string): void { - this.captureEvent(PostHogClient.EVENTS.TASK.CREATED, { taskId }) - } - - public captureTaskRestarted(taskId: string): void { - this.captureEvent(PostHogClient.EVENTS.TASK.RESTARTED, { taskId }) - } - - public captureTaskCompleted(taskId: string): void { - this.captureEvent(PostHogClient.EVENTS.TASK.COMPLETED, { taskId }) - } - - public captureConversationMessage(taskId: string, source: "user" | "assistant"): void { - this.captureEvent(PostHogClient.EVENTS.TASK.CONVERSATION_MESSAGE, { - taskId, - source, - }) - } - - public captureModeSwitch(taskId: string, newMode: string): void { - this.captureEvent(PostHogClient.EVENTS.TASK.MODE_SWITCH, { - taskId, - newMode, - }) - } - - public captureToolUsage(taskId: string, tool: string): void { - this.captureEvent(PostHogClient.EVENTS.TASK.TOOL_USED, { - taskId, - tool, - }) - } - - public captureCheckpointCreated(taskId: string): void { - this.captureEvent(PostHogClient.EVENTS.TASK.CHECKPOINT_CREATED, { taskId }) - } - - public captureCheckpointDiffed(taskId: string): void { - this.captureEvent(PostHogClient.EVENTS.TASK.CHECKPOINT_DIFFED, { taskId }) - } - - public captureCheckpointRestored(taskId: string): void { - this.captureEvent(PostHogClient.EVENTS.TASK.CHECKPOINT_RESTORED, { taskId }) - } - - /** - * Checks if telemetry is currently enabled - * @returns Whether telemetry is enabled - */ - public isTelemetryEnabled(): boolean { - if (!this.isReady()) return false - return this.client!.isTelemetryEnabled() - } - - /** - * Shuts down the PostHog client - */ - public async shutdown(): Promise { - if (!this.isReady()) return - await this.client!.shutdown() - } -} - -// Export a singleton instance of the telemetry service wrapper -export const telemetryService = new TelemetryService() diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 73f8127c53a..fb714e45b30 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -8,7 +8,6 @@ import { Mode, CustomModePrompts, ModeConfig } from "./modes" import { CustomSupportPrompts } from "./support-prompt" import { ExperimentId } from "./experiments" import { CheckpointStorage } from "./checkpoints" -import { TelemetrySetting } from "./TelemetrySetting" export interface LanguageModelChatSelector { vendor?: string @@ -139,8 +138,6 @@ export interface ExtensionState { toolRequirements?: Record // Map of tool names to their requirements (e.g. {"apply_diff": true} if diffEnabled) maxOpenTabsContext: number // Maximum number of VSCode open tabs to include in context (0-500) cwd?: string // Current working directory - telemetrySetting: TelemetrySetting - telemetryKey?: string machineId?: string showRooIgnoredFiles: boolean // Whether to show .rooignore'd files in listings } diff --git a/src/shared/TelemetrySetting.ts b/src/shared/TelemetrySetting.ts deleted file mode 100644 index 61444b5a090..00000000000 --- a/src/shared/TelemetrySetting.ts +++ /dev/null @@ -1 +0,0 @@ -export type TelemetrySetting = "unset" | "enabled" | "disabled" diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 724d7e5983b..5494cda7112 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -100,7 +100,6 @@ export interface WebviewMessage { | "humanRelayResponse" | "humanRelayCancel" | "browserToolEnabled" - | "telemetrySetting" | "showRooIgnoredFiles" text?: string disabled?: boolean diff --git a/src/shared/globalState.ts b/src/shared/globalState.ts index 540b7e72be7..b1a4a86891f 100644 --- a/src/shared/globalState.ts +++ b/src/shared/globalState.ts @@ -98,7 +98,6 @@ export const GLOBAL_STATE_KEYS = [ "browserToolEnabled", "lmStudioSpeculativeDecodingEnabled", "lmStudioDraftModelId", - "telemetrySetting", "showRooIgnoredFiles", ] as const diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 389f5709fcd..4827778f05c 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -5,7 +5,6 @@ import { ExtensionMessage } from "../../src/shared/ExtensionMessage" import { ShowHumanRelayDialogMessage } from "../../src/shared/ExtensionMessage" import { vscode } from "./utils/vscode" -import { telemetryClient } from "./utils/TelemetryClient" import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext" import ChatView from "./components/chat/ChatView" import HistoryView from "./components/history/HistoryView" @@ -32,8 +31,7 @@ const tabsByMessageAction: Partial { - const { didHydrateState, showWelcome, shouldShowAnnouncement, telemetrySetting, telemetryKey, machineId } = - useExtensionState() + const { didHydrateState, showWelcome, shouldShowAnnouncement } = useExtensionState() const [showAnnouncement, setShowAnnouncement] = useState(false) const [tab, setTab] = useState("chat") @@ -86,12 +84,6 @@ const App = () => { } }, [shouldShowAnnouncement]) - useEffect(() => { - if (didHydrateState) { - telemetryClient.updateTelemetryState(telemetrySetting, telemetryKey, machineId) - } - }, [telemetrySetting, telemetryKey, machineId, didHydrateState]) - // Tell the extension that we are ready to receive messages. useEffect(() => { vscode.postMessage({ type: "webviewDidLaunch" }) diff --git a/webview-ui/src/__tests__/TelemetryClient.test.ts b/webview-ui/src/__tests__/TelemetryClient.test.ts deleted file mode 100644 index 5ded6b2d15d..00000000000 --- a/webview-ui/src/__tests__/TelemetryClient.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Tests for TelemetryClient - */ -import { telemetryClient } from "../utils/TelemetryClient" -import posthog from "posthog-js" - -describe("TelemetryClient", () => { - // Reset all mocks before each test - beforeEach(() => { - jest.clearAllMocks() - }) - - /** - * Test the singleton pattern - */ - it("should be a singleton", () => { - // Basic test to verify the service exists - expect(telemetryClient).toBeDefined() - - // Get the constructor via prototype - const constructor = Object.getPrototypeOf(telemetryClient).constructor - - // Verify static getInstance returns the same instance - expect(constructor.getInstance()).toBe(telemetryClient) - expect(constructor.getInstance()).toBe(constructor.getInstance()) - }) - - /** - * Tests for the updateTelemetryState method - */ - describe("updateTelemetryState", () => { - it("resets PostHog when called", () => { - // Act - telemetryClient.updateTelemetryState("enabled") - - // Assert - expect(posthog.reset).toHaveBeenCalled() - }) - - it("initializes PostHog when telemetry is enabled with API key and distinctId", () => { - // Arrange - const API_KEY = "test-api-key" - const DISTINCT_ID = "test-user-id" - - // Act - telemetryClient.updateTelemetryState("enabled", API_KEY, DISTINCT_ID) - - // Assert - expect(posthog.init).toHaveBeenCalledWith( - API_KEY, - expect.objectContaining({ - api_host: "https://us.i.posthog.com", - persistence: "localStorage", - loaded: expect.any(Function), - }), - ) - - // Instead of trying to extract and call the callback, manually call identify - // This simulates what would happen when the loaded callback is triggered - posthog.identify(DISTINCT_ID) - - // Now verify identify was called - expect(posthog.identify).toHaveBeenCalled() - }) - - it("doesn't initialize PostHog when telemetry is disabled", () => { - // Act - telemetryClient.updateTelemetryState("disabled") - - // Assert - expect(posthog.init).not.toHaveBeenCalled() - }) - - it("doesn't initialize PostHog when telemetry is unset", () => { - // Act - telemetryClient.updateTelemetryState("unset") - - // Assert - expect(posthog.init).not.toHaveBeenCalled() - }) - }) - - /** - * Tests for the capture method - */ - describe("capture", () => { - it("captures events when telemetry is enabled", () => { - // Arrange - set telemetry to enabled - telemetryClient.updateTelemetryState("enabled", "test-key", "test-user") - jest.clearAllMocks() // Clear previous calls - - // Act - telemetryClient.capture("test_event", { property: "value" }) - - // Assert - expect(posthog.capture).toHaveBeenCalledWith("test_event", { property: "value" }) - }) - - it("doesn't capture events when telemetry is disabled", () => { - // Arrange - set telemetry to disabled - telemetryClient.updateTelemetryState("disabled") - jest.clearAllMocks() // Clear previous calls - - // Act - telemetryClient.capture("test_event") - - // Assert - expect(posthog.capture).not.toHaveBeenCalled() - }) - - /** - * This test verifies that no telemetry events are captured when - * the telemetry setting is unset, further documenting the expected behavior - */ - it("doesn't capture events when telemetry is unset", () => { - // Arrange - set telemetry to unset - telemetryClient.updateTelemetryState("unset") - jest.clearAllMocks() // Clear previous calls - - // Act - telemetryClient.capture("test_event", { property: "test value" }) - - // Assert - expect(posthog.capture).not.toHaveBeenCalled() - }) - }) -}) diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index cfc51a05389..a084a6f4b5f 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -29,7 +29,6 @@ import AutoApproveMenu from "./AutoApproveMenu" import { AudioType } from "../../../../src/shared/WebviewMessage" import { validateCommand } from "../../utils/command-validation" import { getAllModes } from "../../../../src/shared/modes" -import TelemetryBanner from "../common/TelemetryBanner" interface ChatViewProps { isHidden: boolean @@ -63,7 +62,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie alwaysAllowModeSwitch, alwaysAllowSubtasks, customModes, - telemetrySetting, } = useExtensionState() //const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined @@ -1097,7 +1095,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie flexDirection: "column", paddingBottom: "10px", }}> - {telemetrySetting === "unset" && } {showAnnouncement && }

What can Kilo Code do for you?

@@ -1113,17 +1110,17 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
)} - {/* + {/* // Flex layout explanation: // 1. Content div above uses flex: "1 1 0" to: - // - Grow to fill available space (flex-grow: 1) + // - Grow to fill available space (flex-grow: 1) // - Shrink when AutoApproveMenu needs space (flex-shrink: 1) // - Start from zero size (flex-basis: 0) to ensure proper distribution // minHeight: 0 allows it to shrink below its content height // // 2. AutoApproveMenu uses flex: "0 1 auto" to: // - Not grow beyond its content (flex-grow: 0) - // - Shrink when viewport is small (flex-shrink: 1) + // - Shrink when viewport is small (flex-shrink: 1) // - Use its content size as basis (flex-basis: auto) // This ensures it takes its natural height when there's space // but becomes scrollable when the viewport is too small diff --git a/webview-ui/src/components/common/TelemetryBanner.tsx b/webview-ui/src/components/common/TelemetryBanner.tsx deleted file mode 100644 index ee09e20b2d7..00000000000 --- a/webview-ui/src/components/common/TelemetryBanner.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react" -import { memo, useState } from "react" -import styled from "styled-components" -import { vscode } from "../../utils/vscode" -import { TelemetrySetting } from "../../../../src/shared/TelemetrySetting" - -const BannerContainer = styled.div` - background-color: var(--vscode-banner-background); - padding: 12px 20px; - display: flex; - flex-direction: column; - gap: 10px; - flex-shrink: 0; - margin-bottom: 6px; -` - -const ButtonContainer = styled.div` - display: flex; - gap: 8px; - width: 100%; - & > vscode-button { - flex: 1; - } -` - -const TelemetryBanner = () => { - const [hasChosen, setHasChosen] = useState(false) - - const handleAllow = () => { - setHasChosen(true) - vscode.postMessage({ type: "telemetrySetting", text: "enabled" satisfies TelemetrySetting }) - } - - const handleDeny = () => { - setHasChosen(true) - vscode.postMessage({ type: "telemetrySetting", text: "disabled" satisfies TelemetrySetting }) - } - - const handleOpenSettings = () => { - window.postMessage({ type: "action", action: "settingsButtonClicked" }) - } - - return ( - -
- Help Improve Roo Code -
- Send anonymous error and usage data to help us fix bugs and improve the extension. No code, prompts, - or personal information is ever sent. -
- You can always change this at the bottom of the{" "} - - settings - - . -
-
-
- - - Allow - - - Deny - - -
- ) -} - -export default memo(TelemetryBanner) diff --git a/webview-ui/src/components/settings/SettingsFooter.tsx b/webview-ui/src/components/settings/SettingsFooter.tsx index 53c47e1fa8f..80b6c79f973 100644 --- a/webview-ui/src/components/settings/SettingsFooter.tsx +++ b/webview-ui/src/components/settings/SettingsFooter.tsx @@ -1,24 +1,15 @@ import { HTMLAttributes } from "react" -import { VSCodeButton, VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react" +import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react" import { vscode } from "@/utils/vscode" import { cn } from "@/lib/utils" -import { TelemetrySetting } from "../../../../src/shared/TelemetrySetting" type SettingsFooterProps = HTMLAttributes & { version: string - telemetrySetting: TelemetrySetting - setTelemetrySetting: (setting: TelemetrySetting) => void } -export const SettingsFooter = ({ - version, - telemetrySetting, - setTelemetrySetting, - className, - ...props -}: SettingsFooterProps) => ( +export const SettingsFooter = ({ version, className, ...props }: SettingsFooterProps) => (

If you have any questions or feedback, feel free to open an issue at{" "} @@ -31,34 +22,6 @@ export const SettingsFooter = ({

Roo Code v{version}

-
-
- { - const checked = e.target.checked === true - setTelemetrySetting(checked ? "enabled" : "disabled") - }}> - Allow anonymous error and usage reporting - -

- Help improve Roo Code by sending anonymous usage data and error reports. No code, prompts, or - personal information is ever sent. See our{" "} - - privacy policy - {" "} - for more details. -

-
-

Reset all global state and secret storage in the extension.

(({ onDone }, ref) => { const extensionState = useExtensionState() - const { currentApiConfigName, listApiConfigMeta, uriScheme, version } = extensionState + const { currentApiConfigName, listApiConfigMeta, uriScheme } = extensionState const [isDiscardDialogShow, setDiscardDialogShow] = useState(false) const [isChangeDetected, setChangeDetected] = useState(false) @@ -81,7 +79,6 @@ const SettingsView = forwardRef(({ onDone }, screenshotQuality, soundEnabled, soundVolume, - telemetrySetting, terminalOutputLimit, writeDelayMs, showRooIgnoredFiles, @@ -143,19 +140,6 @@ const SettingsView = forwardRef(({ onDone }, }) }, []) - const setTelemetrySetting = useCallback((setting: TelemetrySetting) => { - setCachedState((prevState) => { - if (prevState.telemetrySetting === setting) { - return prevState - } - setChangeDetected(true) - return { - ...prevState, - telemetrySetting: setting, - } - }) - }, []) - const isSettingValid = !errorMessage const handleSubmit = () => { @@ -188,7 +172,6 @@ const SettingsView = forwardRef(({ onDone }, vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: alwaysAllowModeSwitch }) vscode.postMessage({ type: "alwaysAllowSubtasks", bool: alwaysAllowSubtasks }) vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) - vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting }) setChangeDetected(false) } } @@ -408,12 +391,6 @@ const SettingsView = forwardRef(({ onDone }, experiments={experiments} />
- - diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index b2172df6425..17a81cb05a0 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -9,7 +9,6 @@ import { checkExistKey } from "../../../src/shared/checkExistApiConfig" import { Mode, CustomModePrompts, defaultModeSlug, defaultPrompts, ModeConfig } from "../../../src/shared/modes" import { CustomSupportPrompts } from "../../../src/shared/support-prompt" import { experimentDefault, ExperimentId } from "../../../src/shared/experiments" -import { TelemetrySetting } from "../../../src/shared/TelemetrySetting" import { TERMINAL_OUTPUT_LIMIT } from "../../../src/shared/terminal" import { vscode } from "@/utils/vscode" @@ -74,7 +73,6 @@ export interface ExtensionStateContextType extends ExtensionState { customModes: ModeConfig[] setCustomModes: (value: ModeConfig[]) => void setMaxOpenTabsContext: (value: number) => void - setTelemetrySetting: (value: TelemetrySetting) => void machineId?: string } @@ -142,7 +140,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode maxOpenTabsContext: 20, cwd: "", browserToolEnabled: true, - telemetrySetting: "unset", showRooIgnoredFiles: true, // Default to showing .rooignore'd files with lock symbol (current behavior) }) @@ -284,7 +281,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setCustomModes: (value) => setState((prevState) => ({ ...prevState, customModes: value })), setMaxOpenTabsContext: (value) => setState((prevState) => ({ ...prevState, maxOpenTabsContext: value })), setBrowserToolEnabled: (value) => setState((prevState) => ({ ...prevState, browserToolEnabled: value })), - setTelemetrySetting: (value) => setState((prevState) => ({ ...prevState, telemetrySetting: value })), setShowRooIgnoredFiles: (value) => setState((prevState) => ({ ...prevState, showRooIgnoredFiles: value })), } diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx index 0f66413b502..78f2aef431a 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx @@ -118,7 +118,6 @@ describe("mergeExtensionState", () => { customModes: [], maxOpenTabsContext: 20, apiConfiguration: { providerId: "openrouter" } as ApiConfiguration, - telemetrySetting: "unset", showRooIgnoredFiles: true, } diff --git a/webview-ui/src/utils/TelemetryClient.ts b/webview-ui/src/utils/TelemetryClient.ts deleted file mode 100644 index fe8b1ec0cba..00000000000 --- a/webview-ui/src/utils/TelemetryClient.ts +++ /dev/null @@ -1,44 +0,0 @@ -import posthog from "posthog-js" -import { TelemetrySetting } from "../../../src/shared/TelemetrySetting" - -class TelemetryClient { - private static instance: TelemetryClient - private static telemetryEnabled: boolean = false - - public updateTelemetryState(telemetrySetting: TelemetrySetting, apiKey?: string, distinctId?: string) { - posthog.reset() - - if (telemetrySetting === "enabled" && apiKey && distinctId) { - TelemetryClient.telemetryEnabled = true - - posthog.init(apiKey, { - api_host: "https://us.i.posthog.com", - persistence: "localStorage", - loaded: () => posthog.identify(distinctId), - capture_pageview: false, - capture_pageleave: false, - }) - } else { - TelemetryClient.telemetryEnabled = false - } - } - - public static getInstance(): TelemetryClient { - if (!TelemetryClient.instance) { - TelemetryClient.instance = new TelemetryClient() - } - return TelemetryClient.instance - } - - public capture(eventName: string, properties?: Record) { - if (TelemetryClient.telemetryEnabled) { - try { - posthog.capture(eventName, properties) - } catch (error) { - // Silently fail if there's an error capturing an event - } - } - } -} - -export const telemetryClient = TelemetryClient.getInstance() diff --git a/webview-ui/src/utils/__tests__/TelemetryClient.test.ts b/webview-ui/src/utils/__tests__/TelemetryClient.test.ts deleted file mode 100644 index 243d4739d9c..00000000000 --- a/webview-ui/src/utils/__tests__/TelemetryClient.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Basic tests for TelemetryClient - */ -import { beforeEach, describe, expect, it, jest } from "@jest/globals" -import { telemetryClient } from "../TelemetryClient" -import posthog from "posthog-js" - -// Mock posthog-js -jest.mock("posthog-js", () => ({ - reset: jest.fn(), - init: jest.fn(), - identify: jest.fn(), - capture: jest.fn(), -})) - -describe("TelemetryClient", () => { - // Reset all mocks before each test - beforeEach(() => { - jest.clearAllMocks() - }) - - it("should be a singleton", () => { - // Basic test to verify the service exists - expect(telemetryClient).toBeDefined() - }) - - it("should have updateTelemetryState method", () => { - // Test if the method exists - expect(typeof telemetryClient.updateTelemetryState).toBe("function") - - // Call it with different values to verify it doesn't throw errors - expect(() => telemetryClient.updateTelemetryState("enabled")).not.toThrow() - expect(() => telemetryClient.updateTelemetryState("disabled")).not.toThrow() - expect(() => telemetryClient.updateTelemetryState("unset")).not.toThrow() - }) - - it("should have capture method", () => { - // Test if the method exists - expect(typeof telemetryClient.capture).toBe("function") - - // Call it to verify it doesn't throw errors - expect(() => telemetryClient.capture("test_event")).not.toThrow() - expect(() => telemetryClient.capture("test_event", { key: "value" })).not.toThrow() - }) - - it("should reset PostHog when updating telemetry state", () => { - // Act - telemetryClient.updateTelemetryState("enabled") - - // Assert - expect(posthog.reset).toHaveBeenCalled() - }) - - it("should initialize PostHog when telemetry is enabled with API key and distinctId", () => { - // Arrange - const API_KEY = "test-api-key" - const DISTINCT_ID = "test-user-id" - - // Act - telemetryClient.updateTelemetryState("enabled", API_KEY, DISTINCT_ID) - - // Assert - expect(posthog.init).toHaveBeenCalledWith( - API_KEY, - expect.objectContaining({ - api_host: "https://us.i.posthog.com", - persistence: "localStorage", - loaded: expect.any(Function), - }), - ) - - // Instead of trying to extract and call the callback, manually call identify - // This simulates what would happen when the loaded callback is triggered - posthog.identify(DISTINCT_ID) - - // Now verify identify was called - expect(posthog.identify).toHaveBeenCalled() - }) -})