UNPKG

@botpress/adk-cli

Version:

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

2,482 lines 88.5 kB
// @bun
import {
  resolveLoginStatus
} from "./chunk-068pxbk1.js";
import {
  Agent0CognitiveCatalogSource,
  Agent0ConfigStore,
  Agent0OpenCodeStartupTimeoutError,
  Agent0RuntimeManager,
  broadcastComponentChanged,
  broadcastLog,
  closeLogSink,
  ensureAgent0ProjectDirs,
  getLogStore,
  initLogSink,
  startAgent0RuntimeClient,
  startBackendServer,
  startSpanIngestServer,
  stopBackendServer,
  stopSpanIngestServer
} from "./chunk-d6yjgr34.js";
import {
  shutdownDevConsole
} from "./chunk-py8vpzzs.js";
import"./chunk-ssjw4jwy.js";
import"./chunk-2re833ev.js";
import"./chunk-sky56mbb.js";
import {
  buildAndUploadComponents,
  refreshDependencySnapshotOnce
} from "./chunk-xkcrb68a.js";
import"./chunk-ttvctaf1.js";
import {
  assertValidProject
} from "./chunk-wmec2w0t.js";
import {
  open_default
} from "./chunk-fr1k79kd.js";
import"./chunk-5agyx08n.js";
import"./chunk-tefbm840.js";
import"./chunk-bexyahs9.js";
import"./chunk-7sfagm12.js";
import {
  createDeregisterMessage,
  createHeartbeatMessage,
  createLineParser,
  createRegisterMessage,
  encode,
  findAvailablePort
} from "./chunk-x0t4wpfv.js";
import {
  emitProjectReloaded,
  getDevBotRuntimeState,
  getDevCommandStatus,
  getServerConfig,
  onSecretsValuesChanged,
  resetDevBotRuntimeState,
  setAgent0RuntimeClient,
  setDevBotRuntimeState,
  setDevCommandStatus,
  setServerConfig,
  setServerStartTime,
  updateWorkerStats
} from "./chunk-59ayvmxs.js";
import"./chunk-68mqsf42.js";
import"./chunk-ndxsgd72.js";
import"./chunk-r72adjnh.js";
import {
  telemetry_default
} from "./chunk-kwmsaz7n.js";
import {
  getAdkVersion,
  package_default
} from "./chunk-26vqkz52.js";
import {
  resolveCommandContext
} from "./chunk-3ahwp6fe.js";
import {
  ADK_LOGO_LINE1,
  ADK_LOGO_LINE2
} from "./chunk-9e2nksab.js";
import {
  box,
  fg,
  getActiveTheme,
  link,
  mount,
  onKey,
  scrollBox,
  t,
  text,
  timeout
} from "./chunk-m2h26j5f.js";
import"./chunk-8gqzjqmb.js";
import {
  CONSOLE_SOCKET_PATH,
  ensureUiServer
} from "./chunk-sgj6770p.js";
import {
  getInstalledRuntimeVersion
} from "./chunk-ty7sdgd4.js";
import"./chunk-nbasj5jm.js";
import"./chunk-kk3h6qaj.js";
import {
  configureGlobalLogging,
  createCliLogger
} from "./chunk-gzwt1qdr.js";
import"./chunk-nxy2ya5r.js";
import"./chunk-wzj4dc7n.js";
import {
  AdkError,
  AgentProject,
  BP_BUNDLE_SUFFIXES,
  BP_TSX_SUFFIX,
  BpDevCommand,
  COMPONENTS_DIR,
  ConfigManager,
  DevIdManager,
  FileWatcher2,
  KnowledgeManager,
  PreflightChecker,
  ProjectState,
  REQUEST_HANDLING_ERROR_MARKER,
  SecretsManager,
  TableManager,
  ValidationErrorCode,
  auth,
  classifyRecoverableDeployError,
  generateAssetsRuntime,
  generateAssetsTypes,
  generateBotProject,
  getProjectClient,
  toTableSyncFailureDetails
} from "./chunk-p0hjqn4r.js";
import"./chunk-np5wcwfv.js";
import {
  LOG_DELIMITER
} from "./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"./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 {
  __require
} from "./chunk-dhs2bg35.js";

// src/commands/adk-dev.ts
import { createWriteStream, mkdirSync } from "fs";
import path from "path";

// src/components/dev-console-panel.ts
import { spawn } from "child_process";
var MAX_PANEL_LOG_LINES = 100;
function formatLogTime(ts) {
  const date = typeof ts === "string" ? new Date(ts) : new Date;
  const valid = !Number.isNaN(date.getTime()) ? date : new Date;
  const pad = (n) => String(n).padStart(2, "0");
  return `${pad(valid.getHours())}:${pad(valid.getMinutes())}:${pad(valid.getSeconds())}`;
}
function createPanelStore(initial) {
  let snapshot = initial;
  let nextLogId = 0;
  const listeners = new Set;
  const notify = () => {
    for (const listener of listeners)
      listener();
  };
  return {
    get: () => snapshot,
    subscribe: (listener) => {
      listeners.add(listener);
      return () => {
        listeners.delete(listener);
      };
    },
    update: (partial) => {
      snapshot = { ...snapshot, ...partial };
      notify();
    },
    appendLog: ({ level, message, ts }) => {
      const oneLine = message.replace(/\s+/g, " ").trim();
      if (!oneLine)
        return;
      const line = { id: nextLogId++, level, time: formatLogTime(ts), message: oneLine };
      const logs = snapshot.logs;
      const next = logs.length >= MAX_PANEL_LOG_LINES ? [...logs.slice(logs.length - MAX_PANEL_LOG_LINES + 1), line] : [...logs, line];
      snapshot = { ...snapshot, logs: next };
      notify();
    }
  };
}
function createPanelLogTransport(store, opts = {}) {
  const recordFatal = (message, details) => {
    store.update({
      state: "error",
      fatalError: {
        message: String(message),
        details: details === undefined ? undefined : typeof details === "string" ? details : JSON.stringify(details)
      }
    });
  };
  return (record) => {
    if (record.level === "error" && record.fatal === true) {
      recordFatal(record.message, record.details);
      opts.onFatal?.(record);
      return;
    }
    switch (record.event) {
      case "workspace":
        store.update({
          workspace: {
            id: String(record.id ?? ""),
            name: record.name,
            handle: record.handle,
            plan: record.plan
          }
        });
        return;
      case "dev-id":
        if (record.devId)
          store.update({ devId: String(record.devId) });
        return;
      case "config-validation": {
        const issues = record.issues ?? [];
        store.update({
          missingConfigKeys: issues.filter((issue) => !issue.error).map((issue) => issue.key),
          invalidConfigKeys: issues.filter((issue) => issue.error).map((issue) => issue.key)
        });
        return;
      }
      case "server":
        if (record.status === "ready" && record.url)
          store.update({ devConsoleUrl: String(record.url) });
        return;
      case "ready":
        store.update({
          state: "ready",
          botUrl: record.botUrl ? String(record.botUrl) : store.get().botUrl
        });
        return;
    }
    if (record.level === "error") {
      opts.onError?.(record);
    }
    store.appendLog({ level: record.level, message: record.message, ts: record.ts });
  };
}
function renderLogRow(ctx, theme, line) {
  const messageColor = line.level === "error" ? theme.status.error : line.level === "warn" ? theme.status.warning : theme.text.secondary;
  const gutter = `${line.time} `;
  const row = text(ctx, t`${fg(theme.text.dim)(gutter)}${fg(messageColor)(line.message)}`, { wrapMode: "word" });
  row.selectable = true;
  return row;
}
function copyViaClipboardTool(text2) {
  const argv = process.platform === "darwin" ? ["pbcopy"] : process.platform === "win32" ? ["clip"] : ["xclip", "-selection", "clipboard"];
  try {
    const proc = spawn(argv[0], argv.slice(1), { stdio: ["pipe", "ignore", "ignore"] });
    proc.on("error", () => {});
    proc.stdin?.end(text2);
  } catch {}
}
function portOf(url) {
  if (!url)
    return;
  const m = url.match(/:(\d+)(?:\/|$)/);
  return m ? m[1] : undefined;
}
function buildFooterRows(ctx, theme, snap, contentWidth, flash = null) {
  const consoleReady = snap.state === "ready";
  const gap = (leftLen, rightLen) => {
    const pad = contentWidth - leftLen - rightLen;
    return pad >= 2 ? " ".repeat(pad) : null;
  };
  const devSeg = snap.devId ? `   ${snap.devId}` : "";
  const leftPlain1 = snap.agentName + devSeg;
  const wsPlain = snap.workspace?.name ? `${snap.workspace.name}${snap.workspace.plan ? ` ${snap.workspace.plan.toUpperCase()}` : ""}` : "";
  const pad1 = wsPlain ? gap(leftPlain1.length, wsPlain.length) : null;
  const wsChunk = pad1 ? fg(theme.text.dim)(pad1 + wsPlain) : "";
  const line1 = t`${fg(theme.text.primary)(snap.agentName)}${fg(theme.text.secondary)(devSeg)}${wsChunk}`;
  const dcPort = portOf(snap.devConsoleUrl);
  const botPort = portOf(snap.botUrl);
  const dotColor = consoleReady ? theme.status.success : theme.status.warning;
  const showBot = consoleReady && Boolean(botPort) && Boolean(snap.botUrl);
  const consolePlain = dcPort ? `${theme.symbols.dot} Dev Console::${dcPort}${showBot ? ` | Bot::${botPort}` : ""}` : `${theme.symbols.dot} Dev Console starting\u2026`;
  const verPlain = `v${snap.version}`;
  const pad2 = gap(consolePlain.length, verPlain.length);
  const ver = pad2 ? fg(theme.text.dim)(pad2 + verPlain) : "";
  let line2;
  if (dcPort && snap.devConsoleUrl) {
    const dc = fg(dotColor)(link(snap.devConsoleUrl)(`Dev Console::${dcPort}`));
    if (showBot && snap.botUrl) {
      const bot = fg(dotColor)(link(snap.botUrl)(`Bot::${botPort}`));
      line2 = t`${fg(dotColor)(`${theme.symbols.dot} `)}${dc}${fg(theme.text.dim)(" | ")}${bot}${ver}`;
    } else {
      line2 = t`${fg(dotColor)(`${theme.symbols.dot} `)}${dc}${ver}`;
    }
  } else {
    line2 = t`${fg(dotColor)(`${theme.symbols.dot} Dev Console starting\u2026`)}${ver}`;
  }
  const kb = (k) => fg(theme.text.primary)(`[${k}]`);
  const lbl = (s) => fg(theme.text.dim)(` ${s}`);
  const errorHint = () => {
    const keysPlain = `   [E] Errors   [Ctrl-C] Exit`;
    const message = snap.fatalError?.message || "Dev session stopped";
    const budget = Math.max(8, contentWidth - keysPlain.length - 2);
    const summary = message.length > budget ? `${message.slice(0, budget - 1)}\u2026` : message;
    return t`${fg(theme.status.error)(`${theme.symbols.cross} ${summary}`)}   ${kb("E")}${lbl("Errors")}   ${kb("Ctrl-C")}${lbl("Exit")}`;
  };
  const hint = snap.state === "stopping" ? t`${fg(theme.status.warning)("Stopping\u2026")}` : flash ? t`${fg(theme.status.success)(`${theme.symbols.checkmark} ${flash}`)}` : snap.state === "error" ? errorHint() : snap.devConsoleUrl ? t`${kb("Space")}${lbl("Open Dev Console")}   ${kb("E")}${lbl("Errors")}   ${kb("Ctrl-C")}${lbl("Stop")}` : t`${kb("E")}${lbl("Errors")}   ${kb("Ctrl-C")}${lbl("Stop")}`;
  return [
    text(ctx, t`${fg(theme.text.dim)("\u2500".repeat(contentWidth))}`, { wrapMode: "none" }),
    text(ctx, line1, { wrapMode: "none" }),
    text(ctx, line2, { wrapMode: "none" }),
    text(ctx, ""),
    text(ctx, hint, { wrapMode: "none" })
  ];
}
function buildExtendedInfoLines(theme, snap) {
  const lines = [];
  if (snap.loginStatus) {
    lines.push({
      key: "login",
      content: t`${fg(theme.status.success)(theme.symbols.checkmark)} ${fg(theme.text.dim)(`Logged in as ${snap.loginStatus}`)}`
    });
  }
  if (snap.devConsoleUrl) {
    lines.push({
      key: "devconsole-url",
      content: t`${fg(theme.text.dim)(`Dev Console ${theme.symbols.arrow} `)}${fg(theme.text.link)(snap.devConsoleUrl)}`
    });
  }
  if (snap.botUrl) {
    lines.push({
      key: "boturl",
      content: t`${fg(theme.text.dim)(`Dev bot ${theme.symbols.arrow} `)}${fg(theme.text.link)(snap.botUrl)}`
    });
  }
  if (snap.missingConfigKeys.length > 0) {
    lines.push({
      key: "missing-config",
      content: t`${fg(theme.status.warning)(`${theme.symbols.warning} Missing config: ${snap.missingConfigKeys.join(", ")} \u2014 set them in the DevConsole`)}`
    });
  }
  if (snap.invalidConfigKeys.length > 0) {
    lines.push({
      key: "invalid-config",
      content: t`${fg(theme.status.warning)(`${theme.symbols.warning} Invalid config: ${snap.invalidConfigKeys.join(", ")}`)}`
    });
  }
  return lines;
}
function devConsolePanel(renderer, scope, props) {
  const { store, onStop } = props;
  const theme = getActiveTheme();
  let stopping = false;
  const contentWidth = () => Math.max(20, (renderer.width || 80) - 2);
  const logPane = scrollBox(renderer, {
    flexGrow: 1,
    flexShrink: 1,
    stickyScroll: true,
    stickyStart: "bottom",
    scrollX: false,
    scrollY: true,
    contentOptions: { flexDirection: "column" }
  });
  const addLine = (content, wrapMode = "none") => {
    const node = text(renderer, content, { wrapMode });
    node.selectable = true;
    return logPane.add(node);
  };
  addLine("");
  addLine(t`${fg(theme.accent.purple)(ADK_LOGO_LINE1)}`);
  addLine(t`${fg(theme.accent.purple)(ADK_LOGO_LINE2)}`);
  addLine(t`${fg(theme.text.primary)("Botpress ADK")} ${fg(theme.text.dim)(`v${store.get().version} \u2022 ${store.get().agentName}`)}`);
  addLine("");
  let copiedFlash = null;
  const bar = box(renderer, { flexDirection: "column", flexShrink: 0, paddingX: 1 });
  const rebuildBar = () => {
    for (const child of [...bar.getChildren()]) {
      bar.remove(child.id);
      child.destroyRecursively();
    }
    for (const row of buildFooterRows(renderer, theme, store.get(), contentWidth(), copiedFlash))
      bar.add(row);
  };
  let lastFlushedId = -1;
  let wroteLogSpacer = false;
  const flushLogs = () => {
    for (const line of store.get().logs) {
      if (line.id <= lastFlushedId)
        continue;
      if (!wroteLogSpacer) {
        wroteLogSpacer = true;
        addLine("");
      }
      lastFlushedId = line.id;
      logPane.add(renderLogRow(renderer, theme, line));
    }
  };
  const emittedInfo = new Set;
  const emitExtendedInfo = () => {
    for (const { key, content } of buildExtendedInfoLines(theme, store.get())) {
      if (emittedInfo.has(key))
        continue;
      emittedInfo.add(key);
      addLine(content);
    }
  };
  const consoleFocused = () => Boolean(renderer.console?.isFocused);
  scope.add(onKey(renderer, (key) => {
    if (key.ctrl && key.name === "c") {
      if (stopping)
        return;
      stopping = true;
      store.update({ state: "stopping" });
      onStop();
      return;
    }
    if (key.name === "e" && !key.ctrl) {
      try {
        renderer.console.toggle();
      } catch {}
      return;
    }
    if (key.name === "space") {
      const url = store.get().devConsoleUrl;
      if (url)
        open_default(url).catch(() => {});
      return;
    }
    if (consoleFocused())
      return;
    logPane.handleKeyPress(key);
  }));
  let clearFlash;
  const onSelection = (sel) => {
    const selected = sel?.getSelectedText?.() ?? "";
    if (!selected)
      return;
    if (renderer.isOsc52Supported())
      renderer.copyToClipboardOSC52(selected);
    copyViaClipboardTool(selected);
    const lines = selected.split(`
`).filter((l) => l.length > 0).length || 1;
    copiedFlash = `Copied ${lines} line${lines === 1 ? "" : "s"}`;
    rebuildBar();
    clearFlash?.();
    clearFlash = timeout(scope, 1800, () => {
      copiedFlash = null;
      rebuildBar();
    });
  };
  renderer.on("selection", onSelection);
  scope.add(() => renderer.off("selection", onSelection));
  rebuildBar();
  emitExtendedInfo();
  flushLogs();
  scope.add(store.subscribe(() => {
    emitExtendedInfo();
    flushLogs();
    rebuildBar();
  }));
  const onResize = () => rebuildBar();
  renderer.on("resize", onResize);
  scope.add(() => renderer.off("resize", onResize));
  return box(renderer, { flexDirection: "column", width: "100%", height: "100%" }, [logPane, bar]);
}

