prompt-version-manager
Version:
Centralized prompt management system for Human Behavior AI agents
308 lines • 11.8 kB
JavaScript
;
/**
* 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