UNPKG

@botpress/adk-cli

Version:

Command-line interface for the Botpress Agent Development Kit (ADK)

2,336 lines 81.3 kB
#!/usr/bin/env bun
// @bun
import {
  PROTOCOL_VERSION,
  createLineParser,
  encode,
  findAvailablePort,
  parseAgentToServerMessage
} from "./chunk-x0t4wpfv.js";
import {
  errorResponse,
  getCorsHeaders,
  handleCorsPreflightResponse,
  handleFeatureFlags,
  handleProdBotApiRequest,
  jsonResponse,
  startEventLoopLagMonitor,
  successResponse,
  withRequestTiming
} from "./chunk-59ayvmxs.js";
import"./chunk-68mqsf42.js";
import"./chunk-ndxsgd72.js";
import"./chunk-r72adjnh.js";
import {
  ensureTemplatesAvailable
} from "./chunk-h652feqd.js";
import {
  getLoginUrl,
  startCallbackServer
} from "./chunk-4cgz94zj.js";
import {
  installExternalHarnessCapabilities
} from "./chunk-whkrdhta.js";
import"./chunk-kwmsaz7n.js";
import {
  getEmbeddedAsset,
  hasEmbeddedAssets,
  installAgent0Capabilities
} from "./chunk-tt572q4r.js";
import"./chunk-26vqkz52.js";
import {
  CONSOLE_LOCK_FILE,
  CONSOLE_PORT_FILE,
  CONSOLE_SOCKET_PATH,
  DEVCONSOLE_ID_HEADER,
  probeUnixSocket
} from "./chunk-sgj6770p.js";
import {
  RuntimeVersionMismatchError,
  formatRuntimeVersionMismatchMessage,
  getRuntimeMajor,
  getRuntimeMajorRelation,
  getRuntimeVersionStatus,
  preflightRuntimeVersionCheck
} from "./chunk-ty7sdgd4.js";
import {
  detectPackageManagers,
  getPreferredPackageManager
} from "./chunk-nbasj5jm.js";
import {
  findAgentRoot
} from "./chunk-kk3h6qaj.js";
import {
  createCliLogger
} from "./chunk-gzwt1qdr.js";
import {
  EXPECTED_RUNTIME_VERSION
} from "./chunk-nxy2ya5r.js";
import"./chunk-wzj4dc7n.js";
import {
  ADK_MANIFEST_BOT_TAGS,
  AdkError,
  AgentProject,
  AgentProjectGenerator,
  ConfigWriter,
  auth,
  exports_dependencies,
  getProjectClient
} from "./chunk-p0hjqn4r.js";
import"./chunk-np5wcwfv.js";
import"./chunk-dq2xpa24.js";
import"./chunk-6w0knnta.js";
import"./chunk-40x04ckt.js";
import"./chunk-t76d8fxx.js";
import"./chunk-nh2akp42.js";
import"./chunk-0fdvzjbh.js";
import"./chunk-2a5b6azq.js";
import"./chunk-vay209b5.js";
import {
  Uk
} from "./chunk-3xrpxgq4.js";
import"./chunk-rfm3jr1m.js";
import"./chunk-w346ejn9.js";
import"./chunk-knvm2anf.js";
import"./chunk-65h5trb5.js";
import"./chunk-s2akeqpw.js";
import"./chunk-6771vrjp.js";
import"./chunk-g8mm42v1.js";
import"./chunk-50hzjdck.js";
import"./chunk-nn2jb0x0.js";
import"./chunk-v8xvth6j.js";
import"./chunk-kkk13rcb.js";
import"./chunk-ytpp1kam.js";
import"./chunk-na956zz3.js";
import"./chunk-f4bw8q7c.js";
import"./chunk-0v8vgrns.js";
import"./chunk-54qt5g7m.js";
import"./chunk-dhs2bg35.js";

// src/server/ui-server-entry.ts
var {serve } = globalThis.Bun;
import { writeFileSync, mkdirSync, unlinkSync as unlinkSync2, renameSync } from "fs";
import { join as join4, dirname as dirname2 } from "path";

// src/server/routes/static.ts
import { readFileSync, statSync } from "fs";
import { join, resolve } from "path";
import path from "path";
import { fileURLToPath } from "url";

// src/server/utils/mime-types.ts
function getContentType(pathname) {
  const ext = pathname.split(".").pop();
  switch (ext) {
    case "js":
    case "mjs":
      return "application/javascript";
    case "css":
      return "text/css";
    case "html":
      return "text/html";
    case "json":
      return "application/json";
    case "svg":
      return "image/svg+xml";
    case "png":
      return "image/png";
    case "jpg":
    case "jpeg":
      return "image/jpeg";
    case "woff":
      return "font/woff";
    case "woff2":
      return "font/woff2";
    default:
      return "text/plain";
  }
}

// src/server/routes/static.ts
function isFile(path2) {
  try {
    return statSync(path2).isFile();
  } catch {
    return false;
  }
}
function getUIDistPath() {
  const __filename2 = fileURLToPath(import.meta.url);
  const __dirname2 = path.dirname(__filename2);
  const isBinary = !__filename2.endsWith(".js") && !__filename2.endsWith(".ts");
  if (isBinary) {
    const execDir = path.dirname(process.execPath);
    return resolve(execDir, "assets/ui-dist");
  } else {
    const isCompiledDist = __filename2.includes("/dist/");
    return isCompiledDist ? resolve(__dirname2, "../assets/ui-dist") : resolve(__dirname2, "../../../assets/ui-dist");
  }
}
function serveStaticFile(pathname, uiDistPath, req) {
  const headers = { ...getCorsHeaders(req) };
  if (pathname === "/") {
    if (hasEmbeddedAssets()) {
      const asset = getEmbeddedAsset("/index.html");
      if (asset) {
        return new Response(Bun.file(asset.file).stream(), {
          headers: {
            "Content-Type": asset.mimeType,
            ...headers
          }
        });
      }
    }
    const indexPath2 = join(uiDistPath, "index.html");
    if (isFile(indexPath2)) {
      return new Response(readFileSync(indexPath2), {
        headers: {
          "Content-Type": "text/html",
          ...headers
        }
      });
    }
    return new Response('UI not built. Run "bun run build:ui" first.', {
      status: 404,
      headers
    });
  }
  if (hasEmbeddedAssets()) {
    const asset = getEmbeddedAsset(pathname);
    if (asset) {
      return new Response(Bun.file(asset.file).stream(), {
        headers: {
          "Content-Type": asset.mimeType,
          ...headers
        }
      });
    }
  }
  let filePath = join(uiDistPath, pathname);
  if (pathname.startsWith("/assets/")) {
    filePath = join(uiDistPath, pathname);
  }
  if (isFile(filePath)) {
    const file = readFileSync(filePath);
    return new Response(file, {
      headers: {
        "Content-Type": getContentType(pathname),
        ...headers
      }
    });
  }
  if (hasEmbeddedAssets()) {
    const asset = getEmbeddedAsset("/index.html");
    if (asset) {
      return new Response(Bun.file(asset.file).stream(), {
        headers: {
          "Content-Type": asset.mimeType,
          ...headers
        }
      });
    }
  }
  const indexPath = join(uiDistPath, "index.html");
  if (isFile(indexPath)) {
    return new Response(readFileSync(indexPath), {
      headers: {
        "Content-Type": "text/html",
        ...headers
      }
    });
  }
  return new Response("Not Found", {
    status: 404,
    headers
  });
}

// src/server/proxy.ts
var logger = createCliLogger({ tag: "proxy" });
var HOP_BY_HOP_REQUEST_HEADERS = new Set([
  "connection",
  "keep-alive",
  "proxy-authenticate",
  "proxy-authorization",
  "te",
  "trailer",
  "transfer-encoding",
  "upgrade",
  "host"
]);
var HOP_BY_HOP_RESPONSE_HEADERS = new Set([
  "connection",
  "keep-alive",
  "proxy-authenticate",
  "proxy-authorization",
  "te",
  "trailer",
  "transfer-encoding",
  "upgrade"
]);
function filterHeaders(source, denyList) {
  const out = new Headers;
  source.forEach((value, key) => {
    if (!denyList.has(key.toLowerCase())) {
      out.set(key, value);
    }
  });
  return out;
}
var LONG_LIVED_BACKEND_PATHS = new Set([
  "/api/evals/run",
  "/api/components/events",
  "/api/agent0/events",
  "/api/cognitive/v1/chat/completions"
]);
function isLongLivedProxyRequest(req, backendPathname) {
  const url = new URL(req.url);
  const pathname = backendPathname ?? url.pathname;
  const accept = req.headers.get("accept") ?? "";
  return pathname.includes("/stream") || LONG_LIVED_BACKEND_PATHS.has(pathname) || accept.includes("text/event-stream") && pathname.startsWith("/api/");
}
var PROXY_RETRY_BASE_DELAY_MS = 500;
var PROXY_MAX_RETRIES = 3;
async function proxyToBackend(req, backendPort, options = {}) {
  const url = new URL(req.url);
  const backendUrl = `http://127.0.0.1:${backendPort}${options.pathname ?? url.pathname}${options.search ?? url.search}`;
  const requestLabel = options.pathname ? `${url.pathname} -> ${options.pathname}` : url.pathname;
  const isLongLived = isLongLivedProxyRequest(req, options.pathname);
  const bodyBuffer = !isLongLived && req.body ? await req.arrayBuffer() : null;
  const canRetry = !isLongLived;
  const attempt = async () => {
    const init = {
      method: req.method,
      headers: filterHeaders(req.headers, HOP_BY_HOP_REQUEST_HEADERS),
      body: bodyBuffer ?? req.body,
      redirect: "manual",
      signal: isLongLived ? undefined : AbortSignal.timeout(30000)
    };
    if (init.body) {
      init.duplex = "half";
    }
    const response = await fetch(backendUrl, init);
    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: filterHeaders(response.headers, HOP_BY_HOP_RESPONSE_HEADERS)
    });
  };
  for (let retry = 0;; retry++) {
    try {
      return await withRequestTiming(`${req.method} ${requestLabel} -> 127.0.0.1:${backendPort}`, attempt);
    } catch (error) {
      if (canRetry && retry < PROXY_MAX_RETRIES) {
        const delay = PROXY_RETRY_BASE_DELAY_MS * 2 ** retry;
        logger.warn(`Retry ${retry + 1}/${PROXY_MAX_RETRIES} in ${delay}ms \u2192 ${req.method} ${options.pathname ?? url.pathname} \u2192 127.0.0.1:${backendPort}`);
        await new Promise((resolve2) => setTimeout(resolve2, delay));
        continue;
      }
      const errorDetail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
      logger.warn(`502 \u2192 ${req.method} ${options.pathname ?? url.pathname} \u2192 127.0.0.1:${backendPort} \u2014 ${errorDetail}`);
      return new Response(JSON.stringify({
        error: "Backend unavailable",
        message: "The agent backend server is not responding. It may be restarting."
      }), {
        status: 502,
        headers: {
          "Content-Type": "application/json",
          ...getCorsHeaders(req)
        }
      });
    }
  }
}

