// lvmh worker bridge: headless pi session via SDK; the lvmh plugin (installed // at /root/.pi/agent/extensions/lvmh-agent.ts) dials the daemon and delivers // web prompts through pi.sendUserMessage. This process just hosts the session. // Global npm layout: resolve pi via absolute path (NODE_PATH does not apply to ESM). import { spawn } from "node:child_process"; import { existsSync } from "node:fs"; import { createAgentSession } from "/usr/local/lib/node_modules/@earendil-works/pi-coding-agent/dist/index.js"; const SETUP_PATH = "/workspace/.lvmh/setup.sh"; const SETUP_TIMEOUT_MS = 10 * 60 * 1000; // Repo-level setup hook: if the repo ships .lvmh/setup.sh, run it before the // session starts (apt-get/go install/pip — anything the agent needs). // /workspace is a persistent per-repo volume, so the hook survives container // restarts and re-runs on every spawn. Failure is non-fatal: log and continue. async function runRepoSetup() { if (!existsSync(SETUP_PATH)) return; console.error(`[lvmh-bridge] running repo setup: ${SETUP_PATH}`); const code = await new Promise((resolve) => { const child = spawn("bash", [SETUP_PATH], { cwd: "/workspace", stdio: ["ignore", "inherit", "inherit"], }); const timer = setTimeout(() => { console.error(`[lvmh-bridge] setup timed out after ${SETUP_TIMEOUT_MS}ms, killing`); child.kill("SIGKILL"); resolve(124); }, SETUP_TIMEOUT_MS); timer.unref?.(); child.on("error", (err) => { clearTimeout(timer); console.error("[lvmh-bridge] setup spawn error:", err); resolve(1); }); child.on("exit", (c) => { clearTimeout(timer); resolve(c ?? 1); }); }); if (code === 0) console.error("[lvmh-bridge] setup completed"); else console.error(`[lvmh-bridge] setup exited ${code} — continuing anyway`); } process.on("unhandledRejection", (err) => { console.error("[lvmh-bridge] unhandledRejection:", err); }); try { await runRepoSetup(); const { session } = await createAgentSession(); // SDK does not bind extensions implicitly (unlike TUI/RPC modes); without // bindExtensions the session_start event never fires, so the lvmh plugin // would never dial the daemon. await session.bindExtensions({ mode: "rpc" }); console.error(`[lvmh-bridge] session up, cwd=${process.cwd()}`); session.subscribe((event) => { // Keep the loop warm; the extension does the mirroring. if (event.type === "agent_settled") { console.error("[lvmh-bridge] agent settled"); } }); for (const sig of ["SIGTERM", "SIGINT"]) { process.on(sig, () => { console.error(`[lvmh-bridge] ${sig}, exiting`); process.exit(0); }); } // Stay alive forever. setInterval(() => {}, 1 << 30); } catch (err) { console.error("[lvmh-bridge] failed to create agent session", err); process.exit(1); }