UNPKG

prompt-version-manager

Version:

Centralized prompt management system for Human Behavior AI agents

308 lines 11.8 kB
"use strict"; /** * Execution tracking for PVM TypeScript - stores execution data for dashboard. * Matches Python's execution storage format exactly. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ExecutionTracker = void 0; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); class ExecutionTracker { repoPath; executionsDir; indexFile; index; constructor(repoPath = '.pvm') { this.repoPath = path.resolve(repoPath); this.executionsDir = path.join(this.repoPath, 'executions'); this.indexFile = path.join(this.executionsDir, 'index.json'); this.index = this.loadIndexSync(); } loadIndexSync() { try { const data = require('fs').readFileSync(this.indexFile, 'utf8'); return JSON.parse(data); } catch { return { executions: [], chains: {}, metrics: { total_executions: 0, total_tokens: 0, total_cost: 0.0, models_used: {} } }; } } async loadIndex() { try { await fs.access(this.indexFile); const data = await fs.readFile(this.indexFile, 'utf8'); return JSON.parse(data); } catch { return { executions: [], chains: {}, metrics: { total_executions: 0, total_tokens: 0, total_cost: 0.0, models_used: {} } }; } } async saveIndex() { await fs.writeFile(this.indexFile, JSON.stringify(this.index, null, 2)); } async trackExecution(executionId, model, promptHash, tag, tokens, latencyMs, cost, chainId = null, parentExecutionId = null, metadata = {}) { // Ensure executions directory exists await fs.mkdir(this.executionsDir, { recursive: true }); // Reload index to get latest state this.index = await this.loadIndex(); const execution = { id: executionId, timestamp: new Date().toISOString(), model, prompt_hash: promptHash, tag, tokens, latency_ms: latencyMs, cost, chain_id: chainId, parent_execution_id: parentExecutionId, metadata: metadata || {} }; // Save execution file const execFile = path.join(this.executionsDir, `${executionId}.json`); await fs.writeFile(execFile, JSON.stringify(execution, null, 2)); // Update index this.index.executions.push({ id: executionId, timestamp: execution.timestamp, model, tag, tokens: tokens.total || 0, cost, chain_id: chainId }); // Update metrics this.index.metrics.total_executions += 1; this.index.metrics.total_tokens += tokens.total || 0; this.index.metrics.total_cost += cost; // Update model usage if (!this.index.metrics.models_used[model]) { this.index.metrics.models_used[model] = { count: 0, tokens: 0, cost: 0.0 }; } this.index.metrics.models_used[model].count += 1; this.index.metrics.models_used[model].tokens += tokens.total || 0; this.index.metrics.models_used[model].cost += cost; // Update chains if (chainId) { if (!this.index.chains[chainId]) { this.index.chains[chainId] = { id: chainId, executions: [], created: new Date().toISOString() }; } this.index.chains[chainId].executions.push(executionId); } await this.saveIndex(); // Queue for online sync if in online mode console.log('[DEBUG] About to call queueForSync'); await this.queueForSync(execution); console.log('[DEBUG] queueForSync completed'); return executionId; } async queueForSync(execution) { /** * Queue execution for online sync. * This enables automatic syncing to Supabase when in online mode. */ console.log('[DEBUG] queueForSync called'); // Try to load .env file if PVM_REPO_KEY not already set if (!process.env.PVM_REPO_KEY) { console.log('[DEBUG] PVM_REPO_KEY not in env, trying to load .env file'); try { // Check for .env file in repo root const envPath = path.join(path.dirname(this.repoPath), '.env'); console.log('[DEBUG] Looking for .env at:', envPath); if (await fs.access(envPath).then(() => true).catch(() => false)) { const envContent = await fs.readFile(envPath, 'utf-8'); const match = envContent.match(/PVM_REPO_KEY=(.+)/); if (match) { process.env.PVM_REPO_KEY = match[1].trim(); console.log('[DEBUG] Loaded PVM_REPO_KEY from .env:', process.env.PVM_REPO_KEY.substring(0, 20) + '...'); } } } catch (error) { console.log('[DEBUG] Error loading .env:', error); } } // Check if we have a repo key (indicates online mode) const repoKey = process.env.PVM_REPO_KEY; console.log('[DEBUG] Final repo key check:', repoKey ? repoKey.substring(0, 20) + '...' : 'Not set'); if (!repoKey) { console.log('[DEBUG] No repo key, skipping sync queue'); return; // Not in online mode } // Create sync queue file path const syncQueueFile = path.join(this.repoPath, 'sync_queue.json'); console.log('[DEBUG] Sync queue path:', syncQueueFile); // Load existing queue let queue = []; try { const queueContent = await fs.readFile(syncQueueFile, 'utf-8'); queue = JSON.parse(queueContent); console.log('[DEBUG] Loaded existing queue with', queue.length, 'items'); } catch (error) { // File doesn't exist or is invalid, start with empty queue console.log('[DEBUG] No existing queue, starting fresh'); queue = []; } // Add to queue queue.push(execution); console.log('[DEBUG] Queue now has', queue.length, 'items'); // Save queue try { await fs.writeFile(syncQueueFile, JSON.stringify(queue, null, 2)); console.log('[DEBUG] ✅ Sync queue saved successfully'); } catch (error) { console.log('[DEBUG] ❌ Error saving sync queue:', error); } // Try to sync immediately in background console.log('[DEBUG] Attempting background sync'); this.trySyncAsync(); } trySyncAsync() { /** * Try to sync executions in background. * This runs asynchronously without blocking the main execution. */ // Import dynamically to avoid circular dependencies Promise.resolve().then(() => __importStar(require('child_process'))).then(({ spawn }) => { // Run sync in a separate process const syncScript = ` import asyncio import sys import os sys.path.insert(0, '${path.resolve(__dirname, '../../')}') from pvm.storage.execution_tracker import ExecutionTracker tracker = ExecutionTracker('${this.repoPath}') asyncio.run(tracker._sync_executions()) `; const pythonProcess = spawn('python', ['-c', syncScript], { detached: true, stdio: 'ignore' }); pythonProcess.unref(); // Allow parent to exit }).catch(() => { // Silently fail if sync doesn't work // The queue will remain for next time }); } async getExecutions(limit) { const executions = []; // Sort by timestamp descending const sortedExecs = [...this.index.executions].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); const execsToLoad = limit ? sortedExecs.slice(0, limit) : sortedExecs; // Load full execution data for (const execSummary of execsToLoad) { const execFile = path.join(this.executionsDir, `${execSummary.id}.json`); try { const data = await fs.readFile(execFile, 'utf8'); executions.push(JSON.parse(data)); } catch { // Skip if file doesn't exist } } return executions; } getMetrics() { return this.index.metrics; } getChains() { return this.index.chains; } async getTimeline(_days = 7) { const timeline = []; const dailyStats = {}; for (const execSummary of this.index.executions) { const timestamp = new Date(execSummary.timestamp); const dateKey = timestamp.toISOString().split('T')[0]; if (!dailyStats[dateKey]) { dailyStats[dateKey] = { date: dateKey, executions: 0, tokens: 0, cost: 0.0, models: new Set() }; } dailyStats[dateKey].executions += 1; dailyStats[dateKey].tokens += execSummary.tokens || 0; dailyStats[dateKey].cost += execSummary.cost || 0.0; dailyStats[dateKey].models.add(execSummary.model || 'unknown'); } // Convert to array for (const [, stats] of Object.entries(dailyStats)) { timeline.push({ date: stats.date, executions: stats.executions, tokens: stats.tokens, cost: stats.cost, models: Array.from(stats.models) }); } // Sort by date timeline.sort((a, b) => a.date.localeCompare(b.date)); return timeline; } } exports.ExecutionTracker = ExecutionTracker; //# sourceMappingURL=execution-tracker.js.map