// src/utils/project-lock.ts
import { unlinkSync, readFileSync } from "fs";
import { promises as fsp } from "fs";
import { join } from "path";
var LOCK_FILENAME = "dev.lock";
function readLockPid(lockPath) {
  try {
    const pid = parseInt(readFileSync(lockPath, "utf-8").trim(), 10);
    return Number.isFinite(pid) && pid > 0 ? pid : null;
  } catch {
    return null;
  }
}
function isProcessAlive(pid) {
  try {
    process.kill(pid, 0);
    return true;
  } catch {
    return false;
  }
}
function errorCode(err) {
  return err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : undefined;
}
function alreadyRunningError(lockPath, pid) {
  const pidHint = pid !== null ? ` (PID ${pid})` : "";
  return new AdkError({
    code: "DEV_SESSION_ALREADY_RUNNING",
    message: `Another \`adk dev\` session is already running for this project${pidHint}.
` + `If the session is stuck or already exited, remove the stale lock:
  rm ${lockPath}`,
    expected: true
  });
}
async function acquireProjectLock(agentPath) {
  const lockPath = join(agentPath, ".adk", LOCK_FILENAME);
  await fsp.mkdir(join(agentPath, ".adk"), { recursive: true });
  try {
    await fsp.writeFile(lockPath, String(process.pid), { flag: "wx", encoding: "utf-8", mode: 384 });
  } catch (err) {
    if (errorCode(err) !== "EEXIST")
      throw err;
    const pid = readLockPid(lockPath);
    if (pid !== null && isProcessAlive(pid)) {
      throw alreadyRunningError(lockPath, pid);
    }
    try {
      unlinkSync(lockPath);
    } catch {}
    try {
      await fsp.writeFile(lockPath, String(process.pid), { flag: "wx", encoding: "utf-8", mode: 384 });
    } catch (retryErr) {
      if (errorCode(retryErr) !== "EEXIST")
        throw retryErr;
      throw alreadyRunningError(lockPath, readLockPid(lockPath));
    }
  }
  return () => {
    try {
      unlinkSync(lockPath);
    } catch {}
  };
}

// src/utils/CriticalError.ts
class CriticalError extends Error {
  details;
  stderr;
  constructor(message, details, stderr) {
    super(message);
    this.details = details;
    this.stderr = stderr;
    this.name = "CriticalError";
  }
}

// src/server/agent0-runtime.ts
var DEFAULT_AGENT0_RUNTIME_START_ATTEMPTS = 2;
var DEFAULT_AGENT0_RUNTIME_RETRY_DELAY_MS = 1000;
async function startAgent0DevRuntime(options) {
  const manager = new Agent0RuntimeManager({
    agentPath: options.agentPath,
    adkDevConsolePort: options.adkDevConsolePort,
    startupTimeoutMs: options.startupTimeoutMs,
    onLog: options.onLog,
    configStore: options.configStore ?? new Agent0ConfigStore,
    resolveProjectDirs: options.resolveProjectDirs ?? ensureAgent0ProjectDirs,
    cognitiveSource: createAgent0DevRuntimeCognitiveSource(options),
    startRuntimeClient: options.startRuntimeClient ?? startAgent0RuntimeClient
  });
  await manager.start();
  return manager;
}
async function startAgent0DevRuntimeWithRetry(options, retryOptions = {}) {
  const maxAttempts = retryOptions.attempts ?? DEFAULT_AGENT0_RUNTIME_START_ATTEMPTS;
  const retryDelayMs = retryOptions.retryDelayMs ?? DEFAULT_AGENT0_RUNTIME_RETRY_DELAY_MS;
  const sleep = retryOptions.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
  for (let attempt = 1;; attempt++) {
    try {
      return await startAgent0DevRuntime(options);
    } catch (error) {
      if (attempt >= maxAttempts || !isAgent0OpenCodeStartupTimeout(error)) {
        throw error;
      }
      retryOptions.onRetry?.({ attempt: attempt + 1, maxAttempts, error });
      await sleep(retryDelayMs);
    }
  }
}
function createAgent0DevRuntimeCognitiveSource(options) {
  if (options.cognitiveSource)
    return options.cognitiveSource;
  if (!options.credentials?.token || !options.devBotId)
    return;
  return new Agent0CognitiveCatalogSource({
    auth: {
      token: options.credentials.token,
      botId: options.devBotId,
      apiUrl: options.credentials.apiUrl
    }
  });
}
function isAgent0OpenCodeStartupTimeout(error) {
  const seen = new Set;
  let current = error;
  while (current !== undefined && current !== null && !seen.has(current)) {
    seen.add(current);
    if (current instanceof Agent0OpenCodeStartupTimeoutError || isAgent0OpenCodeStartupTimeoutErrorLike(current)) {
      return true;
    }
    if (typeof current !== "object") {
      break;
    }
    current = "cause" in current ? current.cause : undefined;
  }
  return false;
}
function isAgent0OpenCodeStartupTimeoutErrorLike(error) {
  return typeof error === "object" && error !== null && "code" in error && error.code === "AGENT0_OPENCODE_STARTUP_TIMEOUT";
}
function createUnavailableAgent0RuntimeClient(error) {
  const reason = error instanceof Error ? error : new Error(String(error));
  return {
    async getStatus() {
      throw reason;
    },
    async stop() {}
  };
}