// src/server/socket/server.ts
var {listen } = globalThis.Bun;
import { existsSync, unlinkSync, chmodSync } from "fs";
var logger2 = createCliLogger({ tag: "socket-server" });
var HEARTBEAT_TIMEOUT_MS = 15000;
var EVICTION_SWEEP_INTERVAL_MS = HEARTBEAT_TIMEOUT_MS / 2;
var MANAGED_SHUTDOWN_GRACE_MS = 2000;
function formatRuntimeMajorMismatchUpgradeCommand(runtimeVersion, expectedRuntimeVersion) {
  const expectedMajor = getRuntimeMajor(expectedRuntimeVersion);
  const supportedRuntime = expectedMajor !== null ? `^${expectedMajor}.x` : `v${expectedRuntimeVersion}`;
  const prefix = `This DevConsole supports @botpress/runtime ${supportedRuntime}, but the agent is running v${runtimeVersion}.`;
  const relation = getRuntimeMajorRelation(runtimeVersion, expectedRuntimeVersion);
  if (relation === "newer") {
    return `${prefix} Run \`adk self-upgrade\` to update this CLI/DevConsole. ` + `If you intentionally need this older CLI line, pin the agent's runtime back to ${supportedRuntime}.`;
  }
  if (relation === "older") {
    return `${prefix} Run \`adk project upgrade --dry-run\` to review required project updates, ` + "then `adk project upgrade` to apply them.";
  }
  return `${prefix} Run \`adk self-upgrade\` to update this CLI/DevConsole, ` + "or run `adk project upgrade --dry-run` to review project updates.";
}

class ConsoleSocketServer {
  options;
  server = null;
  registry = new Map;
  clients = new Set;
  heartbeatTimer = null;
  shutdownTimer = null;
  constructor(options) {
    this.options = options;
  }
  async start() {
    const { socketPath } = this.options;
    if (existsSync(socketPath)) {
      if (await probeUnixSocket(socketPath)) {
        throw new AdkError({
          code: "SOCKET_ALREADY_BOUND",
          message: `${socketPath} is already bound by a live process. ` + `Refusing to unlink \u2014 reuse the existing singleton instead.`
        });
      }
      try {
        unlinkSync(socketPath);
      } catch (err) {
        const reason = err instanceof Error ? err.message : String(err);
        throw new AdkError({
          code: "SOCKET_UNLINK_FAILED",
          message: `Cannot remove stale socket at ${socketPath}: ${reason}. ` + `Check file ownership, or remove stale files with: rm ~/.adk/console.lock ~/.adk/console.port ~/.adk/console.sock`,
          cause: err
        });
      }
    }
    this.server = listen({
      unix: socketPath,
      socket: {
        data: (socket, data) => this.handleData(socket, data),
        open: (socket) => this.handleOpen(socket),
        close: (socket) => this.handleClose(socket),
        error: (_socket, error) => {
          logger2.error(`Connection error: ${error.message}`);
        }
      }
    });
    try {
      chmodSync(socketPath, 384);
    } catch (err) {
      logger2.warn(`Could not tighten permissions on ${socketPath}: ${err instanceof Error ? err.message : String(err)}`);
    }
    this.heartbeatTimer = setInterval(() => this.evictStaleAgents(), EVICTION_SWEEP_INTERVAL_MS);
  }
  stop() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
    if (this.shutdownTimer) {
      clearTimeout(this.shutdownTimer);
      this.shutdownTimer = null;
    }
    if (this.server) {
      this.server.stop(true);
      this.server = null;
    }
    try {
      if (existsSync(this.options.socketPath))
        unlinkSync(this.options.socketPath);
    } catch {}
    this.registry.clear();
    this.clients.clear();
  }
  getAgents() {
    return Array.from(this.registry.values());
  }
  requestAgentShutdown(agentPath) {
    let sent = false;
    const msg = {
      v: PROTOCOL_VERSION,
      type: "shutdown",
      agentPath
    };
    for (const client of this.clients) {
      if (client.data?.agentPath !== agentPath)
        continue;
      this.sendTo(client, msg);
      sent = true;
    }
    return sent;
  }
  handleOpen(socket) {
    socket.data = {
      parser: createLineParser()
    };
    this.clients.add(socket);
  }
  handleClose(socket) {
    this.clients.delete(socket);
    if (!socket.data?.agentPath)
      return;
    const agentPath = socket.data.agentPath;
    socket.data.agentPath = undefined;
    const successor = Array.from(this.clients).some((c) => c.data?.agentPath === agentPath);
    if (successor)
      return;
    this.registry.delete(agentPath);
    this.broadcastAgentsUpdated();
    this.checkEmpty();
  }
  handleData(socket, data) {
    if (!socket.data?.parser)
      return;
    const rawMessages = socket.data.parser.feed(data.toString());
    for (const raw of rawMessages) {
      const msg = parseAgentToServerMessage(raw);
      if (!msg) {
        logger2.warn("Ignoring malformed or unsupported agent message");
        continue;
      }
      switch (msg.type) {
        case "register":
          this.handleRegister(socket, msg);
          break;
        case "deregister":
          this.handleDeregister(socket, msg.agentPath);
          break;
        case "heartbeat":
          this.handleHeartbeat(socket, msg.agentPath, msg.status, msg.process);
          break;
      }
    }
  }
  handleRegister(socket, msg) {
    const { name, agentPath, backendPort, botPort, runtimeVersion, cliVersion, process: processInfo } = msg;
    if (runtimeVersion === undefined) {
      logger2.warn("agent registered without runtime version; run `adk project upgrade`");
    }
    let outdated = false;
    if (runtimeVersion !== undefined && this.options.expectedRuntimeVersion) {
      const status = getRuntimeVersionStatus(runtimeVersion, this.options.expectedRuntimeVersion);
      const expectedMajor = getRuntimeMajor(this.options.expectedRuntimeVersion);
      if (status === "major_mismatch" && expectedMajor !== null) {
        const upgradeCommand = formatRuntimeMajorMismatchUpgradeCommand(runtimeVersion, this.options.expectedRuntimeVersion);
        logger2.warn(`rejecting agent at ${agentPath}: runtime v${runtimeVersion} is on a different major, server expects major ${expectedMajor}`);
        this.sendTo(socket, {
          v: PROTOCOL_VERSION,
          type: "rejected",
          reason: "runtime_major_mismatch",
          agentRuntime: runtimeVersion,
          expectedRuntimeMajor: expectedMajor,
          upgradeCommand
        });
        socket.end();
        this.checkEmpty();
        return;
      }
      if (status === "outdated") {
        outdated = true;
      }
    }
    const now = Date.now();
    const agent = {
      name,
      agentPath,
      backendPort,
      botPort,
      status: "starting",
      connectedAt: now,
      lastHeartbeat: now,
      runtimeVersion: runtimeVersion ?? "unknown",
      cliVersion,
      outdated: outdated || undefined,
      process: processInfo ?? undefined
    };
    const existing = this.registry.get(agentPath);
    if (existing) {
      for (const client of this.clients) {
        if (client !== socket && client.data?.agentPath === agentPath) {
          client.data.agentPath = undefined;
        }
      }
    }
    this.registry.set(agentPath, agent);
    socket.data.agentPath = agentPath;
    if (existing) {
      logger2.warn(`Replacing existing agent at ${agentPath}`);
    }
    if (this.shutdownTimer) {
      clearTimeout(this.shutdownTimer);
      this.shutdownTimer = null;
    }
    this.sendTo(socket, { v: PROTOCOL_VERSION, type: "registered", agents: this.getAgents() });
    this.broadcastAgentsUpdated();
  }
  handleDeregister(socket, agentPath) {
    if (socket.data?.agentPath !== agentPath)
      return;
    socket.data.agentPath = undefined;
    this.registry.delete(agentPath);
    this.broadcastAgentsUpdated();
    this.checkEmpty();
  }
  handleHeartbeat(socket, agentPath, status, processInfo) {
    if (socket.data?.agentPath !== agentPath)
      return;
    const agent = this.registry.get(agentPath);
    if (agent) {
      const previousStatus = agent.status;
      agent.lastHeartbeat = Date.now();
      agent.status = status;
      if (processInfo !== undefined) {
        agent.process = processInfo;
      }
      if (previousStatus !== status) {
        this.broadcastAgentsUpdated();
      }
    }
  }
  evictStaleAgents() {
    const now = Date.now();
    let changed = false;
    for (const [path2, agent] of this.registry) {
      if (now - agent.lastHeartbeat > HEARTBEAT_TIMEOUT_MS) {
        this.registry.delete(path2);
        for (const client of this.clients) {
          if (client.data?.agentPath === path2) {
            client.data.agentPath = undefined;
            try {
              client.end();
            } catch {}
          }
        }
        changed = true;
      }
    }
    if (changed) {
      this.broadcastAgentsUpdated();
      this.checkEmpty();
    }
  }
  broadcastAgentsUpdated() {
    const msg = {
      v: PROTOCOL_VERSION,
      type: "agents_updated",
      agents: this.getAgents()
    };
    const encoded = encode(msg);
    for (const client of this.clients) {
      try {
        client.write(encoded);
      } catch {}
    }
  }
  sendTo(socket, msg) {
    try {
      socket.write(encode(msg));
    } catch {}
  }
  checkEmpty() {
    if (this.registry.size > 0)
      return;
    if (this.options.standalone)
      return;
    if (this.shutdownTimer)
      return;
    this.shutdownTimer = setTimeout(() => {
      if (this.registry.size === 0) {
        this.options.onEmpty?.();
      }
      this.shutdownTimer = null;
    }, MANAGED_SHUTDOWN_GRACE_MS);
  }
}

