Skip to content

Commit 50f7658

Browse files
committed
Add telemetry and privacy policy
1 parent c306566 commit 50f7658

30 files changed

+905
-11
lines changed

.env.sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# PostHog API Keys for telemetry
2+
POSTHOG_API_KEY=key-goes-here

.github/workflows/marketplace-publish.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ jobs:
3030
run: |
3131
npm install -g vsce ovsx
3232
npm run install:ci
33+
- name: Create .env file
34+
run: |
35+
echo "# PostHog API Keys for telemetry" > .env
36+
echo "POSTHOG_API_KEY=${{ secrets.POSTHOG_API_KEY }}" >> .env
3337
- name: Package and Publish Extension
3438
env:
3539
VSCE_PAT: ${{ secrets.VSCE_PAT }}
@@ -43,6 +47,7 @@ jobs:
4347
echo "$package" | grep -q "dist/extension.js" || exit 1
4448
echo "$package" | grep -q "extension/webview-ui/build/assets/index.js" || exit 1
4549
echo "$package" | grep -q "extension/node_modules/@vscode/codicons/dist/codicon.ttf" || exit 1
50+
echo "$package" | grep -q ".env" || exit 1
4651
4752
npm run publish:marketplace
4853
echo "Successfully published version $current_package_version to VS Code Marketplace"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ roo-cline-*.vsix
2121
docs/_site/
2222

2323
# Dotenv
24+
.env
2425
.env.integration
2526

2627
#Local lint config

.rooignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.env

.vscodeignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,6 @@ webview-ui/node_modules/**
4848

4949
# Include icons
5050
!assets/icons/**
51+
52+
# Include .env file for telemetry
53+
!.env

PRIVACY.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Roo Code Privacy Policy
2+
3+
**Last Updated: March 7th, 2025**
4+
5+
Roo Code respects your privacy and is committed to transparency about how we handle your data. Below is a simple breakdown of where key pieces of data go—and, importantly, where they don’t.
6+
7+
### **Where Your Data Goes (And Where It Doesn’t)**
8+
9+
- **Code & Files**: Roo Code accesses files on your local machine when needed for AI-assisted features. When you send commands to Roo Code, relevant files may be transmitted to your chosen AI model provider (e.g., OpenAI, Anthropic, OpenRouter) to generate responses. We do not have access to this data, but AI providers may store it per their privacy policies.
10+
- **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.
11+
- **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.
12+
- **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.
13+
- **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.
14+
15+
### **How We Use Your Data (If Collected)**
16+
17+
- If you opt-in to telemetry, we use it to understand feature usage and improve Roo Code.
18+
- We do **not** sell or share your data.
19+
- We do **not** train any models on your data.
20+
21+
### **Your Choices & Control**
22+
23+
- You can run models locally to prevent data being sent to third-parties.
24+
- By default, telemetry collection is off and if you turn it on, you can opt out of telemetry at any time.
25+
- You can delete Roo Code to stop all data collection.
26+
27+
### **Security & Updates**
28+
29+
We take reasonable measures to secure your data, but no system is 100% secure. If our privacy policy changes, we will notify you within the extension.
30+
31+
### **Contact Us**
32+
33+
For any privacy-related questions, reach out to us at [email protected].
34+
35+
---
36+
37+
By using Roo Code, you agree to this Privacy Policy.

package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@
295295
"os-name": "^6.0.0",
296296
"p-wait-for": "^5.0.2",
297297
"pdf-parse": "^1.1.1",
298+
"posthog-node": "^4.7.0",
298299
"pretty-bytes": "^6.1.1",
299300
"puppeteer-chromium-resolver": "^23.0.0",
300301
"puppeteer-core": "^23.4.0",

src/core/Cline.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import { McpHub } from "../services/mcp/McpHub"
7171
import crypto from "crypto"
7272
import { insertGroups } from "./diff/insert-groups"
7373
import { OutputBuilder } from "../integrations/terminal/OutputBuilder"
74+
import { telemetryService } from "../services/telemetry/TelemetryService"
7475

7576
const cwd =
7677
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
@@ -190,6 +191,12 @@ export class Cline {
190191
this.enableCheckpoints = enableCheckpoints
191192
this.checkpointStorage = checkpointStorage
192193

194+
if (historyItem) {
195+
telemetryService.captureTaskRestarted(this.taskId)
196+
} else {
197+
telemetryService.captureTaskCreated(this.taskId)
198+
}
199+
193200
// Initialize diffStrategy based on current state
194201
this.updateDiffStrategy(
195202
Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.DIFF_STRATEGY),
@@ -1446,6 +1453,10 @@ export class Cline {
14461453
await this.browserSession.closeBrowser()
14471454
}
14481455

1456+
if (!block.partial) {
1457+
telemetryService.captureToolUsage(this.taskId, block.name)
1458+
}
1459+
14491460
// Validate tool use before execution
14501461
const { mode, customModes } = (await this.providerRef.deref()?.getState()) ?? {}
14511462
try {
@@ -2928,6 +2939,7 @@ export class Cline {
29282939
if (lastMessage && lastMessage.ask !== "command") {
29292940
// havent sent a command message yet so first send completion_result then command
29302941
await this.say("completion_result", result, undefined, false)
2942+
telemetryService.captureTaskCompleted(this.taskId)
29312943
if (this.isSubTask) {
29322944
// tell the provider to remove the current subtask and resume the previous task in the stack
29332945
await this.providerRef
@@ -2952,6 +2964,7 @@ export class Cline {
29522964
commandResult = execCommandResult
29532965
} else {
29542966
await this.say("completion_result", result, undefined, false)
2967+
telemetryService.captureTaskCompleted(this.taskId)
29552968
if (this.isSubTask) {
29562969
// tell the provider to remove the current subtask and resume the previous task in the stack
29572970
await this.providerRef
@@ -3117,6 +3130,7 @@ export class Cline {
31173130
userContent.push({ type: "text", text: environmentDetails })
31183131

31193132
await this.addToApiConversationHistory({ role: "user", content: userContent })
3133+
telemetryService.captureConversationMessage(this.taskId, "user")
31203134

31213135
// 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
31223136
const lastApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started")
@@ -3318,6 +3332,7 @@ export class Cline {
33183332
role: "assistant",
33193333
content: [{ type: "text", text: assistantMessage }],
33203334
})
3335+
telemetryService.captureConversationMessage(this.taskId, "assistant")
33213336

33223337
// 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.
33233338
// in case the content blocks finished

src/core/webview/ClineProvider.ts

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ import { Cline, ClineOptions } from "../Cline"
5353
import { openMention } from "../mentions"
5454
import { getNonce } from "./getNonce"
5555
import { getUri } from "./getUri"
56+
import { telemetryService } from "../../services/telemetry/TelemetryService"
57+
import { TelemetrySetting } from "../../shared/TelemetrySetting"
5658

5759
/**
5860
* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -82,6 +84,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
8284
this.outputChannel.appendLine("ClineProvider instantiated")
8385
this.contextProxy = new ContextProxy(context)
8486
ClineProvider.activeInstances.add(this)
87+
88+
// Register this provider with the telemetry service to enable it to add properties like mode and provider
89+
telemetryService.setProvider(this)
90+
8591
this.workspaceTracker = new WorkspaceTracker(this)
8692
this.configManager = new ConfigManager(this.context)
8793
this.customModesManager = new CustomModesManager(this.context, async () => {
@@ -620,8 +626,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
620626
`font-src ${webview.cspSource}`,
621627
`style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`,
622628
`img-src ${webview.cspSource} data:`,
623-
`script-src 'unsafe-eval' https://* http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
624-
`connect-src https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
629+
`script-src 'unsafe-eval' https://* https://*.posthog.com http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
630+
`connect-src https://* https://*.posthog.com ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
625631
]
626632

627633
return /*html*/ `
@@ -710,7 +716,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
710716
<meta charset="utf-8">
711717
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
712718
<meta name="theme-color" content="#000000">
713-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} data:; script-src 'nonce-${nonce}';">
719+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} data:; script-src 'nonce-${nonce}' https://us-assets.i.posthog.com; connect-src https://us.i.posthog.com https://us-assets.i.posthog.com;">
714720
<link rel="stylesheet" type="text/css" href="${stylesUri}">
715721
<link href="${codiconsUri}" rel="stylesheet" />
716722
<title>Roo Code</title>
@@ -925,6 +931,13 @@ export class ClineProvider implements vscode.WebviewViewProvider {
925931
),
926932
)
927933

934+
// If user already opted in to telemetry, enable telemetry service
935+
this.getStateToPostToWebview().then((state) => {
936+
const { telemetrySetting } = state
937+
const isOptedIn = telemetrySetting === "enabled"
938+
telemetryService.updateTelemetryState(isOptedIn)
939+
})
940+
928941
this.isViewLaunched = true
929942
break
930943
case "newTask":
@@ -1776,6 +1789,15 @@ export class ClineProvider implements vscode.WebviewViewProvider {
17761789
})
17771790
}
17781791
break
1792+
1793+
case "telemetrySetting": {
1794+
const telemetrySetting = message.text as TelemetrySetting
1795+
await this.updateGlobalState("telemetrySetting", telemetrySetting)
1796+
const isOptedIn = telemetrySetting === "enabled"
1797+
telemetryService.updateTelemetryState(isOptedIn)
1798+
await this.postStateToWebview()
1799+
break
1800+
}
17791801
}
17801802
},
17811803
null,
@@ -1835,6 +1857,12 @@ export class ClineProvider implements vscode.WebviewViewProvider {
18351857
* @param newMode The mode to switch to
18361858
*/
18371859
public async handleModeSwitch(newMode: Mode) {
1860+
// Capture mode switch telemetry event
1861+
const currentTaskId = this.getCurrentCline()?.taskId
1862+
if (currentTaskId) {
1863+
telemetryService.captureModeSwitch(currentTaskId, newMode)
1864+
}
1865+
18381866
await this.updateGlobalState("mode", newMode)
18391867

18401868
// Load the saved API config for the new mode if it exists
@@ -2172,7 +2200,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
21722200
experiments,
21732201
maxOpenTabsContext,
21742202
browserToolEnabled,
2203+
telemetrySetting,
21752204
} = await this.getState()
2205+
const telemetryKey = process.env.POSTHOG_API_KEY
2206+
const machineId = vscode.env.machineId
21762207

21772208
const allowedCommands = vscode.workspace.getConfiguration("roo-cline").get<string[]>("allowedCommands") || []
21782209

@@ -2200,7 +2231,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
22002231
diffEnabled: diffEnabled ?? true,
22012232
enableCheckpoints: enableCheckpoints ?? true,
22022233
checkpointStorage: checkpointStorage ?? "task",
2203-
shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId,
2234+
shouldShowAnnouncement:
2235+
telemetrySetting !== "unset" && lastShownAnnouncementId !== this.latestAnnouncementId,
22042236
allowedCommands,
22052237
soundVolume: soundVolume ?? 0.5,
22062238
browserViewportSize: browserViewportSize ?? "900x600",
@@ -2225,8 +2257,11 @@ export class ClineProvider implements vscode.WebviewViewProvider {
22252257
experiments: experiments ?? experimentDefault,
22262258
mcpServers: this.mcpHub?.getAllServers() ?? [],
22272259
maxOpenTabsContext: maxOpenTabsContext ?? 20,
2228-
cwd: cwd,
2260+
cwd,
22292261
browserToolEnabled: browserToolEnabled ?? true,
2262+
telemetrySetting,
2263+
telemetryKey,
2264+
machineId,
22302265
}
22312266
}
22322267

@@ -2405,6 +2440,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
24052440
maxOpenTabsContext: stateValues.maxOpenTabsContext ?? 20,
24062441
openRouterUseMiddleOutTransform: stateValues.openRouterUseMiddleOutTransform ?? true,
24072442
browserToolEnabled: stateValues.browserToolEnabled ?? true,
2443+
telemetrySetting: stateValues.telemetrySetting || "unset",
24082444
}
24092445
}
24102446

@@ -2483,4 +2519,27 @@ export class ClineProvider implements vscode.WebviewViewProvider {
24832519
public getMcpHub(): McpHub | undefined {
24842520
return this.mcpHub
24852521
}
2522+
2523+
/**
2524+
* Returns properties to be included in every telemetry event
2525+
* This method is called by the telemetry service to get context information
2526+
* like the current mode, API provider, etc.
2527+
*/
2528+
public async getTelemetryProperties(): Promise<Record<string, any>> {
2529+
const { mode, apiConfiguration } = await this.getState()
2530+
2531+
const properties: Record<string, any> = {}
2532+
2533+
// Add current mode
2534+
if (mode) {
2535+
properties.mode = mode
2536+
}
2537+
2538+
// Add API provider
2539+
if (apiConfiguration?.apiProvider) {
2540+
properties.apiProvider = apiConfiguration.apiProvider
2541+
}
2542+
2543+
return properties
2544+
}
24862545
}

0 commit comments

Comments
 (0)