// src/server/socket/client.ts
var {connect } = globalThis.Bun;
var logger = createCliLogger({ tag: "socket-client" });

class ConsoleSocketRegistrationRejectedError extends Error {
  info;
  constructor(info) {
    super([
      `DevConsole rejected this agent registration (${info.reason}).`,
      `Agent runtime: ${info.agentRuntime}.`,
      `Supported runtime major: ${info.expectedRuntimeMajor}.x.`,
      info.upgradeCommand
    ].join(" "));
    this.info = info;
    this.name = "ConsoleSocketRegistrationRejectedError";
  }
}
var DEFAULT_HEARTBEAT_MS = 5000;
var DEFAULT_REGISTRATION_TIMEOUT_MS = 5000;
var RECONNECT_BASE_DELAY_MS = 1000;
var RECONNECT_MAX_DELAY_MS = 1e4;

class ConsoleSocketClient {
  options;
  socket = null;
  heartbeatTimer = null;
  reconnectTimer = null;
  reconnectAttempts = 0;
  status = "starting";
  pendingRegistration = null;
  rejected = false;
  intentionallyDisconnected = false;
  constructor(options) {
    this.options = options;
  }
  async connect() {
    const { socketPath } = this.options;
    this.rejected = false;
    this.intentionallyDisconnected = false;
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    const registration = this.createRegistrationWait();
    try {
      this.socket = await connect({
        unix: socketPath,
        socket: {
          data: (_socket, data) => this.handleData(data),
          open: (socket) => {
            socket.data = {
              parser: createLineParser()
            };
            const msg = createRegisterMessage({
              name: this.options.name,
              agentPath: this.options.agentPath,
              backendPort: this.options.backendPort,
              botPort: this.options.botPort,
              runtimeVersion: getInstalledRuntimeVersion(this.options.agentPath) ?? undefined,
              cliVersion: package_default.version,
              process: this.options.getProcessInfo?.()
            });
            socket.write(encode(msg));
          },
          close: () => {
            this.failRegistration(new Error("DevConsole socket closed before registration completed"));
            this.cleanup();
            if (this.rejected || this.intentionallyDisconnected) {
              return;
            }
            this.options.onDisconnect?.();
            this.scheduleReconnect();
          },
          error: (_socket, error) => {
            logger.error(`Connection error: ${error.message}`);
          },
          connectError: (_socket, error) => {
            logger.error(`Failed to connect: ${error.message}`);
            this.failRegistration(error);
            if (!this.intentionallyDisconnected) {
              this.scheduleReconnect();
            }
          }
        }
      });
    } catch (err) {
      this.failRegistration(err instanceof Error ? err : new Error(String(err)));
      registration.catch(() => {});
      if (!this.intentionallyDisconnected) {
        this.scheduleReconnect();
      }
      throw err;
    }
    await registration;
  }
  setStatus(status) {
    const changed = this.status !== status;
    this.status = status;
    if (changed)
      this.sendHeartbeat();
  }
  async disconnect() {
    this.intentionallyDisconnected = true;
    this.failRegistration(new Error("DevConsole registration cancelled"));
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    if (this.socket) {
      const msg = createDeregisterMessage(this.options.agentPath);
      try {
        this.socket.end(encode(msg));
      } catch {}
    }
    this.cleanup();
  }
  scheduleReconnect() {
    if (this.reconnectTimer)
      return;
    if (this.rejected || this.intentionallyDisconnected)
      return;
    const delayMs = Math.min(RECONNECT_BASE_DELAY_MS * 2 ** this.reconnectAttempts, RECONNECT_MAX_DELAY_MS);
    this.reconnectAttempts++;
    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = null;
      this.connect().catch(() => {});
    }, delayMs);
  }
  createRegistrationWait() {
    this.failRegistration(new Error("DevConsole registration superseded"));
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        this.failRegistration(new Error("DevConsole registration timed out"));
        try {
          this.socket?.end();
        } catch {}
      }, DEFAULT_REGISTRATION_TIMEOUT_MS);
      this.pendingRegistration = {
        resolve: () => {
          clearTimeout(timer);
          this.pendingRegistration = null;
          resolve();
        },
        reject: (error) => {
          clearTimeout(timer);
          this.pendingRegistration = null;
          reject(error);
        },
        timer
      };
    });
  }
  finishRegistration() {
    if (!this.pendingRegistration)
      return;
    const interval = this.options.heartbeatInterval ?? DEFAULT_HEARTBEAT_MS;
    this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), interval);
    this.sendHeartbeat();
    this.reconnectAttempts = 0;
    this.pendingRegistration.resolve();
  }
  failRegistration(error) {
    this.pendingRegistration?.reject(error);
  }
  sendHeartbeat() {
    if (!this.socket)
      return;
    const msg = createHeartbeatMessage(this.options.agentPath, this.status, this.options.getProcessInfo?.());
    try {
      this.socket.write(encode(msg));
    } catch {}
  }
  handleData(data) {
    if (!this.socket)
      return;
    const messages = this.socket.data.parser.feed(data.toString());
    for (const msg of messages) {
      switch (msg.type) {
        case "registered":
          this.finishRegistration();
          this.options.onAgentsUpdated?.(msg.agents);
          break;
        case "agents_updated":
          this.options.onAgentsUpdated?.(msg.agents);
          break;
        case "shutdown":
          if (msg.agentPath !== this.options.agentPath)
            break;
          this.intentionallyDisconnected = true;
          if (this.reconnectTimer) {
            clearTimeout(this.reconnectTimer);
            this.reconnectTimer = null;
          }
          this.options.onShutdown?.();
          break;
        case "rejected":
          this.rejected = true;
          const info = {
            reason: msg.reason,
            agentRuntime: msg.agentRuntime,
            expectedRuntimeMajor: msg.expectedRuntimeMajor,
            upgradeCommand: msg.upgradeCommand
          };
          this.options.onRejected?.(info);
          this.failRegistration(new ConsoleSocketRegistrationRejectedError(info));
          break;
      }
    }
  }
  cleanup() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
    this.socket = null;
  }
}

// src/utils/component-change-classifier.ts
var COMPONENTS_PREFIX = `${COMPONENTS_DIR}/`;
var INDEX_PATH = `${COMPONENTS_DIR}/index.ts`;
function classifyComponentPath(relPath) {
  const posix = relPath.split(/[\\/]+/).join("/");
  if (!posix.startsWith(COMPONENTS_PREFIX))
    return null;
  if (posix === INDEX_PATH)
    return { kind: "list" };
  for (const suffix of BP_BUNDLE_SUFFIXES) {
    if (posix.endsWith(suffix)) {
      return { kind: "bundle", source: posix.slice(0, -suffix.length) + BP_TSX_SUFFIX };
    }
  }
  return { kind: "unknown" };
}

