mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
fix(vscode): close runtime telemetry consent gap with the CLI
The previous `watchTelemetryState` fix updated the webview UI in real time but left the CLI subprocess's PostHog client stuck on its spawn-time `KILO_TELEMETRY_LEVEL` value. A user who started VS Code with telemetry off and toggled it on at runtime saw the thumbs UI appear (good) but every webview event was silently dropped at the CLI's `Client.capture()` gate (bad). Add a runtime sync channel: - New `POST /telemetry/setEnabled` Hono route on the CLI server that calls `Telemetry.setEnabled(enabled)` to flip the `posthog-node` client's opt state. - New `TelemetryProxy.setEnabled(enabled)` method that POSTs to it, using the same fire-and-forget pattern as `capture`. - Extension calls `telemetry.setEnabled(vscode.env.isTelemetryEnabled)` immediately after `telemetry.configure(...)` on every `connected` state change, so a freshly-spawned CLI gets corrected even when its spawn-time env var is stale. - Extension subscribes to `vscode.env.onDidChangeTelemetryEnabled` to forward runtime consent changes to the CLI as they happen. All three changes live in Kilo-owned files (the route is already a `kilocode_change - new file`, and the extension is Kilo-only). Zero upstream OpenCode merge surface. Closes #9872 fully (the previous `d67c5e307c` covered only the webview UI).
This commit is contained in:
@@ -57,6 +57,11 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
const config = connectionService.getServerConfig()
|
||||
if (config) {
|
||||
telemetry.configure(config.baseUrl, config.password)
|
||||
// Sync the CLI's PostHog client with the current consent state. The
|
||||
// CLI reads KILO_TELEMETRY_LEVEL once at spawn, so without this call
|
||||
// a fresh CLI started while VS Code telemetry was off would stay
|
||||
// opted out for the rest of the session.
|
||||
telemetry.setEnabled(vscode.env.isTelemetryEnabled)
|
||||
}
|
||||
try {
|
||||
remoteService.setClient(connectionService.getClient())
|
||||
@@ -72,6 +77,14 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
})
|
||||
|
||||
// Propagate runtime telemetry consent changes to the CLI subprocess so its
|
||||
// PostHog client stays in sync with the user's VS Code telemetry setting.
|
||||
context.subscriptions.push(
|
||||
vscode.env.onDidChangeTelemetryEnabled((enabled) => {
|
||||
telemetry.setEnabled(enabled)
|
||||
}),
|
||||
)
|
||||
|
||||
// Prewarm the CLI backend early so autocomplete is ready before first editor use.
|
||||
ensureBackendForAutocomplete(connectionService)
|
||||
|
||||
|
||||
@@ -61,6 +61,26 @@ export class TelemetryProxy {
|
||||
}).catch((err) => console.error("[Kilo New] Telemetry capture failed:", err))
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagate runtime telemetry consent changes to the CLI. The CLI subprocess
|
||||
* reads `KILO_TELEMETRY_LEVEL` once at spawn — without this call, toggling
|
||||
* VS Code telemetry consent leaves the CLI's PostHog client stuck on its
|
||||
* spawn-time state until the process restarts.
|
||||
*/
|
||||
setEnabled(enabled: boolean) {
|
||||
if (!this.url || !this.password) return
|
||||
|
||||
const auth = buildTelemetryAuthHeader(this.password)
|
||||
fetch(`${this.url}/telemetry/setEnabled`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: auth,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ enabled }),
|
||||
}).catch((err) => console.error("[Kilo New] Telemetry setEnabled failed:", err))
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op — the CLI server handles PostHog shutdown.
|
||||
*/
|
||||
|
||||
@@ -7,39 +7,68 @@ import { lazy } from "@/util/lazy"
|
||||
import { errors } from "../../error"
|
||||
|
||||
export const TelemetryRoutes = lazy(() =>
|
||||
new Hono().post(
|
||||
"/capture",
|
||||
describeRoute({
|
||||
summary: "Capture telemetry event",
|
||||
description: "Forward a telemetry event to PostHog via kilo-telemetry.",
|
||||
operationId: "telemetry.capture",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Event captured",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
new Hono()
|
||||
.post(
|
||||
"/capture",
|
||||
describeRoute({
|
||||
summary: "Capture telemetry event",
|
||||
description: "Forward a telemetry event to PostHog via kilo-telemetry.",
|
||||
operationId: "telemetry.capture",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Event captured",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(400),
|
||||
},
|
||||
...errors(400),
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
event: z.string().meta({ description: "Event name" }),
|
||||
properties: z.record(z.string(), z.any()).optional().meta({ description: "Event properties" }),
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
event: z.string().meta({ description: "Event name" }),
|
||||
properties: z.record(z.string(), z.any()).optional().meta({ description: "Event properties" }),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const body = c.req.valid("json")
|
||||
try {
|
||||
Telemetry.track(body.event as any, body.properties)
|
||||
} catch {
|
||||
// fire-and-forget: swallow errors
|
||||
}
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/setEnabled",
|
||||
describeRoute({
|
||||
summary: "Set PostHog telemetry enabled state",
|
||||
description:
|
||||
"Update the PostHog client's opt-in/out state at runtime. " +
|
||||
"The CLI reads KILO_TELEMETRY_LEVEL once at spawn — this route lets clients " +
|
||||
"(e.g. the VS Code extension) propagate runtime telemetry consent changes.",
|
||||
operationId: "telemetry.setEnabled",
|
||||
responses: {
|
||||
200: {
|
||||
description: "State updated",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(400),
|
||||
},
|
||||
}),
|
||||
validator("json", z.object({ enabled: z.boolean() })),
|
||||
async (c) => {
|
||||
const body = c.req.valid("json")
|
||||
Telemetry.setEnabled(body.enabled)
|
||||
return c.json(true)
|
||||
},
|
||||
),
|
||||
async (c) => {
|
||||
const body = c.req.valid("json")
|
||||
try {
|
||||
Telemetry.track(body.event as any, body.properties)
|
||||
} catch {
|
||||
// fire-and-forget: swallow errors
|
||||
}
|
||||
return c.json(true)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -221,6 +221,8 @@ import type {
|
||||
SyncStartResponses,
|
||||
TelemetryCaptureErrors,
|
||||
TelemetryCaptureResponses,
|
||||
TelemetrySetEnabledErrors,
|
||||
TelemetrySetEnabledResponses,
|
||||
TextPartInput,
|
||||
ToolIdsErrors,
|
||||
ToolIdsResponses,
|
||||
@@ -4936,6 +4938,45 @@ export class Telemetry extends HeyApiClient {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Set PostHog telemetry enabled state
|
||||
*
|
||||
* Update the PostHog client's opt-in/out state at runtime. The CLI reads KILO_TELEMETRY_LEVEL once at spawn — this route lets clients (e.g. the VS Code extension) propagate runtime telemetry consent changes.
|
||||
*/
|
||||
public setEnabled<ThrowOnError extends boolean = false>(
|
||||
parameters?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
enabled?: boolean
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
{ in: "body", key: "enabled" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<TelemetrySetEnabledResponses, TelemetrySetEnabledErrors, ThrowOnError>(
|
||||
{
|
||||
url: "/telemetry/setEnabled",
|
||||
...options,
|
||||
...params,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
...params.headers,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class Remote extends HeyApiClient {
|
||||
|
||||
@@ -6465,6 +6465,36 @@ export type TelemetryCaptureResponses = {
|
||||
|
||||
export type TelemetryCaptureResponse = TelemetryCaptureResponses[keyof TelemetryCaptureResponses]
|
||||
|
||||
export type TelemetrySetEnabledData = {
|
||||
body?: {
|
||||
enabled: boolean
|
||||
}
|
||||
path?: never
|
||||
query?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
}
|
||||
url: "/telemetry/setEnabled"
|
||||
}
|
||||
|
||||
export type TelemetrySetEnabledErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
}
|
||||
|
||||
export type TelemetrySetEnabledError = TelemetrySetEnabledErrors[keyof TelemetrySetEnabledErrors]
|
||||
|
||||
export type TelemetrySetEnabledResponses = {
|
||||
/**
|
||||
* State updated
|
||||
*/
|
||||
200: boolean
|
||||
}
|
||||
|
||||
export type TelemetrySetEnabledResponse = TelemetrySetEnabledResponses[keyof TelemetrySetEnabledResponses]
|
||||
|
||||
export type RemoteEnableData = {
|
||||
body?: never
|
||||
path?: never
|
||||
|
||||
Reference in New Issue
Block a user