UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

571 lines (497 loc) 16.7 kB
/** * @claude-flow/browser - Browser Service * Core application service integrating agent-browser with agentic-flow */ import { AgentBrowserAdapter } from '../infrastructure/agent-browser-adapter.js'; import { createMemoryManager, type BrowserMemoryManager } from '../infrastructure/memory-integration.js'; import { getSecurityScanner, type BrowserSecurityScanner, type ThreatScanResult } from '../infrastructure/security-integration.js'; import type { Snapshot, SnapshotOptions, ActionResult, BrowserSession, BrowserTrajectory, BrowserTrajectoryStep, BrowserSwarmConfig, BrowserAgentConfig, } from '../domain/types.js'; // ============================================================================ // Trajectory Tracking for ReasoningBank Integration // ============================================================================ interface TrajectoryTracker { id: string; sessionId: string; goal: string; steps: BrowserTrajectoryStep[]; startedAt: string; lastSnapshot?: Snapshot; } const activeTrajectories = new Map<string, TrajectoryTracker>(); // ============================================================================ // Browser Service Class // ============================================================================ export interface BrowserServiceConfig extends Partial<BrowserAgentConfig> { enableMemory?: boolean; enableSecurity?: boolean; requireHttps?: boolean; blockedDomains?: string[]; allowedDomains?: string[]; } export class BrowserService { private adapter: AgentBrowserAdapter; private sessionId: string; private currentTrajectory?: string; private snapshots: Map<string, Snapshot> = new Map(); private memoryManager?: BrowserMemoryManager; private securityScanner?: BrowserSecurityScanner; private config: BrowserServiceConfig; constructor(config: BrowserServiceConfig = {}) { this.config = config; this.sessionId = config.sessionId || `browser-${Date.now()}`; this.adapter = new AgentBrowserAdapter({ session: this.sessionId, timeout: config.defaultTimeout || 30000, headless: config.headless !== false, }); // Initialize memory integration if enabled (default: true) if (config.enableMemory !== false) { this.memoryManager = createMemoryManager(this.sessionId); } // Initialize security scanning if enabled (default: true) if (config.enableSecurity !== false) { this.securityScanner = getSecurityScanner({ requireHttps: config.requireHttps, blockedDomains: config.blockedDomains, allowedDomains: config.allowedDomains, }); } } // =========================================================================== // Trajectory Management (for ReasoningBank/SONA learning) // =========================================================================== /** * Start a new trajectory for learning */ startTrajectory(goal: string): string { const id = `traj-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; activeTrajectories.set(id, { id, sessionId: this.sessionId, goal, steps: [], startedAt: new Date().toISOString(), }); this.currentTrajectory = id; return id; } /** * Record a step in the current trajectory */ private recordStep(action: string, input: Record<string, unknown> | object, result: ActionResult): void { if (!this.currentTrajectory) return; const trajectory = activeTrajectories.get(this.currentTrajectory); if (!trajectory) return; trajectory.steps.push({ action, input: input as Record<string, unknown>, result, snapshot: trajectory.lastSnapshot, timestamp: new Date().toISOString(), }); } /** * End trajectory and return for learning (also stores in memory) */ async endTrajectory(success: boolean, verdict?: string): Promise<BrowserTrajectory | null> { if (!this.currentTrajectory) return null; const trajectory = activeTrajectories.get(this.currentTrajectory); if (!trajectory) return null; const completed: BrowserTrajectory = { ...trajectory, completedAt: new Date().toISOString(), success, verdict, }; // Store in memory for learning if (this.memoryManager) { await this.memoryManager.storeTrajectory(completed); } activeTrajectories.delete(this.currentTrajectory); this.currentTrajectory = undefined; return completed; } /** * Get current trajectory for inspection */ getCurrentTrajectory(): TrajectoryTracker | null { if (!this.currentTrajectory) return null; return activeTrajectories.get(this.currentTrajectory) || null; } // =========================================================================== // Core Browser Operations // =========================================================================== /** * Navigate to URL with trajectory tracking and security scanning */ async open(url: string, options?: { waitUntil?: 'load' | 'domcontentloaded' | 'networkidle'; headers?: Record<string, string>; skipSecurityCheck?: boolean }): Promise<ActionResult> { // Security check before navigation if (this.securityScanner && !options?.skipSecurityCheck) { const scanResult = await this.securityScanner.scanUrl(url); if (!scanResult.safe) { const threats = scanResult.threats.map(t => `${t.type}: ${t.description}`).join('; '); return { success: false, error: `Security scan failed: ${threats}`, data: { scanResult }, }; } } const result = await this.adapter.open({ url, waitUntil: options?.waitUntil, headers: options?.headers, }); this.recordStep('open', { url, ...options }, result); return result; } /** * Scan URL for security threats without navigating */ async scanUrl(url: string): Promise<ThreatScanResult> { if (!this.securityScanner) { return { safe: true, threats: [], pii: [], score: 1, scanDuration: 0 }; } return this.securityScanner.scanUrl(url); } /** * Get snapshot with automatic caching */ async snapshot(options: SnapshotOptions = {}): Promise<ActionResult<Snapshot>> { const result = await this.adapter.snapshot({ interactive: options.interactive !== false, compact: options.compact !== false, ...options, }); if (result.success && result.data) { // Cache snapshot and update trajectory const snapshot = result.data as Snapshot; this.snapshots.set('latest', snapshot); if (this.currentTrajectory) { const trajectory = activeTrajectories.get(this.currentTrajectory); if (trajectory) { trajectory.lastSnapshot = snapshot; } } } this.recordStep('snapshot', options, result); return result as ActionResult<Snapshot>; } /** * Click with trajectory tracking */ async click(target: string, options?: { button?: 'left' | 'right' | 'middle'; force?: boolean }): Promise<ActionResult> { const result = await this.adapter.click({ target, ...options, }); this.recordStep('click', { target, ...options }, result); return result; } /** * Fill input with trajectory tracking and PII scanning */ async fill(target: string, value: string, options?: { force?: boolean; skipPIICheck?: boolean }): Promise<ActionResult> { // Check for PII in the value if (this.securityScanner && !options?.skipPIICheck) { const scanResult = this.securityScanner.scanContent(value, target); if (scanResult.pii.length > 0) { // Log masked values for security const maskedPII = scanResult.pii.map(p => `${p.type}: ${p.masked}`).join(', '); console.log(`[security] PII detected in form field ${target}: ${maskedPII}`); } } const result = await this.adapter.fill({ target, value, ...options, }); this.recordStep('fill', { target, value: this.securityScanner ? '[REDACTED]' : value, ...options }, result); return result; } /** * Check if content contains PII */ scanForPII(content: string, context?: string): ThreatScanResult { if (!this.securityScanner) { return { safe: true, threats: [], pii: [], score: 1, scanDuration: 0 }; } return this.securityScanner.scanContent(content, context); } /** * Type text with trajectory tracking */ async type(target: string, text: string, options?: { delay?: number }): Promise<ActionResult> { const result = await this.adapter.type({ target, text, ...options, }); this.recordStep('type', { target, text, ...options }, result); return result; } /** * Press key with trajectory tracking */ async press(key: string, delay?: number): Promise<ActionResult> { const result = await this.adapter.press(key, delay); this.recordStep('press', { key, delay }, result); return result; } /** * Wait for condition */ async wait(options: { selector?: string; timeout?: number; text?: string; url?: string; load?: 'load' | 'domcontentloaded' | 'networkidle'; fn?: string }): Promise<ActionResult> { const result = await this.adapter.wait(options); this.recordStep('wait', options, result); return result; } /** * Get element text */ async getText(target: string): Promise<ActionResult<string>> { const result = await this.adapter.getText(target); this.recordStep('getText', { target }, result); return result; } /** * Execute JavaScript */ async eval<T = unknown>(script: string): Promise<ActionResult<T>> { const result = await this.adapter.eval<T>({ script }); this.recordStep('eval', { script }, result); return result; } /** * Take screenshot */ async screenshot(options?: { path?: string; fullPage?: boolean }): Promise<ActionResult<string>> { const result = await this.adapter.screenshot(options || {}); this.recordStep('screenshot', options || {}, result); return result; } /** * Close browser */ async close(): Promise<ActionResult> { const result = await this.adapter.close(); this.recordStep('close', {}, result); return result; } // =========================================================================== // High-Level Workflow Operations // =========================================================================== /** * Authenticate using header injection (skips login UI) */ async authenticateWithHeaders(url: string, headers: Record<string, string>): Promise<ActionResult> { return this.open(url, { headers }); } /** * Fill and submit a form */ async submitForm(fields: Array<{ target: string; value: string }>, submitButton: string): Promise<ActionResult> { // Fill all fields for (const field of fields) { const result = await this.fill(field.target, field.value); if (!result.success) return result; } // Click submit return this.click(submitButton); } /** * Extract data using snapshot refs */ async extractData(refs: string[]): Promise<Record<string, string>> { const data: Record<string, string> = {}; for (const ref of refs) { const result = await this.getText(ref); if (result.success && result.data) { data[ref] = result.data; } } return data; } /** * Navigate and wait for specific element */ async navigateAndWait(url: string, selector: string, timeout?: number): Promise<ActionResult> { const navResult = await this.open(url); if (!navResult.success) return navResult; return this.wait({ selector, timeout }); } // =========================================================================== // Session State // =========================================================================== /** * Get cached snapshot */ getLatestSnapshot(): Snapshot | null { return this.snapshots.get('latest') || null; } /** * Get session ID */ getSessionId(): string { return this.sessionId; } /** * Get underlying adapter for advanced operations */ getAdapter(): AgentBrowserAdapter { return this.adapter; } /** * Get memory manager for direct memory operations */ getMemoryManager(): BrowserMemoryManager | undefined { return this.memoryManager; } /** * Get security scanner for direct security operations */ getSecurityScanner(): BrowserSecurityScanner | undefined { return this.securityScanner; } /** * Find similar trajectories for a goal (uses HNSW search) */ async findSimilarTrajectories(goal: string, topK = 5): Promise<BrowserTrajectory[]> { if (!this.memoryManager) return []; return this.memoryManager.findSimilarTrajectories(goal, topK); } /** * Get session memory statistics */ async getMemoryStats(): Promise<{ trajectories: number; patterns: number; snapshots: number; errors: number; successRate: number; } | null> { if (!this.memoryManager) return null; return this.memoryManager.getSessionStats(); } } // ============================================================================ // Browser Swarm Coordinator // ============================================================================ export class BrowserSwarmCoordinator { private config: BrowserSwarmConfig; private services: Map<string, BrowserService> = new Map(); private sharedData: Map<string, unknown> = new Map(); constructor(config: BrowserSwarmConfig) { this.config = config; } /** * Spawn a new browser agent in the swarm */ async spawnAgent(role: 'navigator' | 'scraper' | 'validator' | 'tester' | 'monitor'): Promise<BrowserService> { if (this.services.size >= this.config.maxSessions) { throw new Error(`Max sessions (${this.config.maxSessions}) reached`); } const sessionId = `${this.config.sessionPrefix}-${role}-${Date.now()}`; const service = new BrowserService({ sessionId, role, capabilities: this.getCapabilitiesForRole(role), defaultTimeout: 30000, headless: true, }); this.services.set(sessionId, service); return service; } /** * Get capabilities for a role */ private getCapabilitiesForRole(role: string): string[] { switch (role) { case 'navigator': return ['navigation', 'authentication', 'session-management']; case 'scraper': return ['snapshot', 'extraction', 'pagination']; case 'validator': return ['assertions', 'state-checks', 'screenshots']; case 'tester': return ['forms', 'interactions', 'assertions']; case 'monitor': return ['network', 'console', 'errors']; default: return []; } } /** * Share data between agents */ shareData(key: string, value: unknown): void { this.sharedData.set(key, value); } /** * Get shared data */ getSharedData<T>(key: string): T | undefined { return this.sharedData.get(key) as T | undefined; } /** * Get all active sessions */ getSessions(): string[] { return Array.from(this.services.keys()); } /** * Get a specific service */ getService(sessionId: string): BrowserService | undefined { return this.services.get(sessionId); } /** * Close all sessions */ async closeAll(): Promise<void> { const closePromises = Array.from(this.services.values()).map(s => s.close()); await Promise.all(closePromises); this.services.clear(); } /** * Get coordinator stats */ getStats(): { activeSessions: number; maxSessions: number; topology: string } { return { activeSessions: this.services.size, maxSessions: this.config.maxSessions, topology: this.config.topology, }; } } // ============================================================================ // Factory Functions // ============================================================================ /** * Create a standalone browser service */ export function createBrowserService(options?: Partial<BrowserAgentConfig>): BrowserService { return new BrowserService(options); } /** * Create a browser swarm coordinator */ export function createBrowserSwarm(config?: Partial<BrowserSwarmConfig>): BrowserSwarmCoordinator { return new BrowserSwarmCoordinator({ topology: config?.topology || 'hierarchical', maxSessions: config?.maxSessions || 5, sessionPrefix: config?.sessionPrefix || 'swarm', sharedCookies: config?.sharedCookies, coordinatorSession: config?.coordinatorSession, }); } export default BrowserService;