// src/server/agent-resolver.ts
function resolveTargetAgent(url, req, currentAgents) {
  if (currentAgents.length === 0)
    return { kind: "no-agents" };
  const agentParam = url.searchParams.get("agent");
  if (agentParam) {
    const found = currentAgents.find((a) => a.agentPath === agentParam);
    return found ? { kind: "found", agent: found } : { kind: "unknown-agent", requested: agentParam, agents: currentAgents };
  }
  const agentHeader = req.headers.get("x-agent-path");
  if (agentHeader) {
    const found = currentAgents.find((a) => a.agentPath === agentHeader);
    return found ? { kind: "found", agent: found } : { kind: "unknown-agent", requested: agentHeader, agents: currentAgents };
  }
  if (currentAgents.length === 1) {
    return { kind: "found", agent: currentAgents[0] };
  }
  return { kind: "ambiguous", agents: currentAgents };
}
function agentResolutionError(result) {
  const agentSummary = (a) => ({ name: a.name, agentPath: a.agentPath });
  if (result.kind === "no-agents") {
    return new Response(JSON.stringify({
      error: "No agents connected",
      message: "No agent backends are registered. Open a project or start one with `adk dev`.",
      agents: []
    }), { status: 503, headers: { "Content-Type": "application/json" } });
  }
  if (result.kind === "unknown-agent") {
    return new Response(JSON.stringify({
      error: "Unknown agent",
      message: `Agent '${result.requested}' is not registered. Available: ${result.agents.map((a) => a.agentPath).join(", ")}.`,
      requested: result.requested,
      agents: result.agents.map(agentSummary)
    }), { status: 404, headers: { "Content-Type": "application/json" } });
  }
  return new Response(JSON.stringify({
    error: "Ambiguous agent",
    message: `${result.agents.length} agents are registered. Specify which one via '?agent=<agentPath>' (browser) or 'X-Agent-Path: <agentPath>' (CLI). Available: ${result.agents.map((a) => a.agentPath).join(", ")}.`,
    agents: result.agents.map(agentSummary)
  }), { status: 400, headers: { "Content-Type": "application/json" } });
}

// src/server/handlers/auth.ts
var logger3 = createCliLogger({ tag: "auth" });
var activeSession = null;
async function handleAuthRequest(pathname, req, options = {}) {
  if (pathname === "/api/auth/login") {
    if (req.method !== "POST")
      return errorResponse("Method not allowed", "Only POST is supported", 405);
    return handleAuthLogin();
  }
  if (pathname === "/api/auth/login/status") {
    if (req.method !== "GET")
      return errorResponse("Method not allowed", "Only GET is supported", 405);
    return handleAuthLoginStatus(req);
  }
  if (pathname === "/api/auth/login/token") {
    if (req.method !== "POST")
      return errorResponse("Method not allowed", "Only POST is supported", 405);
    return handleAuthLoginToken(req);
  }
  if (pathname === "/api/auth/login/cancel") {
    if (req.method !== "POST")
      return errorResponse("Method not allowed", "Only POST is supported", 405);
    return handleAuthLoginCancel();
  }
  if (pathname === "/api/auth/identity") {
    if (req.method !== "GET")
      return errorResponse("Method not allowed", "Only GET is supported", 405);
    return handleAuthIdentity();
  }
  if (pathname === "/api/auth/profiles") {
    if (req.method !== "GET")
      return errorResponse("Method not allowed", "Only GET is supported", 405);
    return handleAuthProfiles();
  }
  if (pathname === "/api/auth/profiles/switch") {
    if (req.method !== "POST")
      return errorResponse("Method not allowed", "Only POST is supported", 405);
    return handleAuthProfileSwitch(req);
  }
  if (pathname === "/api/auth/logout") {
    if (req.method !== "POST")
      return errorResponse("Method not allowed", "Only POST is supported", 405);
    return handleAuthLogout(req, options);
  }
  return null;
}
async function handleAuthLogin() {
  if (activeSession) {
    activeSession.cancel();
    activeSession = null;
  }
  let callbackUrl;
  let waitForToken;
  let cancel;
  try {
    ({ url: callbackUrl, waitForToken, cancel } = await startCallbackServer({ timeout: 300000 }));
  } catch (error) {
    return errorResponse("Login start failed", error instanceof Error ? error.message : "Failed to start login", 500);
  }
  const loginUrl = getLoginUrl(callbackUrl);
  const sessionId = crypto.randomUUID();
  const session = {
    id: sessionId,
    cancel,
    resolved: false,
    result: null,
    loginSaved: false,
    loginPromise: null
  };
  waitForToken().then((result) => {
    if (activeSession?.id === sessionId) {
      session.resolved = true;
      session.result = result;
    }
  }).catch((err) => {
    logger3.debug(`login callback rejected: ${err instanceof Error ? err.message : String(err)}`);
    if (activeSession?.id === sessionId) {
      session.resolved = true;
      session.result = { token: "", success: false };
    }
  });
  activeSession = session;
  return successResponse({ loginUrl, sessionId });
}
async function handleAuthLoginStatus(req) {
  const url = new URL(req.url);
  const sessionId = url.searchParams.get("session");
  if (!activeSession || activeSession.id !== sessionId) {
    return successResponse({ status: "expired" });
  }
  if (!activeSession.resolved) {
    return successResponse({ status: "pending" });
  }
  const result = activeSession.result;
  if (!result?.success || !result.token) {
    activeSession = null;
    return successResponse({ status: "error", message: "Login timed out or was cancelled." });
  }
  if (activeSession.loginSaved) {
    if (activeSession.loginPromise) {
      try {
        await activeSession.loginPromise;
      } catch (error) {
        if (activeSession?.id === sessionId)
          activeSession = null;
        return successResponse({
          status: "error",
          message: error instanceof Error ? error.message : "Login failed"
        });
      }
    }
    const profile = await auth.getCurrentProfileDetails();
    if (activeSession?.id === sessionId)
      activeSession = null;
    return successResponse({
      status: "success",
      identity: {
        accountId: profile?.accountId,
        email: profile?.email,
        displayName: profile?.displayName
      }
    });
  }
  activeSession.loginSaved = true;
  const sessionRef = activeSession;
  activeSession.loginPromise = auth.login(result.token);
  try {
    await activeSession.loginPromise;
    const profile = await auth.getCurrentProfileDetails();
    if (activeSession === sessionRef)
      activeSession = null;
    return successResponse({
      status: "success",
      identity: {
        accountId: profile?.accountId,
        email: profile?.email,
        displayName: profile?.displayName
      }
    });
  } catch (error) {
    if (activeSession === sessionRef)
      activeSession = null;
    return successResponse({
      status: "error",
      message: error instanceof Error ? error.message : "Login failed"
    });
  }
}
async function handleAuthLoginToken(req) {
  let body;
  try {
    body = await req.json();
  } catch {
    return errorResponse("Invalid body", "Expected JSON body with token field", 400);
  }
  if (!body.token || typeof body.token !== "string") {
    return errorResponse("Missing token", "Token is required", 400);
  }
  try {
    await auth.login(body.token);
    const profile = await auth.getCurrentProfileDetails();
    return successResponse({
      identity: {
        accountId: profile?.accountId,
        email: profile?.email,
        displayName: profile?.displayName
      }
    });
  } catch (error) {
    return errorResponse("Login failed", error instanceof Error ? error.message : "Invalid token", 401);
  }
}
async function handleAuthLoginCancel() {
  if (activeSession) {
    activeSession.cancel();
    activeSession = null;
  }
  return successResponse({ status: "cancelled" });
}
async function handleAuthIdentity() {
  try {
    const profile = await auth.getCurrentProfileDetails();
    if (!profile) {
      return successResponse({ authenticated: false });
    }
    return successResponse({
      authenticated: true,
      identity: {
        accountId: profile.accountId,
        email: profile.email,
        displayName: profile.displayName
      }
    });
  } catch (err) {
    logger3.debug(`identity lookup failed: ${err instanceof Error ? err.message : String(err)}`);
    return successResponse({ authenticated: false });
  }
}
async function handleAuthProfiles() {
  try {
    const profiles = await auth.listProfiles();
    const currentProfile = await auth.getCurrentProfile();
    return successResponse({
      profiles: profiles.map((p) => ({
        name: p.name,
        email: p.email,
        displayName: p.displayName,
        apiUrl: p.apiUrl,
        isCurrent: p.name === currentProfile
      })),
      currentProfile
    });
  } catch (err) {
    logger3.debug(`listing profiles failed: ${err instanceof Error ? err.message : String(err)}`);
    return successResponse({ profiles: [], currentProfile: null });
  }
}
async function handleAuthProfileSwitch(req) {
  let body;
  try {
    body = await req.json();
  } catch {
    return errorResponse("Invalid body", "Expected JSON body with profile field", 400);
  }
  if (!body.profile || typeof body.profile !== "string") {
    return errorResponse("Missing profile", "Profile name is required", 400);
  }
  try {
    await auth.setCurrentProfile(body.profile);
    const details = await auth.getCurrentProfileDetails();
    return successResponse({
      identity: {
        accountId: details?.accountId,
        email: details?.email,
        displayName: details?.displayName
      }
    });
  } catch (error) {
    return errorResponse("Switch failed", error instanceof Error ? error.message : "Failed to switch profile", 400);
  }
}
async function handleAuthLogout(req, options) {
  let body = {};
  try {
    body = await req.json();
  } catch {}
  try {
    const profile = typeof body.profile === "string" ? body.profile : undefined;
    await auth.logout(profile);
    try {
      await options.onLogout?.();
    } catch {}
    return successResponse({ status: "logged-out" });
  } catch (error) {
    return errorResponse("Logout failed", error instanceof Error ? error.message : "Failed to logout", 500);
  }
}

