#!/usr/bin/env bun
// @bun

// dap_websocket_server_bun.ts
var {spawn } = globalThis.Bun;
import { mkdtemp, writeFile, unlink, rmdir, symlink } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
var LOG_LEVEL = process.env.LOG_LEVEL || "DEBUG";
var logger = {
  debug: (...args) => {
    if (LOG_LEVEL === "DEBUG")
      console.log("[DEBUG]", new Date().toISOString(), ...args);
  },
  info: (...args) => console.log("[INFO]", new Date().toISOString(), ...args),
  warn: (...args) => console.warn("[WARN]", new Date().toISOString(), ...args),
  error: (...args) => console.error("[ERROR]", new Date().toISOString(), ...args)
};
var WINDMILL_BASE_URL = process.env.WINDMILL_BASE_URL || process.env.BASE_INTERNAL_URL;
var REQUIRE_SIGNED_REQUESTS = process.env.REQUIRE_SIGNED_DEBUG_REQUESTS !== "false";
var cachedPublicKey = null;
var publicKeyFetchPromise = null;
async function getPublicKey() {
  if (cachedPublicKey) {
    return cachedPublicKey;
  }
  if (publicKeyFetchPromise) {
    return publicKeyFetchPromise;
  }
  if (!WINDMILL_BASE_URL) {
    logger.warn("WINDMILL_BASE_URL not set - cannot fetch public key");
    return null;
  }
  publicKeyFetchPromise = (async () => {
    try {
      const jwksUrl = `${WINDMILL_BASE_URL.replace(/\/$/, "")}/api/debug/jwks`;
      logger.info(`Fetching JWKS from ${jwksUrl}`);
      const response = await fetch(jwksUrl);
      if (!response.ok) {
        throw new Error(`Failed to fetch JWKS: ${response.status} ${response.statusText}`);
      }
      const jwks = await response.json();
      if (!jwks.keys || jwks.keys.length === 0) {
        throw new Error("No keys in JWKS");
      }
      const jwk = jwks.keys[0];
      if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519") {
        throw new Error(`Unsupported key type: ${jwk.kty}/${jwk.crv}`);
      }
      const publicKeyBytes = Uint8Array.from(atob(jwk.x.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
      const key = await crypto.subtle.importKey("raw", publicKeyBytes, { name: "Ed25519" }, true, ["verify"]);
      cachedPublicKey = key;
      logger.info("Successfully loaded Ed25519 public key from JWKS");
      return key;
    } catch (error) {
      logger.error(`Failed to fetch/parse JWKS: ${error}`);
      return null;
    } finally {
      publicKeyFetchPromise = null;
    }
  })();
  return publicKeyFetchPromise;
}
async function computeCodeHash(code) {
  const encoder = new TextEncoder;
  const data = encoder.encode(code);
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
  const hashArray = new Uint8Array(hashBuffer);
  return Array.from(hashArray.slice(0, 16)).map((b) => b.toString(16).padStart(2, "0")).join("");
}
function base64urlDecode(str) {
  const padding = "=".repeat((4 - str.length % 4) % 4);
  const base64 = str.replace(/-/g, "+").replace(/_/g, "/") + padding;
  const binary = atob(base64);
  return Uint8Array.from(binary, (c) => c.charCodeAt(0));
}
async function verifyDebugToken(token, code) {
  const publicKey = await getPublicKey();
  if (!publicKey) {
    if (REQUIRE_SIGNED_REQUESTS) {
      return "Public key not available but signed requests are required. Set WINDMILL_BASE_URL.";
    }
    logger.warn("Public key not available - signature verification disabled");
    return null;
  }
  const parts = token.split(".");
  if (parts.length !== 3) {
    return "Invalid JWT format";
  }
  const [headerB64, claimsB64, signatureB64] = parts;
  try {
    const message = new TextEncoder().encode(`${headerB64}.${claimsB64}`);
    const signature = base64urlDecode(signatureB64);
    const isValid = await crypto.subtle.verify({ name: "Ed25519" }, publicKey, signature, message);
    if (!isValid) {
      return "Invalid JWT signature";
    }
    const claimsJson = new TextDecoder().decode(base64urlDecode(claimsB64));
    const claims = JSON.parse(claimsJson);
    const now = Math.floor(Date.now() / 1000);
    if (now > claims.exp) {
      return `Token expired: ${now - claims.exp} seconds ago`;
    }
    const expectedHash = await computeCodeHash(code);
    if (claims.code_hash !== expectedHash) {
      return "Code hash mismatch - code was modified after signing";
    }
    logger.info(`Verified debug token from ${claims.email} in workspace ${claims.workspace_id} (job: ${claims.job_id})`);
    return null;
  } catch (error) {
    return `JWT verification error: ${error}`;
  }
}
function removePinnedImports(code) {
  const IMPORTS_VERSION = /^((?:@[^/@]+\/[^/@]+)|(?:[^/@]+))(?:@[^/]+)?(.*)$/;
  const importRegex = /(?:import|export).*?from\s+['"]([^'"]+)['"]/g;
  let result = code;
  let match;
  const imports = [];
  while ((match = importRegex.exec(code)) !== null) {
    imports.push(match[1]);
  }
  imports.sort((a, b) => b.length - a.length);
  for (const importPath of imports) {
    const versionMatch = IMPORTS_VERSION.exec(importPath);
    if (versionMatch) {
      const packageName = versionMatch[1];
      const pathSuffix = versionMatch[2] || "";
      const newImportPath = packageName + pathSuffix;
      if (newImportPath !== importPath) {
        result = result.split(`"${importPath}"`).join(`"${newImportPath}"`);
        result = result.split(`'${importPath}'`).join(`'${newImportPath}'`);
        logger.debug(`Removed version from import: "${importPath}" -> "${newImportPath}"`);
      }
    }
  }
  return result;
}
function decodeVLQ(encoded) {
  const VLQ_BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  const values = [];
  let shift = 0;
  let value = 0;
  for (const char of encoded) {
    const digit = VLQ_BASE64.indexOf(char);
    if (digit === -1)
      continue;
    const hasContinuation = digit & 32;
    value += (digit & 31) << shift;
    if (hasContinuation) {
      shift += 5;
    } else {
      const isNegative = value & 1;
      value = value >> 1;
      values.push(isNegative ? -value : value);
      value = 0;
      shift = 0;
    }
  }
  return values;
}
function parseSourceMapLineMapping(sourceMapUrl) {
  const mappings = {
    originalToTranspiled: new Map,
    transpiledToOriginal: new Map
  };
  try {
    const match = sourceMapUrl.match(/^data:application\/json;base64,(.+)$/);
    if (!match) {
      logger.warn("Source map is not a base64 data URL");
      return mappings;
    }
    const decoded = atob(match[1]);
    const sourceMap = JSON.parse(decoded);
    if (!sourceMap.mappings) {
      return mappings;
    }
    const lines = sourceMap.mappings.split(";");
    let originalLine = 0;
    for (let transpiledLine = 0;transpiledLine < lines.length; transpiledLine++) {
      const segments = lines[transpiledLine].split(",");
      for (const segment of segments) {
        if (!segment)
          continue;
        const values = decodeVLQ(segment);
        if (values.length >= 3) {
          originalLine += values[2];
          if (!mappings.originalToTranspiled.has(originalLine)) {
            mappings.originalToTranspiled.set(originalLine, transpiledLine);
            logger.debug(`Source map: original line ${originalLine} -> transpiled line ${transpiledLine}`);
          }
          const existing = mappings.transpiledToOriginal.get(transpiledLine);
          if (existing === undefined || originalLine > existing) {
            mappings.transpiledToOriginal.set(transpiledLine, originalLine);
            logger.debug(`Source map: transpiled line ${transpiledLine} -> original line ${originalLine}`);
          }
        }
      }
    }
  } catch (error) {
    logger.error("Failed to parse source map:", error);
  }
  return mappings;
}

class DebugSession {
  ws;
  seq = 1;
  initialized = false;
  configured = false;
  running = false;
  terminatedSent = false;
  process = null;
  inspectorWs = null;
  inspectorSeq = 1;
  pendingInspectorRequests = new Map;
  scriptPath = null;
  tempDir = null;
  tempFile = null;
  breakpoints = new Map;
  breakpointIds = new Map;
  scripts = new Map;
  mainScriptId = null;
  sourceMapMappings = {
    originalToTranspiled: new Map,
    transpiledToOriginal: new Map
  };
  callFrames = [];
  variablesRefCounter = 1;
  scopesMap = new Map;
  objectsMap = new Map;
  callMain = false;
  mainArgs = {};
  isStepping = false;
  pendingConsoleOutput = [];
  scriptResult = undefined;
  pausedAtBreakpoint = false;
  envVars = {};
  nsjailConfig;
  bunPath = "bun";
  windmillPath;
  nodeModulesPath;
  constructor(ws, options) {
    this.ws = ws;
    this.nsjailConfig = options?.nsjailConfig;
    this.bunPath = options?.bunPath || "bun";
    this.windmillPath = options?.windmillPath;
  }
  nextSeq() {
    return this.seq++;
  }
  nextInspectorSeq() {
    return this.inspectorSeq++;
  }
  nextVarRef() {
    return this.variablesRefCounter++;
  }
  sendMessage(msg) {
    const data = JSON.stringify(msg);
    logger.debug("Sending DAP:", data);
    this.ws.send(data);
  }
  sendResponse(request, success = true, body = {}, message = "") {
    this.sendMessage({
      seq: this.nextSeq(),
      type: "response",
      command: request.command || "",
      request_seq: request.seq,
      success,
      message,
      body
    });
  }
  sendEvent(event, body = {}) {
    this.sendMessage({
      seq: this.nextSeq(),
      type: "event",
      event,
      body
    });
  }
  async sendInspectorCommand(method, params = {}) {
    if (!this.inspectorWs || this.inspectorWs.readyState !== WebSocket.OPEN) {
      throw new Error("Inspector not connected");
    }
    const id = this.nextInspectorSeq();
    const message = { id, method, params };
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        this.pendingInspectorRequests.delete(id);
        reject(new Error(`Inspector command timeout: ${method}`));
      }, 1e4);
      this.pendingInspectorRequests.set(id, {
        resolve: (value) => {
          clearTimeout(timeout);
          resolve(value);
        },
        reject: (error) => {
          clearTimeout(timeout);
          reject(error);
        }
      });
      logger.debug("Sending to inspector:", JSON.stringify(message));
      this.inspectorWs.send(JSON.stringify(message));
    });
  }
  handleInspectorMessage(data) {
    try {
      const message = JSON.parse(data);
      logger.debug("Inspector message:", data);
      if (message.id !== undefined) {
        const pending = this.pendingInspectorRequests.get(message.id);
        if (pending) {
          this.pendingInspectorRequests.delete(message.id);
          if (message.error) {
            pending.reject(new Error(message.error.message));
          } else {
            pending.resolve(message);
          }
        }
        return;
      }
      if (message.method) {
        this.handleInspectorEvent(message);
      }
    } catch (error) {
      logger.error("Failed to parse inspector message:", error);
    }
  }
  handleInspectorEvent(message) {
    const params = message.params || {};
    switch (message.method) {
      case "Debugger.scriptParsed":
        this.handleScriptParsed(params);
        break;
      case "Debugger.paused":
        this.handlePaused(params);
        break;
      case "Debugger.resumed":
        this.sendEvent("continued", { threadId: 1 });
        break;
      case "Runtime.consoleAPICalled":
        this.handleConsoleOutput(params);
        break;
      case "Console.messageAdded":
        this.handleConsoleMessageAdded(params);
        break;
      case "Runtime.exceptionThrown":
        this.handleException(params);
        break;
      case "Debugger.breakpointResolved":
        logger.info(`Breakpoint resolved: ${JSON.stringify(params)}`);
        break;
      default:
        logger.debug("Unhandled inspector event:", message.method);
    }
  }
  handleScriptParsed(script) {
    this.scripts.set(script.scriptId, script);
    if (script.url && this.scriptPath && script.url.endsWith(this.scriptPath.split("/").pop())) {
      this.mainScriptId = script.scriptId;
      logger.info(`Main script parsed: ${script.url} (ID: ${script.scriptId})`);
      if (script.sourceMapURL) {
        logger.info("Parsing source map for line number mapping...");
        this.sourceMapMappings = parseSourceMapLineMapping(script.sourceMapURL);
        logger.info(`Source map parsed: ${this.sourceMapMappings.originalToTranspiled.size} original->transpiled, ${this.sourceMapMappings.transpiledToOriginal.size} transpiled->original`);
      }
    }
  }
  async applyBreakpointsByUrl() {
    logger.info(`applyBreakpointsByUrl called. scriptPath: ${this.scriptPath}, breakpoints: ${JSON.stringify([...this.breakpoints.entries()])}`);
    if (!this.scriptPath) {
      logger.warn("No scriptPath set, skipping breakpoints");
      return;
    }
    for (const [filePath, ids] of this.breakpointIds) {
      for (const id of ids) {
        try {
          await this.sendInspectorCommand("Debugger.removeBreakpoint", { breakpointId: id });
        } catch {}
      }
    }
    this.breakpointIds.clear();
    for (const [filePath, lines] of this.breakpoints) {
      logger.info(`Setting breakpoints for ${filePath}: lines ${lines}`);
      const ids = [];
      for (const line of lines) {
        try {
          const urlRegex = this.scriptPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
          const originalLine0Indexed = line;
          let transpiledLine = originalLine0Indexed;
          if (this.sourceMapMappings.originalToTranspiled.size > 0) {
            const mappedLine = this.sourceMapMappings.originalToTranspiled.get(originalLine0Indexed);
            if (mappedLine !== undefined) {
              transpiledLine = mappedLine;
              logger.info(`Source map: original line ${originalLine0Indexed} -> transpiled line ${transpiledLine}`);
            } else {
              logger.warn(`Line ${originalLine0Indexed} not in source map, using as-is`);
            }
          }
          logger.info(`Setting breakpoint: user line ${line} -> original line ${originalLine0Indexed} (0-idx) -> transpiled line ${transpiledLine}`);
          const response = await this.sendInspectorCommand("Debugger.setBreakpointByUrl", {
            lineNumber: transpiledLine,
            urlRegex,
            columnNumber: 0
          });
          logger.info(`Breakpoint response: ${JSON.stringify(response)}`);
          if (response.result?.breakpointId) {
            ids.push(response.result.breakpointId);
            logger.info(`Breakpoint set at transpiled line ${transpiledLine} (user line ${line}, ID: ${response.result.breakpointId})`);
          } else {
            logger.warn(`No breakpointId in response for line ${line}`);
          }
        } catch (error) {
          logger.error(`Failed to set breakpoint at line ${line}:`, error);
        }
      }
      this.breakpointIds.set(filePath, ids);
    }
    logger.info(`Finished setting breakpoints. Total IDs: ${[...this.breakpointIds.values()].flat().length}`);
  }
  initialPauseDone = false;
  async handlePaused(params) {
    const callFrames = params.callFrames;
    const reason = params.reason;
    logger.info(`handlePaused called: reason=${reason}, lineNumber=${callFrames?.[0]?.location?.lineNumber}, scriptId=${callFrames?.[0]?.location?.scriptId}`);
    if (callFrames) {
      this.callFrames = callFrames;
    }
    const isInitialPause = !this.initialPauseDone && reason === "DebuggerStatement" && callFrames && callFrames.length > 0 && callFrames[0].location.lineNumber === 0;
    logger.info(`isInitialPause=${isInitialPause}, initialPauseDone=${this.initialPauseDone}`);
    if (isInitialPause) {
      this.initialPauseDone = true;
      logger.info("Initial pause from injected debugger statement");
      logger.info("Re-applying breakpoints after script is parsed...");
      await this.applyBreakpointsByUrl();
      try {
        await this.sendInspectorCommand("Debugger.resume", {});
        logger.info("Auto-resumed after initial pause");
      } catch (error) {
        logger.error("Failed to auto-resume:", error);
      }
      return;
    }
    this.initialPauseDone = true;
    let dapReason;
    switch (reason) {
      case "Breakpoint":
      case "breakpoint":
        dapReason = "breakpoint";
        break;
      case "DebuggerStatement":
        dapReason = "breakpoint";
        break;
      case "step":
        dapReason = "step";
        break;
      case "exception":
        dapReason = "exception";
        break;
      case "debugCommand":
        dapReason = "pause";
        break;
      default:
        dapReason = "step";
    }
    let line;
    if (callFrames && callFrames.length > 0) {
      const transpiledLine = callFrames[0].location.lineNumber;
      if (this.sourceMapMappings.transpiledToOriginal.size > 0) {
        const originalLine = this.sourceMapMappings.transpiledToOriginal.get(transpiledLine);
        if (originalLine !== undefined) {
          line = originalLine;
          logger.info(`Stopped: transpiled line ${transpiledLine} -> original line ${line}`);
        } else {
          line = transpiledLine;
          logger.warn(`Transpiled line ${transpiledLine} not in reverse source map, using as-is`);
        }
      } else {
        line = transpiledLine;
      }
    }
    this.sendEvent("stopped", {
      reason: dapReason,
      threadId: 1,
      allThreadsStopped: true,
      line
    });
    this.isStepping = false;
    for (const outputEvent of this.pendingConsoleOutput) {
      this.sendEvent("output", outputEvent);
    }
    this.pendingConsoleOutput = [];
  }
  handleConsoleOutput(params) {
    const type = params.type;
    const args = params.args;
    if (!args)
      return;
    const output = args.map((arg) => {
      if (arg.value !== undefined)
        return String(arg.value);
      if (arg.description)
        return arg.description;
      return "";
    }).join(" ");
    this.sendEvent("output", {
      category: type === "error" ? "stderr" : "stdout",
      output: output + `
`
    });
  }
  formatConsoleParameter(p) {
    if (p.value !== undefined) {
      if (typeof p.value === "string")
        return p.value;
      return String(p.value);
    }
    if (p.subtype === "null")
      return "null";
    if (p.type === "undefined")
      return "undefined";
    if (p.type === "object" && p.preview && p.preview.properties) {
      const props = p.preview.properties;
      const isArray = p.subtype === "array" || p.preview.subtype === "array";
      if (isArray) {
        const items = props.map((prop) => this.formatPreviewValue(prop));
        const suffix = p.preview.overflow ? ", ..." : "";
        return `[${items.join(", ")}${suffix}]`;
      } else {
        const items = props.map((prop) => `${prop.name}: ${this.formatPreviewValue(prop)}`);
        const suffix = p.preview.overflow ? ", ..." : "";
        return `{ ${items.join(", ")}${suffix} }`;
      }
    }
    if (p.type === "function") {
      return p.description || "[Function]";
    }
    if (p.description)
      return p.description;
    return "";
  }
  formatPreviewValue(prop) {
    if (prop.subtype === "null")
      return "null";
    if (prop.type === "undefined")
      return "undefined";
    if (prop.type === "string")
      return `"${prop.value ?? ""}"`;
    if (prop.type === "number" || prop.type === "boolean")
      return prop.value ?? "";
    if (prop.type === "function")
      return "[Function]";
    if (prop.type === "object") {
      if (prop.subtype === "array")
        return prop.value || "[]";
      return prop.value || "{...}";
    }
    return prop.value ?? "";
  }
  handleConsoleMessageAdded(params) {
    const message = params.message;
    if (!message)
      return;
    let output;
    if (message.parameters && message.parameters.length > 0) {
      output = message.parameters.map((p) => this.formatConsoleParameter(p)).join(" ");
    } else {
      output = message.text || "";
    }
    if (output.startsWith("__WINDMILL_RESULT__:")) {
      try {
        const resultJson = output.substring("__WINDMILL_RESULT__:".length);
        this.scriptResult = JSON.parse(resultJson);
        logger.info(`Captured script result: ${resultJson}`);
        this.isStepping = false;
        for (const pendingOutput of this.pendingConsoleOutput) {
          this.sendEvent("output", pendingOutput);
        }
        this.pendingConsoleOutput = [];
        if (!this.terminatedSent) {
          this.terminatedSent = true;
          this.running = false;
          logger.info("Sending terminated event after result capture");
          this.sendEvent("terminated", { result: this.scriptResult });
        }
      } catch (error) {
        logger.error("Failed to parse script result:", error);
      }
      return;
    }
    const category = message.level === "error" || message.level === "warning" ? "stderr" : "stdout";
    const outputEvent = {
      category,
      output: output + `
`
    };
    if (message.line !== undefined && message.url) {
      outputEvent.source = {
        path: message.url,
        name: message.url.split("/").pop() || "script.ts"
      };
      outputEvent.line = message.line;
      if (message.column !== undefined) {
        outputEvent.column = message.column + 1;
      }
    }
    if (this.isStepping) {
      this.pendingConsoleOutput.push(outputEvent);
    } else {
      this.sendEvent("output", outputEvent);
    }
  }
  handleException(params) {
    const exceptionDetails = params.exceptionDetails;
    if (exceptionDetails) {
      const message = exceptionDetails.exception?.description || exceptionDetails.text || "Unknown exception";
      this.sendEvent("output", {
        category: "stderr",
        output: `Exception: ${message}
`
      });
    }
  }
  async handleInitialize(request) {
    const capabilities = {
      supportsConfigurationDoneRequest: true,
      supportsFunctionBreakpoints: false,
      supportsConditionalBreakpoints: false,
      supportsHitConditionalBreakpoints: false,
      supportsEvaluateForHovers: true,
      exceptionBreakpointFilters: [],
      supportsStepBack: false,
      supportsSetVariable: false,
      supportsRestartFrame: false,
      supportsGotoTargetsRequest: false,
      supportsStepInTargetsRequest: false,
      supportsCompletionsRequest: false,
      supportsModulesRequest: false,
      supportsExceptionOptions: false,
      supportsValueFormattingOptions: false,
      supportsExceptionInfoRequest: false,
      supportTerminateDebuggee: true,
      supportsDelayedStackTraceLoading: false,
      supportsLoadedSourcesRequest: false,
      supportsLogPoints: false,
      supportsTerminateThreadsRequest: false,
      supportsSetExpression: false,
      supportsTerminateRequest: true,
      supportsDataBreakpoints: false,
      supportsReadMemoryRequest: false,
      supportsDisassembleRequest: false,
      supportsCancelRequest: false,
      supportsBreakpointLocationsRequest: false
    };
    this.sendResponse(request, true, capabilities);
    this.initialized = true;
    this.sendEvent("initialized");
  }
  async handleSetBreakpoints(request) {
    const args = request.arguments || {};
    const source = args.source;
    const sourcePath = source?.path || "";
    const breakpointsData = args.breakpoints || [];
    const verifiedBreakpoints = [];
    const lineNumbers = [];
    for (const bp of breakpointsData) {
      const line = bp.line;
      lineNumbers.push(line);
      verifiedBreakpoints.push({
        id: verifiedBreakpoints.length + 1,
        verified: true,
        line,
        source: { path: sourcePath }
      });
    }
    this.breakpoints.set(sourcePath, lineNumbers);
    logger.info(`Stored breakpoints at lines ${lineNumbers} for ${sourcePath}`);
    if (this.inspectorWs) {
      await this.applyBreakpointsByUrl();
    }
    this.sendResponse(request, true, { breakpoints: verifiedBreakpoints });
  }
  async handleConfigurationDone(request) {
    this.configured = true;
    this.sendResponse(request);
  }
  async handleLaunch(request) {
    const args = request.arguments || {};
    let code = args.code;
    this.scriptPath = args.program;
    const cwd = args.cwd || process.cwd();
    this.callMain = args.callMain || false;
    this.mainArgs = args.args || {};
    this.envVars = args.env || {};
    if (code && REQUIRE_SIGNED_REQUESTS) {
      const token = args.token;
      if (!token) {
        logger.error("No debug token provided but signed requests are required");
        this.sendResponse(request, false, {}, "Debug token required. Ensure the debug session was signed by the backend.");
        return;
      }
      const verificationError = await verifyDebugToken(token, code);
      if (verificationError) {
        logger.error(`Token verification failed: ${verificationError}`);
        this.sendResponse(request, false, {}, `Token verification failed: ${verificationError}`);
        return;
      }
    }
    if (process.env.BASE_INTERNAL_URL) {
      this.envVars.WM_BASE_URL = process.env.BASE_INTERNAL_URL;
    }
    if (Object.keys(this.envVars).length > 0) {
      logger.info(`Launch with env vars: ${Object.keys(this.envVars).join(", ")}`);
    }
    if (!this.scriptPath && !code) {
      this.sendResponse(request, false, {}, "No program or code specified");
      return;
    }
    if (code) {
      this.nodeModulesPath = await this.prepareDependencies(code) || undefined;
      code = removePinnedImports(code);
    }
    this.initialPauseDone = false;
    code = `debugger; // Auto-injected by Windmill debugger
${code || ""}`;
    if (this.callMain && code) {
      const argsValues = Object.values(this.mainArgs).map((v) => JSON.stringify(v)).join(", ");
      code = code + `

// Auto-generated call to main entrypoint
` + `globalThis.__windmill_result__ = await main(${argsValues});
` + `console.log("__WINDMILL_RESULT__:" + JSON.stringify(globalThis.__windmill_result__));
` + `// Small delay to ensure console output is delivered via inspector before process exits
` + `await new Promise(r => setTimeout(r, 50));
`;
      logger.info(`Added main() call with args: ${argsValues}`);
    }
    if (code && !this.scriptPath) {
      try {
        this.tempDir = await mkdtemp(join(tmpdir(), "windmill_debug_"));
        this.tempFile = join(this.tempDir, "script.ts");
        await writeFile(this.tempFile, code);
        this.scriptPath = this.tempFile;
        logger.info(`Wrote code to ${this.tempFile}`);
        const lines = code.split(`
`);
        for (let i = 24;i < Math.min(30, lines.length); i++) {
          logger.info(`  Line ${i} (0-idx) / ${i + 1} (1-idx): ${lines[i]?.substring(0, 80)}`);
        }
        if (this.nodeModulesPath) {
          const targetPath = join(this.tempDir, "node_modules");
          try {
            await symlink(this.nodeModulesPath, targetPath);
            logger.info(`Symlinked ${this.nodeModulesPath} -> ${targetPath}`);
          } catch (symlinkError) {
            logger.warn(`Failed to symlink node_modules: ${symlinkError}`);
          }
        }
      } catch (error) {
        this.sendResponse(request, false, {}, `Failed to create temp file: ${error}`);
        return;
      }
    }
    this.sendResponse(request);
    try {
      await this.startBunProcess(cwd);
    } catch (error) {
      this.sendEvent("output", { category: "stderr", output: `Failed to start Bun: ${error}
` });
      this.sendEvent("terminated", { error: String(error) });
    }
  }
  inspectorWsUrl = null;
  inspectorWsUrlPromise = null;
  async prepareDependencies(code, language = "bun") {
    if (!this.windmillPath) {
      logger.info("No windmill binary path configured, skipping dependency preparation");
      return null;
    }
    logger.info(`Preparing dependencies using ${this.windmillPath}`);
    try {
      const input = JSON.stringify({ code, language }) + `
`;
      logger.info(`prepare-deps input length: ${input.length}`);
      const proc = spawn({
        cmd: [this.windmillPath, "prepare-deps"],
        stdin: new Blob([input]),
        stdout: "pipe",
        stderr: "pipe"
      });
      const output = await new Response(proc.stdout).text();
      const stderr = await new Response(proc.stderr).text();
      logger.info(`prepare-deps output: ${output.substring(0, 200)}`);
      logger.info(`prepare-deps stderr: ${stderr.substring(0, 200)}`);
      if (stderr) {
        const filteredStderr = stderr.split(`
`).filter((line) => !line.includes("Running in standalone mode")).join(`
`).trim();
        if (filteredStderr) {
          logger.debug(`prepare-deps stderr: ${filteredStderr}`);
        }
      }
      const response = JSON.parse(output.trim().split(`
`).pop() || "{}");
      if (!response.success) {
        logger.error(`prepare-deps failed: ${response.error}`);
        this.sendEvent("output", {
          category: "console",
          output: `Warning: Failed to prepare dependencies: ${response.error}
`
        });
        return null;
      }
      if (response.node_modules_path) {
        logger.info(`Dependencies installed at: ${response.node_modules_path}`);
        this.sendEvent("output", {
          category: "console",
          output: `Dependencies installed at: ${response.node_modules_path}
`
        });
        return response.node_modules_path;
      }
      logger.info("No external dependencies to install");
      return null;
    } catch (error) {
      logger.error(`Failed to prepare dependencies: ${error}`);
      this.sendEvent("output", {
        category: "console",
        output: `Warning: Failed to prepare dependencies: ${error}
`
      });
      return null;
    }
  }
  async startBunProcess(cwd) {
    if (!this.scriptPath) {
      throw new Error("No script path");
    }
    const inspectPort = 9229 + Math.floor(Math.random() * 1000);
    const inspectUrl = `127.0.0.1:${inspectPort}`;
    let cmd = [this.bunPath, `--inspect-wait=${inspectUrl}`, this.scriptPath];
    if (this.nsjailConfig?.enabled) {
      const nsjailCmd = [this.nsjailConfig.binaryPath];
      if (this.nsjailConfig.configPath) {
        nsjailCmd.push("--config", this.nsjailConfig.configPath);
      }
      if (this.nsjailConfig.extraArgs) {
        nsjailCmd.push(...this.nsjailConfig.extraArgs);
      }
      nsjailCmd.push("--cwd", cwd);
      nsjailCmd.push("--");
      nsjailCmd.push(...cmd);
      cmd = nsjailCmd;
      logger.info(`Starting Bun with nsjail: ${cmd.join(" ")}`);
    } else {
      logger.info(`Starting Bun with --inspect-wait=${inspectUrl}`);
    }
    const wsUrlPromise = new Promise((resolve, reject) => {
      this.inspectorWsUrlPromise = { resolve, reject };
      setTimeout(() => {
        if (this.inspectorWsUrlPromise) {
          reject(new Error("Timeout waiting for inspector WebSocket URL"));
          this.inspectorWsUrlPromise = null;
        }
      }, 1e4);
    });
    const envVars = {
      PATH: process.env.PATH || "/usr/bin:/bin",
      HOME: process.env.HOME,
      ...this.envVars
    };
    if (this.nodeModulesPath) {
      envVars.NODE_PATH = this.nodeModulesPath;
      logger.info(`Setting NODE_PATH=${this.nodeModulesPath}`);
    }
    this.process = spawn({
      cmd,
      cwd,
      stdout: "pipe",
      stderr: "pipe",
      env: envVars
    });
    this.readStderrForInspectorUrl(this.process.stderr);
    const wsUrl = await wsUrlPromise;
    logger.info(`Got inspector WebSocket URL: ${wsUrl}`);
    await this.connectToInspector(wsUrl);
    this.process.exited.then(async (exitCode) => {
      logger.info(`Bun process exited with code: ${exitCode}`);
      this.running = false;
      if (!this.terminatedSent) {
        this.terminatedSent = true;
        logger.info(`Sending terminated event with result: ${JSON.stringify(this.scriptResult)}`);
        try {
          this.sendEvent("terminated", this.scriptResult !== undefined ? { result: this.scriptResult } : {});
          logger.info("Terminated event sent successfully");
        } catch (error) {
          logger.error("Failed to send terminated event:", error);
        }
      }
      await this.cleanup();
    }).catch((error) => {
      logger.error("Error in process.exited handler:", error);
    });
  }
  async readStream(stream, category) {
    if (!stream)
      return;
    const reader = stream.getReader();
    const decoder = new TextDecoder;
    try {
      while (true) {
        const { done, value } = await reader.read();
        if (done)
          break;
        const text = decoder.decode(value);
        this.sendEvent("output", { category, output: text });
      }
    } catch (error) {
      logger.debug(`Stream ${category} ended:`, error);
    }
  }
  async readStderrForInspectorUrl(stream) {
    if (!stream)
      return;
    const reader = stream.getReader();
    const decoder = new TextDecoder;
    let buffer = "";
    try {
      while (true) {
        const { done, value } = await reader.read();
        if (done)
          break;
        const text = decoder.decode(value);
        buffer += text;
        const wsMatch = buffer.match(/ws:\/\/[\d.]+:\d+\/[a-z0-9]+/i);
        if (wsMatch && this.inspectorWsUrlPromise) {
          const wsUrl = wsMatch[0];
          logger.info(`Found inspector WebSocket URL in stderr: ${wsUrl}`);
          this.inspectorWsUrl = wsUrl;
          this.inspectorWsUrlPromise.resolve(wsUrl);
          this.inspectorWsUrlPromise = null;
        }
        if (!text.includes("Bun Inspector") && !text.includes("Listening:") && !text.includes("ws://") && !text.includes("debug.bun.sh")) {
          this.sendEvent("output", { category: "stderr", output: text });
        }
      }
    } catch (error) {
      logger.debug("Stderr stream ended:", error);
    }
  }
  async connectToInspector(wsUrl) {
    logger.info(`Connecting to inspector at ${wsUrl}`);
    return new Promise((resolve, reject) => {
      this.inspectorWs = new WebSocket(wsUrl);
      const timeout = setTimeout(() => {
        reject(new Error("Inspector connection timeout"));
      }, 5000);
      this.inspectorWs.onopen = async () => {
        clearTimeout(timeout);
        logger.info("Connected to inspector");
        try {
          await this.sendInspectorCommand("Inspector.enable", {});
          await this.sendInspectorCommand("Console.enable", {});
          await this.sendInspectorCommand("Debugger.enable", {});
          await this.sendInspectorCommand("Runtime.enable", {});
          await this.sendInspectorCommand("Debugger.setBreakpointsActive", { active: true });
          await this.sendInspectorCommand("Debugger.setPauseOnDebuggerStatements", { enabled: true });
          await this.sendInspectorCommand("Debugger.setPauseOnExceptions", {
            state: "uncaught"
          });
          await this.applyBreakpointsByUrl();
          this.running = true;
          logger.info("Starting script execution with Inspector.initialized...");
          await this.sendInspectorCommand("Inspector.initialized", {});
          resolve();
        } catch (error) {
          reject(error);
        }
      };
      this.inspectorWs.onmessage = (event) => {
        this.handleInspectorMessage(event.data);
      };
      this.inspectorWs.onerror = (error) => {
        logger.error("Inspector WebSocket error:", error);
      };
      this.inspectorWs.onclose = () => {
        logger.info("Inspector WebSocket closed");
        this.inspectorWs = null;
        if (!this.terminatedSent) {
          this.terminatedSent = true;
          this.running = false;
          logger.info(`Inspector closed - sending terminated event with result: ${JSON.stringify(this.scriptResult)}`);
          try {
            this.sendEvent("terminated", this.scriptResult !== undefined ? { result: this.scriptResult } : {});
            logger.info("Terminated event sent successfully via inspector close");
          } catch (error) {
            logger.error("Failed to send terminated event on inspector close:", error);
          }
        }
      };
    });
  }
  async handleThreads(request) {
    this.sendResponse(request, true, {
      threads: [{ id: 1, name: "MainThread" }]
    });
  }
  async handleStackTrace(request) {
    logger.info(`handleStackTrace: callFrames.length=${this.callFrames.length}`);
    const stackFrames = [];
    for (let i = 0;i < this.callFrames.length; i++) {
      const frame = this.callFrames[i];
      const script = this.scripts.get(frame.location.scriptId);
      logger.info(`  frame ${i}: functionName="${frame.functionName}", scriptId=${frame.location.scriptId}, lineNumber=${frame.location.lineNumber}, scriptUrl=${script?.url}`);
      if (!script?.url || !this.scriptPath) {
        logger.info(`  skipping frame ${i}: no script URL or scriptPath`);
        continue;
      }
      const isUserScript = script.url.endsWith(this.scriptPath.split("/").pop());
      if (!isUserScript) {
        logger.info(`  skipping frame ${i}: not from user script (${script.url})`);
        continue;
      }
      const transpiledLine = frame.location.lineNumber;
      let frameLine = transpiledLine;
      if (this.sourceMapMappings.transpiledToOriginal.size > 0) {
        const originalLine = this.sourceMapMappings.transpiledToOriginal.get(transpiledLine);
        if (originalLine !== undefined) {
          frameLine = originalLine;
        }
      }
      stackFrames.push({
        id: i + 1,
        name: frame.functionName || "<module>",
        source: {
          path: script.url || this.scriptPath || "<unknown>",
          name: script.url?.split("/").pop() || "script.ts"
        },
        line: frameLine,
        column: frame.location.columnNumber + 1
      });
    }
    logger.info(`handleStackTrace: returning ${stackFrames.length} frames`);
    this.sendResponse(request, true, {
      stackFrames,
      totalFrames: stackFrames.length
    });
  }
  async handleScopes(request) {
    const args = request.arguments || {};
    const frameId = args.frameId || 1;
    const frameIndex = frameId - 1;
    logger.info(`handleScopes: frameId=${frameId}, frameIndex=${frameIndex}, callFrames.length=${this.callFrames.length}`);
    const frame = this.callFrames[frameIndex];
    if (!frame) {
      logger.warn(`handleScopes: No frame at index ${frameIndex}`);
      this.sendResponse(request, true, { scopes: [] });
      return;
    }
    logger.info(`handleScopes: frame.scopeChain has ${frame.scopeChain.length} scopes`);
    for (const s of frame.scopeChain) {
      logger.info(`  scope: type=${s.type}, name=${s.name}, hasObjectId=${!!s.object.objectId}`);
    }
    const scopes = [];
    for (const scope of frame.scopeChain) {
      const ref = this.nextVarRef();
      const objectId = scope.object.objectId;
      if (objectId) {
        this.scopesMap.set(ref, { type: scope.type, objectId, frameIndex });
        let name;
        if (scope.name) {
          name = scope.name;
        } else {
          name = scope.type.charAt(0).toUpperCase() + scope.type.slice(1);
        }
        scopes.push({
          name,
          variablesReference: ref,
          expensive: scope.type === "global"
        });
        logger.info(`  Added scope: name=${name}, ref=${ref}, type=${scope.type}`);
      } else {
        logger.warn(`  Scope ${scope.type} has no objectId, skipping`);
      }
    }
    logger.info(`handleScopes: returning ${scopes.length} scopes`);
    this.sendResponse(request, true, { scopes });
  }
  async handleVariables(request) {
    const args = request.arguments || {};
    const variablesRef = args.variablesReference || 0;
    logger.info(`handleVariables: variablesRef=${variablesRef}`);
    const scopeInfo = this.scopesMap.get(variablesRef);
    const objectId = this.objectsMap.get(variablesRef) || scopeInfo?.objectId;
    logger.info(`handleVariables: scopeInfo=${JSON.stringify(scopeInfo)}, objectId=${objectId}`);
    if (!objectId) {
      logger.warn(`handleVariables: No objectId found for ref ${variablesRef}`);
      this.sendResponse(request, true, { variables: [] });
      return;
    }
    try {
      const scopeType = scopeInfo?.type || "unknown";
      const isGlobalScope = scopeType === "global";
      logger.info(`handleVariables: calling Runtime.getProperties for objectId=${objectId}, scopeType=${scopeType}`);
      const response = await this.sendInspectorCommand("Runtime.getProperties", {
        objectId,
        ownProperties: true,
        generatePreview: true
      });
      const allProps = response.result?.properties || [];
      logger.info(`handleVariables: all property names: ${allProps.map((p) => p.name).join(", ")}`);
      logger.info(`handleVariables: got response with ${response.result?.properties?.length || 0} properties for scope type: ${scopeType}`);
      const variables = [];
      const properties = response.result?.properties || [];
      for (const prop of properties) {
        if (!prop.value)
          continue;
        if (prop.name.startsWith("__") && prop.name !== "__windmill_result__")
          continue;
        if (prop.value.type === "function" && prop.value.description?.includes("[native code]"))
          continue;
        const builtInNames = new Set([
          "NaN",
          "Infinity",
          "undefined",
          "globalThis",
          "global",
          "self",
          "window",
          "console",
          "Bun",
          "process",
          "navigator",
          "performance",
          "crypto",
          "Loader",
          "onmessage",
          "onerror",
          "toString",
          "toLocaleString",
          "valueOf",
          "hasOwnProperty",
          "propertyIsEnumerable",
          "isPrototypeOf",
          "constructor",
          "Reflect",
          "JSON",
          "Math",
          "Atomics",
          "Intl",
          "WebAssembly",
          "Proxy",
          "Object",
          "Array",
          "Function",
          "Boolean",
          "Symbol",
          "Number",
          "BigInt",
          "String",
          "RegExp",
          "Date",
          "Promise",
          "Map",
          "Set",
          "WeakMap",
          "WeakSet",
          "Error",
          "TypeError",
          "RangeError",
          "SyntaxError",
          "ReferenceError",
          "EvalError",
          "URIError",
          "AggregateError",
          "ArrayBuffer",
          "DataView",
          "Int8Array",
          "Uint8Array",
          "Uint8ClampedArray",
          "Int16Array",
          "Uint16Array",
          "Int32Array",
          "Uint32Array",
          "Float32Array",
          "Float64Array",
          "BigInt64Array",
          "BigUint64Array",
          "SharedArrayBuffer"
        ]);
        if (builtInNames.has(prop.name))
          continue;
        if (prop.value.type === "function" && /^[A-Z][a-zA-Z0-9]*$/.test(prop.name))
          continue;
        let value;
        let varRef = 0;
        let displayType = prop.value.type;
        const className = prop.value.className;
        logger.debug(`Variable ${prop.name}: type=${prop.value.type}, className=${className}, description=${prop.value.description}, value=${JSON.stringify(prop.value.value)}`);
        if (prop.value.type === "object" && prop.value.objectId) {
          if (className === "String" && prop.value.description) {
            value = prop.value.description.startsWith('"') ? prop.value.description : `"${prop.value.description}"`;
            displayType = "string";
          } else if (className === "Number" && prop.value.description) {
            value = prop.value.description;
            displayType = "number";
          } else if (className === "Boolean" && prop.value.description) {
            value = prop.value.description;
            displayType = "boolean";
          } else {
            varRef = this.nextVarRef();
            this.objectsMap.set(varRef, prop.value.objectId);
            if (prop.value.preview && prop.value.preview.properties) {
              const previewProps = prop.value.preview.properties;
              const isArray = prop.value.subtype === "array" || prop.value.preview.subtype === "array";
              if (isArray) {
                const items = previewProps.map((p) => this.formatPreviewValue(p));
                const suffix = prop.value.preview.overflow ? ", ..." : "";
                value = `[${items.join(", ")}${suffix}]`;
              } else {
                const items = previewProps.map((p) => `${p.name}: ${this.formatPreviewValue(p)}`);
                const suffix = prop.value.preview.overflow ? ", ..." : "";
                value = `{ ${items.join(", ")}${suffix} }`;
              }
            } else {
              value = prop.value.description || `[${prop.value.subtype || className || "Object"}]`;
            }
          }
        } else if (prop.value.type === "string") {
          value = prop.value.value !== undefined ? JSON.stringify(prop.value.value) : prop.value.description || '""';
          displayType = "string";
        } else if (prop.value.value !== undefined) {
          value = JSON.stringify(prop.value.value);
        } else {
          value = prop.value.description || String(prop.value.type);
        }
        variables.push({
          name: prop.name,
          value,
          type: displayType,
          variablesReference: varRef
        });
      }
      logger.info(`handleVariables: returning ${variables.length} variables (filtered from ${properties.length})`);
      this.sendResponse(request, true, { variables });
    } catch (error) {
      logger.error("Failed to get variables:", error);
      this.sendResponse(request, true, { variables: [] });
    }
  }
  async handleEvaluate(request) {
    const args = request.arguments || {};
    let expression = args.expression || "";
    const frameId = args.frameId;
    const shouldAwait = /^\s*await\s+/.test(expression);
    if (shouldAwait) {
      expression = expression.replace(/^\s*await\s+/, "");
    }
    try {
      let result;
      let varRef = 0;
      const formatResult = async (evalResult) => {
        let result2 = "undefined";
        let varRef2 = 0;
        if (!evalResult)
          return { result: result2, varRef: varRef2 };
        if (evalResult.subtype === "promise" || evalResult.className === "Promise") {
          if (evalResult.preview?.properties) {
            const props = evalResult.preview.properties;
            const statusProp = props.find((p) => p.name === "status");
            const resultProp = props.find((p) => p.name === "result");
            if (statusProp?.value === "fulfilled" && resultProp) {
              if (resultProp.value !== undefined) {
                return { result: resultProp.value, varRef: 0 };
              }
              if (resultProp.type === "object") {
                if (resultProp.valuePreview?.properties) {
                  const objProps = resultProp.valuePreview.properties.map((p) => `${p.name}: ${p.value ?? "..."}`).join(", ");
                  return { result: `{${objProps}}`, varRef: 0 };
                }
                return { result: resultProp.subtype || "[Object]", varRef: 0 };
              }
              return { result: resultProp.type || "undefined", varRef: 0 };
            } else if (statusProp?.value === "rejected" && resultProp) {
              return { result: `Rejected: ${resultProp.value ?? resultProp.type}`, varRef: 0 };
            }
            if (statusProp?.value === "pending") {
              return { result: "Promise { <pending> }", varRef: 0 };
            }
          }
        }
        if (evalResult.type === "undefined") {
          result2 = "undefined";
        } else if (evalResult.value !== undefined) {
          result2 = JSON.stringify(evalResult.value);
        } else if (evalResult.objectId) {
          varRef2 = this.nextVarRef();
          this.objectsMap.set(varRef2, evalResult.objectId);
          if (evalResult.preview?.properties) {
            const props = evalResult.preview.properties.map((p) => `${p.name}: ${p.value}`).join(", ");
            result2 = `{${props}}`;
          } else {
            result2 = evalResult.description || "[Object]";
          }
        } else {
          result2 = evalResult.description || String(evalResult.type);
        }
        return { result: result2, varRef: varRef2 };
      };
      if (frameId !== undefined && this.callFrames[frameId - 1]) {
        const frame = this.callFrames[frameId - 1];
        const response = await this.sendInspectorCommand("Debugger.evaluateOnCallFrame", {
          callFrameId: frame.callFrameId,
          expression,
          returnByValue: false,
          generatePreview: true
        });
        const evalResult = response.result?.result;
        const formatted = await formatResult(evalResult);
        result = formatted.result;
        varRef = formatted.varRef;
      } else {
        const response = await this.sendInspectorCommand("Runtime.evaluate", {
          expression,
          returnByValue: false,
          generatePreview: true
        });
        const evalResult = response.result?.result;
        const formatted = await formatResult(evalResult);
        result = formatted.result;
        varRef = formatted.varRef;
      }
      this.sendResponse(request, true, { result, variablesReference: varRef });
    } catch (error) {
      this.sendResponse(request, true, {
        result: `Error: ${error}`,
        variablesReference: 0
      });
    }
  }
  async handleContinue(request) {
    try {
      this.isStepping = true;
      this.pausedAtBreakpoint = false;
      await this.sendInspectorCommand("Debugger.resume", {});
      this.sendResponse(request, true, { allThreadsContinued: true });
    } catch (error) {
      this.isStepping = false;
      this.sendResponse(request, false, {}, String(error));
    }
  }
  async handleNext(request) {
    try {
      this.isStepping = true;
      this.pausedAtBreakpoint = false;
      await this.sendInspectorCommand("Debugger.stepOver", {});
      this.sendResponse(request);
    } catch (error) {
      this.isStepping = false;
      this.sendResponse(request, false, {}, String(error));
    }
  }
  async handleStepIn(request) {
    try {
      this.isStepping = true;
      this.pausedAtBreakpoint = false;
      await this.sendInspectorCommand("Debugger.stepInto", {});
      this.sendResponse(request);
    } catch (error) {
      this.isStepping = false;
      this.sendResponse(request, false, {}, String(error));
    }
  }
  async handleStepOut(request) {
    try {
      this.isStepping = true;
      this.pausedAtBreakpoint = false;
      await this.sendInspectorCommand("Debugger.stepOut", {});
      this.sendResponse(request);
    } catch (error) {
      this.isStepping = false;
      this.sendResponse(request, false, {}, String(error));
    }
  }
  async handlePause(request) {
    try {
      await this.sendInspectorCommand("Debugger.pause", {});
      this.sendResponse(request);
    } catch (error) {
      this.sendResponse(request, false, {}, String(error));
    }
  }
  async handleDisconnect(request) {
    this.running = false;
    await this.cleanup();
    this.sendResponse(request);
  }
  async handleTerminate(request) {
    this.running = false;
    const shouldSendTerminated = !this.terminatedSent;
    this.terminatedSent = true;
    await this.cleanup();
    this.sendResponse(request);
    if (shouldSendTerminated) {
      this.sendEvent("terminated", this.scriptResult !== undefined ? { result: this.scriptResult } : {});
    }
  }
  async cleanup() {
    if (this.inspectorWs) {
      this.inspectorWs.close();
      this.inspectorWs = null;
    }
    if (this.process) {
      this.process.kill();
      this.process = null;
    }
    if (this.tempFile) {
      try {
        await unlink(this.tempFile);
      } catch {}
      this.tempFile = null;
    }
    if (this.tempDir) {
      try {
        await rmdir(this.tempDir);
      } catch {}
      this.tempDir = null;
    }
  }
  async handleRequest(request) {
    const command = request.command || "";
    logger.debug(`Handling command: ${command}`);
    const handlers = {
      initialize: (req) => this.handleInitialize(req),
      setBreakpoints: (req) => this.handleSetBreakpoints(req),
      configurationDone: (req) => this.handleConfigurationDone(req),
      launch: (req) => this.handleLaunch(req),
      threads: (req) => this.handleThreads(req),
      stackTrace: (req) => this.handleStackTrace(req),
      scopes: (req) => this.handleScopes(req),
      variables: (req) => this.handleVariables(req),
      evaluate: (req) => this.handleEvaluate(req),
      continue: (req) => this.handleContinue(req),
      next: (req) => this.handleNext(req),
      stepIn: (req) => this.handleStepIn(req),
      stepOut: (req) => this.handleStepOut(req),
      pause: (req) => this.handlePause(req),
      disconnect: (req) => this.handleDisconnect(req),
      terminate: (req) => this.handleTerminate(req)
    };
    const handler = handlers[command];
    if (handler) {
      await handler(request);
    } else {
      logger.warn(`Unhandled command: ${command}`);
      this.sendResponse(request, false, {}, `Unsupported command: ${command}`);
    }
  }
}
var sessions = new Map;
var args = process.argv.slice(2);
var host = "localhost";
var port = 5680;
var windmillPath;
for (let i = 0;i < args.length; i++) {
  if (args[i] === "--host" && args[i + 1]) {
    host = args[i + 1];
    i++;
  } else if (args[i] === "--port" && args[i + 1]) {
    port = parseInt(args[i + 1], 10);
    i++;
  } else if (args[i] === "--windmill" && args[i + 1]) {
    windmillPath = args[i + 1];
    i++;
  }
}
if (windmillPath) {
  logger.info(`Windmill binary path: ${windmillPath}`);
}
logger.info(`Starting DAP WebSocket server on ws://${host}:${port}`);
var server = Bun.serve({
  hostname: host,
  port,
  fetch(req, server2) {
    if (server2.upgrade(req)) {
      return;
    }
    return new Response("DAP WebSocket Server for Bun/TypeScript", { status: 200 });
  },
  websocket: {
    open(ws) {
      logger.info("New client connected");
      const wsWrapper = {
        send: (data) => ws.send(data),
        close: () => ws.close(),
        readyState: WebSocket.OPEN,
        CONNECTING: WebSocket.CONNECTING,
        OPEN: WebSocket.OPEN,
        CLOSING: WebSocket.CLOSING,
        CLOSED: WebSocket.CLOSED
      };
      const session = new DebugSession(wsWrapper, { windmillPath });
      sessions.set(ws, session);
    },
    async message(ws, message) {
      const session = sessions.get(ws);
      if (!session)
        return;
      try {
        const data = JSON.parse(message);
        logger.debug("Received:", JSON.stringify(data));
        if (data.type === "request") {
          await session.handleRequest(data);
        }
      } catch (error) {
        logger.error("Error handling message:", error);
      }
    },
    close(ws) {
      logger.info("Client disconnected");
      sessions.delete(ws);
    }
  }
});
logger.info(`Server started on ${server.url}`);
export {
  DebugSession
};
