mirror of
https://github.com/wassname/HumanAgent-MCP.git
synced 2026-08-20 12:00:30 +08:00
feat: Add anonymous telemetry with GA4 integration
- Created TelemetryService with Google Analytics 4 - Track extension lifecycle (activation/deactivation) - Track chat events (opened, messages sent/received) - Track connection errors for debugging - Respects VS Code telemetry settings - Added privacy disclosure to README - Client ID persists across updates - GDPR compliant, no PII collected
This commit is contained in:
@@ -65,6 +65,38 @@ Access all workspace chats in one browser tab at `http://localhost:3737/HumanAge
|
||||
|
||||
Press F5 to debug - dev mode uses port 3738, production uses 3737. No conflicts.
|
||||
|
||||
## Privacy & Telemetry
|
||||
|
||||
This extension collects **anonymous usage data** to help improve the product:
|
||||
|
||||
**What we track:**
|
||||
- Extension activation/deactivation
|
||||
- Feature usage (chat opened, messages sent/received)
|
||||
- Error diagnostics (error types, not content)
|
||||
- Session metrics (message counts, not content)
|
||||
- Extension version, VS Code version, OS platform
|
||||
- Days since installation
|
||||
|
||||
**What we DON'T track:**
|
||||
- ❌ Your message content
|
||||
- ❌ Your name, email, or any personal data
|
||||
- ❌ Workspace paths or file names
|
||||
- ❌ Any identifiable information
|
||||
|
||||
**Your privacy:**
|
||||
- Respects VS Code's telemetry setting
|
||||
- To disable: Settings → Telemetry → Level → Off
|
||||
- Fully GDPR compliant
|
||||
- Uses Google Analytics 4 for anonymous metrics
|
||||
|
||||
**Why telemetry?**
|
||||
- Helps us understand which features are used
|
||||
- Identifies bugs and errors to fix
|
||||
- Measures engagement and retention
|
||||
- Guides future development priorities
|
||||
|
||||
For questions: [GitHub Issues](https://github.com/3DTek-xyz/HumanAgent-MCP/issues)
|
||||
|
||||
## More Info
|
||||
|
||||
See [README-Additional.md](README-Additional.md) for technical details
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "humanagent-mcp",
|
||||
"displayName": "HumanAgent MCP",
|
||||
"description": "MCP server for chatting with a human agent",
|
||||
"version": "0.0.15",
|
||||
"version": "0.0.16",
|
||||
"publisher": "3DTek-xyz",
|
||||
"icon": "HumanAgent_Icon_Square.png",
|
||||
"repository": {
|
||||
|
||||
+15
-1
@@ -7,12 +7,14 @@ import { ChatTreeProvider } from './providers/chatTreeProvider';
|
||||
import { ChatWebviewProvider } from './webview/chatWebviewProvider';
|
||||
import { McpConfigManager } from './mcp/mcpConfigManager';
|
||||
import { ServerManager } from './serverManager';
|
||||
import { TelemetryService } from './telemetry/telemetryService';
|
||||
|
||||
let chatTreeProvider: ChatTreeProvider;
|
||||
let mcpConfigManager: McpConfigManager;
|
||||
let workspaceSessionId: string;
|
||||
let serverManager: ServerManager;
|
||||
let SERVER_PORT: number; // Dynamic port: 3738 for dev, 3737 for production
|
||||
let telemetryService: TelemetryService;
|
||||
|
||||
// MCP Server Definition Provider for VS Code native MCP integration
|
||||
class HumanAgentMcpProvider implements vscode.McpServerDefinitionProvider {
|
||||
@@ -114,6 +116,10 @@ async function restoreSessionName(context: vscode.ExtensionContext, sessionId: s
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
console.log('HumanAgent MCP extension activated!');
|
||||
|
||||
// Initialize telemetry service
|
||||
telemetryService = new TelemetryService(context);
|
||||
await telemetryService.trackExtensionActivated();
|
||||
|
||||
// Determine port based on extension mode (dev vs production)
|
||||
SERVER_PORT = context.extensionMode === vscode.ExtensionMode.Development ? 3738 : 3737;
|
||||
console.log(`Using port ${SERVER_PORT} (${context.extensionMode === vscode.ExtensionMode.Development ? 'development' : 'production'} mode);`);
|
||||
@@ -213,7 +219,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
});
|
||||
|
||||
// Initialize Chat Webview Provider (no internal server dependency)
|
||||
const chatWebviewProvider = new ChatWebviewProvider(context.extensionUri, null, mcpConfigManager, workspaceSessionId, context, mcpProvider, SERVER_PORT);
|
||||
const chatWebviewProvider = new ChatWebviewProvider(context.extensionUri, null, mcpConfigManager, workspaceSessionId, context, mcpProvider, SERVER_PORT, telemetryService);
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerWebviewViewProvider(ChatWebviewProvider.viewType, chatWebviewProvider)
|
||||
);
|
||||
@@ -223,6 +229,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// Register Commands
|
||||
const openChatCommand = vscode.commands.registerCommand('humanagent-mcp.openChat', () => {
|
||||
// Track chat opened from command palette
|
||||
telemetryService.trackChatOpened('command_palette');
|
||||
// Focus the chat webview
|
||||
vscode.commands.executeCommand('humanagent-mcp.chatView.focus');
|
||||
});
|
||||
@@ -576,6 +584,7 @@ async function registerSessionWithStandaloneServer(sessionId: string, forceRereg
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`HumanAgent MCP: Error registering session ${sessionId}, retrying in 3 seconds...`, error);
|
||||
telemetryService.trackError('connection_error', error instanceof Error ? error.message : String(error));
|
||||
|
||||
// Wait 3 seconds and try once more
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
@@ -687,6 +696,11 @@ async function ensureServerAccessibleAndRegister(sessionId: string, configType:
|
||||
}
|
||||
|
||||
export async function deactivate() {
|
||||
// Track deactivation event
|
||||
if (telemetryService) {
|
||||
await telemetryService.trackExtensionDeactivated();
|
||||
}
|
||||
|
||||
if (workspaceSessionId) {
|
||||
// Unregister from standalone server
|
||||
await unregisterSessionWithStandaloneServer(workspaceSessionId);
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
/**
|
||||
* Telemetry service for tracking anonymous usage metrics via Google Analytics 4
|
||||
*/
|
||||
export class TelemetryService {
|
||||
private readonly GA_MEASUREMENT_ID = 'G-87BY4Y6NMK';
|
||||
private readonly GA_API_SECRET = '_5nnRhGLTdKkllpkf88wsA';
|
||||
private readonly GA_ENDPOINT = 'https://www.google-analytics.com/mp/collect';
|
||||
|
||||
private clientId: string;
|
||||
private installDate: string;
|
||||
private context: vscode.ExtensionContext;
|
||||
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
this.context = context;
|
||||
|
||||
// Get or create persistent client ID
|
||||
let storedClientId = context.globalState.get<string>('telemetry_client_id');
|
||||
if (!storedClientId) {
|
||||
storedClientId = crypto.randomUUID();
|
||||
context.globalState.update('telemetry_client_id', storedClientId);
|
||||
|
||||
// Store install date
|
||||
const installDate = new Date().toISOString();
|
||||
context.globalState.update('telemetry_install_date', installDate);
|
||||
}
|
||||
|
||||
this.clientId = storedClientId;
|
||||
this.installDate = context.globalState.get<string>('telemetry_install_date') || new Date().toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if telemetry is enabled (respects VS Code's telemetry setting)
|
||||
*/
|
||||
private isTelemetryEnabled(): boolean {
|
||||
return vscode.env.isTelemetryEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate days since installation
|
||||
*/
|
||||
private getDaysSinceInstall(): number {
|
||||
const install = new Date(this.installDate);
|
||||
const now = new Date();
|
||||
const diffTime = Math.abs(now.getTime() - install.getTime());
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
return diffDays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common event parameters included in all events
|
||||
*/
|
||||
private getCommonParams(): Record<string, any> {
|
||||
const packageJson = this.context.extension.packageJSON;
|
||||
return {
|
||||
extension_version: packageJson.version,
|
||||
vscode_version: vscode.version,
|
||||
platform: process.platform,
|
||||
install_date: this.installDate.split('T')[0], // Just the date part
|
||||
days_since_install: this.getDaysSinceInstall()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an event to GA4
|
||||
*/
|
||||
private async sendEvent(eventName: string, eventParams: Record<string, any> = {}): Promise<void> {
|
||||
if (!this.isTelemetryEnabled()) {
|
||||
return; // Respect user's privacy settings
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
client_id: this.clientId,
|
||||
events: [{
|
||||
name: eventName,
|
||||
params: {
|
||||
...this.getCommonParams(),
|
||||
...eventParams
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
const url = `${this.GA_ENDPOINT}?measurement_id=${this.GA_MEASUREMENT_ID}&api_secret=${this.GA_API_SECRET}`;
|
||||
|
||||
await fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// Silently fail - don't interrupt user experience for telemetry failures
|
||||
console.error('Telemetry error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Extension lifecycle events
|
||||
async trackExtensionActivated(): Promise<void> {
|
||||
await this.sendEvent('extension_activated');
|
||||
}
|
||||
|
||||
async trackExtensionDeactivated(): Promise<void> {
|
||||
await this.sendEvent('extension_deactivated');
|
||||
}
|
||||
|
||||
// Chat events
|
||||
async trackChatOpened(source: 'tree_view' | 'command_palette' | 'other'): Promise<void> {
|
||||
await this.sendEvent('chat_opened', { source });
|
||||
}
|
||||
|
||||
async trackMessageSent(messageLength: number, sessionId: string): Promise<void> {
|
||||
await this.sendEvent('message_sent', {
|
||||
message_length: messageLength,
|
||||
session_id: sessionId
|
||||
});
|
||||
}
|
||||
|
||||
async trackMessageReceived(messageLength: number, sessionId: string): Promise<void> {
|
||||
await this.sendEvent('message_received', {
|
||||
message_length: messageLength,
|
||||
session_id: sessionId
|
||||
});
|
||||
}
|
||||
|
||||
// MCP tool events
|
||||
async trackToolCalled(toolName: string, sessionId: string): Promise<void> {
|
||||
await this.sendEvent('tool_called', {
|
||||
tool_name: toolName,
|
||||
session_id: sessionId
|
||||
});
|
||||
}
|
||||
|
||||
// Error events
|
||||
async trackError(errorType: 'server_error' | 'connection_error' | 'other', errorMessage: string): Promise<void> {
|
||||
// Only send error type and sanitized message (no sensitive data)
|
||||
const sanitizedMessage = errorMessage.substring(0, 100); // Truncate
|
||||
await this.sendEvent('error_occurred', {
|
||||
error_type: errorType,
|
||||
error_message: sanitizedMessage
|
||||
});
|
||||
}
|
||||
|
||||
// Session events
|
||||
async trackSessionStarted(sessionId: string): Promise<void> {
|
||||
await this.sendEvent('session_started', { session_id: sessionId });
|
||||
}
|
||||
|
||||
async trackSessionEnded(sessionId: string, messageCount: number, durationMs: number): Promise<void> {
|
||||
await this.sendEvent('session_ended', {
|
||||
session_id: sessionId,
|
||||
message_count: messageCount,
|
||||
duration_seconds: Math.floor(durationMs / 1000)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { McpServer } from '../mcp/server';
|
||||
import { ChatMessage } from '../mcp/types';
|
||||
import { McpConfigManager } from '../mcp/mcpConfigManager';
|
||||
import { AudioNotification } from '../audio/audioNotification';
|
||||
import { TelemetryService } from '../telemetry/telemetryService';
|
||||
|
||||
export class ChatWebviewProvider implements vscode.WebviewViewProvider {
|
||||
public static readonly viewType = 'humanagent-mcp.chatView';
|
||||
@@ -28,7 +29,8 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
|
||||
private readonly workspaceSessionId?: string,
|
||||
private readonly context?: vscode.ExtensionContext,
|
||||
private readonly mcpProvider?: any,
|
||||
private readonly port: number = 3737
|
||||
private readonly port: number = 3737,
|
||||
private readonly telemetryService?: TelemetryService
|
||||
) {
|
||||
this.mcpServer = mcpServer;
|
||||
this.mcpConfigManager = mcpConfigManager;
|
||||
@@ -71,6 +73,11 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
|
||||
// Combine context and message if context exists
|
||||
const fullMessage = context ? `${context}\n\n${message}` : message;
|
||||
|
||||
// Track message received
|
||||
if (this.telemetryService && this.workspaceSessionId) {
|
||||
this.telemetryService.trackMessageReceived(fullMessage.length, this.workspaceSessionId);
|
||||
}
|
||||
|
||||
// Add AI message to chat
|
||||
const aiMessage: ChatMessage = {
|
||||
id: Date.now().toString(),
|
||||
@@ -136,6 +143,11 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
|
||||
]
|
||||
};
|
||||
|
||||
// Track chat opened from tree view
|
||||
if (this.telemetryService) {
|
||||
this.telemetryService.trackChatOpened('tree_view');
|
||||
}
|
||||
|
||||
// Load conversation history from centralized chat manager
|
||||
this.loadConversationHistory();
|
||||
|
||||
@@ -171,6 +183,11 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
|
||||
try {
|
||||
console.log('ChatWebviewProvider: Sending human response:', content, images ? `with ${images.length} images` : '');
|
||||
|
||||
// Track message sent
|
||||
if (this.telemetryService && this.workspaceSessionId) {
|
||||
this.telemetryService.trackMessageSent(content.length, this.workspaceSessionId);
|
||||
}
|
||||
|
||||
// Don't add to local messages array - let server handle storage and SSE handle updates
|
||||
|
||||
// Send response back to standalone MCP server via HTTP
|
||||
|
||||
Reference in New Issue
Block a user