// src/server/project-supervisor.ts
import { spawn, execFile } from "child_process";
import { createWriteStream, existsSync as existsSync2 } from "fs";
import { mkdir, readFile, rename, stat, writeFile } from "fs/promises";
import { homedir } from "os";
import { basename, dirname, join as join2, resolve as resolve2 } from "path";
var __dirname = "/home/runner/work/agent-lack/agent-lack/packages/cli/src/server";
var ADK_DIR = join2(homedir(), ".adk");
var SHUTDOWN_GRACE_MS = 4000;
var FORCE_GRACE_MS = 2000;
var MANAGED_PRUNE_MS = 60000;
var jsonWriteCounter = 0;
function jsonErrorMessage(error) {
  return error instanceof Error ? error.message : String(error);
}
function expandHome(rawPath) {
  if (rawPath === "~")
    return homedir();
  if (rawPath.startsWith("~/"))
    return join2(homedir(), rawPath.slice(2));
  return rawPath;
}
function timestampForFile(date = new Date) {
  return date.toISOString().replaceAll(":", "-").replaceAll(".", "-");
}
async function readJsonFile(path2, fallback) {
  try {
    return JSON.parse(await readFile(path2, "utf-8"));
  } catch {
    return fallback;
  }
}
async function writeJsonFile(path2, value) {
  await mkdir(dirname(path2), { recursive: true });
  const tmpPath = join2(dirname(path2), `.${basename(path2)}.${process.pid}.${process.hrtime.bigint()}.${++jsonWriteCounter}.tmp`);
  await writeFile(tmpPath, `${JSON.stringify(value, null, 2)}
`, { encoding: "utf-8", mode: 384 });
  await rename(tmpPath, path2);
}
function execFileString(command, args) {
  return new Promise((resolve3, reject) => {
    execFile(command, args, { encoding: "utf-8" }, (error, stdout, stderr) => {
      if (error) {
        reject(new Error(stderr.trim() || error.message));
        return;
      }
      resolve3(stdout.trim());
    });
  });
}
function isMissingCommandError(error) {
  const message = jsonErrorMessage(error).toLowerCase();
  return message.includes("enoent") || message.includes("not found");
}
function isUserCancelledDialogError(error) {
  const message = jsonErrorMessage(error);
  return message.includes("-128") || message.toLowerCase().includes("cancel");
}
async function pickFolder() {
  try {
    if (process.platform === "darwin") {
      const path2 = await execFileString("osascript", [
        "-e",
        'POSIX path of (choose folder with prompt "Choose an ADK project folder")'
      ]);
      return path2 ? { path: path2 } : { cancelled: true };
    }
    if (process.platform === "win32") {
      const path2 = await execFileString("powershell.exe", [
        "-NoProfile",
        "-STA",
        "-Command",
        [
          "Add-Type -AssemblyName System.Windows.Forms;",
          "$dialog = New-Object System.Windows.Forms.FolderBrowserDialog;",
          '$dialog.Description = "Choose an ADK project folder";',
          "if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $dialog.SelectedPath }"
        ].join(" ")
      ]);
      return path2 ? { path: path2 } : { cancelled: true };
    }
    try {
      const path2 = await execFileString("zenity", ["--file-selection", "--directory", "--title", "Choose ADK project"]);
      return path2 ? { path: path2 } : { cancelled: true };
    } catch (error) {
      if (!isMissingCommandError(error))
        return { cancelled: true };
      try {
        const path2 = await execFileString("kdialog", ["--getexistingdirectory", homedir()]);
        return path2 ? { path: path2 } : { cancelled: true };
      } catch (fallbackError) {
        if (!isMissingCommandError(fallbackError))
          return { cancelled: true };
        throw fallbackError;
      }
    }
  } catch (error) {
    if (isUserCancelledDialogError(error)) {
      return { cancelled: true };
    }
    throw error;
  }
}
function resolveCliSpawnCommand() {
  const isVirtualBunPath = (path2) => path2.includes("$bunfs");
  const isCompiledExecutable = process.argv.some((arg) => isVirtualBunPath(arg));
  if (isCompiledExecutable) {
    return { command: process.execPath, argsPrefix: [] };
  }
  const scriptDir = process.argv[1] && !isVirtualBunPath(process.argv[1]) ? dirname(process.argv[1]) : __dirname;
  const candidates = [
    join2(__dirname, "..", "cli.ts"),
    join2(__dirname, "..", "cli.js"),
    join2(scriptDir, "..", "cli.ts"),
    join2(scriptDir, "..", "cli.js"),
    join2(scriptDir, "cli.js")
  ];
  for (const candidate of candidates) {
    if (!isVirtualBunPath(candidate) && existsSync2(candidate))
      return { command: process.execPath, argsPrefix: [candidate] };
  }
  throw new AdkError({
    code: "CLI_ENTRY_NOT_FOUND",
    message: "Could not locate the adk CLI entry point. " + `Searched: ${candidates.map((c) => c.replace(homedir(), "~")).join(", ")}`
  });
}
function signalChild(child, signal) {
  if (!child.pid)
    return false;
  if (process.platform === "win32")
    return child.kill(signal);
  try {
    process.kill(-child.pid, signal);
    return true;
  } catch {
    return child.kill(signal);
  }
}
function isProcessAlive(project) {
  return !!project && project.status !== "exited" && project.exitCode == null && project.signal == null && project.child.exitCode === null && project.child.signalCode == null;
}
function isLaunchInProgress(project) {
  return isProcessAlive(project) && project.status === "launching";
}
async function sleep(ms) {
  await new Promise((resolve3) => setTimeout(resolve3, ms));
}
function parseFatalLogRecord(line) {
  try {
    const parsed = JSON.parse(line);
    if (parsed.level !== "error" || parsed.fatal !== true || typeof parsed.message !== "string")
      return null;
    return {
      message: parsed.message,
      details: parsed.details,
      stack: typeof parsed.stack === "string" ? parsed.stack : undefined
    };
  } catch {
    return null;
  }
}
function formatLaunchExitError(project) {
  if (project.exitCode === 0 && !project.signal) {
    return {
      message: "adk dev exited cleanly before registering with the Dev Console. " + "This usually means a project-level validation failed. Check the log for details.",
      details: { exitCode: 0 },
      logPath: project.logPath
    };
  }
  const exitReason = project.exitCode === null || project.exitCode === undefined ? project.signal ? `signal ${project.signal}` : "unknown exit reason" : `exit code ${project.exitCode}`;
  return {
    message: `adk dev exited before registering with the Dev Console (${exitReason}).`,
    logPath: project.logPath
  };
}
function createLineCollector(onLine) {
  let pending = "";
  return (chunk) => {
    pending += chunk.toString();
    const lines = pending.split(/\r?\n/);
    pending = lines.pop() ?? "";
    for (const line of lines) {
      if (line.trim())
        onLine(line);
    }
  };
}

