@botpress/adk-cli
Version:
Command-line interface for the Botpress Agent Development Kit (ADK)
2,336 lines • 81.3 kB
JavaScript
#!/usr/bin/env bun
// @bun
import {
PROTOCOL_VERSION,
createLineParser,
encode,
findAvailablePort,
parseAgentToServerMessage
} from "./chunk-x0t4wpfv.js";
import {
errorResponse,
getCorsHeaders,
handleCorsPreflightResponse,
handleFeatureFlags,
handleProdBotApiRequest,
jsonResponse,
startEventLoopLagMonitor,
successResponse,
withRequestTiming
} from "./chunk-59ayvmxs.js";
import"./chunk-68mqsf42.js";
import"./chunk-ndxsgd72.js";
import"./chunk-r72adjnh.js";
import {
ensureTemplatesAvailable
} from "./chunk-h652feqd.js";
import {
getLoginUrl,
startCallbackServer
} from "./chunk-4cgz94zj.js";
import {
installExternalHarnessCapabilities
} from "./chunk-whkrdhta.js";
import"./chunk-kwmsaz7n.js";
import {
getEmbeddedAsset,
hasEmbeddedAssets,
installAgent0Capabilities
} from "./chunk-tt572q4r.js";
import"./chunk-26vqkz52.js";
import {
CONSOLE_LOCK_FILE,
CONSOLE_PORT_FILE,
CONSOLE_SOCKET_PATH,
DEVCONSOLE_ID_HEADER,
probeUnixSocket
} from "./chunk-sgj6770p.js";
import {
RuntimeVersionMismatchError,
formatRuntimeVersionMismatchMessage,
getRuntimeMajor,
getRuntimeMajorRelation,
getRuntimeVersionStatus,
preflightRuntimeVersionCheck
} from "./chunk-ty7sdgd4.js";
import {
detectPackageManagers,
getPreferredPackageManager
} from "./chunk-nbasj5jm.js";
import {
findAgentRoot
} from "./chunk-kk3h6qaj.js";
import {
createCliLogger
} from "./chunk-gzwt1qdr.js";
import {
EXPECTED_RUNTIME_VERSION
} from "./chunk-nxy2ya5r.js";
import"./chunk-wzj4dc7n.js";
import {
ADK_MANIFEST_BOT_TAGS,
AdkError,
AgentProject,
AgentProjectGenerator,
ConfigWriter,
auth,
exports_dependencies,
getProjectClient
} from "./chunk-p0hjqn4r.js";
import"./chunk-np5wcwfv.js";
import"./chunk-dq2xpa24.js";
import"./chunk-6w0knnta.js";
import"./chunk-40x04ckt.js";
import"./chunk-t76d8fxx.js";
import"./chunk-nh2akp42.js";
import"./chunk-0fdvzjbh.js";
import"./chunk-2a5b6azq.js";
import"./chunk-vay209b5.js";
import {
Uk
} from "./chunk-3xrpxgq4.js";
import"./chunk-rfm3jr1m.js";
import"./chunk-w346ejn9.js";
import"./chunk-knvm2anf.js";
import"./chunk-65h5trb5.js";
import"./chunk-s2akeqpw.js";
import"./chunk-6771vrjp.js";
import"./chunk-g8mm42v1.js";
import"./chunk-50hzjdck.js";
import"./chunk-nn2jb0x0.js";
import"./chunk-v8xvth6j.js";
import"./chunk-kkk13rcb.js";
import"./chunk-ytpp1kam.js";
import"./chunk-na956zz3.js";
import"./chunk-f4bw8q7c.js";
import"./chunk-0v8vgrns.js";
import"./chunk-54qt5g7m.js";
import"./chunk-dhs2bg35.js";
// src/server/ui-server-entry.ts
var {serve } = globalThis.Bun;
import { writeFileSync, mkdirSync, unlinkSync as unlinkSync2, renameSync } from "fs";
import { join as join4, dirname as dirname2 } from "path";
// src/server/routes/static.ts
import { readFileSync, statSync } from "fs";
import { join, resolve } from "path";
import path from "path";
import { fileURLToPath } from "url";
// src/server/utils/mime-types.ts
function getContentType(pathname) {
const ext = pathname.split(".").pop();
switch (ext) {
case "js":
case "mjs":
return "application/javascript";
case "css":
return "text/css";
case "html":
return "text/html";
case "json":
return "application/json";
case "svg":
return "image/svg+xml";
case "png":
return "image/png";
case "jpg":
case "jpeg":
return "image/jpeg";
case "woff":
return "font/woff";
case "woff2":
return "font/woff2";
default:
return "text/plain";
}
}
// src/server/routes/static.ts
function isFile(path2) {
try {
return statSync(path2).isFile();
} catch {
return false;
}
}
function getUIDistPath() {
const __filename2 = fileURLToPath(import.meta.url);
const __dirname2 = path.dirname(__filename2);
const isBinary = !__filename2.endsWith(".js") && !__filename2.endsWith(".ts");
if (isBinary) {
const execDir = path.dirname(process.execPath);
return resolve(execDir, "assets/ui-dist");
} else {
const isCompiledDist = __filename2.includes("/dist/");
return isCompiledDist ? resolve(__dirname2, "../assets/ui-dist") : resolve(__dirname2, "../../../assets/ui-dist");
}
}
function serveStaticFile(pathname, uiDistPath, req) {
const headers = { ...getCorsHeaders(req) };
if (pathname === "/") {
if (hasEmbeddedAssets()) {
const asset = getEmbeddedAsset("/index.html");
if (asset) {
return new Response(Bun.file(asset.file).stream(), {
headers: {
"Content-Type": asset.mimeType,
...headers
}
});
}
}
const indexPath2 = join(uiDistPath, "index.html");
if (isFile(indexPath2)) {
return new Response(readFileSync(indexPath2), {
headers: {
"Content-Type": "text/html",
...headers
}
});
}
return new Response('UI not built. Run "bun run build:ui" first.', {
status: 404,
headers
});
}
if (hasEmbeddedAssets()) {
const asset = getEmbeddedAsset(pathname);
if (asset) {
return new Response(Bun.file(asset.file).stream(), {
headers: {
"Content-Type": asset.mimeType,
...headers
}
});
}
}
let filePath = join(uiDistPath, pathname);
if (pathname.startsWith("/assets/")) {
filePath = join(uiDistPath, pathname);
}
if (isFile(filePath)) {
const file = readFileSync(filePath);
return new Response(file, {
headers: {
"Content-Type": getContentType(pathname),
...headers
}
});
}
if (hasEmbeddedAssets()) {
const asset = getEmbeddedAsset("/index.html");
if (asset) {
return new Response(Bun.file(asset.file).stream(), {
headers: {
"Content-Type": asset.mimeType,
...headers
}
});
}
}
const indexPath = join(uiDistPath, "index.html");
if (isFile(indexPath)) {
return new Response(readFileSync(indexPath), {
headers: {
"Content-Type": "text/html",
...headers
}
});
}
return new Response("Not Found", {
status: 404,
headers
});
}
// src/server/proxy.ts
var logger = createCliLogger({ tag: "proxy" });
var HOP_BY_HOP_REQUEST_HEADERS = new Set([
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailer",
"transfer-encoding",
"upgrade",
"host"
]);
var HOP_BY_HOP_RESPONSE_HEADERS = new Set([
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailer",
"transfer-encoding",
"upgrade"
]);
function filterHeaders(source, denyList) {
const out = new Headers;
source.forEach((value, key) => {
if (!denyList.has(key.toLowerCase())) {
out.set(key, value);
}
});
return out;
}
var LONG_LIVED_BACKEND_PATHS = new Set([
"/api/evals/run",
"/api/components/events",
"/api/agent0/events",
"/api/cognitive/v1/chat/completions"
]);
function isLongLivedProxyRequest(req, backendPathname) {
const url = new URL(req.url);
const pathname = backendPathname ?? url.pathname;
const accept = req.headers.get("accept") ?? "";
return pathname.includes("/stream") || LONG_LIVED_BACKEND_PATHS.has(pathname) || accept.includes("text/event-stream") && pathname.startsWith("/api/");
}
var PROXY_RETRY_BASE_DELAY_MS = 500;
var PROXY_MAX_RETRIES = 3;
async function proxyToBackend(req, backendPort, options = {}) {
const url = new URL(req.url);
const backendUrl = `http://127.0.0.1:${backendPort}${options.pathname ?? url.pathname}${options.search ?? url.search}`;
const requestLabel = options.pathname ? `${url.pathname} -> ${options.pathname}` : url.pathname;
const isLongLived = isLongLivedProxyRequest(req, options.pathname);
const bodyBuffer = !isLongLived && req.body ? await req.arrayBuffer() : null;
const canRetry = !isLongLived;
const attempt = async () => {
const init = {
method: req.method,
headers: filterHeaders(req.headers, HOP_BY_HOP_REQUEST_HEADERS),
body: bodyBuffer ?? req.body,
redirect: "manual",
signal: isLongLived ? undefined : AbortSignal.timeout(30000)
};
if (init.body) {
init.duplex = "half";
}
const response = await fetch(backendUrl, init);
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: filterHeaders(response.headers, HOP_BY_HOP_RESPONSE_HEADERS)
});
};
for (let retry = 0;; retry++) {
try {
return await withRequestTiming(`${req.method} ${requestLabel} -> 127.0.0.1:${backendPort}`, attempt);
} catch (error) {
if (canRetry && retry < PROXY_MAX_RETRIES) {
const delay = PROXY_RETRY_BASE_DELAY_MS * 2 ** retry;
logger.warn(`Retry ${retry + 1}/${PROXY_MAX_RETRIES} in ${delay}ms \u2192 ${req.method} ${options.pathname ?? url.pathname} \u2192 127.0.0.1:${backendPort}`);
await new Promise((resolve2) => setTimeout(resolve2, delay));
continue;
}
const errorDetail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
logger.warn(`502 \u2192 ${req.method} ${options.pathname ?? url.pathname} \u2192 127.0.0.1:${backendPort} \u2014 ${errorDetail}`);
return new Response(JSON.stringify({
error: "Backend unavailable",
message: "The agent backend server is not responding. It may be restarting."
}), {
status: 502,
headers: {
"Content-Type": "application/json",
...getCorsHeaders(req)
}
});
}
}
}
// src/server/socket/server.ts
var {listen } = globalThis.Bun;
import { existsSync, unlinkSync, chmodSync } from "fs";
var logger2 = createCliLogger({ tag: "socket-server" });
var HEARTBEAT_TIMEOUT_MS = 15000;
var EVICTION_SWEEP_INTERVAL_MS = HEARTBEAT_TIMEOUT_MS / 2;
var MANAGED_SHUTDOWN_GRACE_MS = 2000;
function formatRuntimeMajorMismatchUpgradeCommand(runtimeVersion, expectedRuntimeVersion) {
const expectedMajor = getRuntimeMajor(expectedRuntimeVersion);
const supportedRuntime = expectedMajor !== null ? `^${expectedMajor}.x` : `v${expectedRuntimeVersion}`;
const prefix = `This DevConsole supports @botpress/runtime ${supportedRuntime}, but the agent is running v${runtimeVersion}.`;
const relation = getRuntimeMajorRelation(runtimeVersion, expectedRuntimeVersion);
if (relation === "newer") {
return `${prefix} Run \`adk self-upgrade\` to update this CLI/DevConsole. ` + `If you intentionally need this older CLI line, pin the agent's runtime back to ${supportedRuntime}.`;
}
if (relation === "older") {
return `${prefix} Run \`adk project upgrade --dry-run\` to review required project updates, ` + "then `adk project upgrade` to apply them.";
}
return `${prefix} Run \`adk self-upgrade\` to update this CLI/DevConsole, ` + "or run `adk project upgrade --dry-run` to review project updates.";
}
class ConsoleSocketServer {
options;
server = null;
registry = new Map;
clients = new Set;
heartbeatTimer = null;
shutdownTimer = null;
constructor(options) {
this.options = options;
}
async start() {
const { socketPath } = this.options;
if (existsSync(socketPath)) {
if (await probeUnixSocket(socketPath)) {
throw new AdkError({
code: "SOCKET_ALREADY_BOUND",
message: `${socketPath} is already bound by a live process. ` + `Refusing to unlink \u2014 reuse the existing singleton instead.`
});
}
try {
unlinkSync(socketPath);
} catch (err) {
const reason = err instanceof Error ? err.message : String(err);
throw new AdkError({
code: "SOCKET_UNLINK_FAILED",
message: `Cannot remove stale socket at ${socketPath}: ${reason}. ` + `Check file ownership, or remove stale files with: rm ~/.adk/console.lock ~/.adk/console.port ~/.adk/console.sock`,
cause: err
});
}
}
this.server = listen({
unix: socketPath,
socket: {
data: (socket, data) => this.handleData(socket, data),
open: (socket) => this.handleOpen(socket),
close: (socket) => this.handleClose(socket),
error: (_socket, error) => {
logger2.error(`Connection error: ${error.message}`);
}
}
});
try {
chmodSync(socketPath, 384);
} catch (err) {
logger2.warn(`Could not tighten permissions on ${socketPath}: ${err instanceof Error ? err.message : String(err)}`);
}
this.heartbeatTimer = setInterval(() => this.evictStaleAgents(), EVICTION_SWEEP_INTERVAL_MS);
}
stop() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
if (this.shutdownTimer) {
clearTimeout(this.shutdownTimer);
this.shutdownTimer = null;
}
if (this.server) {
this.server.stop(true);
this.server = null;
}
try {
if (existsSync(this.options.socketPath))
unlinkSync(this.options.socketPath);
} catch {}
this.registry.clear();
this.clients.clear();
}
getAgents() {
return Array.from(this.registry.values());
}
requestAgentShutdown(agentPath) {
let sent = false;
const msg = {
v: PROTOCOL_VERSION,
type: "shutdown",
agentPath
};
for (const client of this.clients) {
if (client.data?.agentPath !== agentPath)
continue;
this.sendTo(client, msg);
sent = true;
}
return sent;
}
handleOpen(socket) {
socket.data = {
parser: createLineParser()
};
this.clients.add(socket);
}
handleClose(socket) {
this.clients.delete(socket);
if (!socket.data?.agentPath)
return;
const agentPath = socket.data.agentPath;
socket.data.agentPath = undefined;
const successor = Array.from(this.clients).some((c) => c.data?.agentPath === agentPath);
if (successor)
return;
this.registry.delete(agentPath);
this.broadcastAgentsUpdated();
this.checkEmpty();
}
handleData(socket, data) {
if (!socket.data?.parser)
return;
const rawMessages = socket.data.parser.feed(data.toString());
for (const raw of rawMessages) {
const msg = parseAgentToServerMessage(raw);
if (!msg) {
logger2.warn("Ignoring malformed or unsupported agent message");
continue;
}
switch (msg.type) {
case "register":
this.handleRegister(socket, msg);
break;
case "deregister":
this.handleDeregister(socket, msg.agentPath);
break;
case "heartbeat":
this.handleHeartbeat(socket, msg.agentPath, msg.status, msg.process);
break;
}
}
}
handleRegister(socket, msg) {
const { name, agentPath, backendPort, botPort, runtimeVersion, cliVersion, process: processInfo } = msg;
if (runtimeVersion === undefined) {
logger2.warn("agent registered without runtime version; run `adk project upgrade`");
}
let outdated = false;
if (runtimeVersion !== undefined && this.options.expectedRuntimeVersion) {
const status = getRuntimeVersionStatus(runtimeVersion, this.options.expectedRuntimeVersion);
const expectedMajor = getRuntimeMajor(this.options.expectedRuntimeVersion);
if (status === "major_mismatch" && expectedMajor !== null) {
const upgradeCommand = formatRuntimeMajorMismatchUpgradeCommand(runtimeVersion, this.options.expectedRuntimeVersion);
logger2.warn(`rejecting agent at ${agentPath}: runtime v${runtimeVersion} is on a different major, server expects major ${expectedMajor}`);
this.sendTo(socket, {
v: PROTOCOL_VERSION,
type: "rejected",
reason: "runtime_major_mismatch",
agentRuntime: runtimeVersion,
expectedRuntimeMajor: expectedMajor,
upgradeCommand
});
socket.end();
this.checkEmpty();
return;
}
if (status === "outdated") {
outdated = true;
}
}
const now = Date.now();
const agent = {
name,
agentPath,
backendPort,
botPort,
status: "starting",
connectedAt: now,
lastHeartbeat: now,
runtimeVersion: runtimeVersion ?? "unknown",
cliVersion,
outdated: outdated || undefined,
process: processInfo ?? undefined
};
const existing = this.registry.get(agentPath);
if (existing) {
for (const client of this.clients) {
if (client !== socket && client.data?.agentPath === agentPath) {
client.data.agentPath = undefined;
}
}
}
this.registry.set(agentPath, agent);
socket.data.agentPath = agentPath;
if (existing) {
logger2.warn(`Replacing existing agent at ${agentPath}`);
}
if (this.shutdownTimer) {
clearTimeout(this.shutdownTimer);
this.shutdownTimer = null;
}
this.sendTo(socket, { v: PROTOCOL_VERSION, type: "registered", agents: this.getAgents() });
this.broadcastAgentsUpdated();
}
handleDeregister(socket, agentPath) {
if (socket.data?.agentPath !== agentPath)
return;
socket.data.agentPath = undefined;
this.registry.delete(agentPath);
this.broadcastAgentsUpdated();
this.checkEmpty();
}
handleHeartbeat(socket, agentPath, status, processInfo) {
if (socket.data?.agentPath !== agentPath)
return;
const agent = this.registry.get(agentPath);
if (agent) {
const previousStatus = agent.status;
agent.lastHeartbeat = Date.now();
agent.status = status;
if (processInfo !== undefined) {
agent.process = processInfo;
}
if (previousStatus !== status) {
this.broadcastAgentsUpdated();
}
}
}
evictStaleAgents() {
const now = Date.now();
let changed = false;
for (const [path2, agent] of this.registry) {
if (now - agent.lastHeartbeat > HEARTBEAT_TIMEOUT_MS) {
this.registry.delete(path2);
for (const client of this.clients) {
if (client.data?.agentPath === path2) {
client.data.agentPath = undefined;
try {
client.end();
} catch {}
}
}
changed = true;
}
}
if (changed) {
this.broadcastAgentsUpdated();
this.checkEmpty();
}
}
broadcastAgentsUpdated() {
const msg = {
v: PROTOCOL_VERSION,
type: "agents_updated",
agents: this.getAgents()
};
const encoded = encode(msg);
for (const client of this.clients) {
try {
client.write(encoded);
} catch {}
}
}
sendTo(socket, msg) {
try {
socket.write(encode(msg));
} catch {}
}
checkEmpty() {
if (this.registry.size > 0)
return;
if (this.options.standalone)
return;
if (this.shutdownTimer)
return;
this.shutdownTimer = setTimeout(() => {
if (this.registry.size === 0) {
this.options.onEmpty?.();
}
this.shutdownTimer = null;
}, MANAGED_SHUTDOWN_GRACE_MS);
}
}
// src/server/agent-resolver.ts
function resolveTargetAgent(url, req, currentAgents) {
if (currentAgents.length === 0)
return { kind: "no-agents" };
const agentParam = url.searchParams.get("agent");
if (agentParam) {
const found = currentAgents.find((a) => a.agentPath === agentParam);
return found ? { kind: "found", agent: found } : { kind: "unknown-agent", requested: agentParam, agents: currentAgents };
}
const agentHeader = req.headers.get("x-agent-path");
if (agentHeader) {
const found = currentAgents.find((a) => a.agentPath === agentHeader);
return found ? { kind: "found", agent: found } : { kind: "unknown-agent", requested: agentHeader, agents: currentAgents };
}
if (currentAgents.length === 1) {
return { kind: "found", agent: currentAgents[0] };
}
return { kind: "ambiguous", agents: currentAgents };
}
function agentResolutionError(result) {
const agentSummary = (a) => ({ name: a.name, agentPath: a.agentPath });
if (result.kind === "no-agents") {
return new Response(JSON.stringify({
error: "No agents connected",
message: "No agent backends are registered. Open a project or start one with `adk dev`.",
agents: []
}), { status: 503, headers: { "Content-Type": "application/json" } });
}
if (result.kind === "unknown-agent") {
return new Response(JSON.stringify({
error: "Unknown agent",
message: `Agent '${result.requested}' is not registered. Available: ${result.agents.map((a) => a.agentPath).join(", ")}.`,
requested: result.requested,
agents: result.agents.map(agentSummary)
}), { status: 404, headers: { "Content-Type": "application/json" } });
}
return new Response(JSON.stringify({
error: "Ambiguous agent",
message: `${result.agents.length} agents are registered. Specify which one via '?agent=<agentPath>' (browser) or 'X-Agent-Path: <agentPath>' (CLI). Available: ${result.agents.map((a) => a.agentPath).join(", ")}.`,
agents: result.agents.map(agentSummary)
}), { status: 400, headers: { "Content-Type": "application/json" } });
}
// src/server/handlers/auth.ts
var logger3 = createCliLogger({ tag: "auth" });
var activeSession = null;
async function handleAuthRequest(pathname, req, options = {}) {
if (pathname === "/api/auth/login") {
if (req.method !== "POST")
return errorResponse("Method not allowed", "Only POST is supported", 405);
return handleAuthLogin();
}
if (pathname === "/api/auth/login/status") {
if (req.method !== "GET")
return errorResponse("Method not allowed", "Only GET is supported", 405);
return handleAuthLoginStatus(req);
}
if (pathname === "/api/auth/login/token") {
if (req.method !== "POST")
return errorResponse("Method not allowed", "Only POST is supported", 405);
return handleAuthLoginToken(req);
}
if (pathname === "/api/auth/login/cancel") {
if (req.method !== "POST")
return errorResponse("Method not allowed", "Only POST is supported", 405);
return handleAuthLoginCancel();
}
if (pathname === "/api/auth/identity") {
if (req.method !== "GET")
return errorResponse("Method not allowed", "Only GET is supported", 405);
return handleAuthIdentity();
}
if (pathname === "/api/auth/profiles") {
if (req.method !== "GET")
return errorResponse("Method not allowed", "Only GET is supported", 405);
return handleAuthProfiles();
}
if (pathname === "/api/auth/profiles/switch") {
if (req.method !== "POST")
return errorResponse("Method not allowed", "Only POST is supported", 405);
return handleAuthProfileSwitch(req);
}
if (pathname === "/api/auth/logout") {
if (req.method !== "POST")
return errorResponse("Method not allowed", "Only POST is supported", 405);
return handleAuthLogout(req, options);
}
return null;
}
async function handleAuthLogin() {
if (activeSession) {
activeSession.cancel();
activeSession = null;
}
let callbackUrl;
let waitForToken;
let cancel;
try {
({ url: callbackUrl, waitForToken, cancel } = await startCallbackServer({ timeout: 300000 }));
} catch (error) {
return errorResponse("Login start failed", error instanceof Error ? error.message : "Failed to start login", 500);
}
const loginUrl = getLoginUrl(callbackUrl);
const sessionId = crypto.randomUUID();
const session = {
id: sessionId,
cancel,
resolved: false,
result: null,
loginSaved: false,
loginPromise: null
};
waitForToken().then((result) => {
if (activeSession?.id === sessionId) {
session.resolved = true;
session.result = result;
}
}).catch((err) => {
logger3.debug(`login callback rejected: ${err instanceof Error ? err.message : String(err)}`);
if (activeSession?.id === sessionId) {
session.resolved = true;
session.result = { token: "", success: false };
}
});
activeSession = session;
return successResponse({ loginUrl, sessionId });
}
async function handleAuthLoginStatus(req) {
const url = new URL(req.url);
const sessionId = url.searchParams.get("session");
if (!activeSession || activeSession.id !== sessionId) {
return successResponse({ status: "expired" });
}
if (!activeSession.resolved) {
return successResponse({ status: "pending" });
}
const result = activeSession.result;
if (!result?.success || !result.token) {
activeSession = null;
return successResponse({ status: "error", message: "Login timed out or was cancelled." });
}
if (activeSession.loginSaved) {
if (activeSession.loginPromise) {
try {
await activeSession.loginPromise;
} catch (error) {
if (activeSession?.id === sessionId)
activeSession = null;
return successResponse({
status: "error",
message: error instanceof Error ? error.message : "Login failed"
});
}
}
const profile = await auth.getCurrentProfileDetails();
if (activeSession?.id === sessionId)
activeSession = null;
return successResponse({
status: "success",
identity: {
accountId: profile?.accountId,
email: profile?.email,
displayName: profile?.displayName
}
});
}
activeSession.loginSaved = true;
const sessionRef = activeSession;
activeSession.loginPromise = auth.login(result.token);
try {
await activeSession.loginPromise;
const profile = await auth.getCurrentProfileDetails();
if (activeSession === sessionRef)
activeSession = null;
return successResponse({
status: "success",
identity: {
accountId: profile?.accountId,
email: profile?.email,
displayName: profile?.displayName
}
});
} catch (error) {
if (activeSession === sessionRef)
activeSession = null;
return successResponse({
status: "error",
message: error instanceof Error ? error.message : "Login failed"
});
}
}
async function handleAuthLoginToken(req) {
let body;
try {
body = await req.json();
} catch {
return errorResponse("Invalid body", "Expected JSON body with token field", 400);
}
if (!body.token || typeof body.token !== "string") {
return errorResponse("Missing token", "Token is required", 400);
}
try {
await auth.login(body.token);
const profile = await auth.getCurrentProfileDetails();
return successResponse({
identity: {
accountId: profile?.accountId,
email: profile?.email,
displayName: profile?.displayName
}
});
} catch (error) {
return errorResponse("Login failed", error instanceof Error ? error.message : "Invalid token", 401);
}
}
async function handleAuthLoginCancel() {
if (activeSession) {
activeSession.cancel();
activeSession = null;
}
return successResponse({ status: "cancelled" });
}
async function handleAuthIdentity() {
try {
const profile = await auth.getCurrentProfileDetails();
if (!profile) {
return successResponse({ authenticated: false });
}
return successResponse({
authenticated: true,
identity: {
accountId: profile.accountId,
email: profile.email,
displayName: profile.displayName
}
});
} catch (err) {
logger3.debug(`identity lookup failed: ${err instanceof Error ? err.message : String(err)}`);
return successResponse({ authenticated: false });
}
}
async function handleAuthProfiles() {
try {
const profiles = await auth.listProfiles();
const currentProfile = await auth.getCurrentProfile();
return successResponse({
profiles: profiles.map((p) => ({
name: p.name,
email: p.email,
displayName: p.displayName,
apiUrl: p.apiUrl,
isCurrent: p.name === currentProfile
})),
currentProfile
});
} catch (err) {
logger3.debug(`listing profiles failed: ${err instanceof Error ? err.message : String(err)}`);
return successResponse({ profiles: [], currentProfile: null });
}
}
async function handleAuthProfileSwitch(req) {
let body;
try {
body = await req.json();
} catch {
return errorResponse("Invalid body", "Expected JSON body with profile field", 400);
}
if (!body.profile || typeof body.profile !== "string") {
return errorResponse("Missing profile", "Profile name is required", 400);
}
try {
await auth.setCurrentProfile(body.profile);
const details = await auth.getCurrentProfileDetails();
return successResponse({
identity: {
accountId: details?.accountId,
email: details?.email,
displayName: details?.displayName
}
});
} catch (error) {
return errorResponse("Switch failed", error instanceof Error ? error.message : "Failed to switch profile", 400);
}
}
async function handleAuthLogout(req, options) {
let body = {};
try {
body = await req.json();
} catch {}
try {
const profile = typeof body.profile === "string" ? body.profile : undefined;
await auth.logout(profile);
try {
await options.onLogout?.();
} catch {}
return successResponse({ status: "logged-out" });
} catch (error) {
return errorResponse("Logout failed", error instanceof Error ? error.message : "Failed to logout", 500);
}
}
// src/server/project-supervisor.ts
import { spawn, execFile } from "child_process";
import { createWriteStream, existsSync as existsSync2 } from "fs";
import { mkdir, readFile, rename, stat, writeFile } from "fs/promises";
import { homedir } from "os";
import { basename, dirname, join as join2, resolve as resolve2 } from "path";
var __dirname = "/home/runner/work/agent-lack/agent-lack/packages/cli/src/server";
var ADK_DIR = join2(homedir(), ".adk");
var SHUTDOWN_GRACE_MS = 4000;
var FORCE_GRACE_MS = 2000;
var MANAGED_PRUNE_MS = 60000;
var jsonWriteCounter = 0;
function jsonErrorMessage(error) {
return error instanceof Error ? error.message : String(error);
}
function expandHome(rawPath) {
if (rawPath === "~")
return homedir();
if (rawPath.startsWith("~/"))
return join2(homedir(), rawPath.slice(2));
return rawPath;
}
function timestampForFile(date = new Date) {
return date.toISOString().replaceAll(":", "-").replaceAll(".", "-");
}
async function readJsonFile(path2, fallback) {
try {
return JSON.parse(await readFile(path2, "utf-8"));
} catch {
return fallback;
}
}
async function writeJsonFile(path2, value) {
await mkdir(dirname(path2), { recursive: true });
const tmpPath = join2(dirname(path2), `.${basename(path2)}.${process.pid}.${process.hrtime.bigint()}.${++jsonWriteCounter}.tmp`);
await writeFile(tmpPath, `${JSON.stringify(value, null, 2)}
`, { encoding: "utf-8", mode: 384 });
await rename(tmpPath, path2);
}
function execFileString(command, args) {
return new Promise((resolve3, reject) => {
execFile(command, args, { encoding: "utf-8" }, (error, stdout, stderr) => {
if (error) {
reject(new Error(stderr.trim() || error.message));
return;
}
resolve3(stdout.trim());
});
});
}
function isMissingCommandError(error) {
const message = jsonErrorMessage(error).toLowerCase();
return message.includes("enoent") || message.includes("not found");
}
function isUserCancelledDialogError(error) {
const message = jsonErrorMessage(error);
return message.includes("-128") || message.toLowerCase().includes("cancel");
}
async function pickFolder() {
try {
if (process.platform === "darwin") {
const path2 = await execFileString("osascript", [
"-e",
'POSIX path of (choose folder with prompt "Choose an ADK project folder")'
]);
return path2 ? { path: path2 } : { cancelled: true };
}
if (process.platform === "win32") {
const path2 = await execFileString("powershell.exe", [
"-NoProfile",
"-STA",
"-Command",
[
"Add-Type -AssemblyName System.Windows.Forms;",
"$dialog = New-Object System.Windows.Forms.FolderBrowserDialog;",
'$dialog.Description = "Choose an ADK project folder";',
"if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $dialog.SelectedPath }"
].join(" ")
]);
return path2 ? { path: path2 } : { cancelled: true };
}
try {
const path2 = await execFileString("zenity", ["--file-selection", "--directory", "--title", "Choose ADK project"]);
return path2 ? { path: path2 } : { cancelled: true };
} catch (error) {
if (!isMissingCommandError(error))
return { cancelled: true };
try {
const path2 = await execFileString("kdialog", ["--getexistingdirectory", homedir()]);
return path2 ? { path: path2 } : { cancelled: true };
} catch (fallbackError) {
if (!isMissingCommandError(fallbackError))
return { cancelled: true };
throw fallbackError;
}
}
} catch (error) {
if (isUserCancelledDialogError(error)) {
return { cancelled: true };
}
throw error;
}
}
function resolveCliSpawnCommand() {
const isVirtualBunPath = (path2) => path2.includes("$bunfs");
const isCompiledExecutable = process.argv.some((arg) => isVirtualBunPath(arg));
if (isCompiledExecutable) {
return { command: process.execPath, argsPrefix: [] };
}
const scriptDir = process.argv[1] && !isVirtualBunPath(process.argv[1]) ? dirname(process.argv[1]) : __dirname;
const candidates = [
join2(__dirname, "..", "cli.ts"),
join2(__dirname, "..", "cli.js"),
join2(scriptDir, "..", "cli.ts"),
join2(scriptDir, "..", "cli.js"),
join2(scriptDir, "cli.js")
];
for (const candidate of candidates) {
if (!isVirtualBunPath(candidate) && existsSync2(candidate))
return { command: process.execPath, argsPrefix: [candidate] };
}
throw new AdkError({
code: "CLI_ENTRY_NOT_FOUND",
message: "Could not locate the adk CLI entry point. " + `Searched: ${candidates.map((c) => c.replace(homedir(), "~")).join(", ")}`
});
}
function signalChild(child, signal) {
if (!child.pid)
return false;
if (process.platform === "win32")
return child.kill(signal);
try {
process.kill(-child.pid, signal);
return true;
} catch {
return child.kill(signal);
}
}
function isProcessAlive(project) {
return !!project && project.status !== "exited" && project.exitCode == null && project.signal == null && project.child.exitCode === null && project.child.signalCode == null;
}
function isLaunchInProgress(project) {
return isProcessAlive(project) && project.status === "launching";
}
async function sleep(ms) {
await new Promise((resolve3) => setTimeout(resolve3, ms));
}
function parseFatalLogRecord(line) {
try {
const parsed = JSON.parse(line);
if (parsed.level !== "error" || parsed.fatal !== true || typeof parsed.message !== "string")
return null;
return {
message: parsed.message,
details: parsed.details,
stack: typeof parsed.stack === "string" ? parsed.stack : undefined
};
} catch {
return null;
}
}
function formatLaunchExitError(project) {
if (project.exitCode === 0 && !project.signal) {
return {
message: "adk dev exited cleanly before registering with the Dev Console. " + "This usually means a project-level validation failed. Check the log for details.",
details: { exitCode: 0 },
logPath: project.logPath
};
}
const exitReason = project.exitCode === null || project.exitCode === undefined ? project.signal ? `signal ${project.signal}` : "unknown exit reason" : `exit code ${project.exitCode}`;
return {
message: `adk dev exited before registering with the Dev Console (${exitReason}).`,
logPath: project.logPath
};
}
function createLineCollector(onLine) {
let pending = "";
return (chunk) => {
pending += chunk.toString();
const lines = pending.split(/\r?\n/);
pending = lines.pop() ?? "";
for (const line of lines) {
if (line.trim())
onLine(line);
}
};
}
class ProjectSupervisor {
options;
managed = new Map;
launchPromises = new Map;
recentProjectsFile;
managedProjectsFile;
constructor(options) {
this.options = options;
const adkDir = options.adkDir ?? ADK_DIR;
this.recentProjectsFile = join2(adkDir, "recent-projects.json");
this.managedProjectsFile = join2(adkDir, "managed-projects.json");
}
async handle(pathname, req) {
try {
if (pathname === "/api/projects/recent") {
if (req.method !== "GET")
return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
return successResponse({ projects: await this.readRecentProjects() });
}
if (pathname === "/api/projects/pick") {
if (req.method !== "POST")
return errorResponse("Method not allowed", "Only POST requests are allowed", 405);
const result = await pickFolder();
return successResponse(result);
}
if (pathname === "/api/projects/open") {
if (req.method !== "POST")
return errorResponse("Method not allowed", "Only POST requests are allowed", 405);
return this.handleOpenProject(req);
}
if (pathname === "/api/projects/launch-status") {
if (req.method !== "GET")
return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
return this.handleLaunchStatus(req);
}
if (pathname === "/api/agents/terminate") {
if (req.method !== "POST")
return errorResponse("Method not allowed", "Only POST requests are allowed", 405);
return this.handleTerminateAgent(req);
}
return null;
} catch (error) {
return errorResponse("Request failed", jsonErrorMessage(error), 500);
}
}
async flushManagedLedger() {
await this.writeManagedProjects();
}
async shutdownManagedProjects() {
const projects = Array.from(this.managed.values());
for (const project of projects) {
if (isProcessAlive(project))
signalChild(project.child, "SIGTERM");
}
await Promise.all(projects.map((project) => this.waitForChildExit(project, 1000)));
for (const project of projects) {
if (isProcessAlive(project))
signalChild(project.child, "SIGKILL");
}
await Promise.all(projects.map((project) => this.waitForChildExit(project, 1000)));
await this.writeManagedProjects();
}
async handleOpenProject(req) {
let body;
try {
body = await req.json();
} catch {
return errorResponse("Invalid body", "Expected a JSON body with a `path` field.", 400);
}
if (typeof body.path !== "string" || !body.path.trim()) {
return errorResponse("Missing path", "Missing required `path` field.", 400);
}
let agentRoot;
try {
agentRoot = await this.resolveAgentRoot(body.path);
} catch (error) {
return errorResponse("Invalid project", jsonErrorMessage(error), 400);
}
const projectInfo = {
path: agentRoot,
name: basename(agentRoot),
lastOpenedAt: new Date().toISOString()
};
try {
await (this.options.preflightRuntimeVersion ?? preflightRuntimeVersionCheck)(agentRoot);
} catch (error) {
if (error instanceof RuntimeVersionMismatchError) {
return jsonResponse({
error: "Runtime version mismatch",
message: formatRuntimeVersionMismatchMessage(error),
project: projectInfo,
details: {
installedVersion: error.installedVersion,
expectedVersion: error.expectedVersion,
status: error.status,
installCommand: error.installCommand,
resolution: error.resolution
}
}, 409);
}
return errorResponse("Runtime check failed", jsonErrorMessage(error), 400);
}
let project;
try {
project = await this.loadProject(agentRoot);
projectInfo.name = project.config?.name || projectInfo.name;
} catch (error) {
return errorResponse("Invalid project", jsonErrorMessage(error), 400);
}
const name = projectInfo.name;
const recent = await this.upsertRecentProject(projectInfo);
const existingAgent = this.options.getAgents().find((agent) => agent.agentPath === agentRoot);
if (existingAgent) {
return successResponse({ status: "already-running", project: recent, agent: existingAgent });
}
const existingLaunch = this.managed.get(agentRoot);
if (isLaunchInProgress(existingLaunch)) {
return successResponse({ status: "launching", project: recent, launch: this.toManagedRecord(existingLaunch) });
}
const pendingLaunch = this.launchPromises.get(agentRoot);
if (pendingLaunch) {
try {
const launch = await pendingLaunch;
return successResponse({ status: "launching", project: recent, launch: this.toManagedRecord(launch) });
} catch (error) {
return errorResponse("Launch failed", jsonErrorMessage(error), 500);
}
}
const failedLaunch = this.managed.get(agentRoot);
if (failedLaunch?.status === "failed" && isProcessAlive(failedLaunch)) {
signalChild(failedLaunch.child, "SIGTERM");
const stopped = await this.waitForChildExit(failedLaunch, FORCE_GRACE_MS);
if (!stopped) {
signalChild(failedLaunch.child, "SIGKILL");
await this.waitForChildExit(failedLaunch, FORCE_GRACE_MS);
}
}
try {
const launchPromise = this.launchProject(agentRoot, name).finally(() => {
this.launchPromises.delete(agentRoot);
});
this.launchPromises.set(agentRoot, launchPromise);
const launch = await launchPromise;
return successResponse({ status: "launching", project: recent, launch: this.toManagedRecord(launch) });
} catch (error) {
return errorResponse("Launch failed", jsonErrorMessage(error), 500);
}
}
async handleLaunchStatus(req) {
const url = new URL(req.url);
const rawPath = url.searchParams.get("path");
if (!rawPath?.trim()) {
return errorResponse("Missing path", "Missing required `path` query parameter.", 400);
}
const agentPath = resolve2(expandHome(rawPath));
const agent = this.options.getAgents().find((entry) => entry.agentPath === agentPath);
if (agent) {
return successResponse({ status: "registered", agent });
}
const launch = this.managed.get(agentPath);
if (!launch) {
return successResponse({ status: "unknown" });
}
if (launch.status === "failed") {
return successResponse({
status: "failed",
message: launch.error?.message ?? "Project failed to launch.",
details: launch.error?.details,
stack: launch.error?.stack,
logPath: launch.error?.logPath ?? launch.logPath
});
}
if (launch.status === "exited") {
const error = launch.error ?? formatLaunchExitError(launch);
return successResponse({
status: "failed",
message: error.message,
details: error.details,
stack: error.stack,
logPath: error.logPath ?? launch.logPath
});
}
return successResponse({ status: "launching", launch: this.toManagedRecord(launch), logPath: launch.logPath });
}
async handleTerminateAgent(req) {
let body;
try {
body = await req.json();
} catch {
return errorResponse("Invalid body", "Expected a JSON body with an `agentPath` field.", 400);
}
if (typeof body.agentPath !== "string" || !body.agentPath.trim()) {
return errorResponse("Missing agentPath", "Missing required `agentPath` field.", 400);
}
const agentPath = resolve2(expandHome(body.agentPath));
const result = await this.stopProjectAt(agentPath);
if (result.status === "not-running") {
return errorResponse("Unknown agent", `No running agent found at ${agentPath}`, 404);
}
if (result.status === "shutdown-requested") {
return successResponse({
status: "shutdown-requested",
agentPath,
graceful: result.graceful,
forced: false
});
}
return successResponse({
status: "stopped",
agentPath,
graceful: result.graceful,
forced: result.forced,
...result.signal && { signal: result.signal }
});
}
async stopProjectAt(agentPath) {
const agent = this.options.getAgents().find((entry) => entry.agentPath === agentPath);
const managed = this.managed.get(agentPath);
if (!agent && !isProcessAlive(managed)) {
return { status: "not-running" };
}
const gracefulRequested = agent ? this.options.requestAgentShutdown(agentPath) : false;
if (gracefulRequested) {
const stopped = await this.waitForAgentGone(agentPath, SHUTDOWN_GRACE_MS);
if (stopped) {
return { status: "stopped", graceful: true, forced: false };
}
}
if (!isProcessAlive(managed)) {
return { status: "shutdown-requested", graceful: gracefulRequested };
}
signalChild(managed.child, "SIGTERM");
const terminated = await this.waitForChildExit(managed, FORCE_GRACE_MS);
if (terminated) {
return { status: "stopped", graceful: gracefulRequested, forced: true, signal: "SIGTERM" };
}
signalChild(managed.child, "SIGKILL");
await this.waitForChildExit(managed, FORCE_GRACE_MS);
return { status: "stopped", graceful: gracefulRequested, forced: true, signal: "SIGKILL" };
}
async resolveAgentRoot(rawPath) {
const absolutePath = resolve2(expandHome(rawPath.trim()));
const pathStat = await stat(absolutePath).catch(() => null);
if (!pathStat)
throw new AdkError({
code: "PROJECT_PATH_MISSING",
message: `Path does not exist: ${absolutePath}`,
expected: true
});
const startPath = pathStat.isDirectory() ? absolutePath : dirname(absolutePath);
const agentRoot = await findAgentRoot(startPath);
if (!agentRoot)
throw new AdkError({
code: "PROJECT_NOT_FOUND",
message: "No ADK project found. Choose a folder inside a project with agent.config.ts.",
expected: true
});
return agentRoot;
}
async loadProject(agentRoot) {
return (this.options.loadProject ?? AgentProject.load)(agentRoot, {
adkCommand: "adk-dev"
});
}
async launchProject(agentRoot, name) {
const logsDir = join2(agentRoot, ".adk", "logs");
await mkdir(logsDir, { recursive: true });
const logPath = join2(logsDir, `devconsole-open-${timestampForFile()}.log`);
const logStream = createWriteStream(logPath, { flags: "a" });
const { command, argsPrefix } = resolveCliSpawnCommand();
const args = [...argsPrefix, "dev", "--non-interactive", "--port-console", String(this.options.consolePort)];
const child = (this.options.spawnDevProcess ?? spawn)(command, args, {
cwd: agentRoot,
detached: process.platform !== "win32",
stdio: ["ignore", "pipe", "pipe"],
env: {
...process.env,
FORCE_COLOR: "0"
}
});
child.stdout?.pipe(logStream);
child.stderr?.pipe(logStream);
const managed = {
agentPath: agentRoot,
name,
pid: child.pid ?? 0,
logPath,
startedAt: new Date().toISOString(),
status: "launching",
child
};
const markLaunchFailed = (error) => {
managed.status = "failed";
managed.error = { ...error, logPath: error.logPath ?? logPath };
this.writeManagedProjects().catch(() => {});
};
const inspectLine = (line) => {
const error = parseFatalLogRecord(line);
if (error)
markLaunchFailed(error);
};
child.stdout?.on("data", createLineCollector(inspectLine));
child.stderr?.on("data", createLineCollector(inspectLine));
child.once("exit", (exitCode, signal) => {
managed.exitCode = exitCode;
managed.signal = signal;
if (managed.status !== "failed") {
managed.status = exitCode === 0 && !signal ? "exited" : "failed";
managed.error = formatLaunchExitError(managed);
}
logStream.end();
this.writeManagedProjects().catch(() => {});
this.scheduleManagedPrune(agentRoot, managed);
});
child.once("error", (error) => {
managed.status = "failed";
managed.exitCode = 1;
managed.error = { message: `Failed to launch adk dev: ${error.message}`, logPath };
logStream.write(`[devconsole] failed to launch: ${error.message}
`);
logStream.end();
this.writeManagedProjects().catch(() => {});
this.scheduleManagedPrune(agentRoot, managed);
});
this.managed.set(agentRoot, managed);
await this.writeManagedProjects();
return managed;
}
async readRecentProjects() {
const projects = await readJsonFile(this.recentProjectsFile, []);
return projects.filter((project) => typeof project.path === "string" && typeof project.name === "string").slice(0, 12);
}
async upsertRecentProject(project) {
const projects = await this.readRecentProjects();
const next = [project, ...projects.filter((entry) => entry.path !== project.path)].slice(0, 12);
await writeJsonFile(this.recentProjectsFile, next);
return project;
}
toManagedRecord(project) {
return {
agentPath: project.agentPath,
name: project.name,
pid: project.pid,
logPath: project.logPath,
startedAt: project.startedAt,
status: project.status,
error: project.error,
exitCode: project.exitCode,
signal: project.signal
};
}
async writeManagedProjects() {
const projects = Array.from(this.managed.values()).map((project) => this.toManagedRecord(project));
await writeJsonFile(this.managedProjectsFile, projects);
}
scheduleManagedPrune(agentRoot, project) {
const timer = setTimeout(() => {
if (this.managed.get(agentRoot) !== project || isProcessAlive(project))
return;
this.managed.delete(agentRoot);
this.writeManagedProjects().catch(() => {});
}, MANAGED_PRUNE_MS);
timer.unref?.();
}
async waitForAgentGone(agentPath, timeoutMs) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
if (!this.options.getAgents().some((agent) => agent.agentPath === agentPath))
return true;
await sleep(150);
}
return !this.options.getAgents().some((agent) => agent.agentPath === agentPath);
}
async waitForChildExit(project, timeoutMs) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
if (!isProcessAlive(project))
return true;
await sleep(150);
}
return !isProcessAlive(project);
}
}
// src/server/project-creator.ts
import { spawn as spawn2 } from "child_process";
import { existsSync as existsSync3 } from "fs";
import { mkdir as mkdir2, readdir, rm, stat as stat2 } from "fs/promises";
import { homedir as homedir2 } from "os";
import { join as join3, resolve as resolve3 } from "path";
var VALID_NAME = /^[a-zA-Z0-9_-]+$/;
var TERMINAL_STATE_TTL_MS = 5 * 60000;
var COMMAND_OUTPUT_LIMIT = 64 * 1024;
var ADK_RUNTIME_BOT_TAGS = { runtime: "adk" };
function expandHome2(rawPath) {
if (rawPath === "~")
return homedir2();
if (rawPath.startsWith("~/"))
return join3(homedir2(), rawPath.slice(2));
return rawPath;
}
function jsonErrorMessage2(error) {
return error instanceof Error ? error.message : String(error);
}
function captureExecError(error) {
if (error instanceof Error) {
const withStderr = error;
const stderr = typeof withStderr.stderr === "string" ? withStderr.stderr.trim() : String(withStderr.stderr ?? "").trim();
if (stderr)
return stderr;
return error.message;
}
return String(error);
}
function parseRef(ref) {
const at = ref.indexOf("@");
if (at < 0)
return { name: ref, version: "latest" };
return { name: ref.slice(0, at), version: ref.slice(at + 1) };
}
function appendCommandOutput(current, chunk) {
const next = current + chunk;
return next.length > COMMAND_OUTPUT_LIMIT ? next.slice(-COMMAND_OUTPUT_LIMIT) : next;
}
class ProjectCreator {
options;
creations = new Map;
constructor(options = {}) {
this.options = options;
}
async handle(pathname, req) {
try {
if (pathname === "/api/templates") {
if (req.method !== "GET")
return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
return successResponse({ templates: this.listTemplates() });
}
if (pathname === "/api/projects/check-path") {
if (req.method !== "GET")
return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
return this.handleCheckPath(req);
}
if (pathname === "/api/projects/create") {
if (req.method !== "POST")
return errorResponse("Method not allowed", "Only POST requests are allowed", 405);
return this.handleCreate(req);
}
if (pathname === "/api/projects/create-status") {
if (req.method !== "GET")
return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
return this.handleCreateStatus(req);
}
if (pathname === "/api/workspaces") {
if (req.method !== "GET")
return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
return this.handleListWorkspaces();
}
const prodBotsMatch = pathname.match(/^\/api\/workspaces\/([^/]+)\/(linkable|viewable)-prod-bots$/);
if (prodBotsMatch) {
if (req.method !== "GET")
return errorResponse("Method not allowed", "Only GET requests are allowed", 405);
return this.handleListProdBots(decodeURIComponent(prodBotsMatch[1]), prodBotsMatch[2]);
}
if (pathname === "/api/projects/link") {
if (req.method !== "POST")
return errorResponse("Method not allowed", "Only POST requests are allowed", 405);
return this.handleLink(req);
}
return null;
} catch (error) {
return errorResponse("Request failed", jsonErrorMessage2(error), 500);
}
}
listTemplates() {
return AgentProjectGenerator.getAvailableTemplates();
}
async handleCreate(req) {
let body;
try {
body = await req.json();
} catch {
return errorResponse("Invalid body", "Expected a JSON body with parentPath, name, and template.", 400);
}
if (typeof body.parentPath !== "string" || !body.parentPath.trim()) {
return errorResponse("Missing parentPath", "Missing required `parentPath` field.", 400);
}
if (typeof body.name !== "string" || !body.name.trim()) {
return errorResponse("Missing name", "Missing required `name` field.", 400);
}
if (typeof body.template !== "string" || !body.template.trim()) {
return errorResponse("Missing template", "Missing required `template` field.", 400);
}
const name = body.name.trim();
if (!VALID_NAME.test(name)) {
return errorResponse("Invalid name", "Project name can only contain letters, numbers, hyphens, and underscores.", 400);
}
const templates = this.listTemplates();
if (!templates.some((t) => t.name === body.template)) {
return errorResponse("Invalid template", `Unknown template: "${body.template}".`, 400);
}
const parentPath = resolve3(expandHome2(body.parentPath.trim()));
const parentStat = await stat2(parentPath).catch(() => null);
if (!parentStat || !parentStat.isDirectory()) {
return errorResponse("Invalid parent", `Parent directory does not exist: ${parentPath}`, 400);
}
const projectPath = join3(parentPath, name);
const previousCreation = this.creations.get(projectPath);
if (previousCreation?.status === "creating") {
return successResponse({ status: "creating", projectPath });
}
const force = body.force === true;
let skipScaffolding = false;
if (existsSync3(projectPath)) {
const existing = await readdir(projectPath).catch(() => []);
if (existing.length > 0) {
if (force) {
await this.options.stopRunningProject?.(projectPath);
await rm(projectPath, { recursive: true, force: true });
} else if (previousCreation?.status !== "failed") {
return errorResponse("Directory exists", `"${projectPath}" already exists and is not empty.`, 409);
} else if (previousCreation.template !== body.template) {
return errorResponse("Directory exists", `"${projectPath}" contains files from a failed "${previousCreation.template}" creation. Choose the same template or a new location.`, 409);
} else if (previousCreation.stage === "scaffolding") {
return errorResponse("Directory exists", `"${projectPath}" contains files from a failed scaffolding attempt. Remove it or choose a new location.`, 409);
} else {
skipScaffolding = true;
}
}
}
const state = {
projectPath,
template: body.template,
stage: skipScaffolding ? "installing" : "scaffolding",
status: "creating",
startedAt: Date.now(),
warnings: [],
skipScaffolding
};
this.creations.set(projectPath, state);
this.runCreate(state);
return successResponse({ status: "creating", projectPath });
}
async runCreate(state) {
try {
await mkdir2(state.projectPath, { recursive: true });
const detect = this.options.detectPackageManagers ?? detectPackageManagers;
const preferred = this.options.getPreferredPackageManager ?? getPreferredPackageManager;
const available = detect().filter((pm) => pm.available);
if (available.length === 0) {
throw new AdkError({
code: "NO_PACKAGE_MANAGER",
message: "No package manager found. Install npm, pnpm, bun, or yarn.",
expected: true
});
}
const packageManager = preferred(state.projectPath, available) ?? available[0];
const generator = this.options.generatorFactory ? this.options.generatorFactory(state.projectPath, packageManager.command, state.template) : new AgentProjectGenerator(state.projectPath, packageManager.command, state.template);
if (!state.skipScaffolding) {
state.stage = "scaffolding";
await generator.generate();
}
state.stage = "installing";
try {
await (this.options.runCommand ?? defaultRunCommand)(packageManager.installCommand, state.projectPath);
} catch (error) {
const stderr = captureExecError(error).slice(0, 2000);
throw new AdkError({
code: "DEP_INSTALL_FAILED",
message: `Failed to install dependencies (${packageManager.installCommand}): ${captureExecError(error)}`,
details: { stderr }
});
}
state.stage = "dependencies";
try {
await installAgent0Capabilities(state.projectPath);
} catch (error) {
state.warnings.push(`Failed to create Agent(0) capabilities: ${jsonErrorMessage2(error)}`);
}
const externalHarness = await installExternalHarnessCapabilities({
projectPath: state.projectPath,
packageManagerCommand: packageManager.command,
commandTargets: [],
runCommand: this.options.runCommand ?? defaultRunCommand
});
if (!externalHarness.skills.success) {
state.warnings.push(`Failed to install ADK skills: ${externalHarness.skills.error ?? "Unknown error"}`);
}
const declared = generator.getDependencies();
state.declaredDependencies = declared;
state.stage = "done";
state.status = "created";
state.finishedAt = Date.now();
} catch (error) {
state.status = "failed";
state.error = jsonErrorMessage2(error);
state.finishedAt = Date.now();
}
}
async handleCreateStatus(req) {
const url = new URL(req.url);
const rawPath = url.searchParams.get("path");
if (!rawPath?.trim()) {
return errorResponse("Missing path", "Missing required `path` query parameter.", 400);
}
const projectPath = resolve3(expandHome2(rawPath.trim()));
const state = this.creations.get(projectPath);
if (!state) {
return successResponse({ status: "unknown" });
}
if (state.status !== "creating" && state.finishedAt && Date.now() - state.finishedAt > TERMINAL_STATE_TTL_MS) {
this.creations.delete(projectPath);
return successResponse({ status: "unknown" });
}
return successResponse({
status: state.status,
stage: state.stage,
projectPath: state.projectPath,
error: state.error,
warnings: state.warnings
});
}
async handleCheckPath(req) {
const url = new URL(req.url);
const rawPath = url.searchParams.get("path");
if (!rawPath?.trim()) {
return errorResponse("Missing path", "Missing required `path` query parameter.", 400);
}
const targetPath = resolve3(expandHome2(rawPath.trim()));
const targetStat = await stat2(targetPath).catch(() => null);
if (!targetStat || !targetStat.isDirectory()) {
return successResponse({ status: "empty" });
}
const entries = await readdir(targetPath).catch(() => []);
if (entries.length === 0) {
return successResponse({ status: "empty" });
}
const hasAgentConfig = entries.includes("agent.config.ts");
if (hasAgentConfig) {
return successResponse({ status: "adk-project" });
}
return successResponse({ status: "non-empty", fileCount: entries.length });
}
async handleListWorkspaces() {
const credentials = await this.getCredentialsOrNull();
if (!credentials)
return notAuthenticated();
try {
const client = this.makeClient(credentials.token, credentials.apiUrl);
const workspaces = await client.list.workspaces({}).collect();
return successResponse({
workspaces: workspaces.map((ws) => ({
id: ws.id,
name: ws.name,
handle: ws.handle,
plan: ws.plan ?? "Free",
profilePicture: ws.profilePicture ?? undefined
}))
});
} catch (error) {
return errorResponse("Failed to list workspaces", jsonErrorMessage2(error), 500);
}
}
async handleListProdBots(workspaceId, purpose) {
if (!workspaceId.trim()) {
return errorResponse("Missing workspaceId", "Workspace ID is required.", 400);
}
const credentials = await this.getCredentialsOrNull();
if (!credentials)
return notAuthenticated();
try {
const client = this.makeClient(credentials.token, credentials.apiUrl, workspaceId);
const bots = await client.list.bots({
dev: false,
tags: purpose === "viewable" ? { ...ADK_RUNTIME_BOT_TAGS, ...ADK_MANIFEST_BOT_TAGS } : ADK_RUNTIME_BOT_TAGS
}).collect();
return successResponse({
bots: bots.map((bot) => ({
id: bot.id,
name: bot.name,
createdAt: bot.createdAt
}))
});
} catch (error) {
return errorResponse("Failed to list bots", jsonErrorMessage2(error), 500);
}
}
async handleLink(req) {
let body;
try {
body = await req.json();
} catch {
return errorResponse("Invalid body", "Expected a JSON body with projectPath and workspaceId.", 400);
}
if (typeof body.projectPath !== "string" || !body.projectPath.trim()) {
return errorResponse("Missing projectPath", "Missing required `projectPath` field.", 400);
}
if (typeof body.workspaceId !== "string" || !body.workspaceId.trim()) {
return errorResponse("Missing workspaceId", "Missing required `workspaceId` field.", 400);
}
const wantsNewBot = typeof body.newBotName === "string" && body.newBotName.trim().length > 0;
if (!wantsNewBot && (typeof body.botId !== "string" || !body.botId.trim())) {
return errorResponse("Missing bot selection", "Provide either `botId` or `newBotName`.", 400);
}
const credentials = await this.getCredentialsOrNull();
if (!credentials)
return notAuthenticated();
const workspaceId = body.workspaceId.trim();
const projectPath = resolve3(expandHome2(body.projectPath.trim()));
const projectStat = await stat2(projectPath).catch(() => null);
if (!projectStat || !projectStat.isDirectory()) {
return errorResponse("Invalid project", `Project directory does not exist: ${projectPath}`, 400);
}
let client = null;
let createdBotId = null;
let createdBotName;
try {
client = this.makeClient(credentials.token, credentials.apiUrl, workspaceId);
let botId;
let botName;
if (wantsNewBot) {
const { bot } = await client.createBot({
name: body.newBotName.trim(),
tags: { runtime: "adk" }
});
botId = bot.id;
botName = bot.name;
createdBotId = bot.id;
createdBotName = bot.name;
} else {
botId = body.botId.trim();
try {
const { bot } = await client.getBot({ id: botId });
botName = bot.name;
} catch {}
}
const load = this.options.loadProject ?? AgentProject.load;
const project = await load(projectPath);
await project.createAgentInfo({
botId,
workspaceId,
apiUrl: credentials.apiUrl
});
if (botName && project.config?.name !== botName) {
try {
const writer = new ConfigWriter(project.path);
await writer.updateName(botName);
} catch {}
}
const linkWarnings = [];
const createState = this.creations.get(projectPath);
const declared = createState?.declaredDependencies;
if (declared && declared.integrations.length + declared.plugins.length > 0) {
try {
const linkedProject = await load(projectPath);
const dmClient = await getProjectClient({
project: linkedProject,
credentials: {
token: credentials.token,
apiUrl: credentials.apiUrl,
workspaceId,
botId
},
workspaceId,
botId
});
const dm = await exports_dependencies.DependencyManager.fromProject({
projectPath,
env: "dev",
client: dmClient
});
for (const ref of declared.integrations) {
try {
const { name, version } = parseRef(ref);
await dm.add("integration", { name, version });
} catch (error) {
linkWarnings.push(`Failed to install integration ${ref}: ${captureExecError(error)}`);
}
}
for (const ref of declared.plugins) {
try {
const { name, version } = parseRef(ref);
await dm.add("plugin", { name, version });
} catch (error) {
linkWarnings.push(`Failed to install plugin ${ref}: ${captureExecError(error)}`);
}
}
} catch (error) {
linkWarnings.push(`Failed to initialize dependency manager: ${captureExecError(error)}`);
}
if (createState) {
createState.warnings.push(...linkWarnings);
}
}
return successResponse({
status: "linked",
projectPath,
botId,
workspaceId,
botName,
...linkWarnings.length > 0 ? { warnings: linkWarnings } : {}
});
} catch (error) {
if (createdBotId && client) {
try {
await client.deleteBot({ id: createdBotId });
} catch (cleanupError) {
const label = createdBotName ? `${createdBotName} (${createdBotId})` : createdBotId;
return errorResponse("Failed to link project", `${jsonErrorMessage2(error)} Created bot ${label} could not be cleaned up: ${jsonErrorMessage2(cleanupError)}`, 500);
}
}
return errorResponse("Failed to link project", jsonErrorMessage2(error), 500);
}
}
async getCredentialsOrNull() {
try {
const get = this.options.getCredentials ?? auth.getActiveCredentials.bind(auth);
return await get();
} catch {
return null;
}
}
makeClient(token, apiUrl, workspaceId) {
if (this.options.clientFactory) {
return this.options.clientFactory(token, apiUrl, workspaceId);
}
return new Uk({
token,
apiUrl,
...workspaceId ? { workspaceId } : {},
headers: { "x-multiple-integrations": "true" }
});
}
}
function defaultRunCommand(command, cwd) {
return new Promise((resolve4, reject) => {
const child = spawn2(command, {
cwd,
env: { ...process.env },
shell: true,
stdio: ["ignore", "pipe", "pipe"]
});
let stdout = "";
let stderr = "";
child.stdout?.setEncoding("utf8");
child.stdout?.on("data", (chunk) => {
stdout = appendCommandOutput(stdout, chunk);
});
child.stderr?.setEncoding("utf8");
child.stderr?.on("data", (chunk) => {
stderr = appendCommandOutput(stderr, chunk);
});
child.on("error", reject);
child.on("close", (code, signal) => {
if (code === 0) {
resolve4();
return;
}
const reason = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
const error = new Error(`Command failed with ${reason}`);
error.stdout = stdout;
error.stderr = stderr;
reject(error);
});
});
}
function notAuthenticated() {
return new Response(JSON.stringify({
error: "not-authenticated",
message: "Sign in with `adk login` to link projects to Botpress."
}), { status: 401, headers: { "Content-Type": "application/json" } });
}
// src/server/prod-bot-selection-manager.ts
function prodBotSelectionPath(botId) {
return `prod:${botId}`;
}
class ProdBotSelectionManager {
bots = new Map;
register(entry) {
const agentPath = prodBotSelectionPath(entry.botId);
const prodBotSelection = {
...entry,
agentPath,
registeredAt: Date.now()
};
this.bots.set(agentPath, prodBotSelection);
return prodBotSelection;
}
unregister(agentPath) {
return this.bots.delete(agentPath);
}
clearAll() {
this.bots.clear();
}
get(agentPath) {
return this.bots.get(agentPath);
}
list() {
return Array.from(this.bots.values());
}
toAgentInfoList() {
return this.list().map((bot) => ({
name: bot.botName,
agentPath: bot.agentPath,
backendPort: 0,
botPort: 0,
status: "ready",
connectedAt: bot.registeredAt,
lastHeartbeat: Date.now(),
consoleMode: "cloud"
}));
}
}
// src/server/ui-server-entry.ts
var logger4 = createCliLogger({ tag: "devconsole-ui" });
function parseArgs(args) {
let port = 3001;
let standalone = false;
for (let i = 0;i < args.length; i++) {
if (args[i] === "--port" && args[i + 1]) {
port = parseInt(args[i + 1], 10);
i++;
}
if (args[i] === "--standalone") {
standalone = true;
}
}
return { port, standalone };
}
function writeConsolePort(port) {
const dir = dirname2(CONSOLE_PORT_FILE);
mkdirSync(dir, { recursive: true });
const tmpFile = join4(dir, `.console.port.${process.pid}.tmp`);
writeFileSync(tmpFile, String(port), { encoding: "utf-8", mode: 384 });
renameSync(tmpFile, CONSOLE_PORT_FILE);
}
function removeConsolePort() {
try {
unlinkSync2(CONSOLE_PORT_FILE);
} catch {}
}
function removeConsoleLock() {
try {
unlinkSync2(CONSOLE_LOCK_FILE);
} catch {}
}
var AGENT_SCOPED_PROXY_PREFIX = "/api/agent-proxy/";
function parseAgentScopedProxyPath(pathname) {
if (!pathname.startsWith(AGENT_SCOPED_PROXY_PREFIX))
return null;
const rest = pathname.slice(AGENT_SCOPED_PROXY_PREFIX.length);
const slashIndex = rest.indexOf("/");
if (slashIndex <= 0) {
return {
error: "Agent-scoped proxy path must include an encoded agent path and backend path."
};
}
let agentPath;
try {
agentPath = decodeURIComponent(rest.slice(0, slashIndex));
} catch {
return {
error: "Agent-scoped proxy path contains an invalid encoded agent path."
};
}
const backendPathname = `/${rest.slice(slashIndex + 1)}`;
if (!backendPathname.startsWith("/api/") && backendPathname !== "/mcp") {
return { error: "Agent-scoped proxy path can only target /api/* or /mcp." };
}
return { agentPath, backendPathname };
}
async function runUiServerEntry(args = process.argv.slice(2)) {
const { port: startPort, standalone } = parseArgs(args);
mkdirSync(dirname2(CONSOLE_SOCKET_PATH), { recursive: true });
const socketServer = new ConsoleSocketServer({
socketPath: CONSOLE_SOCKET_PATH,
standalone,
expectedRuntimeVersion: EXPECTED_RUNTIME_VERSION,
onEmpty: () => {
shutdown();
}
});
await socketServer.start();
const resolvedPort = await findAvailablePort(startPort);
const projectSupervisor = new ProjectSupervisor({
consolePort: resolvedPort,
getAgents: () => socketServer.getAgents(),
requestAgentShutdown: (agentPath) => socketServer.requestAgentShutdown(agentPath)
});
await ensureTemplatesAvailable();
const projectCreator = new ProjectCreator({
stopRunningProject: async (projectPath) => {
const result = await projectSupervisor.stopProjectAt(projectPath);
if (result.status !== "not-running") {
logger4.info(`Stopped dev session at ${projectPath} before overwrite (${result.status})`, {
event: "project-overwrite-stop",
projectPath,
result: result.status
});
}
}
});
const prodBotSelectionManager = new ProdBotSelectionManager;
const stopLagMonitor = startEventLoopLagMonitor("ui server");
const uiDistPath = getUIDistPath();
const httpServer = serve({
port: resolvedPort,
idleTimeout: 60,
async fetch(req) {
const url = new URL(req.url);
if (req.method === "OPTIONS") {
return handleCorsPreflightResponse(req);
}
if (url.pathname === "/api/agents") {
return withRequestTiming("GET /api/agents", () => {
const agents = [...socketServer.getAgents(), ...prodBotSelectionManager.toAgentInfoList()];
return withCors(req, new Response(JSON.stringify({ agents }), {
headers: {
"Content-Type": "application/json",
[DEVCONSOLE_ID_HEADER]: "1"
}
}));
});
}
if (url.pathname === "/api/processes") {
const localAgents = socketServer.getAgents();
const consoleInfo = {
pid: process.pid,
port: resolvedPort,
url: `http://localhost:${resolvedPort}`,
mode: standalone ? "standalone" : "managed"
};
return withCors(req, new Response(JSON.stringify({ console: consoleInfo, agents: localAgents }), {
headers: {
"Content-Type": "application/json",
[DEVCONSOLE_ID_HEADER]: "1"
}
}));
}
if (url.pathname === "/api/cloud-console/select-prod-bot" && req.method === "POST") {
return withCors(req, await handleProdBotSelect(req, prodBotSelectionManager));
}
if (url.pathname === "/api/cloud-console/clear" && req.method === "POST") {
const body = await req.json().catch(() => null);
const agentPath = typeof body?.agentPath === "string" ? body.agentPath : null;
if (!agentPath) {
return withCors(req, new Response(JSON.stringify({
error: "Missing fields",
message: "agentPath is required."
}), {
status: 400,
headers: { "Content-Type": "application/json" }
}));
}
prodBotSelectionManager.unregister(agentPath);
return withCors(req, new Response(JSON.stringify({ status: "cleared" }), {
status: 200,
headers: { "Content-Type": "application/json" }
}));
}
if (url.pathname === "/api/console/shutdown" && req.method === "POST") {
setTimeout(() => void shutdown(), 0);
return withCors(req, new Response(JSON.stringify({ status: "shutdown-requested" }), {
status: 200,
headers: {
"Content-Type": "application/json",
[DEVCONSOLE_ID_HEADER]: "1"
}
}));
}
const projectResponse = await projectSupervisor.handle(url.pathname, req);
if (projectResponse) {
return withCors(req, projectResponse);
}
const createResponse = await projectCreator.handle(url.pathname, req);
if (createResponse) {
return withCors(req, createResponse);
}
const authResponse = await handleAuthRequest(url.pathname, req, {
onLogout: () => prodBotSelectionManager.clearAll()
});
if (authResponse) {
return withCors(req, authResponse);
}
const scopedProxy = parseAgentScopedProxyPath(url.pathname);
if (scopedProxy) {
if ("error" in scopedProxy) {
return withCors(req, new Response(JSON.stringify({
error: "Invalid agent proxy path",
message: scopedProxy.error
}), {
status: 400,
headers: { "Content-Type": "application/json" }
}));
}
const allAgents = [...socketServer.getAgents(), ...prodBotSelectionManager.toAgentInfoList()];
const agent = allAgents.find((a) => a.agentPath === scopedProxy.agentPath);
if (!agent) {
return withCors(req, agentResolutionError({
kind: "unknown-agent",
requested: scopedProxy.agentPath,
agents: allAgents
}));
}
if (agent.consoleMode === "cloud") {
const prodBotSelection = prodBotSelectionManager.get(agent.agentPath);
const backendUrl = new URL(scopedProxy.backendPathname, url.origin);
backendUrl.search = url.search;
return withCors(req, await handleProdBotApiRequest(backendUrl, req, prodBotSelection));
}
return proxyToBackend(req, agent.backendPort, {
pathname: scopedProxy.backendPathname
});
}
if (url.pathname === "/api/feature-flags") {
return withCors(req, await handleFeatureFlags());
}
if (url.pathname.startsWith("/api/") || url.pathname === "/mcp") {
const allAgents = [...socketServer.getAgents(), ...prodBotSelectionManager.toAgentInfoList()];
const result = resolveTargetAgent(url, req, allAgents);
if (result.kind === "found") {
if (result.agent.consoleMode === "cloud") {
const prodBotSelection = prodBotSelectionManager.get(result.agent.agentPath);
return withCors(req, await handleProdBotApiRequest(url, req, prodBotSelection));
}
return proxyToBackend(req, result.agent.backendPort);
}
return withCors(req, agentResolutionError(result));
}
return serveStaticFile(url.pathname, uiDistPath, req);
}
});
writeConsolePort(resolvedPort);
logger4.info(`Listening on http://localhost:${resolvedPort}`);
logger4.info(`Socket at ${CONSOLE_SOCKET_PATH}`);
logger4.info(`Mode: ${standalone ? "standalone" : "managed"}`);
let shutdownStarted = false;
async function shutdown() {
if (shutdownStarted)
return;
shutdownStarted = true;
logger4.info("Shutting down...");
await projectSupervisor.shutdownManagedProjects().catch(() => projectSupervisor.flushManagedLedger());
removeConsolePort();
removeConsoleLock();
stopLagMonitor();
socketServer.stop();
httpServer.stop(true);
process.exit(0);
}
process.on("SIGINT", () => void shutdown());
process.on("SIGTERM", () => void shutdown());
await new Promise(() => {});
}
function withCors(req, res) {
const headers = getCorsHeaders(req);
for (const [k, v] of Object.entries(headers)) {
res.headers.set(k, v);
}
return res;
}
async function handleProdBotSelect(req, manager) {
let body;
try {
body = await req.json();
} catch {
return new Response(JSON.stringify({ error: "Invalid body" }), {
status: 400,
headers: { "Content-Type": "application/json" }
});
}
if (!body.workspaceId || !body.botId || !body.botName) {
return new Response(JSON.stringify({
error: "Missing fields",
message: "workspaceId, botId, and botName are required."
}), { status: 400, headers: { "Content-Type": "application/json" } });
}
let credentials = null;
try {
credentials = await auth.getActiveCredentials();
} catch (err) {
logger4.debug(`active credentials lookup failed: ${err instanceof Error ? err.message : String(err)}`);
}
if (!credentials) {
return new Response(JSON.stringify({
error: "Not authenticated",
message: "Sign in to select prod bots from Botpress Cloud."
}), {
status: 401,
headers: { "Content-Type": "application/json" }
});
}
const entry = manager.register({
botId: body.botId,
botName: body.botName,
workspaceId: body.workspaceId,
token: credentials.token,
apiUrl: credentials.apiUrl
});
return new Response(JSON.stringify({ status: "selected", agentPath: entry.agentPath }), {
status: 200,
headers: { "Content-Type": "application/json" }
});
}
if (false) {}
export {
runUiServerEntry
};