// src/commands/adk-dev-events.ts
function normalizeLogLevel(level) {
  switch ((level || "").toLowerCase()) {
    case "stderr":
    case "error":
      return "error";
    case "warn":
    case "warning":
      return "warn";
    default:
      return "info";
  }
}
function getRuntimeLogMessage(entry) {
  if (entry.message) {
    return entry.message;
  }
  if (Array.isArray(entry.args)) {
    return entry.args.map((value) => {
      if (typeof value === "string") {
        return value;
      }
      try {
        return JSON.stringify(value);
      } catch {
        return String(value);
      }
    }).join(" ").trim();
  }
  return "";
}
var LISTENING_ON_PORT = /listening\s+on\s+port\s+(\d+)/i;
function extractListeningPort(rawLine) {
  if (!rawLine.trim()) {
    return null;
  }
  let message = rawLine;
  try {
    message = getRuntimeLogMessage(JSON.parse(rawLine)) || rawLine;
  } catch {}
  const match = message.match(LISTENING_ON_PORT);
  return match && match[1] ? parseInt(match[1], 10) : null;
}
function shouldFilterRuntimeLog(message) {
  if (!message) {
    return true;
  }
  return [
    "ExperimentalWarning",
    "punycode",
    "Trace endpoints:",
    "ADK_SPAN_INGEST_URL=",
    "OTEL_EXPORTER_OTLP_ENDPOINT="
  ].some((token) => message.includes(token));
}
function logRuntimeLine(logger2, rawLine) {
  if (!rawLine.trim()) {
    return;
  }
  try {
    const parsed = JSON.parse(rawLine);
    if (parsed.type === "worker_stats") {
      return;
    }
    const message = getRuntimeLogMessage(parsed);
    if (shouldFilterRuntimeLog(message)) {
      return;
    }
    logger2.log(normalizeLogLevel(parsed.type || parsed.level), message, {
      event: "log",
      stack: parsed.stack,
      spanId: parsed.spanId,
      traceId: parsed.traceId
    });
  } catch {
    if (shouldFilterRuntimeLog(rawLine)) {
      return;
    }
    logger2.info(rawLine, { event: "log" });
  }
}
function classifyStderrLine(text2) {
  if (text2.startsWith("\u2716"))
    return "error";
  const lower = text2.toLowerCase();
  if (lower.includes("error:") || lower.includes("failed"))
    return "error";
  if (text2.startsWith("\u26A0"))
    return "warning";
  if (lower.includes("warning:") || lower.includes("warn:"))
    return "warning";
  return "info";
}
function normalizeStructuredLogLine(line) {
  try {
    JSON.parse(line);
    return line;
  } catch {
    return JSON.stringify({
      timestamp: new Date().toISOString(),
      type: classifyStderrLine(line),
      args: [line]
    });
  }
}
function deliverRuntimeLog(logger2, rawLine, sink) {
  const normalized = normalizeStructuredLogLine(rawLine);
  sink.appendLine?.(`${normalized}
`);
  if (sink.broadcast) {
    try {
      sink.broadcast(JSON.parse(normalized));
    } catch {}
  }
  logRuntimeLine(logger2, normalized);
}
function logRequestHandlingError(logger2, rawLine, phase) {
  const markerMatch = rawLine.match(REQUEST_HANDLING_ERROR_MARKER);
  if (!markerMatch) {
    return false;
  }
  const afterMarker = rawLine.slice((markerMatch.index ?? 0) + markerMatch[0].length).trim();
  const enrichment = afterMarker.replace(/^[:\s-]+/, "");
  const reqMatch = enrichment.match(/^([A-Z]+)\s+(\/\S*?)(?::|\s|$)/);
  const method = reqMatch?.[1];
  const path = reqMatch?.[2];
  const afterReqMatch = reqMatch ? enrichment.slice(reqMatch[0].length) : enrichment;
  const cause = afterReqMatch.replace(/^[:\s]+/, "").trim() || undefined;
  const reqDesc = method && path ? ` (${method} ${path})` : "";
  const message = phase === "building" ? `A request reached the dev bot while it was rebuilding/restarting${reqDesc} and failed; it will succeed on retry.` : `The dev bot failed to handle a request${reqDesc}.`;
  logger2.error(message, { event: "request", fatal: false, details: { method, path, phase, cause } });
  return true;
}
function logBpDevProgress(logger2, event) {
  switch (event.type) {
    case "generating":
      if (event.endTime === undefined) {
        logger2.info("Generating typings", { event: "build", status: "bundling" });
      }
      return;
    case "bundling":
      if (event.endTime === undefined) {
        logger2.info("Bundling bot", { event: "build", status: "bundling" });
      } else {
        logger2.info("Bundled bot", {
          event: "build",
          status: "ready",
          duration: event.endTime - event.startTime,
          ...event.data?.buildTime ? { esbuild: event.data.buildTime } : {}
        });
      }
      return;
    case "deploying":
      if (event.endTime === undefined) {
        logger2.info("Deploying dev bot", { event: "deploy", status: "deploying" });
      } else {
        logger2.info("Dev bot deployed", {
          event: "deploy",
          status: "deployed",
          duration: event.endTime - event.startTime,
          botId: event.data?.botId,
          tunnelUrl: event.data?.tunnelUrl
        });
      }
      return;
    case "listening":
      logger2.info(`Dev bot listening on http://localhost:${event.data.port}`, { event: "runtime" });
      return;
    default:
      return;
  }
}
function formatTableSyncSummary(result) {
  const parts = [];
  if (result.summary.created > 0)
    parts.push(`${result.summary.created} created`);
  if (result.summary.updated > 0)
    parts.push(`${result.summary.updated} updated`);
  if (result.summary.deleted > 0)
    parts.push(`${result.summary.deleted} deleted`);
  if (result.summary.skipped > 0)
    parts.push(`${result.summary.skipped} retained (not deleted in dev)`);
  return parts.join(", ") || "no changes";
}
function formatAssetSyncSummary(result) {
  return `${result.summary.created} created, ${result.summary.updated} updated, ${result.summary.deleted} deleted`;
}
function formatKnowledgeBaseAutoApproveMessage(result) {
  const syncedCount = result.synced.length;
  const workflowCount = result.websiteSyncs?.length ?? 0;
  const suffix = workflowCount > 0 ? `; ${workflowCount} website crawl workflow(s) triggered` : "";
  return `Auto-approved: ${syncedCount} knowledge base${syncedCount === 1 ? "" : "s"} synced${suffix}`;
}
async function applyPlanSyncs(plan, opts, callbacks) {
  if (plan.tablePlan?.hasChanges && plan.managers.table) {
    try {
      const result = await plan.managers.table.executeSync(plan.tablePlan, { confirmDestructive: true });
      callbacks.onTablesSynced(result);
    } catch (error) {
      callbacks.onTableSyncError(error);
    }
  } else if (plan.tablePlan) {
    callbacks.onTablesUpToDate();
  }
  if (plan.orphanedKBs.length > 0 && plan.managers.kb) {
    try {
      for (const kb of plan.orphanedKBs) {
        await plan.managers.kb.deleteKnowledgeBase(kb.id, kb.name);
      }
      callbacks.onOrphanedKBsDeleted(plan.orphanedKBs.length);
    } catch (error) {
      callbacks.onOrphanedKBDeleteError(error);
    }
  }
  if (plan.kbPlan?.hasChanges && plan.managers.kb) {
    await callbacks.onKbPlanReady(plan.kbPlan, plan.managers.kb);
  } else if (opts.hasKnowledge) {
    callbacks.onKbsUpToDate();
  }
  if (plan.assetPlan?.hasChanges && plan.managers.assets) {
    try {
      const result = await plan.managers.assets.executeSync(plan.assetPlan, { confirmDestructive: true });
      callbacks.onAssetsSynced(result);
    } catch (error) {
      callbacks.onAssetSyncError(error);
    }
  } else if (opts.hasAssets) {
    callbacks.onAssetsUpToDate();
  }
}

// src/utils/fleet-dev-heartbeat.ts
import { randomUUID } from "crypto";
var FLEET_DEV_HEARTBEAT_INTERVAL_MS = 30000;
var FLEET_DEV_BOT_TAGS = {
  enabled: "fleetdev",
  session: "fleetdevsession",
  lastSeen: "fleetdevlastseen"
};
async function markFleetDevHeartbeat(input) {
  const { bot } = await input.client.getBot({ id: input.botId });
  const currentSessionId = bot.tags?.[FLEET_DEV_BOT_TAGS.session];
  if (input.replaceSession === false && currentSessionId !== input.sessionId) {
    return false;
  }
  const tags = {
    ...bot.tags ?? {},
    [FLEET_DEV_BOT_TAGS.enabled]: "true",
    [FLEET_DEV_BOT_TAGS.session]: input.sessionId,
    [FLEET_DEV_BOT_TAGS.lastSeen]: (input.now ?? new Date).toISOString()
  };
  await input.client.updateBot({
    id: input.botId,
    tags
  });
  return true;
}
async function clearFleetDevHeartbeat(input) {
  const { bot } = await input.client.getBot({ id: input.botId });
  const tags = bot.tags ?? {};
  if (tags[FLEET_DEV_BOT_TAGS.session] !== input.sessionId) {
    return false;
  }
  await input.client.updateBot({
    id: input.botId,
    tags: {
      ...tags,
      [FLEET_DEV_BOT_TAGS.enabled]: "",
      [FLEET_DEV_BOT_TAGS.session]: "",
      [FLEET_DEV_BOT_TAGS.lastSeen]: ""
    }
  });
  return true;
}
async function startFleetDevHeartbeat(input) {
  const sessionId = input.sessionId ?? randomUUID();
  await markFleetDevHeartbeat({
    client: input.client,
    botId: input.botId,
    sessionId
  });
  const interval = setInterval(() => {
    markFleetDevHeartbeat({
      client: input.client,
      botId: input.botId,
      sessionId,
      replaceSession: false
    }).catch((error) => input.onError?.(error));
  }, input.intervalMs ?? FLEET_DEV_HEARTBEAT_INTERVAL_MS);
  return {
    sessionId,
    async stop(options = { clear: true }) {
      clearInterval(interval);
      if (options.clear !== false) {
        await clearFleetDevHeartbeat({
          client: input.client,
          botId: input.botId,
          sessionId
        });
      }
    }
  };
}

// src/commands/adk-dev-auth-gate.ts
var DEFAULT_LOGIN_WAIT_INTERVAL_MS = 1000;
function abortError(signal) {
  return signal.reason instanceof Error ? signal.reason : new Error("Login wait cancelled");
}
async function defaultSleep(ms, signal) {
  if (signal?.aborted) {
    throw abortError(signal);
  }
  await new Promise((resolve, reject) => {
    const timeout2 = setTimeout(resolve, ms);
    const onAbort = () => {
      clearTimeout(timeout2);
      reject(abortError(signal));
    };
    signal?.addEventListener("abort", onAbort, { once: true });
  });
}
function isNotAuthenticatedError(error) {
  return typeof error === "object" && error !== null && "code" in error && error.code === "NOT_AUTHENTICATED";
}
async function waitForCredentials(options) {
  const authStore = options.auth ?? auth;
  const intervalMs = options.intervalMs ?? DEFAULT_LOGIN_WAIT_INTERVAL_MS;
  const sleep = options.sleep ?? defaultSleep;
  while (true) {
    if (options.signal?.aborted) {
      throw abortError(options.signal);
    }
    try {
      return await authStore.getActiveCredentials();
    } catch (error) {
      if (!isNotAuthenticatedError(error)) {
        throw error;
      }
    }
    await sleep(intervalMs, options.signal);
  }
}
async function startLoginOnlyDevConsole(options) {
  const ensureServer = options.ensureUiServer ?? ensureUiServer;
  const shutdownConsole = options.shutdownDevConsole ?? shutdownDevConsole;
  options.logger.info("DevConsole starting", {
    event: "server",
    status: "starting",
    port: options.startPort,
    url: `http://localhost:${options.startPort}`
  });
  const { port, wasSpawned, pid } = await ensureServer({ startPort: options.startPort, standalone: true });
  const url = `http://localhost:${port}`;
  options.logger.info("DevConsole ready", {
    event: "server",
    status: "ready",
    port,
    url
  });
  if (!wasSpawned) {
    options.logger.info("Reusing existing control server", { event: "server" });
  }
  let cleanedUp = false;
  return {
    port,
    url,
    wasSpawned,
    pid,
    cleanup: async () => {
      if (cleanedUp)
        return;
      cleanedUp = true;
      if (!wasSpawned)
        return;
      await shutdownConsole({ port, pid });
    }
  };
}