class ProjectSupervisor {
  options;
  managed = new Map;
  launchPromises = new Map;
  recentProjectsFile;
  managedProjectsFile;
  constructor(options) {
    this.options = options;
    const adkDir = options.adkDir ?? ADK_DIR;
    this.recentProjectsFile = join2(adkDir, "recent-projects.json");
    this.managedProjectsFile = join2(adkDir, "managed-projects.json");
  }
  async handle(pathname, req) {
    try {
      if (pathname === "/api/projects/recent") {
        if (req.method !== "GET")
          return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
        return successResponse({ projects: await this.readRecentProjects() });
      }
      if (pathname === "/api/projects/pick") {
        if (req.method !== "POST")
          return errorResponse("Method not allowed", "Only POST requests are allowed", 405);
        const result = await pickFolder();
        return successResponse(result);
      }
      if (pathname === "/api/projects/open") {
        if (req.method !== "POST")
          return errorResponse("Method not allowed", "Only POST requests are allowed", 405);
        return this.handleOpenProject(req);
      }
      if (pathname === "/api/projects/launch-status") {
        if (req.method !== "GET")
          return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
        return this.handleLaunchStatus(req);
      }
      if (pathname === "/api/agents/terminate") {
        if (req.method !== "POST")
          return errorResponse("Method not allowed", "Only POST requests are allowed", 405);
        return this.handleTerminateAgent(req);
      }
      return null;
    } catch (error) {
      return errorResponse("Request failed", jsonErrorMessage(error), 500);
    }
  }
  async flushManagedLedger() {
    await this.writeManagedProjects();
  }
  async shutdownManagedProjects() {
    const projects = Array.from(this.managed.values());
    for (const project of projects) {
      if (isProcessAlive(project))
        signalChild(project.child, "SIGTERM");
    }
    await Promise.all(projects.map((project) => this.waitForChildExit(project, 1000)));
    for (const project of projects) {
      if (isProcessAlive(project))
        signalChild(project.child, "SIGKILL");
    }
    await Promise.all(projects.map((project) => this.waitForChildExit(project, 1000)));
    await this.writeManagedProjects();
  }
  async handleOpenProject(req) {
    let body;
    try {
      body = await req.json();
    } catch {
      return errorResponse("Invalid body", "Expected a JSON body with a `path` field.", 400);
    }
    if (typeof body.path !== "string" || !body.path.trim()) {
      return errorResponse("Missing path", "Missing required `path` field.", 400);
    }
    let agentRoot;
    try {
      agentRoot = await this.resolveAgentRoot(body.path);
    } catch (error) {
      return errorResponse("Invalid project", jsonErrorMessage(error), 400);
    }
    const projectInfo = {
      path: agentRoot,
      name: basename(agentRoot),
      lastOpenedAt: new Date().toISOString()
    };
    try {
      await (this.options.preflightRuntimeVersion ?? preflightRuntimeVersionCheck)(agentRoot);
    } catch (error) {
      if (error instanceof RuntimeVersionMismatchError) {
        return jsonResponse({
          error: "Runtime version mismatch",
          message: formatRuntimeVersionMismatchMessage(error),
          project: projectInfo,
          details: {
            installedVersion: error.installedVersion,
            expectedVersion: error.expectedVersion,
            status: error.status,
            installCommand: error.installCommand,
            resolution: error.resolution
          }
        }, 409);
      }
      return errorResponse("Runtime check failed", jsonErrorMessage(error), 400);
    }
    let project;
    try {
      project = await this.loadProject(agentRoot);
      projectInfo.name = project.config?.name || projectInfo.name;
    } catch (error) {
      return errorResponse("Invalid project", jsonErrorMessage(error), 400);
    }
    const name = projectInfo.name;
    const recent = await this.upsertRecentProject(projectInfo);
    const existingAgent = this.options.getAgents().find((agent) => agent.agentPath === agentRoot);
    if (existingAgent) {
      return successResponse({ status: "already-running", project: recent, agent: existingAgent });
    }
    const existingLaunch = this.managed.get(agentRoot);
    if (isLaunchInProgress(existingLaunch)) {
      return successResponse({ status: "launching", project: recent, launch: this.toManagedRecord(existingLaunch) });
    }
    const pendingLaunch = this.launchPromises.get(agentRoot);
    if (pendingLaunch) {
      try {
        const launch = await pendingLaunch;
        return successResponse({ status: "launching", project: recent, launch: this.toManagedRecord(launch) });
      } catch (error) {
        return errorResponse("Launch failed", jsonErrorMessage(error), 500);
      }
    }
    const failedLaunch = this.managed.get(agentRoot);
    if (failedLaunch?.status === "failed" && isProcessAlive(failedLaunch)) {
      signalChild(failedLaunch.child, "SIGTERM");
      const stopped = await this.waitForChildExit(failedLaunch, FORCE_GRACE_MS);
      if (!stopped) {
        signalChild(failedLaunch.child, "SIGKILL");
        await this.waitForChildExit(failedLaunch, FORCE_GRACE_MS);
      }
    }
    try {
      const launchPromise = this.launchProject(agentRoot, name).finally(() => {
        this.launchPromises.delete(agentRoot);
      });
      this.launchPromises.set(agentRoot, launchPromise);
      const launch = await launchPromise;
      return successResponse({ status: "launching", project: recent, launch: this.toManagedRecord(launch) });
    } catch (error) {
      return errorResponse("Launch failed", jsonErrorMessage(error), 500);
    }
  }
  async handleLaunchStatus(req) {
    const url = new URL(req.url);
    const rawPath = url.searchParams.get("path");
    if (!rawPath?.trim()) {
      return errorResponse("Missing path", "Missing required `path` query parameter.", 400);
    }
    const agentPath = resolve2(expandHome(rawPath));
    const agent = this.options.getAgents().find((entry) => entry.agentPath === agentPath);
    if (agent) {
      return successResponse({ status: "registered", agent });
    }
    const launch = this.managed.get(agentPath);
    if (!launch) {
      return successResponse({ status: "unknown" });
    }
    if (launch.status === "failed") {
      return successResponse({
        status: "failed",
        message: launch.error?.message ?? "Project failed to launch.",
        details: launch.error?.details,
        stack: launch.error?.stack,
        logPath: launch.error?.logPath ?? launch.logPath
      });
    }
    if (launch.status === "exited") {
      const error = launch.error ?? formatLaunchExitError(launch);
      return successResponse({
        status: "failed",
        message: error.message,
        details: error.details,
        stack: error.stack,
        logPath: error.logPath ?? launch.logPath
      });
    }
    return successResponse({ status: "launching", launch: this.toManagedRecord(launch), logPath: launch.logPath });
  }
  async handleTerminateAgent(req) {
    let body;
    try {
      body = await req.json();
    } catch {
      return errorResponse("Invalid body", "Expected a JSON body with an `agentPath` field.", 400);
    }
    if (typeof body.agentPath !== "string" || !body.agentPath.trim()) {
      return errorResponse("Missing agentPath", "Missing required `agentPath` field.", 400);
    }
    const agentPath = resolve2(expandHome(body.agentPath));
    const result = await this.stopProjectAt(agentPath);
    if (result.status === "not-running") {
      return errorResponse("Unknown agent", `No running agent found at ${agentPath}`, 404);
    }
    if (result.status === "shutdown-requested") {
      return successResponse({
        status: "shutdown-requested",
        agentPath,
        graceful: result.graceful,
        forced: false
      });
    }
    return successResponse({
      status: "stopped",
      agentPath,
      graceful: result.graceful,
      forced: result.forced,
      ...result.signal && { signal: result.signal }
    });
  }
  async stopProjectAt(agentPath) {
    const agent = this.options.getAgents().find((entry) => entry.agentPath === agentPath);
    const managed = this.managed.get(agentPath);
    if (!agent && !isProcessAlive(managed)) {
      return { status: "not-running" };
    }
    const gracefulRequested = agent ? this.options.requestAgentShutdown(agentPath) : false;
    if (gracefulRequested) {
      const stopped = await this.waitForAgentGone(agentPath, SHUTDOWN_GRACE_MS);
      if (stopped) {
        return { status: "stopped", graceful: true, forced: false };
      }
    }
    if (!isProcessAlive(managed)) {
      return { status: "shutdown-requested", graceful: gracefulRequested };
    }
    signalChild(managed.child, "SIGTERM");
    const terminated = await this.waitForChildExit(managed, FORCE_GRACE_MS);
    if (terminated) {
      return { status: "stopped", graceful: gracefulRequested, forced: true, signal: "SIGTERM" };
    }
    signalChild(managed.child, "SIGKILL");
    await this.waitForChildExit(managed, FORCE_GRACE_MS);
    return { status: "stopped", graceful: gracefulRequested, forced: true, signal: "SIGKILL" };
  }
  async resolveAgentRoot(rawPath) {
    const absolutePath = resolve2(expandHome(rawPath.trim()));
    const pathStat = await stat(absolutePath).catch(() => null);
    if (!pathStat)
      throw new AdkError({
        code: "PROJECT_PATH_MISSING",
        message: `Path does not exist: ${absolutePath}`,
        expected: true
      });
    const startPath = pathStat.isDirectory() ? absolutePath : dirname(absolutePath);
    const agentRoot = await findAgentRoot(startPath);
    if (!agentRoot)
      throw new AdkError({
        code: "PROJECT_NOT_FOUND",
        message: "No ADK project found. Choose a folder inside a project with agent.config.ts.",
        expected: true
      });
    return agentRoot;
  }
  async loadProject(agentRoot) {
    return (this.options.loadProject ?? AgentProject.load)(agentRoot, {
      adkCommand: "adk-dev"
    });
  }
  async launchProject(agentRoot, name) {
    const logsDir = join2(agentRoot, ".adk", "logs");
    await mkdir(logsDir, { recursive: true });
    const logPath = join2(logsDir, `devconsole-open-${timestampForFile()}.log`);
    const logStream = createWriteStream(logPath, { flags: "a" });
    const { command, argsPrefix } = resolveCliSpawnCommand();
    const args = [...argsPrefix, "dev", "--non-interactive", "--port-console", String(this.options.consolePort)];
    const child = (this.options.spawnDevProcess ?? spawn)(command, args, {
      cwd: agentRoot,
      detached: process.platform !== "win32",
      stdio: ["ignore", "pipe", "pipe"],
      env: {
        ...process.env,
        FORCE_COLOR: "0"
      }
    });
    child.stdout?.pipe(logStream);
    child.stderr?.pipe(logStream);
    const managed = {
      agentPath: agentRoot,
      name,
      pid: child.pid ?? 0,
      logPath,
      startedAt: new Date().toISOString(),
      status: "launching",
      child
    };
    const markLaunchFailed = (error) => {
      managed.status = "failed";
      managed.error = { ...error, logPath: error.logPath ?? logPath };
      this.writeManagedProjects().catch(() => {});
    };
    const inspectLine = (line) => {
      const error = parseFatalLogRecord(line);
      if (error)
        markLaunchFailed(error);
    };
    child.stdout?.on("data", createLineCollector(inspectLine));
    child.stderr?.on("data", createLineCollector(inspectLine));
    child.once("exit", (exitCode, signal) => {
      managed.exitCode = exitCode;
      managed.signal = signal;
      if (managed.status !== "failed") {
        managed.status = exitCode === 0 && !signal ? "exited" : "failed";
        managed.error = formatLaunchExitError(managed);
      }
      logStream.end();
      this.writeManagedProjects().catch(() => {});
      this.scheduleManagedPrune(agentRoot, managed);
    });
    child.once("error", (error) => {
      managed.status = "failed";
      managed.exitCode = 1;
      managed.error = { message: `Failed to launch adk dev: ${error.message}`, logPath };
      logStream.write(`[devconsole] failed to launch: ${error.message}
`);
      logStream.end();
      this.writeManagedProjects().catch(() => {});
      this.scheduleManagedPrune(agentRoot, managed);
    });
    this.managed.set(agentRoot, managed);
    await this.writeManagedProjects();
    return managed;
  }
  async readRecentProjects() {
    const projects = await readJsonFile(this.recentProjectsFile, []);
    return projects.filter((project) => typeof project.path === "string" && typeof project.name === "string").slice(0, 12);
  }
  async upsertRecentProject(project) {
    const projects = await this.readRecentProjects();
    const next = [project, ...projects.filter((entry) => entry.path !== project.path)].slice(0, 12);
    await writeJsonFile(this.recentProjectsFile, next);
    return project;
  }
  toManagedRecord(project) {
    return {
      agentPath: project.agentPath,
      name: project.name,
      pid: project.pid,
      logPath: project.logPath,
      startedAt: project.startedAt,
      status: project.status,
      error: project.error,
      exitCode: project.exitCode,
      signal: project.signal
    };
  }
  async writeManagedProjects() {
    const projects = Array.from(this.managed.values()).map((project) => this.toManagedRecord(project));
    await writeJsonFile(this.managedProjectsFile, projects);
  }
  scheduleManagedPrune(agentRoot, project) {
    const timer = setTimeout(() => {
      if (this.managed.get(agentRoot) !== project || isProcessAlive(project))
        return;
      this.managed.delete(agentRoot);
      this.writeManagedProjects().catch(() => {});
    }, MANAGED_PRUNE_MS);
    timer.unref?.();
  }
  async waitForAgentGone(agentPath, timeoutMs) {
    const start = Date.now();
    while (Date.now() - start < timeoutMs) {
      if (!this.options.getAgents().some((agent) => agent.agentPath === agentPath))
        return true;
      await sleep(150);
    }
    return !this.options.getAgents().some((agent) => agent.agentPath === agentPath);
  }
  async waitForChildExit(project, timeoutMs) {
    const start = Date.now();
    while (Date.now() - start < timeoutMs) {
      if (!isProcessAlive(project))
        return true;
      await sleep(150);
    }
    return !isProcessAlive(project);
  }
}

