diff --git a/docs/cli/telemetry.md b/docs/cli/telemetry.md index 2068759213..fec0fb41c3 100644 --- a/docs/cli/telemetry.md +++ b/docs/cli/telemetry.md @@ -306,6 +306,7 @@ Emitted at startup with the CLI configuration. - `extension_ids` (string) - `extensions_count` (int) - `auth_type` (string) +- `worktree_active` (boolean) - `github_workflow_name` (string, optional) - `github_repository_hash` (string, optional) - `github_event_name` (string, optional) diff --git a/packages/cli/src/test-utils/mockConfig.ts b/packages/cli/src/test-utils/mockConfig.ts index d4f11212e3..e1505df970 100644 --- a/packages/cli/src/test-utils/mockConfig.ts +++ b/packages/cli/src/test-utils/mockConfig.ts @@ -44,6 +44,7 @@ export const createMockConfig = (overrides: Partial = {}): Config => getDeleteSession: vi.fn(() => undefined), setSessionId: vi.fn(), getSessionId: vi.fn().mockReturnValue('mock-session-id'), + getWorktreeSettings: vi.fn(() => undefined), getContentGeneratorConfig: vi.fn(() => ({ authType: 'google' })), getAcpMode: vi.fn(() => false), isBrowserLaunchSuppressed: vi.fn(() => false), diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts index 2f059030ca..11433db3e8 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts @@ -687,6 +687,11 @@ export class ClearcutLogger { gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_EXTENSION_IDS, value: event.extension_ids.toString(), }, + { + gemini_cli_key: + EventMetadataKey.GEMINI_CLI_START_SESSION_WORKTREE_ACTIVE, + value: event.worktree_active.toString(), + }, ]; // Add hardware information only to the start session event diff --git a/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts b/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts index 632730aeeb..b7b9c0fd3a 100644 --- a/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts +++ b/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts @@ -452,6 +452,9 @@ export enum EventMetadataKey { // Logs the name of extensions as a comma-separated string GEMINI_CLI_START_SESSION_EXTENSION_IDS = 120, + // Logs whether the session is running in a Git worktree. + GEMINI_CLI_START_SESSION_WORKTREE_ACTIVE = 191, + // Logs the setting scope for an extension enablement. GEMINI_CLI_EXTENSION_ENABLE_SETTING_SCOPE = 102, diff --git a/packages/core/src/telemetry/loggers.test.ts b/packages/core/src/telemetry/loggers.test.ts index 4373a6b96c..27c23e7baa 100644 --- a/packages/core/src/telemetry/loggers.test.ts +++ b/packages/core/src/telemetry/loggers.test.ts @@ -195,48 +195,51 @@ describe('loggers', () => { }); describe('logCliConfiguration', () => { + const baseMockConfig = { + getSessionId: () => 'test-session-id', + getModel: () => 'test-model', + getEmbeddingModel: () => 'test-embedding-model', + getSandbox: () => true, + getCoreTools: () => ['ls', 'read-file'], + getApprovalMode: () => 'default', + getContentGeneratorConfig: () => ({ + model: 'test-model', + apiKey: 'test-api-key', + authType: AuthType.USE_VERTEX_AI, + }), + getTelemetryEnabled: () => true, + getUsageStatisticsEnabled: () => true, + getTelemetryLogPromptsEnabled: () => true, + getFileFilteringRespectGitIgnore: () => true, + getFileFilteringAllowBuildArtifacts: () => false, + getDebugMode: () => true, + getMcpServers: () => { + throw new Error('Should not call'); + }, + getQuestion: () => 'test-question', + getTargetDir: () => 'target-dir', + getProxy: () => 'http://test.proxy.com:8080', + getOutputFormat: () => OutputFormat.JSON, + getExtensions: () => + [ + { name: 'ext-one', id: 'id-one' }, + { name: 'ext-two', id: 'id-two' }, + ] as GeminiCLIExtension[], + getMcpClientManager: () => ({ + getMcpServers: () => ({ + 'test-server': { + command: 'test-command', + }, + }), + }), + isInteractive: () => false, + getExperiments: () => undefined, + getExperimentsAsync: async () => undefined, + getWorktreeSettings: () => undefined, + } as unknown as Config; + it('should log the cli configuration', async () => { - const mockConfig = { - getSessionId: () => 'test-session-id', - getModel: () => 'test-model', - getEmbeddingModel: () => 'test-embedding-model', - getSandbox: () => true, - getCoreTools: () => ['ls', 'read-file'], - getApprovalMode: () => 'default', - getContentGeneratorConfig: () => ({ - model: 'test-model', - apiKey: 'test-api-key', - authType: AuthType.USE_VERTEX_AI, - }), - getTelemetryEnabled: () => true, - getUsageStatisticsEnabled: () => true, - getTelemetryLogPromptsEnabled: () => true, - getFileFilteringRespectGitIgnore: () => true, - getFileFilteringAllowBuildArtifacts: () => false, - getDebugMode: () => true, - getMcpServers: () => { - throw new Error('Should not call'); - }, - getQuestion: () => 'test-question', - getTargetDir: () => 'target-dir', - getProxy: () => 'http://test.proxy.com:8080', - getOutputFormat: () => OutputFormat.JSON, - getExtensions: () => - [ - { name: 'ext-one', id: 'id-one' }, - { name: 'ext-two', id: 'id-two' }, - ] as GeminiCLIExtension[], - getMcpClientManager: () => ({ - getMcpServers: () => ({ - 'test-server': { - command: 'test-command', - }, - }), - }), - isInteractive: () => false, - getExperiments: () => undefined, - getExperimentsAsync: async () => undefined, - } as unknown as Config; + const mockConfig = baseMockConfig; const startSessionEvent = new StartSessionEvent(mockConfig); logCliConfiguration(mockConfig, startSessionEvent); @@ -270,9 +273,32 @@ describe('loggers', () => { extensions_count: 2, extensions: 'ext-one,ext-two', auth_type: 'vertex-ai', + worktree_active: false, }, }); }); + + it('should set worktree_active to true when worktree settings are present', async () => { + const mockConfig = { + ...baseMockConfig, + getWorktreeSettings: () => ({ + name: 'test-worktree', + path: '/path/to/worktree', + baseSha: 'test-sha', + }), + } as unknown as Config; + + const startSessionEvent = new StartSessionEvent(mockConfig); + logCliConfiguration(mockConfig, startSessionEvent); + + await new Promise(process.nextTick); + expect(mockLogger.emit).toHaveBeenCalledWith({ + body: 'CLI configuration loaded.', + attributes: expect.objectContaining({ + worktree_active: true, + }), + }); + }); }); describe('logUserPrompt', () => { diff --git a/packages/core/src/telemetry/types.ts b/packages/core/src/telemetry/types.ts index 0ee6e63503..1e0e3abc6e 100644 --- a/packages/core/src/telemetry/types.ts +++ b/packages/core/src/telemetry/types.ts @@ -77,6 +77,7 @@ export class StartSessionEvent implements BaseTelemetryEvent { extensions: string; extension_ids: string; auth_type?: string; + worktree_active: boolean; constructor(config: Config, toolRegistry?: ToolRegistry) { const generatorConfig = config.getContentGeneratorConfig(); @@ -114,6 +115,7 @@ export class StartSessionEvent implements BaseTelemetryEvent { this.extensions = extensions.map((e) => e.name).join(','); this.extension_ids = extensions.map((e) => e.id).join(','); this.auth_type = generatorConfig?.authType; + this.worktree_active = !!config.getWorktreeSettings(); if (toolRegistry) { const mcpTools = toolRegistry .getAllTools() @@ -147,6 +149,7 @@ export class StartSessionEvent implements BaseTelemetryEvent { extensions_count: this.extensions_count, extension_ids: this.extension_ids, auth_type: this.auth_type, + worktree_active: this.worktree_active, }; }