/** * Context Monitor - Estimates context usage and triggers backups */ const fs = require('fs-extra'); const path = require('path'); class ContextMonitor { constructor() { this.estimatedTokens = 0; this.promptCount = 0; this.toolExecutions = 0; this.sessionStartTime = Date.now(); // Configuration this.maxContextTokens = 200000; // Conservative estimate this.backupThreshold = 0.85; // 85% of max context this.timeThresholdMinutes = 30; // 30 minutes this.toolThreshold = 25; // 25 tool executions this.lastBackupTime = Date.now(); this.lastBackupToolCount = 0; } /** * Update context estimates from user prompt */ updateFromPrompt(promptData) { this.promptCount++; // Estimate tokens from prompt const prompt = promptData.prompt || ''; const estimatedPromptTokens = this._estimateTokens(prompt); this.estimatedTokens += estimatedPromptTokens; // Add context size if provided if (promptData.context_size) { this.estimatedTokens += promptData.context_size; } } /** * Update context estimates from tool usage */ updateFromToolUse(toolData) { this.toolExecutions++; // Estimate tokens from tool parameters and output const parameters = JSON.stringify(toolData.parameters || {}); const output = toolData.output || ''; const error = toolData.error || ''; const toolTokens = this._estimateTokens(parameters + output + error); this.estimatedTokens += toolTokens; // File operations add more context if (toolData.tool === 'Read' || toolData.tool === 'Edit') { this.estimatedTokens += 2000; // Typical file size estimate } else if (toolData.tool === 'Bash') { this.estimatedTokens += 500; // Command output estimate } } /** * Check if backup should be triggered */ checkBackupTriggers(hookType, data) { const decisions = []; // Context threshold trigger const contextRatio = this.getContextUsageRatio(); if (contextRatio > this.backupThreshold) { decisions.push({ shouldBackup: true, reason: `Context usage ${(contextRatio * 100).toFixed(1)}%`, urgency: 'high' }); } // Time-based trigger const sessionMinutes = (Date.now() - this.sessionStartTime) / (1000 * 60); const timeSinceBackup = (Date.now() - this.lastBackupTime) / (1000 * 60); if (timeSinceBackup > this.timeThresholdMinutes) { decisions.push({ shouldBackup: true, reason: `${this.timeThresholdMinutes} minutes since last backup`, urgency: 'medium' }); } // Tool-based trigger const toolsSinceBackup = this.toolExecutions - this.lastBackupToolCount; if (toolsSinceBackup >= this.toolThreshold) { decisions.push({ shouldBackup: true, reason: `${this.toolThreshold} tools since last backup`, urgency: 'medium' }); } // Return highest priority decision if (decisions.length > 0) { const urgencyOrder = { high: 3, medium: 2, low: 1 }; decisions.sort((a, b) => urgencyOrder[b.urgency] - urgencyOrder[a.urgency]); return decisions[0]; } return { shouldBackup: false }; } /** * Get current context usage ratio (0.0 to 1.0) */ getContextUsageRatio() { return Math.min(1.0, this.estimatedTokens / this.maxContextTokens); } /** * Mark that a backup was performed */ markBackupPerformed() { this.lastBackupTime = Date.now(); this.lastBackupToolCount = this.toolExecutions; } /** * Estimate tokens from text (rough approximation) */ _estimateTokens(text) { if (!text) return 0; // Rough estimate: ~4 characters per token for English text // Add some buffer for formatting and special tokens return Math.ceil(text.length / 3.5); } /** * Get context usage statistics */ getStats() { const sessionMinutes = (Date.now() - this.sessionStartTime) / (1000 * 60); return { estimatedTokens: this.estimatedTokens, contextUsageRatio: this.getContextUsageRatio(), promptCount: this.promptCount, toolExecutions: this.toolExecutions, sessionMinutes: Math.round(sessionMinutes), lastBackupMinutesAgo: Math.round((Date.now() - this.lastBackupTime) / (1000 * 60)) }; } } module.exports = { ContextMonitor };