// src/server/project-creator.ts
import { spawn as spawn2 } from "child_process";
import { existsSync as existsSync3 } from "fs";
import { mkdir as mkdir2, readdir, rm, stat as stat2 } from "fs/promises";
import { homedir as homedir2 } from "os";
import { join as join3, resolve as resolve3 } from "path";
var VALID_NAME = /^[a-zA-Z0-9_-]+$/;
var TERMINAL_STATE_TTL_MS = 5 * 60000;
var COMMAND_OUTPUT_LIMIT = 64 * 1024;
var ADK_RUNTIME_BOT_TAGS = { runtime: "adk" };
function expandHome2(rawPath) {
  if (rawPath === "~")
    return homedir2();
  if (rawPath.startsWith("~/"))
    return join3(homedir2(), rawPath.slice(2));
  return rawPath;
}
function jsonErrorMessage2(error) {
  return error instanceof Error ? error.message : String(error);
}
function captureExecError(error) {
  if (error instanceof Error) {
    const withStderr = error;
    const stderr = typeof withStderr.stderr === "string" ? withStderr.stderr.trim() : String(withStderr.stderr ?? "").trim();
    if (stderr)
      return stderr;
    return error.message;
  }
  return String(error);
}
function parseRef(ref) {
  const at = ref.indexOf("@");
  if (at < 0)
    return { name: ref, version: "latest" };
  return { name: ref.slice(0, at), version: ref.slice(at + 1) };
}
function appendCommandOutput(current, chunk) {
  const next = current + chunk;
  return next.length > COMMAND_OUTPUT_LIMIT ? next.slice(-COMMAND_OUTPUT_LIMIT) : next;
}

