Skip to main content
Glama
pending-breakpoints.ts6.85 kB
/** * Pending Breakpoints Manager * Stores breakpoints before a debug session starts and applies them when a session connects. */ import { EventEmitter } from 'events'; import { HitCondition } from '../dbgp/types.js'; import { DebugSession } from './session.js'; import { logger } from '../utils/logger.js'; export interface PendingBreakpoint { id: string; type: 'line' | 'exception' | 'call'; file?: string; line?: number; condition?: string; hitValue?: number; hitCondition?: HitCondition; exception?: string; functionName?: string; enabled: boolean; createdAt: Date; } export interface AppliedBreakpoint { pendingId: string; sessionId: string; breakpointId: string; } export class PendingBreakpointsManager extends EventEmitter { private pendingBreakpoints: Map<string, PendingBreakpoint> = new Map(); private appliedBreakpoints: Map<string, AppliedBreakpoint[]> = new Map(); private breakpointIdCounter: number = 0; /** * Add a pending line breakpoint */ addLineBreakpoint( file: string, line: number, options?: { condition?: string; hitValue?: number; hitCondition?: HitCondition; } ): PendingBreakpoint { const id = `pending_${++this.breakpointIdCounter}`; const bp: PendingBreakpoint = { id, type: 'line', file, line, condition: options?.condition, hitValue: options?.hitValue, hitCondition: options?.hitCondition, enabled: true, createdAt: new Date(), }; this.pendingBreakpoints.set(id, bp); logger.info(`Pending breakpoint added: ${id} at ${file}:${line}`); this.emit('breakpointAdded', bp); return bp; } /** * Add a pending exception breakpoint */ addExceptionBreakpoint(exception: string = '*'): PendingBreakpoint { const id = `pending_${++this.breakpointIdCounter}`; const bp: PendingBreakpoint = { id, type: 'exception', exception, enabled: true, createdAt: new Date(), }; this.pendingBreakpoints.set(id, bp); logger.info(`Pending exception breakpoint added: ${id} for ${exception}`); this.emit('breakpointAdded', bp); return bp; } /** * Add a pending call breakpoint */ addCallBreakpoint(functionName: string): PendingBreakpoint { const id = `pending_${++this.breakpointIdCounter}`; const bp: PendingBreakpoint = { id, type: 'call', functionName, enabled: true, createdAt: new Date(), }; this.pendingBreakpoints.set(id, bp); logger.info(`Pending call breakpoint added: ${id} for ${functionName}`); this.emit('breakpointAdded', bp); return bp; } /** * Remove a pending breakpoint */ removeBreakpoint(id: string): boolean { const removed = this.pendingBreakpoints.delete(id); if (removed) { logger.info(`Pending breakpoint removed: ${id}`); this.emit('breakpointRemoved', id); } return removed; } /** * Get all pending breakpoints */ getAllPendingBreakpoints(): PendingBreakpoint[] { return Array.from(this.pendingBreakpoints.values()); } /** * Get a specific pending breakpoint */ getPendingBreakpoint(id: string): PendingBreakpoint | undefined { return this.pendingBreakpoints.get(id); } /** * Enable/disable a pending breakpoint */ setBreakpointEnabled(id: string, enabled: boolean): boolean { const bp = this.pendingBreakpoints.get(id); if (bp) { bp.enabled = enabled; return true; } return false; } /** * Apply all pending breakpoints to a new session */ async applyToSession(session: DebugSession): Promise<AppliedBreakpoint[]> { const applied: AppliedBreakpoint[] = []; for (const bp of this.pendingBreakpoints.values()) { if (!bp.enabled) continue; try { let result; switch (bp.type) { case 'line': if (bp.file && bp.line) { result = await session.setLineBreakpoint(bp.file, bp.line, { condition: bp.condition, hitValue: bp.hitValue, hitCondition: bp.hitCondition, }); applied.push({ pendingId: bp.id, sessionId: session.id, breakpointId: result.id, }); logger.debug(`Applied breakpoint ${bp.id} -> ${result.id}`); } break; case 'exception': if (bp.exception) { result = await session.setExceptionBreakpoint(bp.exception); applied.push({ pendingId: bp.id, sessionId: session.id, breakpointId: result.id, }); logger.debug(`Applied exception breakpoint ${bp.id} -> ${result.id}`); } break; case 'call': if (bp.functionName) { result = await session.setCallBreakpoint(bp.functionName); applied.push({ pendingId: bp.id, sessionId: session.id, breakpointId: result.id, }); logger.debug(`Applied call breakpoint ${bp.id} -> ${result.id}`); } break; } } catch (error) { logger.error(`Failed to apply breakpoint ${bp.id}:`, error); } } // Store applied breakpoints for this session this.appliedBreakpoints.set(session.id, applied); logger.info(`Applied ${applied.length} breakpoints to session ${session.id}`); this.emit('breakpointsApplied', session.id, applied); return applied; } /** * Get applied breakpoints for a session */ getAppliedBreakpoints(sessionId: string): AppliedBreakpoint[] { return this.appliedBreakpoints.get(sessionId) || []; } /** * Clear applied breakpoints for a session (when session ends) */ clearSessionBreakpoints(sessionId: string): void { this.appliedBreakpoints.delete(sessionId); } /** * Clear all pending breakpoints */ clearAll(): void { this.pendingBreakpoints.clear(); logger.info('All pending breakpoints cleared'); } /** * Export configuration */ exportConfig(): PendingBreakpoint[] { return this.getAllPendingBreakpoints(); } /** * Import configuration */ importConfig(breakpoints: PendingBreakpoint[]): void { for (const bp of breakpoints) { this.pendingBreakpoints.set(bp.id, { ...bp, createdAt: new Date(bp.createdAt), }); // Update counter to avoid ID conflicts const num = parseInt(bp.id.replace('pending_', ''), 10); if (!isNaN(num) && num >= this.breakpointIdCounter) { this.breakpointIdCounter = num + 1; } } } get count(): number { return this.pendingBreakpoints.size; } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kpanuragh/xdebug-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server