zerobyte/app/server/modules/agents/local/process.ts
Nico 28ba8657f9
feat(runtime): start and ship the local agent (#767)
* feat(runtime): start and ship the local agent

* refactor: gate local agent behind feature flag

* chore: skip agent manager if flag is false

* fix: hot reload agents

* test: fix config tests
2026-04-10 00:00:30 +02:00

78 lines
2.2 KiB
TypeScript

import { type ChildProcess, spawn } from "node:child_process";
import { existsSync } from "node:fs";
import path from "node:path";
import { logger } from "@zerobyte/core/node";
import { config } from "../../../core/config";
import { deriveLocalAgentToken } from "../helpers/tokens";
type LocalAgentState = {
localAgent: ChildProcess | null;
};
export async function spawnLocalAgentProcess(runtime: LocalAgentState) {
await stopLocalAgentProcess(runtime);
if (!config.flags.enableLocalAgent) {
return;
}
const sourceEntryPoint = path.join(process.cwd(), "apps", "agent", "src", "index.ts");
const productionEntryPoint = path.join(process.cwd(), ".output", "agent", "index.mjs");
if (config.__prod__ && !existsSync(productionEntryPoint)) {
throw new Error(`Local agent entrypoint not found at ${productionEntryPoint}`);
}
const agentEntryPoint = config.__prod__ ? productionEntryPoint : sourceEntryPoint;
const agentToken = await deriveLocalAgentToken();
const args = config.__prod__ ? ["run", agentEntryPoint] : ["run", "--watch", agentEntryPoint];
const agentProcess = spawn("bun", args, {
env: {
...process.env,
ZEROBYTE_CONTROLLER_URL: "ws://localhost:3001",
ZEROBYTE_AGENT_TOKEN: agentToken,
},
stdio: ["ignore", "pipe", "pipe"],
});
runtime.localAgent = agentProcess;
agentProcess.stdout?.on("data", (data: Buffer) => {
const line = data.toString().trim();
if (line) logger.info(`[agent] ${line}`);
});
agentProcess.stderr?.on("data", (data: Buffer) => {
const line = data.toString().trim();
if (line) logger.error(`[agent] ${line}`);
});
agentProcess.on("exit", (code, signal) => {
if (runtime.localAgent === agentProcess) {
runtime.localAgent = null;
}
logger.info(`Agent process exited with code ${code} and signal ${signal}`);
});
}
export async function stopLocalAgentProcess(runtime: LocalAgentState) {
if (!runtime.localAgent) {
return;
}
const agentProcess = runtime.localAgent;
runtime.localAgent = null;
if (agentProcess.exitCode !== null || agentProcess.signalCode !== null) {
return;
}
const exited = new Promise<void>((resolve) => {
agentProcess.once("exit", () => {
resolve();
});
});
agentProcess.kill();
await exited;
}