@ai-capabilities-suite/mcp-debugger-core
Version:
Core debugging engine for Node.js and TypeScript applications. Provides Inspector Protocol integration, breakpoint management, variable inspection, execution control, profiling, hang detection, and source map support.
355 lines • 10.6 kB
JavaScript
"use strict";
/**
* Multi-target debugging support for debugging multiple processes simultaneously
* Coordinates breakpoints, aggregates logs, and supports parent-child process debugging
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MultiTargetDebugger = void 0;
const events_1 = require("events");
/**
* Manages debugging of multiple processes simultaneously
*/
class MultiTargetDebugger extends events_1.EventEmitter {
constructor() {
super(...arguments);
this.targets = new Map();
this.globalBreakpoints = new Map();
this.logs = [];
this.maxLogSize = 10000;
}
/**
* Add a debug target
*/
addTarget(id, name, session, parentId) {
if (this.targets.has(id)) {
throw new Error(`Target already exists: ${id}`);
}
const target = {
id,
name,
session,
parentId,
children: [],
};
this.targets.set(id, target);
// Update parent's children list
if (parentId) {
const parent = this.targets.get(parentId);
if (parent) {
parent.children.push(id);
}
}
// Set up log aggregation
this.setupLogAggregation(target);
this.emit("target-added", target);
}
/**
* Remove a debug target
*/
removeTarget(id) {
const target = this.targets.get(id);
if (!target) {
return false;
}
// Remove from parent's children list
if (target.parentId) {
const parent = this.targets.get(target.parentId);
if (parent) {
parent.children = parent.children.filter((childId) => childId !== id);
}
}
// Remove all children
for (const childId of target.children) {
this.removeTarget(childId);
}
this.targets.delete(id);
this.emit("target-removed", id);
return true;
}
/**
* Get a debug target by ID
*/
getTarget(id) {
return this.targets.get(id);
}
/**
* Get all debug targets
*/
getAllTargets() {
return Array.from(this.targets.values());
}
/**
* Get root targets (targets without parents)
*/
getRootTargets() {
return Array.from(this.targets.values()).filter((t) => !t.parentId);
}
/**
* Get children of a target
*/
getChildren(targetId) {
const target = this.targets.get(targetId);
if (!target) {
return [];
}
return target.children
.map((childId) => this.targets.get(childId))
.filter((child) => child !== undefined);
}
/**
* Set a breakpoint across multiple targets
*/
async setGlobalBreakpoint(file, line, targetIds, condition) {
const breakpointId = `bp-${Date.now()}-${Math.random()
.toString(36)
.substring(2, 11)}`;
const breakpoint = {
file,
line,
condition,
targets: [],
};
// Set breakpoint on each target
for (const targetId of targetIds) {
const target = this.targets.get(targetId);
if (!target) {
continue;
}
try {
await target.session.setBreakpoint(file, line, condition);
breakpoint.targets.push(targetId);
}
catch (error) {
this.emit("breakpoint-error", {
targetId,
file,
line,
error,
});
}
}
this.globalBreakpoints.set(breakpointId, breakpoint);
this.emit("global-breakpoint-set", breakpointId, breakpoint);
return breakpointId;
}
/**
* Remove a global breakpoint
*/
async removeGlobalBreakpoint(breakpointId) {
const breakpoint = this.globalBreakpoints.get(breakpointId);
if (!breakpoint) {
return false;
}
// Remove breakpoint from each target
for (const targetId of breakpoint.targets) {
const target = this.targets.get(targetId);
if (!target) {
continue;
}
try {
// Find and remove the breakpoint
const breakpoints = target.session.getAllBreakpoints();
const matchingBp = breakpoints.find((bp) => bp.file === breakpoint.file && bp.line === breakpoint.line);
if (matchingBp) {
await target.session.removeBreakpoint(matchingBp.id);
}
}
catch (error) {
this.emit("breakpoint-error", {
targetId,
error,
});
}
}
this.globalBreakpoints.delete(breakpointId);
this.emit("global-breakpoint-removed", breakpointId);
return true;
}
/**
* Get all global breakpoints
*/
getGlobalBreakpoints() {
return new Map(this.globalBreakpoints);
}
/**
* Continue execution on all targets
*/
async continueAll() {
const promises = [];
for (const target of this.targets.values()) {
promises.push(target.session.resume().catch((error) => {
this.emit("target-error", {
targetId: target.id,
operation: "resume",
error,
});
}));
}
await Promise.all(promises);
}
/**
* Continue execution on specific targets
*/
async continueTargets(targetIds) {
const promises = [];
for (const targetId of targetIds) {
const target = this.targets.get(targetId);
if (!target) {
continue;
}
promises.push(target.session.resume().catch((error) => {
this.emit("target-error", {
targetId,
operation: "resume",
error,
});
}));
}
await Promise.all(promises);
}
/**
* Pause execution on all targets
*/
async pauseAll() {
const promises = [];
for (const target of this.targets.values()) {
promises.push(target.session.pause().catch((error) => {
this.emit("target-error", {
targetId: target.id,
operation: "pause",
error,
});
}));
}
await Promise.all(promises);
}
/**
* Get aggregated logs from all targets
*/
getAggregatedLogs(options) {
let logs = this.logs;
// Filter by target IDs
if (options?.targetIds) {
const targetIdSet = new Set(options.targetIds);
logs = logs.filter((log) => targetIdSet.has(log.targetId));
}
// Filter by level
if (options?.level) {
logs = logs.filter((log) => log.level === options.level);
}
// Filter by timestamp
if (options?.since !== undefined) {
logs = logs.filter((log) => log.timestamp >= options.since);
}
// Apply limit
if (options?.limit) {
logs = logs.slice(-options.limit);
}
return logs;
}
/**
* Clear aggregated logs
*/
clearLogs() {
this.logs = [];
this.emit("logs-cleared");
}
/**
* Set up log aggregation for a target
*/
setupLogAggregation(target) {
// Get the process from the session
const process = target.session.getProcess();
if (!process) {
return;
}
// Listen for stdout
if (process.stdout) {
process.stdout.on("data", (data) => {
this.addLog({
timestamp: Date.now(),
targetId: target.id,
targetName: target.name,
level: "stdout",
message: data.toString(),
});
});
}
// Listen for stderr
if (process.stderr) {
process.stderr.on("data", (data) => {
this.addLog({
timestamp: Date.now(),
targetId: target.id,
targetName: target.name,
level: "stderr",
message: data.toString(),
});
});
}
// Note: Debug-level logs would require inspector integration
// which is not exposed through the DebugSession API
}
/**
* Add a log entry
*/
addLog(log) {
this.logs.push(log);
// Trim logs if exceeding max size
if (this.logs.length > this.maxLogSize) {
this.logs = this.logs.slice(-this.maxLogSize);
}
this.emit("log", log);
}
/**
* Set maximum log size
*/
setMaxLogSize(size) {
if (size <= 0) {
throw new Error("Max log size must be positive");
}
this.maxLogSize = size;
// Trim existing logs if needed
if (this.logs.length > this.maxLogSize) {
this.logs = this.logs.slice(-this.maxLogSize);
}
}
/**
* Get target hierarchy as a tree structure
*/
getTargetTree() {
return this.getRootTargets().map((root) => this.buildTargetTree(root));
}
/**
* Build target tree recursively
*/
buildTargetTree(target) {
const children = this.getChildren(target.id);
return {
...target,
children: children.map((child) => child.id),
};
}
/**
* Stop all debug targets
*/
async stopAll() {
const promises = [];
for (const target of this.targets.values()) {
promises.push(target.session.cleanup().catch((error) => {
this.emit("target-error", {
targetId: target.id,
operation: "cleanup",
error,
});
}));
}
await Promise.all(promises);
// Clear all targets
this.targets.clear();
this.globalBreakpoints.clear();
this.emit("all-stopped");
}
}
exports.MultiTargetDebugger = MultiTargetDebugger;
//# sourceMappingURL=multi-target-debugger.js.map