class ProjectCreator {
  options;
  creations = new Map;
  constructor(options = {}) {
    this.options = options;
  }
  async handle(pathname, req) {
    try {
      if (pathname === "/api/templates") {
        if (req.method !== "GET")
          return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
        return successResponse({ templates: this.listTemplates() });
      }
      if (pathname === "/api/projects/check-path") {
        if (req.method !== "GET")
          return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
        return this.handleCheckPath(req);
      }
      if (pathname === "/api/projects/create") {
        if (req.method !== "POST")
          return errorResponse("Method not allowed", "Only POST requests are allowed", 405);
        return this.handleCreate(req);
      }
      if (pathname === "/api/projects/create-status") {
        if (req.method !== "GET")
          return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
        return this.handleCreateStatus(req);
      }
      if (pathname === "/api/workspaces") {
        if (req.method !== "GET")
          return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
        return this.handleListWorkspaces();
      }
      const prodBotsMatch = pathname.match(/^\/api\/workspaces\/([^/]+)\/(linkable|viewable)-prod-bots$/);
      if (prodBotsMatch) {
        if (req.method !== "GET")
          return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
        return this.handleListProdBots(decodeURIComponent(prodBotsMatch[1]), prodBotsMatch[2]);
      }
      if (pathname === "/api/projects/link") {
        if (req.method !== "POST")
          return errorResponse("Method not allowed", "Only POST requests are allowed", 405);
        return this.handleLink(req);
      }
      return null;
    } catch (error) {
      return errorResponse("Request failed", jsonErrorMessage2(error), 500);
    }
  }
  listTemplates() {
    return AgentProjectGenerator.getAvailableTemplates();
  }
  async handleCreate(req) {
    let body;
    try {
      body = await req.json();
    } catch {
      return errorResponse("Invalid body", "Expected a JSON body with parentPath, name, and template.", 400);
    }
    if (typeof body.parentPath !== "string" || !body.parentPath.trim()) {
      return errorResponse("Missing parentPath", "Missing required `parentPath` field.", 400);
    }
    if (typeof body.name !== "string" || !body.name.trim()) {
      return errorResponse("Missing name", "Missing required `name` field.", 400);
    }
    if (typeof body.template !== "string" || !body.template.trim()) {
      return errorResponse("Missing template", "Missing required `template` field.", 400);
    }
    const name = body.name.trim();
    if (!VALID_NAME.test(name)) {
      return errorResponse("Invalid name", "Project name can only contain letters, numbers, hyphens, and underscores.", 400);
    }
    const templates = this.listTemplates();
    if (!templates.some((t) => t.name === body.template)) {
      return errorResponse("Invalid template", `Unknown template: "${body.template}".`, 400);
    }
    const parentPath = resolve3(expandHome2(body.parentPath.trim()));
    const parentStat = await stat2(parentPath).catch(() => null);
    if (!parentStat || !parentStat.isDirectory()) {
      return errorResponse("Invalid parent", `Parent directory does not exist: ${parentPath}`, 400);
    }
    const projectPath = join3(parentPath, name);
    const previousCreation = this.creations.get(projectPath);
    if (previousCreation?.status === "creating") {
      return successResponse({ status: "creating", projectPath });
    }
    const force = body.force === true;
    let skipScaffolding = false;
    if (existsSync3(projectPath)) {
      const existing = await readdir(projectPath).catch(() => []);
      if (existing.length > 0) {
        if (force) {
          await this.options.stopRunningProject?.(projectPath);
          await rm(projectPath, { recursive: true, force: true });
        } else if (previousCreation?.status !== "failed") {
          return errorResponse("Directory exists", `"${projectPath}" already exists and is not empty.`, 409);
        } else if (previousCreation.template !== body.template) {
          return errorResponse("Directory exists", `"${projectPath}" contains files from a failed "${previousCreation.template}" creation. Choose the same template or a new location.`, 409);
        } else if (previousCreation.stage === "scaffolding") {
          return errorResponse("Directory exists", `"${projectPath}" contains files from a failed scaffolding attempt. Remove it or choose a new location.`, 409);
        } else {
          skipScaffolding = true;
        }
      }
    }
    const state = {
      projectPath,
      template: body.template,
      stage: skipScaffolding ? "installing" : "scaffolding",
      status: "creating",
      startedAt: Date.now(),
      warnings: [],
      skipScaffolding
    };
    this.creations.set(projectPath, state);
    this.runCreate(state);
    return successResponse({ status: "creating", projectPath });
  }
  async runCreate(state) {
    try {
      await mkdir2(state.projectPath, { recursive: true });
      const detect = this.options.detectPackageManagers ?? detectPackageManagers;
      const preferred = this.options.getPreferredPackageManager ?? getPreferredPackageManager;
      const available = detect().filter((pm) => pm.available);
      if (available.length === 0) {
        throw new AdkError({
          code: "NO_PACKAGE_MANAGER",
          message: "No package manager found. Install npm, pnpm, bun, or yarn.",
          expected: true
        });
      }
      const packageManager = preferred(state.projectPath, available) ?? available[0];
      const generator = this.options.generatorFactory ? this.options.generatorFactory(state.projectPath, packageManager.command, state.template) : new AgentProjectGenerator(state.projectPath, packageManager.command, state.template);
      if (!state.skipScaffolding) {
        state.stage = "scaffolding";
        await generator.generate();
      }
      state.stage = "installing";
      try {
        await (this.options.runCommand ?? defaultRunCommand)(packageManager.installCommand, state.projectPath);
      } catch (error) {
        const stderr = captureExecError(error).slice(0, 2000);
        throw new AdkError({
          code: "DEP_INSTALL_FAILED",
          message: `Failed to install dependencies (${packageManager.installCommand}): ${captureExecError(error)}`,
          details: { stderr }
        });
      }
      state.stage = "dependencies";
      try {
        await installAgent0Capabilities(state.projectPath);
      } catch (error) {
        state.warnings.push(`Failed to create Agent(0) capabilities: ${jsonErrorMessage2(error)}`);
      }
      const externalHarness = await installExternalHarnessCapabilities({
        projectPath: state.projectPath,
        packageManagerCommand: packageManager.command,
        commandTargets: [],
        runCommand: this.options.runCommand ?? defaultRunCommand
      });
      if (!externalHarness.skills.success) {
        state.warnings.push(`Failed to install ADK skills: ${externalHarness.skills.error ?? "Unknown error"}`);
      }
      const declared = generator.getDependencies();
      state.declaredDependencies = declared;
      state.stage = "done";
      state.status = "created";
      state.finishedAt = Date.now();
    } catch (error) {
      state.status = "failed";
      state.error = jsonErrorMessage2(error);
      state.finishedAt = Date.now();
    }
  }
  async handleCreateStatus(req) {
    const url = new URL(req.url);
    const rawPath = url.searchParams.get("path");
    if (!rawPath?.trim()) {
      return errorResponse("Missing path", "Missing required `path` query parameter.", 400);
    }
    const projectPath = resolve3(expandHome2(rawPath.trim()));
    const state = this.creations.get(projectPath);
    if (!state) {
      return successResponse({ status: "unknown" });
    }
    if (state.status !== "creating" && state.finishedAt && Date.now() - state.finishedAt > TERMINAL_STATE_TTL_MS) {
      this.creations.delete(projectPath);
      return successResponse({ status: "unknown" });
    }
    return successResponse({
      status: state.status,
      stage: state.stage,
      projectPath: state.projectPath,
      error: state.error,
      warnings: state.warnings
    });
  }
  async handleCheckPath(req) {
    const url = new URL(req.url);
    const rawPath = url.searchParams.get("path");
    if (!rawPath?.trim()) {
      return errorResponse("Missing path", "Missing required `path` query parameter.", 400);
    }
    const targetPath = resolve3(expandHome2(rawPath.trim()));
    const targetStat = await stat2(targetPath).catch(() => null);
    if (!targetStat || !targetStat.isDirectory()) {
      return successResponse({ status: "empty" });
    }
    const entries = await readdir(targetPath).catch(() => []);
    if (entries.length === 0) {
      return successResponse({ status: "empty" });
    }
    const hasAgentConfig = entries.includes("agent.config.ts");
    if (hasAgentConfig) {
      return successResponse({ status: "adk-project" });
    }
    return successResponse({ status: "non-empty", fileCount: entries.length });
  }
  async handleListWorkspaces() {
    const credentials = await this.getCredentialsOrNull();
    if (!credentials)
      return notAuthenticated();
    try {
      const client = this.makeClient(credentials.token, credentials.apiUrl);
      const workspaces = await client.list.workspaces({}).collect();
      return successResponse({
        workspaces: workspaces.map((ws) => ({
          id: ws.id,
          name: ws.name,
          handle: ws.handle,
          plan: ws.plan ?? "Free",
          profilePicture: ws.profilePicture ?? undefined
        }))
      });
    } catch (error) {
      return errorResponse("Failed to list workspaces", jsonErrorMessage2(error), 500);
    }
  }
  async handleListProdBots(workspaceId, purpose) {
    if (!workspaceId.trim()) {
      return errorResponse("Missing workspaceId", "Workspace ID is required.", 400);
    }
    const credentials = await this.getCredentialsOrNull();
    if (!credentials)
      return notAuthenticated();
    try {
      const client = this.makeClient(credentials.token, credentials.apiUrl, workspaceId);
      const bots = await client.list.bots({
        dev: false,
        tags: purpose === "viewable" ? { ...ADK_RUNTIME_BOT_TAGS, ...ADK_MANIFEST_BOT_TAGS } : ADK_RUNTIME_BOT_TAGS
      }).collect();
      return successResponse({
        bots: bots.map((bot) => ({
          id: bot.id,
          name: bot.name,
          createdAt: bot.createdAt
        }))
      });
    } catch (error) {
      return errorResponse("Failed to list bots", jsonErrorMessage2(error), 500);
    }
  }
  async handleLink(req) {
    let body;
    try {
      body = await req.json();
    } catch {
      return errorResponse("Invalid body", "Expected a JSON body with projectPath and workspaceId.", 400);
    }
    if (typeof body.projectPath !== "string" || !body.projectPath.trim()) {
      return errorResponse("Missing projectPath", "Missing required `projectPath` field.", 400);
    }
    if (typeof body.workspaceId !== "string" || !body.workspaceId.trim()) {
      return errorResponse("Missing workspaceId", "Missing required `workspaceId` field.", 400);
    }
    const wantsNewBot = typeof body.newBotName === "string" && body.newBotName.trim().length > 0;
    if (!wantsNewBot && (typeof body.botId !== "string" || !body.botId.trim())) {
      return errorResponse("Missing bot selection", "Provide either `botId` or `newBotName`.", 400);
    }
    const credentials = await this.getCredentialsOrNull();
    if (!credentials)
      return notAuthenticated();
    const workspaceId = body.workspaceId.trim();
    const projectPath = resolve3(expandHome2(body.projectPath.trim()));
    const projectStat = await stat2(projectPath).catch(() => null);
    if (!projectStat || !projectStat.isDirectory()) {
      return errorResponse("Invalid project", `Project directory does not exist: ${projectPath}`, 400);
    }
    let client = null;
    let createdBotId = null;
    let createdBotName;
    try {
      client = this.makeClient(credentials.token, credentials.apiUrl, workspaceId);
      let botId;
      let botName;
      if (wantsNewBot) {
        const { bot } = await client.createBot({
          name: body.newBotName.trim(),
          tags: { runtime: "adk" }
        });
        botId = bot.id;
        botName = bot.name;
        createdBotId = bot.id;
        createdBotName = bot.name;
      } else {
        botId = body.botId.trim();
        try {
          const { bot } = await client.getBot({ id: botId });
          botName = bot.name;
        } catch {}
      }
      const load = this.options.loadProject ?? AgentProject.load;
      const project = await load(projectPath);
      await project.createAgentInfo({
        botId,
        workspaceId,
        apiUrl: credentials.apiUrl
      });
      if (botName && project.config?.name !== botName) {
        try {
          const writer = new ConfigWriter(project.path);
          await writer.updateName(botName);
        } catch {}
      }
      const linkWarnings = [];
      const createState = this.creations.get(projectPath);
      const declared = createState?.declaredDependencies;
      if (declared && declared.integrations.length + declared.plugins.length > 0) {
        try {
          const linkedProject = await load(projectPath);
          const dmClient = await getProjectClient({
            project: linkedProject,
            credentials: {
              token: credentials.token,
              apiUrl: credentials.apiUrl,
              workspaceId,
              botId
            },
            workspaceId,
            botId
          });
          const dm = await exports_dependencies.DependencyManager.fromProject({
            projectPath,
            env: "dev",
            client: dmClient
          });
          for (const ref of declared.integrations) {
            try {
              const { name, version } = parseRef(ref);
              await dm.add("integration", { name, version });
            } catch (error) {
              linkWarnings.push(`Failed to install integration ${ref}: ${captureExecError(error)}`);
            }
          }
          for (const ref of declared.plugins) {
            try {
              const { name, version } = parseRef(ref);
              await dm.add("plugin", { name, version });
            } catch (error) {
              linkWarnings.push(`Failed to install plugin ${ref}: ${captureExecError(error)}`);
            }
          }
        } catch (error) {
          linkWarnings.push(`Failed to initialize dependency manager: ${captureExecError(error)}`);
        }
        if (createState) {
          createState.warnings.push(...linkWarnings);
        }
      }
      return successResponse({
        status: "linked",
        projectPath,
        botId,
        workspaceId,
        botName,
        ...linkWarnings.length > 0 ? { warnings: linkWarnings } : {}
      });
    } catch (error) {
      if (createdBotId && client) {
        try {
          await client.deleteBot({ id: createdBotId });
        } catch (cleanupError) {
          const label = createdBotName ? `${createdBotName} (${createdBotId})` : createdBotId;
          return errorResponse("Failed to link project", `${jsonErrorMessage2(error)} Created bot ${label} could not be cleaned up: ${jsonErrorMessage2(cleanupError)}`, 500);
        }
      }
      return errorResponse("Failed to link project", jsonErrorMessage2(error), 500);
    }
  }
  async getCredentialsOrNull() {
    try {
      const get = this.options.getCredentials ?? auth.getActiveCredentials.bind(auth);
      return await get();
    } catch {
      return null;
    }
  }
  makeClient(token, apiUrl, workspaceId) {
    if (this.options.clientFactory) {
      return this.options.clientFactory(token, apiUrl, workspaceId);
    }
    return new Uk({
      token,
      apiUrl,
      ...workspaceId ? { workspaceId } : {},
      headers: { "x-multiple-integrations": "true" }
    });
  }
}
function defaultRunCommand(command, cwd) {
  return new Promise((resolve4, reject) => {
    const child = spawn2(command, {
      cwd,
      env: { ...process.env },
      shell: true,
      stdio: ["ignore", "pipe", "pipe"]
    });
    let stdout = "";
    let stderr = "";
    child.stdout?.setEncoding("utf8");
    child.stdout?.on("data", (chunk) => {
      stdout = appendCommandOutput(stdout, chunk);
    });
    child.stderr?.setEncoding("utf8");
    child.stderr?.on("data", (chunk) => {
      stderr = appendCommandOutput(stderr, chunk);
    });
    child.on("error", reject);
    child.on("close", (code, signal) => {
      if (code === 0) {
        resolve4();
        return;
      }
      const reason = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
      const error = new Error(`Command failed with ${reason}`);
      error.stdout = stdout;
      error.stderr = stderr;
      reject(error);
    });
  });
}
function notAuthenticated() {
  return new Response(JSON.stringify({
    error: "not-authenticated",
    message: "Sign in with `adk login` to link projects to Botpress."
  }), { status: 401, headers: { "Content-Type": "application/json" } });
}

// src/server/prod-bot-selection-manager.ts
function prodBotSelectionPath(botId) {
  return `prod:${botId}`;
}

class ProdBotSelectionManager {
  bots = new Map;
  register(entry) {
    const agentPath = prodBotSelectionPath(entry.botId);
    const prodBotSelection = {
      ...entry,
      agentPath,
      registeredAt: Date.now()
    };
    this.bots.set(agentPath, prodBotSelection);
    return prodBotSelection;
  }
  unregister(agentPath) {
    return this.bots.delete(agentPath);
  }
  clearAll() {
    this.bots.clear();
  }
  get(agentPath) {
    return this.bots.get(agentPath);
  }
  list() {
    return Array.from(this.bots.values());
  }
  toAgentInfoList() {
    return this.list().map((bot) => ({
      name: bot.botName,
      agentPath: bot.agentPath,
      backendPort: 0,
      botPort: 0,
      status: "ready",
      connectedAt: bot.registeredAt,
      lastHeartbeat: Date.now(),
      consoleMode: "cloud"
    }));
  }
}

