import { randomBytes } from "node:crypto"; import { mkdir } from "node:fs/promises"; import { createServer } from "node:net"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import dotenv from "dotenv"; const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); dotenv.config({ path: join(projectRoot, ".local.env"), override: false, quiet: true }); const agentPort = readPort("INTEGRATION_AGENT_PORT", 18_787); const opencodePort = readPort("INTEGRATION_OPENCODE_PORT", 14_096); const dataRoot = resolve( process.env.INTEGRATION_DATA_DIR ?? join(projectRoot, "data", `integration-${agentPort}`), ); const bridgeToken = process.env.AGENT_INTERNAL_TOKEN?.trim() || randomBytes(32).toString("hex"); const agentBaseUrl = `http://127.0.0.1:${agentPort}`; const opencodeBaseUrl = `http://127.0.0.1:${opencodePort}`; await assertPortAvailable(agentPort); await assertPortAvailable(opencodePort); await mkdir(dataRoot, { recursive: true }); const sharedEnvironment = { ...process.env, AGENT_INTERNAL_TOKEN: bridgeToken, TJWATER_AGENT_INTERNAL_BASE_URL: agentBaseUrl, // Retained for tools loaded by an older compatible OpenCode process. TJWATER_AGENT_INTERNAL_TOKEN: bridgeToken, }; const opencode = Bun.spawn([ "opencode", "serve", "--hostname=127.0.0.1", `--port=${opencodePort}`, ], { cwd: projectRoot, env: { ...sharedEnvironment, XDG_CACHE_HOME: join(dataRoot, "xdg-cache"), XDG_DATA_HOME: join(dataRoot, "xdg-data"), XDG_STATE_HOME: join(dataRoot, "xdg-state"), }, stderr: "inherit", stdout: "inherit", }); let agent: ReturnType | undefined; let shuttingDown = false; const shutdown = async () => { if (shuttingDown) return; shuttingDown = true; agent?.kill("SIGTERM"); opencode.kill("SIGTERM"); await Promise.allSettled([ ...(agent ? [agent.exited] : []), opencode.exited, ]); }; process.once("SIGINT", () => void shutdown()); process.once("SIGTERM", () => void shutdown()); try { await waitUntilHealthy(`${opencodeBaseUrl}/global/health`, "OpenCode", opencode); agent = Bun.spawn([process.execPath, "src/server.ts"], { cwd: projectRoot, env: { ...sharedEnvironment, HOST: "127.0.0.1", PORT: String(agentPort), OPENCODE_MODE: "client", OPENCODE_CLIENT_BASE_URL: opencodeBaseUrl, LLM_REQUEST_AUDIT_LOG_PATH: join(dataRoot, "logs", "llm-request-audit.log"), LEARNING_AUDIT_LOG_PATH: join(dataRoot, "logs", "learning-audit.log"), MEMORY_STORAGE_DIR: join(dataRoot, "memory"), OPENCODE_SKILLS_ROOT_DIR: join(dataRoot, "skills"), PERSISTENCE_BACKUP_DIR: join(dataRoot, "backup"), RESULT_REF_STORAGE_DIR: join(dataRoot, "result-refs"), SESSION_LEARNING_STATE_STORAGE_DIR: join(dataRoot, "session-learning-state"), SESSION_METADATA_STORAGE_DIR: join(dataRoot, "session-metadata"), SESSION_TRANSCRIPT_STORAGE_DIR: join(dataRoot, "session-transcripts"), SESSION_UI_STATE_STORAGE_DIR: join(dataRoot, "session-ui-states"), }, stderr: "inherit", stdout: "inherit", }); await waitUntilHealthy(`${agentBaseUrl}/health`, "Agent", agent); console.log(`isolated integration pair ready: Agent ${agentBaseUrl}, OpenCode ${opencodeBaseUrl}`); console.log(`isolated runtime data: ${dataRoot}`); const result = await Promise.race([ agent.exited.then((exitCode) => ({ exitCode, process: "Agent" })), opencode.exited.then((exitCode) => ({ exitCode, process: "OpenCode" })), ]); if (!shuttingDown) { console.error(`${result.process} exited with code ${result.exitCode}; stopping its paired process`); process.exitCode = result.exitCode || 1; } } catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; } finally { await shutdown(); } function readPort(name: string, fallback: number) { const raw = process.env[name]?.trim(); const value = raw ? Number(raw) : fallback; if (!Number.isInteger(value) || value < 1 || value > 65_535) { throw new Error(`${name} must be a valid TCP port`); } return value; } function assertPortAvailable(port: number) { return new Promise((resolvePromise, reject) => { const server = createServer(); server.unref(); server.once("error", (error: NodeJS.ErrnoException) => { reject(new Error(`port ${port} is unavailable: ${error.code ?? error.message}`)); }); server.listen({ host: "127.0.0.1", port, exclusive: true }, () => { server.close((error) => (error ? reject(error) : resolvePromise())); }); }); } async function waitUntilHealthy( url: string, label: string, child: ReturnType, ) { const deadline = Date.now() + 30_000; while (Date.now() < deadline) { if (child.exitCode !== null) { throw new Error(`${label} exited before becoming ready (code ${child.exitCode})`); } try { const response = await fetch(url, { signal: AbortSignal.timeout(1_000) }); if (response.ok) return; } catch { // The child is still starting; readiness is decided only by its health endpoint. } await new Promise((resolvePromise) => setTimeout(resolvePromise, 100)); } throw new Error(`${label} did not become healthy within 30 seconds`); }