diff --git a/packages/kilo-vscode/src/services/telemetry/types.ts b/packages/kilo-vscode/src/services/telemetry/types.ts index 928170b19a4..a4a587362d7 100644 --- a/packages/kilo-vscode/src/services/telemetry/types.ts +++ b/packages/kilo-vscode/src/services/telemetry/types.ts @@ -67,6 +67,7 @@ export enum TelemetryEventName { // Kilo-specific COMMIT_MSG_GENERATED = "Commit Message Generated", AGENT_MANAGER_OPENED = "Agent Manager Opened", + AGENT_MANAGER_BUTTON_CLICKED = "Agent Manager Button Clicked", AGENT_MANAGER_SESSION_STARTED = "Agent Manager Session Started", AGENT_MANAGER_SESSION_COMPLETED = "Agent Manager Session Completed", AGENT_MANAGER_SESSION_STOPPED = "Agent Manager Session Stopped", diff --git a/packages/kilo-vscode/tests/unit/agent-manager-telemetry.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-telemetry.test.ts new file mode 100644 index 00000000000..6e0adf55132 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/agent-manager-telemetry.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, it } from "bun:test" +import type { TelemetryRequest } from "../../webview-ui/src/types/messages/webview-messages" +import { capture, tracker } from "../../webview-ui/agent-manager/telemetry" +import { TelemetryEventName } from "../../src/services/telemetry/types" + +describe("Agent Manager telemetry", () => { + it("uses one stable event with low-cardinality button metadata", () => { + const messages: TelemetryRequest[] = [] + + capture({ postMessage: (message) => messages.push(message) }, "fullscreen_review", "tab_toolbar", { + action: "open", + fileCount: 3, + }) + + expect(messages).toEqual([ + { + type: "telemetry", + event: TelemetryEventName.AGENT_MANAGER_BUTTON_CLICKED, + properties: { + action: "open", + fileCount: 3, + source: "agent-manager", + button: "fullscreen_review", + surface: "tab_toolbar", + }, + }, + ]) + }) + + it("does not allow callers to override event dimensions", () => { + const messages: TelemetryRequest[] = [] + + capture({ postMessage: (message) => messages.push(message) }, "apply_to_local", "apply_dialog", { + source: "other", + button: "other", + surface: "other", + }) + + expect(messages[0]?.properties).toMatchObject({ + source: "agent-manager", + button: "apply_to_local", + surface: "apply_dialog", + }) + }) + + it("resolves current properties before running wrapped actions", () => { + const messages: TelemetryRequest[] = [] + const order: string[] = [] + const metrics = tracker({ + postMessage: (message) => { + messages.push(message) + order.push("telemetry") + }, + }) + const state = { action: "run" } + const click = metrics.click( + "run_script", + "tab_toolbar", + () => order.push("action"), + () => state, + ) + state.action = "stop" + + click() + + expect(messages[0]?.properties?.action).toBe("stop") + expect(order).toEqual(["telemetry", "action"]) + }) +}) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index f7026acfb25..7f9ee074816 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -134,6 +134,7 @@ import { createSidebarCollapse } from "./sidebar-collapse" import { SidebarToggleButton } from "./SidebarToggleButton" import { setTabWidths } from "./tab-widths" import { buildShortcutCategories } from "./shortcuts" +import { tracker } from "./telemetry" import "./agent-manager.css" import "./agent-manager-review.css" const REVIEW_TAB_ID = "review" @@ -203,6 +204,7 @@ const AgentManagerContent: Component = () => { const [worktrees, setWorktrees] = createSignal([]) const [managedSessions, setManagedSessions] = createSignal([]) const [selection, setSelection] = createSignal(LOCAL) + const metrics = tracker(vscode) const [repoBranch, setRepoBranch] = createSignal() const [busyWorktrees, setBusyWorktrees] = createSignal>(new Map()) const [staleWorktreeIds, setStaleWorktreeIds] = createSignal>(new Set()) @@ -447,6 +449,7 @@ const AgentManagerContent: Component = () => { if (!target) return if (!applyHasSelection()) return if (applyBusyForTarget()) return + metrics.track("apply_to_local", "apply_dialog", { fileCount: applySelectedFiles().length }) applyToLocal(target, applySelectedFiles()) } @@ -497,6 +500,7 @@ const AgentManagerContent: Component = () => { if (!sel || sel === LOCAL) return vscode.postMessage({ type: "agentManager.openWorktree", worktreeId: sel }) } + const openWindow = metrics.click("open_worktree_window", "tab_toolbar", openWorktreeDirectory) const runWorktree = (id: string) => { const state = runStatuses()[id]?.state ?? "idle" @@ -1600,6 +1604,7 @@ const AgentManagerContent: Component = () => { const handleConfigureSetupScript = () => { vscode.postMessage({ type: "agentManager.configureSetupScript" }) } + const setupScript = metrics.click("configure_setup_script", "worktree_settings", handleConfigureSetupScript) const handleChangeDefaultBaseBranch = () => { const [search, setSearch] = createSignal("") @@ -1735,6 +1740,7 @@ const AgentManagerContent: Component = () => { expandSidebar() vscode.postMessage({ type: "agentManager.createWorktree" }) } + const createWorktree = metrics.click("new_worktree", "worktrees", handleCreateWorktree) // Advanced worktree dialog — opens a full dialog with prompt, versions, model, mode const showAdvancedWorktreeDialog = () => { @@ -1829,6 +1835,7 @@ const AgentManagerContent: Component = () => { const handlePromote = (sessionId: string, e: MouseEvent) => { e.stopPropagation() if (!loaded()) return + metrics.track("promote_session", "unassigned_session") vscode.postMessage({ type: "agentManager.promoteSession", sessionId }) } @@ -2204,7 +2211,7 @@ const AgentManagerContent: Component = () => { size="small" variant="ghost" label={t("agentManager.worktree.new")} - onClick={handleCreateWorktree} + onClick={createWorktree} disabled={!loaded()} /> @@ -2217,7 +2224,7 @@ const AgentManagerContent: Component = () => { - +