// src/server/ui-server-entry.ts
var logger4 = createCliLogger({ tag: "devconsole-ui" });
function parseArgs(args) {
  let port = 3001;
  let standalone = false;
  for (let i = 0;i < args.length; i++) {
    if (args[i] === "--port" && args[i + 1]) {
      port = parseInt(args[i + 1], 10);
      i++;
    }
    if (args[i] === "--standalone") {
      standalone = true;
    }
  }
  return { port, standalone };
}
function writeConsolePort(port) {
  const dir = dirname2(CONSOLE_PORT_FILE);
  mkdirSync(dir, { recursive: true });
  const tmpFile = join4(dir, `.console.port.${process.pid}.tmp`);
  writeFileSync(tmpFile, String(port), { encoding: "utf-8", mode: 384 });
  renameSync(tmpFile, CONSOLE_PORT_FILE);
}
function removeConsolePort() {
  try {
    unlinkSync2(CONSOLE_PORT_FILE);
  } catch {}
}
function removeConsoleLock() {
  try {
    unlinkSync2(CONSOLE_LOCK_FILE);
  } catch {}
}
var AGENT_SCOPED_PROXY_PREFIX = "/api/agent-proxy/";
function parseAgentScopedProxyPath(pathname) {
  if (!pathname.startsWith(AGENT_SCOPED_PROXY_PREFIX))
    return null;
  const rest = pathname.slice(AGENT_SCOPED_PROXY_PREFIX.length);
  const slashIndex = rest.indexOf("/");
  if (slashIndex <= 0) {
    return {
      error: "Agent-scoped proxy path must include an encoded agent path and backend path."
    };
  }
  let agentPath;
  try {
    agentPath = decodeURIComponent(rest.slice(0, slashIndex));
  } catch {
    return {
      error: "Agent-scoped proxy path contains an invalid encoded agent path."
    };
  }
  const backendPathname = `/${rest.slice(slashIndex + 1)}`;
  if (!backendPathname.startsWith("/api/") && backendPathname !== "/mcp") {
    return { error: "Agent-scoped proxy path can only target /api/* or /mcp." };
  }
  return { agentPath, backendPathname };
}
async function runUiServerEntry(args = process.argv.slice(2)) {
  const { port: startPort, standalone } = parseArgs(args);
  mkdirSync(dirname2(CONSOLE_SOCKET_PATH), { recursive: true });
  const socketServer = new ConsoleSocketServer({
    socketPath: CONSOLE_SOCKET_PATH,
    standalone,
    expectedRuntimeVersion: EXPECTED_RUNTIME_VERSION,
    onEmpty: () => {
      shutdown();
    }
  });
  await socketServer.start();
  const resolvedPort = await findAvailablePort(startPort);
  const projectSupervisor = new ProjectSupervisor({
    consolePort: resolvedPort,
    getAgents: () => socketServer.getAgents(),
    requestAgentShutdown: (agentPath) => socketServer.requestAgentShutdown(agentPath)
  });
  await ensureTemplatesAvailable();
  const projectCreator = new ProjectCreator({
    stopRunningProject: async (projectPath) => {
      const result = await projectSupervisor.stopProjectAt(projectPath);
      if (result.status !== "not-running") {
        logger4.info(`Stopped dev session at ${projectPath} before overwrite (${result.status})`, {
          event: "project-overwrite-stop",
          projectPath,
          result: result.status
        });
      }
    }
  });
  const prodBotSelectionManager = new ProdBotSelectionManager;
  const stopLagMonitor = startEventLoopLagMonitor("ui server");
  const uiDistPath = getUIDistPath();
  const httpServer = serve({
    port: resolvedPort,
    idleTimeout: 60,
    async fetch(req) {
      const url = new URL(req.url);
      if (req.method === "OPTIONS") {
        return handleCorsPreflightResponse(req);
      }
      if (url.pathname === "/api/agents") {
        return withRequestTiming("GET /api/agents", () => {
          const agents = [...socketServer.getAgents(), ...prodBotSelectionManager.toAgentInfoList()];
          return withCors(req, new Response(JSON.stringify({ agents }), {
            headers: {
              "Content-Type": "application/json",
              [DEVCONSOLE_ID_HEADER]: "1"
            }
          }));
        });
      }
      if (url.pathname === "/api/processes") {
        const localAgents = socketServer.getAgents();
        const consoleInfo = {
          pid: process.pid,
          port: resolvedPort,
          url: `http://localhost:${resolvedPort}`,
          mode: standalone ? "standalone" : "managed"
        };
        return withCors(req, new Response(JSON.stringify({ console: consoleInfo, agents: localAgents }), {
          headers: {
            "Content-Type": "application/json",
            [DEVCONSOLE_ID_HEADER]: "1"
          }
        }));
      }
      if (url.pathname === "/api/cloud-console/select-prod-bot" && req.method === "POST") {
        return withCors(req, await handleProdBotSelect(req, prodBotSelectionManager));
      }
      if (url.pathname === "/api/cloud-console/clear" && req.method === "POST") {
        const body = await req.json().catch(() => null);
        const agentPath = typeof body?.agentPath === "string" ? body.agentPath : null;
        if (!agentPath) {
          return withCors(req, new Response(JSON.stringify({
            error: "Missing fields",
            message: "agentPath is required."
          }), {
            status: 400,
            headers: { "Content-Type": "application/json" }
          }));
        }
        prodBotSelectionManager.unregister(agentPath);
        return withCors(req, new Response(JSON.stringify({ status: "cleared" }), {
          status: 200,
          headers: { "Content-Type": "application/json" }
        }));
      }
      if (url.pathname === "/api/console/shutdown" && req.method === "POST") {
        setTimeout(() => void shutdown(), 0);
        return withCors(req, new Response(JSON.stringify({ status: "shutdown-requested" }), {
          status: 200,
          headers: {
            "Content-Type": "application/json",
            [DEVCONSOLE_ID_HEADER]: "1"
          }
        }));
      }
      const projectResponse = await projectSupervisor.handle(url.pathname, req);
      if (projectResponse) {
        return withCors(req, projectResponse);
      }
      const createResponse = await projectCreator.handle(url.pathname, req);
      if (createResponse) {
        return withCors(req, createResponse);
      }
      const authResponse = await handleAuthRequest(url.pathname, req, {
        onLogout: () => prodBotSelectionManager.clearAll()
      });
      if (authResponse) {
        return withCors(req, authResponse);
      }
      const scopedProxy = parseAgentScopedProxyPath(url.pathname);
      if (scopedProxy) {
        if ("error" in scopedProxy) {
          return withCors(req, new Response(JSON.stringify({
            error: "Invalid agent proxy path",
            message: scopedProxy.error
          }), {
            status: 400,
            headers: { "Content-Type": "application/json" }
          }));
        }
        const allAgents = [...socketServer.getAgents(), ...prodBotSelectionManager.toAgentInfoList()];
        const agent = allAgents.find((a) => a.agentPath === scopedProxy.agentPath);
        if (!agent) {
          return withCors(req, agentResolutionError({
            kind: "unknown-agent",
            requested: scopedProxy.agentPath,
            agents: allAgents
          }));
        }
        if (agent.consoleMode === "cloud") {
          const prodBotSelection = prodBotSelectionManager.get(agent.agentPath);
          const backendUrl = new URL(scopedProxy.backendPathname, url.origin);
          backendUrl.search = url.search;
          return withCors(req, await handleProdBotApiRequest(backendUrl, req, prodBotSelection));
        }
        return proxyToBackend(req, agent.backendPort, {
          pathname: scopedProxy.backendPathname
        });
      }
      if (url.pathname === "/api/feature-flags") {
        return withCors(req, await handleFeatureFlags());
      }
      if (url.pathname.startsWith("/api/") || url.pathname === "/mcp") {
        const allAgents = [...socketServer.getAgents(), ...prodBotSelectionManager.toAgentInfoList()];
        const result = resolveTargetAgent(url, req, allAgents);
        if (result.kind === "found") {
          if (result.agent.consoleMode === "cloud") {
            const prodBotSelection = prodBotSelectionManager.get(result.agent.agentPath);
            return withCors(req, await handleProdBotApiRequest(url, req, prodBotSelection));
          }
          return proxyToBackend(req, result.agent.backendPort);
        }
        return withCors(req, agentResolutionError(result));
      }
      return serveStaticFile(url.pathname, uiDistPath, req);
    }
  });
  writeConsolePort(resolvedPort);
  logger4.info(`Listening on http://localhost:${resolvedPort}`);
  logger4.info(`Socket at ${CONSOLE_SOCKET_PATH}`);
  logger4.info(`Mode: ${standalone ? "standalone" : "managed"}`);
  let shutdownStarted = false;
  async function shutdown() {
    if (shutdownStarted)
      return;
    shutdownStarted = true;
    logger4.info("Shutting down...");
    await projectSupervisor.shutdownManagedProjects().catch(() => projectSupervisor.flushManagedLedger());
    removeConsolePort();
    removeConsoleLock();
    stopLagMonitor();
    socketServer.stop();
    httpServer.stop(true);
    process.exit(0);
  }
  process.on("SIGINT", () => void shutdown());
  process.on("SIGTERM", () => void shutdown());
  await new Promise(() => {});
}
function withCors(req, res) {
  const headers = getCorsHeaders(req);
  for (const [k, v] of Object.entries(headers)) {
    res.headers.set(k, v);
  }
  return res;
}
async function handleProdBotSelect(req, manager) {
  let body;
  try {
    body = await req.json();
  } catch {
    return new Response(JSON.stringify({ error: "Invalid body" }), {
      status: 400,
      headers: { "Content-Type": "application/json" }
    });
  }
  if (!body.workspaceId || !body.botId || !body.botName) {
    return new Response(JSON.stringify({
      error: "Missing fields",
      message: "workspaceId, botId, and botName are required."
    }), { status: 400, headers: { "Content-Type": "application/json" } });
  }
  let credentials = null;
  try {
    credentials = await auth.getActiveCredentials();
  } catch (err) {
    logger4.debug(`active credentials lookup failed: ${err instanceof Error ? err.message : String(err)}`);
  }
  if (!credentials) {
    return new Response(JSON.stringify({
      error: "Not authenticated",
      message: "Sign in to select prod bots from Botpress Cloud."
    }), {
      status: 401,
      headers: { "Content-Type": "application/json" }
    });
  }
  const entry = manager.register({
    botId: body.botId,
    botName: body.botName,
    workspaceId: body.workspaceId,
    token: credentials.token,
    apiUrl: credentials.apiUrl
  });
  return new Response(JSON.stringify({ status: "selected", agentPath: entry.agentPath }), {
    status: 200,
    headers: { "Content-Type": "application/json" }
  });
}
if (false) {}
export {
  runUiServerEntry
};