import { randomUUID } from 'crypto'; import type { HookAction, HookResult, PendingConfirmation, HookConfig } from './types.js'; export class HookEngine { private confirmPatterns: RegExp[]; private logPatterns: RegExp[]; private pendingConfirmations: Map = new Map(); constructor(config: HookConfig) { this.confirmPatterns = config.confirm.map(p => this.patternToRegex(p)); this.logPatterns = config.log.map(p => this.patternToRegex(p)); } private patternToRegex(pattern: string): RegExp { const escaped = pattern .replace(/[.+^${}()|[\]\\]/g, '\\$&') .replace(/\*/g, '.*'); return new RegExp(`^${escaped}$`); } getAction(tool: string): HookAction { if (this.confirmPatterns.some(p => p.test(tool))) { return 'confirm'; } if (this.logPatterns.some(p => p.test(tool))) { return 'log'; } return 'silent'; } async requestConfirmation(tool: string, args: Record): Promise { const id = randomUUID(); return new Promise((resolve) => { const pending: PendingConfirmation = { id, tool, args, resolve, createdAt: new Date(), }; this.pendingConfirmations.set(id, pending); }); } resolveConfirmation(id: string, result: HookResult): boolean { const pending = this.pendingConfirmations.get(id); if (!pending) { return false; } pending.resolve(result); this.pendingConfirmations.delete(id); return true; } getPendingConfirmations(): PendingConfirmation[] { return Array.from(this.pendingConfirmations.values()); } clearExpiredConfirmations(maxAgeMs: number = 5 * 60 * 1000): number { const now = Date.now(); let cleared = 0; for (const [id, pending] of this.pendingConfirmations) { if (now - pending.createdAt.getTime() > maxAgeMs) { pending.resolve({ approved: false, reason: 'Confirmation timed out' }); this.pendingConfirmations.delete(id); cleared++; } } return cleared; } }