// src/commands/adk-dev.ts
var AGENT0_STARTUP_TIMEOUT_MS = (() => {
  const raw = process.env.ADK_AGENT0_STARTUP_TIMEOUT_MS;
  if (raw === undefined || raw.trim() === "") {
    return 15000;
  }
  const parsed = Number(raw);
  if (Number.isFinite(parsed) && parsed > 0) {
    return parsed;
  }
  process.stderr.write(`[adk dev] Ignoring invalid ADK_AGENT0_STARTUP_TIMEOUT_MS=${JSON.stringify(raw)}: ` + `expected a positive number of milliseconds. Falling back to 15000ms.
`);
  return 15000;
})();
function createDevLogFilePath(agentRoot) {
  const now = new Date;
  const dateStr = now.toISOString().replace(/[:.]/g, "-").split("T")[0];
  const timeStr = now.toTimeString().split(" ")[0].replace(/:/g, "-");
  const logFileName = `adk-dev-${dateStr}-${timeStr}.log`;
  const logsDir = path.join(agentRoot, ".adk", "logs");
  mkdirSync(logsDir, { recursive: true });
  return path.join(logsDir, logFileName);
}
async function collectConfigValidationSummary(agentProject, botId, credentials) {
  const configSchema = agentProject.config?.configuration?.schema;
  if (!configSchema || !botId) {
    return { validKeys: [], issues: [], errors: [] };
  }
  const manager = new ConfigManager(botId, {
    project: agentProject,
    credentials
  });
  const validation = await manager.validate(configSchema);
  if (validation.valid) {
    return { manager, schema: configSchema, shape: undefined, validKeys: [], issues: [], errors: [] };
  }
  const shape = configSchema.shape;
  const allKeys = Object.keys(shape);
  const currentConfig = await manager.getAll();
  const validKeys = [];
  const issues = [];
  for (const key of allKeys) {
    const fieldSchema = shape[key];
    const currentValue = currentConfig[key];
    const description = fieldSchema.description;
    if (currentValue === undefined) {
      issues.push({ key, description });
      continue;
    }
    const result = fieldSchema.safeParse(currentValue);
    if (result.success) {
      validKeys.push(key);
      continue;
    }
    issues.push({
      key,
      description,
      error: result.error.issues[0]?.message || "Invalid value"
    });
  }
  return {
    manager,
    shape,
    schema: configSchema,
    validKeys,
    issues,
    errors: validation.errors
  };
}
function isWorkerStatsPayload(obj) {
  return typeof obj === "object" && obj !== null && "type" in obj && obj.type === "worker_stats" && "stats" in obj;
}
async function pollForDevIdForSession(devIdManager, onFound) {
  let attempts = 0;
  const maxAttempts = 30 * 4;
  while (attempts < maxAttempts) {
    try {
      const projectCache = await devIdManager.readProjectCache();
      if (projectCache.devId) {
        await devIdManager.preserveDevId();
        onFound?.(projectCache.devId);
        return projectCache.devId;
      }
    } catch {}
    attempts++;
    await new Promise((resolve) => setTimeout(resolve, 250));
  }
  return;
}
async function runDevSession(port, options, logger2, sessionOpts = {}) {
  const { preloaded, keepAlive = true, registerCleanup, onBeforeTeardown, holdOnFatal = false } = sessionOpts;
  const adkDevConsolePortStr = options.adkDevConsolePortStr || "3001";
  const portOtlp = options.portOtlp ? parseInt(options.portOtlp, 10) : options.otlp ? 4318 : undefined;
  const watchEnabled = options.watch !== false;
  let logStream;
  let fileWatcher;
  let botRuntimeProcess;
  let removeDevCommandHandlers;
  let unsubSecretsChanged;
  let agent0RuntimeClient = null;
  let socketClient = null;
  let releaseProjectLock;
  let fleetDevHeartbeat;
  let isShuttingDown = false;
  const devLogSink = {
    appendLine: (line) => {
      logStream?.write(line);
      getLogStore()?.enqueueLine(line);
    },
    broadcast: broadcastLog
  };
  let onBotListening = null;
  const handleRuntimeLogLine = (rawLine) => {
    deliverRuntimeLog(logger2, rawLine, devLogSink);
    const port2 = extractListeningPort(rawLine);
    if (port2 !== null) {
      onBotListening?.(port2);
    }
  };
  let heldFatalExit = null;
  const stopFleetDevHeartbeat = async (clear) => {
    const heartbeat = fleetDevHeartbeat;
    fleetDevHeartbeat = undefined;
    if (!heartbeat) {
      return;
    }
    try {
      await heartbeat.stop({ clear });
      logger2.info(clear ? "Fleet dev heartbeat cleared" : "Fleet dev heartbeat stopped", {
        event: "fleet-dev-heartbeat",
        sessionId: heartbeat.sessionId
      });
    } catch (err) {
      logger2.debug(`Teardown: fleetDevHeartbeat.stop failed: ${err instanceof Error ? err.message : String(err)}`);
    }
  };
  const releaseResources = async () => {
    try {
      removeDevCommandHandlers?.();
    } catch (err) {
      logger2.debug(`Teardown: removeDevCommandHandlers failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    try {
      fileWatcher?.stop();
    } catch (err) {
      logger2.debug(`Teardown: fileWatcher.stop failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    try {
      unsubSecretsChanged?.();
    } catch (err) {
      logger2.debug(`Teardown: unsubSecretsChanged failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    try {
      await botRuntimeProcess?.waitForExit();
    } catch (err) {
      logger2.debug(`Teardown: botRuntimeProcess.waitForExit failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    try {
      setAgent0RuntimeClient(null);
      await agent0RuntimeClient?.stop();
    } catch (err) {
      logger2.debug(`Teardown: agent0RuntimeClient.stop failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    try {
      await socketClient?.disconnect();
    } catch (err) {
      logger2.debug(`Teardown: socketClient.disconnect failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    try {
      stopSpanIngestServer();
    } catch (err) {
      logger2.debug(`Teardown: stopSpanIngestServer failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    try {
      closeLogSink();
    } catch (err) {
      logger2.debug(`Teardown: closeLogSink failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    try {
      stopBackendServer();
    } catch (err) {
      logger2.debug(`Teardown: stopBackendServer failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    try {
      logStream?.end();
    } catch (err) {
      logger2.debug(`Teardown: logStream.end failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    try {
      await Promise.race([telemetry_default.shutdown(), new Promise((resolve) => setTimeout(resolve, 1000))]);
    } catch (err) {
      logger2.debug(`Teardown: telemetry.shutdown failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    try {
      releaseProjectLock?.();
    } catch (err) {
      logger2.debug(`Teardown: releaseProjectLock failed: ${err instanceof Error ? err.message : String(err)}`);
    }
  };
  const cleanup = async (exitCode, fatalError) => {
    if (heldFatalExit) {
      try {
        onBeforeTeardown?.();
      } catch (err) {
        logger2.debug(`Teardown: onBeforeTeardown failed: ${err instanceof Error ? err.message : String(err)}`);
      }
      await heldFatalExit.release;
      process.exit(heldFatalExit.code);
    }
    if (isShuttingDown) {
      return;
    }
    isShuttingDown = true;
    if (fatalError) {
      setDevCommandStatus("error");
      if (holdOnFatal) {
        try {
          logger2.fatal(fatalError);
        } catch {}
      }
    }
    if (holdOnFatal && exitCode !== 0) {
      await stopFleetDevHeartbeat(false);
      const release = releaseResources();
      heldFatalExit = { code: exitCode, release };
      await release;
      return;
    }
    try {
      onBeforeTeardown?.();
    } catch (err) {
      logger2.debug(`Teardown: onBeforeTeardown failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    await stopFleetDevHeartbeat(exitCode === 0 && !fatalError);
    await releaseResources();
    if (fatalError) {
      logger2.fatal(fatalError);
    }
    process.exit(exitCode);
  };
  registerCleanup?.(cleanup);
  process.once("SIGINT", () => {
    cleanup(0);
  });
  process.once("SIGTERM", () => {
    cleanup(0);
  });
  try {
    logger2.info("Starting dev session", { event: "init" });
    let agentRoot;
    let agentProject;
    let credentials;
    let workspaceId;
    if (preloaded) {
      agentRoot = preloaded.context.agentRoot;
      agentProject = preloaded.context.project;
      credentials = preloaded.context.credentials;
      workspaceId = preloaded.context.workspaceId;
    } else {
      const context = await resolveCommandContext({
        target: "dev",
        projectLoadOptions: { adkCommand: "adk-dev" },
        require: ["project", "credentials", "workspace"],
        createClient: false
      });
      agentRoot = context.agentRoot;
      agentProject = context.project;
      credentials = context.credentials;
      workspaceId = context.workspaceId;
      await assertValidProject(agentProject, { exitOnError: false, skipRender: true, throwOnError: true });
    }
    const dependencyRefreshBotId = agentProject.agentInfo?.devId ?? agentProject.agentInfo?.botId;
    if (dependencyRefreshBotId) {
      let dependencyRefreshClient = preloaded?.context.client;
      if (!dependencyRefreshClient) {
        dependencyRefreshClient = await getProjectClient({
          project: agentProject,
          credentials,
          botId: dependencyRefreshBotId
        }).catch(() => {
          return;
        });
      }
      const refreshed = await refreshDependencySnapshotOnce({
        projectPath: agentProject.path,
        env: "dev",
        botId: dependencyRefreshBotId,
        client: dependencyRefreshClient,
        logger: logger2,
        event: "dependency-refresh"
      });
      if (refreshed) {
        await agentProject.reload();
      }
    }
    releaseProjectLock = await acquireProjectLock(agentProject.path);
    const logFilePath = createDevLogFilePath(agentRoot);
    logStream = createWriteStream(logFilePath, { flags: "a" });
    logger2.info(`Project loaded: ${agentProject.config?.name || "Unknown"}`, {
      event: "project-load",
      ...options.verbose ? { projectPath: agentRoot, logFilePath } : {}
    });
    resetDevBotRuntimeState();
    try {
      const workspaceClient = await getProjectClient({
        credentials,
        workspaceId,
        headers: { "x-multiple-integrations": "true" }
      });
      const workspaceInfo = await workspaceClient.getWorkspace({ id: workspaceId });
      logger2.info(workspaceInfo.name ?? workspaceId, {
        event: "workspace",
        id: workspaceId,
        name: workspaceInfo.name,
        handle: workspaceInfo.handle,
        plan: workspaceInfo.plan
      });
    } catch (error) {
      logger2.warn("Failed to fetch workspace info", {
        event: "workspace",
        id: workspaceId,
        error: error instanceof Error ? error.message : String(error)
      });
    }
    const assetsStart = Date.now();
    logger2.info("Generating assets", { event: "generate" });
    try {
      await generateAssetsTypes(agentProject.path);
      await generateAssetsRuntime(agentProject.path, agentProject.agentInfo?.botId, { dev: true });
      logger2.info("Assets generated", { event: "generate", duration: Date.now() - assetsStart });
    } catch (error) {
      logger2.warn("Asset generation skipped", {
        event: "generate",
        duration: Date.now() - assetsStart,
        error: error instanceof Error ? error.message : String(error)
      });
    }
    const existingDevId = agentProject.agentInfo?.devId;
    if (existingDevId && agentProject.customComponents.length > 0) {
      logger2.info("Syncing custom components", { event: "component-sync" });
      try {
        const client = await getProjectClient({
          project: agentProject,
          credentials,
          botId: existingDevId
        });
        await buildAndUploadComponents({
          agentRoot: agentProject.path,
          project: agentProject,
          client,
          log: (msg) => logger2.info(msg, { event: "component-sync" })
        });
        logger2.info("Components synced", { event: "component-sync" });
      } catch (error) {
        logger2.warn(`Component sync failed: ${error instanceof Error ? error.message : String(error)}`, {
          event: "component-sync"
        });
      }
    }
    const botPath = path.join(agentProject.path, ".adk", "bot");
    const botGenerationStart = Date.now();
    logger2.info("Generating bot project", { event: "bot-generate" });
    await generateBotProject({
      projectPath: agentProject.path,
      outputPath: botPath,
      adkCommand: "adk-dev",
      callbacks: {
        onProgress: (message) => logger2.info(message, { event: "bot-generate" }),
        onSuccess: (message) => logger2.info(message, { event: "bot-generate" }),
        onError: (message) => logger2.info(message, { event: "bot-generate" })
      }
    });
    logger2.info("Bot project generated", { event: "bot-generate", duration: Date.now() - botGenerationStart });
    const devIdManager = new DevIdManager(agentProject.path, botPath);
    const devBotExists = await devIdManager.checkDevBotExists();
    if (!devBotExists && agentProject.agentInfo?.devId) {
      await agentProject.updateAgentLocalInfo({ devId: undefined });
    }
    await devIdManager.restoreDevId();
    logger2.info("Dev IDs restored", { event: "dev-id" });
    let configuration;
    if (agentProject.agentInfo?.devId) {
      try {
        const manager = new ConfigManager(agentProject.agentInfo.devId, {
          project: agentProject,
          credentials
        });
        configuration = await manager.getAll();
        logger2.info("Configuration loaded for dev bot", { event: "config-load" });
      } catch (error) {
        logger2.info(`Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`, {
          event: "config-load"
        });
      }
    }
    const requestedConsolePort = parseInt(adkDevConsolePortStr, 10);
    logger2.info("DevConsole starting", {
      event: "server",
      status: "starting",
      port: requestedConsolePort,
      url: `http://localhost:${requestedConsolePort}`
    });
    const { port: adkDevConsolePort, wasSpawned } = await ensureUiServer({ startPort: requestedConsolePort });
    const backend = startBackendServer(agentProject.path);
    const spanIngestPort = startSpanIngestServer();
    initLogSink((rawLine) => handleRuntimeLogLine(rawLine));
    setServerConfig({
      port: adkDevConsolePort,
      agentPath: agentProject.path,
      project: agentProject,
      logFilePath,
      adkVersion: getAdkVersion(),
      credentials: {
        token: credentials.token,
        apiUrl: credentials.apiUrl,
        workspaceId,
        devBotId: agentProject.agentInfo?.devId
      }
    });
    setServerStartTime(new Date);
    if (adkDevConsolePort !== requestedConsolePort) {
      logger2.info(`Port ${requestedConsolePort} in use, console using ${adkDevConsolePort}`, { event: "port" });
    }
    logger2.info("DevConsole ready", {
      event: "server",
      status: "ready",
      port: adkDevConsolePort,
      url: `http://localhost:${adkDevConsolePort}`
    });
    logger2.info(`Span ingest on port ${spanIngestPort}`, { event: "span-ingest" });
    if (!wasSpawned) {
      logger2.info("Reusing existing control server", { event: "server" });
    }
    const existingDevBotId = agentProject.agentInfo?.devId;
    const agent0RuntimeStartOptions = {
      agentPath: agentProject.path,
      adkDevConsolePort,
      credentials,
      startupTimeoutMs: AGENT0_STARTUP_TIMEOUT_MS,
      onLog: (msg) => devLogSink.appendLine?.(`[agent0] ${msg}
`)
    };
    let holdConsoleReadyUntilAgent0RuntimeSettles = !existingDevBotId;
    setAgent0RuntimeClient(null);
    try {
      const startOptions = { ...agent0RuntimeStartOptions, devBotId: existingDevBotId };
      agent0RuntimeClient = existingDevBotId ? await startAgent0DevRuntimeWithRetry(startOptions, {
        onRetry: ({ attempt, maxAttempts }) => logger2.info(`Agent(0) runtime start timed out; retrying (attempt ${attempt}/${maxAttempts})`, {
          event: "agent0"
        })
      }) : await startAgent0DevRuntime(startOptions);
      if (existingDevBotId) {
        setAgent0RuntimeClient(agent0RuntimeClient);
      }
      logger2.info(existingDevBotId ? "Agent(0) runtime ready" : "Agent(0) runtime started (awaiting dev bot)", {
        event: "agent0"
      });
    } catch (error) {
      const errorMsg = error instanceof Error ? error.message : String(error);
      agent0RuntimeClient = createUnavailableAgent0RuntimeClient(error);
      setAgent0RuntimeClient(agent0RuntimeClient);
      logger2.warn(`Agent(0) runtime failed: ${errorMsg}`, { event: "agent0" });
    }
    const declaredSecretsRestart = agentProject.config?.secrets ?? {};
    const secretsMgr = new SecretsManager(agentProject.path);
    const devSecretsForRestart = await secretsMgr.getAll("dev", declaredSecretsRestart);
    const missingReq = Object.entries(declaredSecretsRestart).filter(([name, def]) => !def.optional && !(name in devSecretsForRestart)).map(([name]) => name);
    if (missingReq.length > 0) {
      throw new AdkError({
        code: "MISSING_SECRETS",
        message: `Missing required secrets: ${missingReq.join(", ")}
` + `Set them with: ${missingReq.map((n) => `adk secret:set ${n} <value>`).join(", ")}`,
        expected: true
      });
    }
    const requestedBotPort = parseInt(port);
    const resolvedBotPort = await findAvailablePort(requestedBotPort);
    if (resolvedBotPort !== requestedBotPort) {
      logger2.info(`Port ${requestedBotPort} in use, using ${resolvedBotPort} for bot runtime`, { event: "port" });
    }
    botRuntimeProcess = new BpDevCommand({
      port: String(resolvedBotPort),
      botPath,
      agentPath: agentProject.path,
      workspaceId,
      credentials: {
        token: credentials.token,
        apiUrl: credentials.apiUrl
      },
      devBotId: agentProject.agentInfo?.devId,
      sourceMap: true,
      watch: watchEnabled,
      configuration,
      secrets: Object.keys(devSecretsForRestart).length > 0 ? devSecretsForRestart : undefined,
      spanIngestPort,
      otlpPort: portOtlp,
      botName: agentProject.config?.name
    });
    const niDevSessionStartedAt = Date.now();
    socketClient = new ConsoleSocketClient({
      socketPath: CONSOLE_SOCKET_PATH,
      name: agentProject.config?.name || path.basename(agentProject.path),
      agentPath: agentProject.path,
      backendPort: backend.port,
      botPort: resolvedBotPort,
      onDisconnect: () => logger2.info("Lost connection to DevConsole", { event: "socket" }),
      onShutdown: () => {
        cleanup(0);
      },
      getProcessInfo: () => ({
        adkPid: process.pid,
        botPid: botRuntimeProcess?.pid ?? undefined,
        startedAt: niDevSessionStartedAt,
        updatedAt: Date.now()
      })
    });
    try {
      await socketClient.connect();
      logger2.info("Registered with DevConsole", { event: "socket" });
    } catch (error) {
      if (error instanceof ConsoleSocketRegistrationRejectedError) {
        throw new CriticalError("DevConsole rejected agent registration", error.message);
      }
      logger2.info(`Failed to register with DevConsole: ${error instanceof Error ? error.message : String(error)}`, {
        event: "socket"
      });
    }
    const runtimePort = resolvedBotPort;
    setDevCommandStatus("building");
    const handleBotRuntimeLogData = (data) => {
      const lines = data.split(LOG_DELIMITER).map((line) => line.replaceAll(LOG_DELIMITER, "").trim());
      for (const line of lines) {
        if (!line) {
          continue;
        }
        let parsed = null;
        try {
          parsed = JSON.parse(line);
        } catch {}
        if (isWorkerStatsPayload(parsed)) {
          updateWorkerStats(parsed.stats);
          continue;
        }
        if (logRequestHandlingError(logger2, line, getDevCommandStatus())) {
          devLogSink.appendLine?.(`${normalizeStructuredLogLine(line)}
`);
          continue;
        }
        if (classifyRecoverableDeployError(line)) {
          continue;
        }
        handleRuntimeLogLine(line);
      }
    };
    let listeningHandled = false;
    let recoverableDeployFailure = false;
    let awaitingWorkerListen = false;
    const markBotListening = (botPort) => {
      if (listeningHandled) {
        return;
      }
      listeningHandled = true;
      awaitingWorkerListen = false;
      setDevCommandStatus("ready");
      socketClient?.setStatus(holdConsoleReadyUntilAgent0RuntimeSettles ? "starting" : "ready");
      setDevBotRuntimeState({
        running: true,
        port: botPort,
        url: `http://localhost:${botPort}`,
        botId: agentProject.agentInfo?.devId || null
      });
    };
    onBotListening = markBotListening;
    removeDevCommandHandlers = (() => {
      const handleLogData = (data) => {
        handleBotRuntimeLogData(data);
      };
      const handleProgress = (event) => {
        logBpDevProgress(logger2, event);
        switch (event.type) {
          case "generating":
          case "bundling":
          case "deploying":
            if (event.endTime === undefined) {
              listeningHandled = false;
              setDevCommandStatus("building");
              socketClient?.setStatus("starting");
              if (event.type === "bundling") {
                awaitingWorkerListen = true;
              }
            } else if (event.type === "deploying" && !awaitingWorkerListen && !recoverableDeployFailure && getDevBotRuntimeState().running) {
              setDevCommandStatus("ready");
              socketClient?.setStatus("ready");
            }
            break;
        }
      };
      const handleError = (error) => {
        resetDevBotRuntimeState();
        const recoverable = classifyRecoverableDeployError(error.stderr || error.message);
        if (recoverable) {
          recoverableDeployFailure = true;
          socketClient?.setStatus("error");
          setDevCommandStatus("error");
          const { failures } = recoverable;
          const names = failures.map((f) => f.name);
          const summary = failures.length ? failures.map((f) => f.reason ? `${f.name} (${f.reason})` : f.name).join("; ") : "some integrations";
          const target = names.length ? names.join(", ") : "the integration";
          logger2.warn(`Couldn't register ${summary}. The dev console is still available \u2014 open Integrations, ` + `finish setup for ${target} (connect or configure it as needed), then restart \`adk dev\`.`, { event: "integration-registration", integrations: names, fatal: false });
          return;
        }
        socketClient?.setStatus("error");
        cleanup(1, new CriticalError("Dev bot failed to start", error.message, error.stderr));
      };
      botRuntimeProcess.on("stdout", handleLogData);
      botRuntimeProcess.on("stderr", handleLogData);
      botRuntimeProcess.on("progress", handleProgress);
      botRuntimeProcess.on("error", handleError);
      return () => {
        botRuntimeProcess.off("stdout", handleLogData);
        botRuntimeProcess.off("stderr", handleLogData);
        botRuntimeProcess.off("progress", handleProgress);
        botRuntimeProcess.off("error", handleError);
      };
    })();
    await botRuntimeProcess.run();
    const devId = await pollForDevIdForSession(devIdManager, (foundDevId) => {
      setDevBotRuntimeState({ botId: foundDevId });
    });
    const botIdForSync = devId || agentProject.agentInfo?.devId || "";
    if (!botIdForSync) {
      throw new CriticalError("Failed to obtain dev bot ID", "The development bot ID could not be detected after 30 seconds. Please check the logs for errors.");
    }
    logger2.info(botIdForSync, { event: "dev-id", devId: botIdForSync });
    if (options.fleet) {
      try {
        const fleetDevClient = await getProjectClient({
          project: agentProject,
          credentials,
          botId: botIdForSync
        });
        fleetDevHeartbeat = await startFleetDevHeartbeat({
          client: fleetDevClient,
          botId: botIdForSync,
          onError: (error) => {
            logger2.warn(`Fleet dev heartbeat failed: ${error instanceof Error ? error.message : String(error)}`, {
              event: "fleet-dev-heartbeat"
            });
          }
        });
        logger2.info("Fleet dev heartbeat started", {
          event: "fleet-dev-heartbeat",
          sessionId: fleetDevHeartbeat.sessionId
        });
      } catch (error) {
        logger2.warn(`Fleet dev heartbeat unavailable: ${error instanceof Error ? error.message : String(error)}`, {
          event: "fleet-dev-heartbeat"
        });
      }
    }
    if (!existingDevId && botIdForSync && agentProject.customComponents.length > 0) {
      logger2.info("Syncing custom components (first run)", { event: "component-sync" });
      try {
        const client = await getProjectClient({
          project: agentProject,
          credentials,
          botId: botIdForSync
        });
        await buildAndUploadComponents({
          agentRoot: agentProject.path,
          project: agentProject,
          client,
          log: (msg) => logger2.info(msg, { event: "component-sync" })
        });
        logger2.info("Components synced", { event: "component-sync" });
      } catch (error) {
        logger2.warn(`Component sync failed: ${error instanceof Error ? error.message : String(error)}`, {
          event: "component-sync"
        });
      }
    }
    await agentProject.reload();
    const serverConfig = getServerConfig();
    setServerConfig({
      ...serverConfig,
      project: agentProject,
      credentials: {
        ...serverConfig.credentials,
        devBotId: botIdForSync
      }
    });
    if (!existingDevBotId && botIdForSync) {
      logger2.info("Restarting Agent(0) with dev bot credentials", { event: "agent0" });
      try {
        await agent0RuntimeClient?.stop();
      } catch (err) {
        logger2.debug(`Agent(0) stop before restart failed: ${err instanceof Error ? err.message : String(err)}`);
      }
      setAgent0RuntimeClient(null);
      try {
        agent0RuntimeClient = await startAgent0DevRuntimeWithRetry({ ...agent0RuntimeStartOptions, devBotId: botIdForSync }, {
          onRetry: ({ attempt, maxAttempts }) => logger2.info(`Agent(0) runtime restart timed out; retrying (attempt ${attempt}/${maxAttempts})`, {
            event: "agent0"
          })
        });
        setAgent0RuntimeClient(agent0RuntimeClient);
        holdConsoleReadyUntilAgent0RuntimeSettles = false;
        socketClient?.setStatus("ready");
        logger2.info("Agent(0) runtime ready", { event: "agent0" });
      } catch (error) {
        const errorMsg = error instanceof Error ? error.message : String(error);
        agent0RuntimeClient = createUnavailableAgent0RuntimeClient(error);
        setAgent0RuntimeClient(agent0RuntimeClient);
        holdConsoleReadyUntilAgent0RuntimeSettles = false;
        socketClient?.setStatus("ready");
        logger2.warn(`Agent(0) runtime restart failed: ${errorMsg}`, { event: "agent0" });
      }
    }
    const configValidation = await collectConfigValidationSummary(agentProject, botIdForSync, credentials);
    if (configValidation.issues.length > 0) {
      logger2.info(`Configuration validation reported ${configValidation.issues.length} issue(s); continuing`, {
        event: "config-validation",
        issues: configValidation.issues
      });
    } else {
      logger2.info("Configuration validated", { event: "config-validation" });
    }
    const runStartupSync = async () => {
      if (!botIdForSync)
        return;
      const checker = new PreflightChecker(agentProject.path, { credentials });
      const plan = await checker.computeDeployPlan(botIdForSync, "dev");
      if (plan.preflight.result.hasChanges) {
        await plan.preflight.apply({
          onProgress: (message) => logger2.info(message, { event: "preflight" }),
          onSuccess: (message) => logger2.info(`Auto-approved: ${message}`, { event: "preflight" }),
          onError: (message) => logger2.log("error", message, { event: "log" })
        });
      } else {
        logger2.info("No preflight changes detected", { event: "preflight" });
      }
      await applyPlanSyncs(plan, { hasKnowledge: agentProject.knowledge.length > 0, hasAssets: await agentProject.hasAssetsDirectory() }, {
        onTablesSynced: (result) => {
          const failures = toTableSyncFailureDetails(result.failed);
          if (failures.length > 0) {
            logger2.error(`Table sync completed with ${failures.length} failure(s)`, {
              event: "table-sync",
              summary: result.summary,
              failures
            });
            for (const failure of failures) {
              logger2.error(`   \u2022 ${failure.table} (${failure.operation}, ${failure.errorType}): ${failure.message}`, {
                event: "table-sync"
              });
            }
            return;
          }
          logger2.info(`Auto-applied: ${formatTableSyncSummary(result)}`, {
            event: "table-sync",
            summary: result.summary
          });
        },
        onTablesUpToDate: () => logger2.info("Tables up to date", { event: "table-sync" }),
        onTableSyncError: (error) => logger2.log("error", `Table sync failed: ${error instanceof Error ? error.message : String(error)}`, {
          event: "log"
        }),
        onOrphanedKBsDeleted: (count) => logger2.info(`Auto-deleted ${count} orphaned knowledge base${count === 1 ? "" : "s"}`, { event: "kb-sync" }),
        onOrphanedKBDeleteError: (error) => logger2.log("error", `Failed to delete orphaned KBs: ${error instanceof Error ? error.message : String(error)}`, { event: "log" }),
        onKbPlanReady: async (_kbPlan, kbManager) => {
          const result = await kbManager.executeSync(_kbPlan, { confirmDestructive: true });
          logger2.info(formatKnowledgeBaseAutoApproveMessage(result), {
            event: "kb-sync",
            synced: result.synced.map((item) => item.name),
            skipped: result.skipped,
            failed: result.failed,
            websiteSyncs: result.websiteSyncs
          });
        },
        onKbsUpToDate: () => logger2.info("Knowledge bases up to date", { event: "kb-sync" }),
        onAssetsSynced: (result) => logger2.info(`Assets synced (${formatAssetSyncSummary(result)})`, {
          event: "asset-sync",
          ...result.summary
        }),
        onAssetsUpToDate: () => logger2.info("Assets up to date", { event: "asset-sync" }),
        onAssetSyncError: (error) => logger2.log("error", `Asset sync failed: ${error instanceof Error ? error.message : String(error)}`, {
          event: "log"
        })
      });
    };
    await runStartupSync();
    if (watchEnabled) {
      fileWatcher = new FileWatcher2({
        projectPath: agentProject.path,
        debounceMs: 100
      });
    }
    let isRestartingSecrets = false;
    const restartBotWithSecrets = async (reason) => {
      if (isRestartingSecrets || !botRuntimeProcess)
        return;
      isRestartingSecrets = true;
      try {
        logger2.info(`Restarting dev bot (${reason})...`, { event: "secrets" });
        setDevCommandStatus("building");
        socketClient?.setStatus("starting");
        removeDevCommandHandlers?.();
        await botRuntimeProcess.waitForExit();
        const declaredSecrets = agentProject.config?.secrets ?? {};
        const mgr = new SecretsManager(agentProject.path);
        const devSecrets = await mgr.getAll("dev", declaredSecrets);
        const missingReqSecrets = Object.entries(declaredSecrets).filter(([name, def]) => !def.optional && !(name in devSecrets)).map(([name]) => name);
        if (missingReqSecrets.length > 0) {
          logger2.info(`Warning: unset required secrets: ${missingReqSecrets.join(", ")}`, { event: "secrets" });
        }
        let freshConfig;
        if (agentProject.agentInfo?.devId) {
          try {
            const cfgMgr = new ConfigManager(agentProject.agentInfo.devId, {
              project: agentProject,
              credentials
            });
            freshConfig = await cfgMgr.getAll();
          } catch (err) {
            logger2.debug(`Dev config refresh failed: ${err instanceof Error ? err.message : String(err)}`);
          }
        }
        botRuntimeProcess = new BpDevCommand({
          port: String(resolvedBotPort),
          botPath,
          agentPath: agentProject.path,
          workspaceId,
          credentials: { token: credentials.token, apiUrl: credentials.apiUrl },
          devBotId: agentProject.agentInfo?.devId,
          sourceMap: true,
          watch: watchEnabled,
          configuration: freshConfig,
          secrets: Object.keys(devSecrets).length > 0 ? devSecrets : undefined,
          spanIngestPort,
          otlpPort: portOtlp,
          botName: agentProject.config?.name
        });
        if (logStream) {
          removeDevCommandHandlers = (() => {
            const handleLogData = (data) => {
              handleBotRuntimeLogData(data);
            };
            botRuntimeProcess.on("stdout", handleLogData);
            botRuntimeProcess.on("stderr", handleLogData);
            return () => {
              botRuntimeProcess.off("stdout", handleLogData);
              botRuntimeProcess.off("stderr", handleLogData);
            };
          })();
        }
        await botRuntimeProcess.run();
        socketClient?.setStatus("ready");
        logger2.info(`Dev bot restarted (${reason})`, { event: "secrets" });
      } catch (error) {
        socketClient?.setStatus("error");
        logger2.log("error", `Failed to restart: ${error instanceof Error ? error.message : String(error)}`, {
          event: "log"
        });
      } finally {
        isRestartingSecrets = false;
      }
    };
    unsubSecretsChanged = onSecretsValuesChanged(() => {
      restartBotWithSecrets("secret values updated");
    });
    if (watchEnabled && fileWatcher) {
      let lastGeneratedConversationCount = agentProject.conversations.length;
      let sawEmptyConversationScan = false;
      fileWatcher.on("change", async (event) => {
        setDevCommandStatus("building");
        socketClient?.setStatus("starting");
        logger2.info("Files changed", {
          event: "file",
          changes: event.changes.map((change) => ({ path: change.path, type: change.type }))
        });
        const componentEvents = event.changes.map((change) => classifyComponentPath(change.path)).filter((e) => e !== null);
        try {
          const oldSecretKeys = new Set(Object.keys(agentProject.config?.secrets ?? {}));
          await agentProject.reload();
          emitProjectReloaded();
          logger2.info("Agent project reloaded", { event: "reload" });
          const scanInfo = agentProject.info;
          const importFailures = scanInfo.warnings.filter((w) => w.code === ValidationErrorCode.IMPORT_ERROR);
          const conversationsEmpty = lastGeneratedConversationCount > 0 && agentProject.conversations.length === 0;
          const conversationsVanished = conversationsEmpty && !sawEmptyConversationScan;
          sawEmptyConversationScan = conversationsEmpty;
          if (agentProject.state === ProjectState.Error || importFailures.length > 0 || conversationsVanished) {
            const reason = importFailures.length > 0 ? `${importFailures.length} source file(s) failed to import: ${importFailures.map((w) => w.file ?? "(unknown file)").join(", ")}` : conversationsVanished ? "the scan found no conversations although the running bot has them \u2014 likely a transient mid-save read" : `project scan reported ${scanInfo.errorCount} error(s)`;
            logger2.log("error", `Skipping hot-reload regeneration \u2014 ${reason}. Keeping the last working bot bundle; fix the error and save again to redeploy.`, { event: "rebuild-skipped" });
            for (const issue of [...importFailures, ...scanInfo.errors]) {
              const detail = `${issue.file ? `${issue.file}: ` : ""}${issue.message}`;
              logger2.log("error", `  ${detail}`, { event: "log" });
              devLogSink.appendLine?.(JSON.stringify({
                timestamp: new Date().toISOString(),
                type: "error",
                level: "error",
                message: detail
              }) + `
`);
            }
            setDevCommandStatus("ready");
            socketClient?.setStatus("ready");
            return;
          }
          for (const ev of componentEvents)
            broadcastComponentChanged(ev);
          const newSecretKeys = new Set(Object.keys(agentProject.config?.secrets ?? {}));
          const secretsChanged = oldSecretKeys.size !== newSecretKeys.size || [...newSecretKeys].some((k) => !oldSecretKeys.has(k)) || [...oldSecretKeys].some((k) => !newSecretKeys.has(k));
          if (secretsChanged && botRuntimeProcess) {
            logger2.info("Secret declarations changed, stopping dev bot before regeneration...", { event: "secrets" });
            removeDevCommandHandlers?.();
            await botRuntimeProcess.waitForExit();
          }
          if (botIdForSync) {
            const checker = new PreflightChecker(agentProject.path, { credentials });
            const pending = await checker.checkWithPendingApply(botIdForSync);
            if (pending.result.hasChanges) {
              await pending.apply({
                onProgress: (message) => logger2.info(message, { event: "preflight" }),
                onSuccess: (message) => logger2.info(`Auto-approved: ${message}`, { event: "preflight" }),
                onError: (message) => logger2.log("error", message, { event: "log" })
              });
            }
          }
          if (botIdForSync && agentProject.customComponents.length > 0) {
            const changedComponentNames = [];
            for (const comp of agentProject.customComponents) {
              const wrapperAbs = path.join(agentProject.path, comp.path);
              if (event.changes.some((c) => c.path === wrapperAbs || c.path === comp.source)) {
                changedComponentNames.push(comp.definition.name);
              }
            }
            if (changedComponentNames.length > 0) {
              logger2.info(`Building component(s): ${changedComponentNames.join(", ")}`, { event: "component-sync" });
              try {
                const client = await getProjectClient({ project: agentProject, credentials, botId: botIdForSync });
                await buildAndUploadComponents({
                  agentRoot: agentProject.path,
                  project: agentProject,
                  client,
                  log: (msg) => logger2.info(msg, { event: "component-sync" }),
                  only: changedComponentNames
                });
                logger2.info("Components synced", { event: "component-sync" });
              } catch (error) {
                logger2.warn(`Component sync failed: ${error instanceof Error ? error.message : String(error)}`, {
                  event: "component-sync"
                });
              }
            }
          }
          const dependencyChanged = event.changes.some((change) => change.path.includes(path.join(".adk", "dependencies")));
          if (dependencyChanged) {
            AgentProject.clearCacheForPath(agentProject.path);
          }
          const rebuildStart = Date.now();
          await generateBotProject({
            projectPath: agentProject.path,
            outputPath: botPath,
            adkCommand: "adk-dev",
            callbacks: {
              onProgress: (message) => logger2.info(message, { event: "rebuild" }),
              onSuccess: (message) => logger2.info(message, { event: "rebuild" }),
              onError: (message) => logger2.log("warn", message, { event: "log" })
            }
          });
          logger2.info("Bot project regenerated", { event: "rebuild", duration: Date.now() - rebuildStart });
          lastGeneratedConversationCount = agentProject.conversations.length;
          if (secretsChanged) {
            await restartBotWithSecrets("secret declarations changed");
          }
          const knowledgeFileChanged = event.changes.some((change) => change.path.includes("knowledge") || change.path.endsWith(".md") || change.path.endsWith(".pdf") || change.path.endsWith(".txt"));
          const tableFileChanged = event.changes.some((change) => change.path.includes(`${path.sep}tables${path.sep}`) && change.path.endsWith(".ts"));
          if (tableFileChanged && botIdForSync) {
            const tableManager = new TableManager({ project: agentProject, botId: botIdForSync, credentials });
            const tablePlan = await tableManager.createSyncPlan();
            if (tablePlan.hasChanges) {
              const syncResult = await tableManager.executeSync(tablePlan, { confirmDestructive: true });
              logger2.info(`Auto-applied: ${formatTableSyncSummary(syncResult)}`, {
                event: "table-sync",
                summary: syncResult.summary
              });
            }
          }
          if (knowledgeFileChanged && botIdForSync && agentProject.knowledge.length > 0) {
            const kbManager = new KnowledgeManager({ project: agentProject, botId: botIdForSync, credentials });
            kbManager.clearHashCache();
            const kbPlan = await kbManager.createSyncPlan();
            if (kbPlan.hasChanges) {
              const result = await kbManager.executeSync(kbPlan, { confirmDestructive: true });
              logger2.info(formatKnowledgeBaseAutoApproveMessage(result), {
                event: "kb-sync",
                synced: result.synced.map((item) => item.name),
                skipped: result.skipped,
                failed: result.failed,
                websiteSyncs: result.websiteSyncs
              });
            }
          }
          setDevCommandStatus("ready");
          socketClient?.setStatus("ready");
        } catch (error) {
          setDevCommandStatus("error");
          socketClient?.setStatus("error");
          logger2.error(error instanceof Error ? error.message : String(error), {
            event: "reload",
            fatal: false,
            details: error instanceof Error ? error.stack : undefined
          });
        }
      });
      fileWatcher.start();
    }
    if (!recoverableDeployFailure) {
      setDevCommandStatus("ready");
      socketClient?.setStatus("ready");
    }
    telemetry_default.track("dev", {
      adkVersion: getAdkVersion(),
      port: runtimePort
    });
    logger2.info("Dev session ready", {
      event: "ready",
      serverPort: adkDevConsolePort,
      healthUrl: `http://localhost:${adkDevConsolePort}/api/health`,
      botPort: resolvedBotPort,
      botUrl: `http://localhost:${resolvedBotPort}`,
      agentPath: agentProject.path,
      spanIngestPort
    });
    if (keepAlive) {
      await new Promise(() => {});
    }
  } catch (error) {
    await cleanup(1, error);
  }
}
async function runStaticPanelDev(port, options) {
  const adkDevConsolePortStr = options.adkDevConsolePortStr || "3001";
  const initialSnapshot = {
    state: "starting",
    version: getAdkVersion(),
    agentName: path.basename(process.cwd()),
    loginStatus: null,
    missingConfigKeys: [],
    invalidConfigKeys: [],
    logs: []
  };
  const store = createPanelStore(initialSnapshot);
  let app;
  let lastFatal = null;
  const surfaceFatal = (record) => {
    const stack = typeof record.stack === "string" && record.stack ? record.stack : undefined;
    const details = record.details === undefined ? undefined : typeof record.details === "string" ? record.details : JSON.stringify(record.details, null, 2);
    lastFatal = { message: record.message, stack, details };
    console.error(record.message);
    if (stack)
      console.error(stack);
    if (details)
      console.error(details);
    try {
      app?.renderer.console.show();
    } catch {}
  };
  configureGlobalLogging({
    consoleOutput: false,
    transports: [
      createPanelLogTransport(store, {
        onFatal: surfaceFatal,
        onError: (record) => {
          console.error(record.message);
        }
      })
    ],
    onFatal: () => false
  });
  const logger2 = createCliLogger({ tag: "dev" });
  const stripAnsi = (str) => str.replace(/\u001b\[[0-9;]*m/g, "");
  const authLogger = (message) => logger2.info(stripAnsi(message), { event: "auth" });
  let cleanupFn;
  let stopRequested = false;
  let panelUnmounted = false;
  const unmountPanel = () => {
    if (panelUnmounted)
      return;
    panelUnmounted = true;
    try {
      app?.unmount();
    } catch {}
    if (lastFatal) {
      const parts = [`\u2717 ${lastFatal.message}`, lastFatal.stack, lastFatal.details].filter(Boolean);
      process.stderr.write(`
${parts.join(`
`)}
`);
    }
  };
  app = await mount((renderer, scope) => devConsolePanel(renderer, scope, {
    store,
    onStop: () => {
      unmountPanel();
      if (cleanupFn) {
        cleanupFn(0);
      } else {
        stopRequested = true;
        process.exit(0);
      }
    }
  }), {
    screenMode: "alternate-screen",
    useMouse: true,
    exitOnCtrlC: false,
    maxFps: 15,
    consoleOptions: {
      title: "Errors \u2014 [E] Close",
      keyBindings: [{ name: "y", action: "copy-selection" }],
      onCopySelection: (text2) => copyViaClipboardTool(text2)
    }
  });
  const resolveInteractiveContext = async () => {
    while (!stopRequested) {
      authLogger("Resolving project credentials");
      try {
        return await resolveCommandContext({
          target: "dev",
          projectLoadOptions: { adkCommand: "adk-dev" },
          require: ["project", "credentials", "workspace"],
          createClient: false
        });
      } catch (error) {
        if (!isNotAuthenticatedError(error)) {
          throw error;
        }
      }
      if (stopRequested)
        return;
      const requestedConsolePort = parseInt(adkDevConsolePortStr, 10);
      authLogger("No credentials found; starting Dev Console sign-in");
      const loginConsole = await startLoginOnlyDevConsole({
        startPort: requestedConsolePort,
        logger: logger2
      });
      store.update({ devConsoleUrl: loginConsole.url });
      logger2.info("Sign in from the Dev Console to continue", {
        event: "auth",
        url: loginConsole.url
      });
      const abortController = new AbortController;
      let loginConsoleCleanedUp = false;
      const cleanupLoginConsole = async () => {
        if (loginConsoleCleanedUp)
          return;
        loginConsoleCleanedUp = true;
        await loginConsole.cleanup();
      };
      const loginCleanup = async () => {
        stopRequested = true;
        abortController.abort(new Error("Login wait cancelled"));
        await cleanupLoginConsole();
      };
      cleanupFn = loginCleanup;
      try {
        await waitForCredentials({ signal: abortController.signal });
      } catch (error) {
        if (stopRequested || abortController.signal.aborted) {
          return;
        }
        throw error;
      } finally {
        if (cleanupFn === loginCleanup) {
          cleanupFn = undefined;
        }
        await cleanupLoginConsole();
      }
      if (stopRequested)
        return;
      authLogger("Credentials detected; continuing dev startup");
      resolveLoginStatus().then((loginStatus) => store.update({ loginStatus })).catch(() => {});
    }
    return;
  };
  try {
    const context = await resolveInteractiveContext();
    if (!context)
      return;
    const agentRoot = context.agentRoot;
    const agentProject = context.project;
    await assertValidProject(agentProject, { exitOnError: false, skipRender: true, throwOnError: true });
    store.update({ agentName: agentProject.config?.name || path.basename(agentRoot) });
    resolveLoginStatus().then((loginStatus) => store.update({ loginStatus })).catch(() => {});
    if (stopRequested)
      return;
    runDevSession(port, options, logger2, {
      preloaded: { context },
      keepAlive: false,
      registerCleanup: (cleanup) => {
        cleanupFn = cleanup;
      },
      onBeforeTeardown: unmountPanel,
      holdOnFatal: true
    });
  } catch (error) {
    try {
      logger2.fatal(error);
    } catch {}
  }
  await app.waitUntilExit();
}
async function adkDev(port = "3000", options = { ui: true }) {
  const { checkNodeVersion } = await import("./chunk-c07hkzqm.js");
  checkNodeVersion(true);
  const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);
  const useNdjson = options.nonInteractive || !isTTY;
  if (useNdjson) {
    configureGlobalLogging({ format: "json", ndjson: true });
    await runDevSession(port, options, createCliLogger({ tag: "dev" }));
    return;
  }
  await runStaticPanelDev(port, options);
}
export {
  adkDev
};