@botpress/adk-cli
Version:
Command-line interface for the Botpress Agent Development Kit (ADK)
2,482 lines • 88.5 kB
JavaScript
// @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
};