pit-manager
Version:
Centralized prompt management system for Human Behavior AI agents
269 lines • 10.3 kB
JavaScript
"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