UNPKG

pit-manager

Version:

Centralized prompt management system for Human Behavior AI agents

269 lines 10.3 kB
"use strict"; /** * Online sync functionality for PIT execution tracking * Automatically syncs execution data to Supabase in the background */ 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.OnlineSyncManager = void 0; exports.initializeOnlineSync = initializeOnlineSync; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const crypto = __importStar(require("crypto")); const supabase_js_1 = require("@supabase/supabase-js"); // Singleton instance let syncManager = null; class OnlineSyncManager { supabaseClient = null; repoId = null; syncInterval = null; isShuttingDown = false; repoPath; // Configuration SYNC_INTERVAL_MS = 30000; // 30 seconds SYNC_BATCH_SIZE = 50; constructor(repoPath) { this.repoPath = repoPath; this.initialize(); } async initialize() { // Load environment variables await this.loadEnvFile(); // Check for ALL required PIT environment variables const url = process.env.PIT_SUPABASE_URL; const serviceKey = process.env.PIT_SUPABASE_SERVICE_ROLE_KEY; const repoKey = process.env.PIT_REPO_KEY; // All three are required for online sync to work if (!url || !serviceKey || !repoKey) { const missing = []; if (!url) missing.push('PIT_SUPABASE_URL'); if (!serviceKey) missing.push('PIT_SUPABASE_SERVICE_ROLE_KEY'); if (!repoKey) missing.push('PIT_REPO_KEY'); console.log(`[PIT-SYNC] Online sync disabled. Missing: ${missing.join(', ')}`); return; } // Initialize Supabase client this.supabaseClient = (0, supabase_js_1.createClient)(url, serviceKey); // Validate repo key this.repoId = await this.validateRepoKey(repoKey); if (!this.repoId) { console.log('[PIT-SYNC] Invalid repository key. Online sync disabled.'); this.supabaseClient = null; return; } console.log('[PIT-SYNC] Online sync initialized for repo:', this.repoId); // Start background sync this.startBackgroundSync(); // Setup graceful shutdown this.setupShutdownHandlers(); } async loadEnvFile() { try { // Look for .env file in project root (parent of .pit directory) const projectRoot = path.dirname(this.repoPath); const envPath = path.join(projectRoot, '.env'); if (await fs.access(envPath).then(() => true).catch(() => false)) { const envContent = await fs.readFile(envPath, 'utf-8'); // Parse simple KEY=VALUE format const lines = envContent.split('\n'); for (const line of lines) { const match = line.match(/^([^#=]+)=(.*)$/); if (match) { const key = match[1].trim(); const value = match[2].trim(); if (!process.env[key]) { process.env[key] = value; } } } } } catch { // Ignore errors loading .env } } hashRepoKey(repoKey) { return crypto.createHash('sha256').update(repoKey).digest('hex'); } async validateRepoKey(repoKey) { if (!this.supabaseClient) return null; try { const keyHash = this.hashRepoKey(repoKey); const { data, error } = await this.supabaseClient .from('repo_keys') .select('repo_id') .eq('key_hash', keyHash) .single(); if (error || !data) { return null; } return data.repo_id; } catch { return null; } } startBackgroundSync() { if (this.syncInterval) return; // Initial sync after 5 seconds setTimeout(() => { if (!this.isShuttingDown) { this.syncExecutions().catch(console.error); } }, 5000); // Regular sync interval this.syncInterval = setInterval(() => { if (!this.isShuttingDown) { this.syncExecutions().catch(console.error); } }, this.SYNC_INTERVAL_MS); console.log('[PIT-SYNC] Background sync started (30s interval)'); } async syncExecutions() { if (!this.supabaseClient || !this.repoId || this.isShuttingDown) { return; } const syncQueueFile = path.join(this.repoPath, 'sync_queue.json'); try { // Load queue let queue; try { const data = await fs.readFile(syncQueueFile, 'utf-8'); queue = JSON.parse(data); } catch { // No queue file return; } if (!queue || queue.length === 0) return; console.log(`[PIT-SYNC] Syncing ${queue.length} executions...`); // Process in batches const batch = queue.slice(0, this.SYNC_BATCH_SIZE); const remaining = queue.slice(this.SYNC_BATCH_SIZE); let successCount = 0; const failed = []; for (const execution of batch) { if (this.isShuttingDown) break; try { const supabaseData = { repo_id: this.repoId, execution_id: execution.id, model: execution.model, prompt_hash: execution.prompt_hash, tag: execution.tag, tokens: execution.tokens, latency_ms: execution.latency_ms, cost: execution.cost, chain_id: execution.chain_id, parent_execution_id: execution.parent_execution_id, chain_group_id: execution.chain_group_id, // NEW: Include chain group ID metadata: execution.metadata || {}, created_at: execution.timestamp }; const { error } = await this.supabaseClient .from('executions') .insert(supabaseData); if (error) { // Check if it's a duplicate key error if (error.message?.includes('duplicate')) { // Skip duplicates silently successCount++; } else { console.error(`[PIT-SYNC] Failed: ${error.message}`); failed.push(execution); } } else { successCount++; } } catch (error) { failed.push(execution); } } // Update queue const newQueue = [...remaining, ...failed]; if (newQueue.length === 0) { // All synced, remove queue file await fs.unlink(syncQueueFile).catch(() => { }); console.log(`[PIT-SYNC] ✅ Synced ${successCount} executions`); } else { // Save remaining queue await fs.writeFile(syncQueueFile, JSON.stringify(newQueue, null, 2)); console.log(`[PIT-SYNC] Synced ${successCount}, ${newQueue.length} remaining`); } } catch (error) { console.error('[PIT-SYNC] Sync error:', error); } } setupShutdownHandlers() { const shutdown = async () => { if (this.isShuttingDown) return; console.log('[PIT-SYNC] Shutting down, performing final sync...'); this.isShuttingDown = true; if (this.syncInterval) { clearInterval(this.syncInterval); this.syncInterval = null; } await this.syncExecutions(); process.exit(0); }; process.on('SIGINT', shutdown); process.on('SIGTERM', shutdown); } static getInstance(repoPath) { if (!syncManager) { syncManager = new OnlineSyncManager(repoPath); } return syncManager; } } exports.OnlineSyncManager = OnlineSyncManager; // Export function to initialize sync function initializeOnlineSync(repoPath) { return OnlineSyncManager.getInstance(repoPath); } //# sourceMappingURL=online-sync.js.map