74 lines · 3.2 KB · TypeScript
RawDownload
// Standalone agent-runtime process (Phase 5b of the pi-parity refactor).//// Why this exists: Next 16's standalone server (`npm run start`) buffers// locally-generated SSE in route handlers — only proxied/pass-through upstream// streams flush. Running the runtime here and letting the Next routes proxy// (`return new Response(upstream.body, …)`) is the pass-through case that// streams. The in-Next path (transpilePackages) remains the default; this// process is opt-in via LOCAL_STUDIO_AGENT_RUNTIME_URL on the Next side.//// Routes mirror the Next paths one-to-one so the proxy is a pathname-preserving// passthrough. Handlers are the same functions the Next routes call in-process// (src/http/handlers.ts, src/http/browser-handlers.ts).//// Security: this is a localhost sidecar. It binds 127.0.0.1 ONLY and performs// no authentication of its own — the Next layer keeps running// requireApiAccess() on the privileged routes before proxying, and nothing// else can reach this port off-box. Do not bind 0.0.0.0 without adding auth.//// Runtime: node + tsc build (dist/). NOT tsx (cannot load @earendil-works/// pi-ai's entrypoint) and NOT bun (node-pty / Chrome-spawn risk). import { serve } from "@hono/node-server";import { Hono } from "hono";import {  handleAgentAbort,  handleAgentCompact,  handleAgentTurn,  handleRuntimeEvents,  handleRuntimeSessions,  handleRuntimeStatus,  handleSetupChecks,} from "./http/handlers";import {  handleBrowserFetch,  handleBrowserFrame,  handleBrowserInput,  handleBrowserLocalhosts,  handleBrowserState,  handleBrowserVerb,  handleBrowserViewport,} from "./http/browser-handlers"; const app = new Hono(); app.get("/health", (c) =>  c.json({ ok: true, service: "local-studio-agent-runtime", pid: process.pid }),); // The 7 runtime endpoints.app.post("/api/agent/turn", (c) => handleAgentTurn(c.req.raw));app.post("/api/agent/abort", (c) => handleAgentAbort(c.req.raw));app.post("/api/agent/compact", (c) => handleAgentCompact(c.req.raw));app.get("/api/agent/runtime/sessions", () => handleRuntimeSessions());app.get("/api/agent/runtime/status", (c) => handleRuntimeStatus(c.req.raw));app.get("/api/agent/runtime/events", (c) => handleRuntimeEvents(c.req.raw));app.get("/api/agent/setup-checks", () => handleSetupChecks()); // Browser-host endpoints. The fixed paths must be registered before the// :verb catch-all so e.g. /browser/fetch is not treated as a verb.app.get("/api/agent/browser/fetch", (c) => handleBrowserFetch(c.req.raw));app.get("/api/agent/browser/frame", () => handleBrowserFrame());app.post("/api/agent/browser/input", (c) => handleBrowserInput(c.req.raw));app.get("/api/agent/browser/localhosts", (c) => handleBrowserLocalhosts(c.req.raw));app.get("/api/agent/browser/state", () => handleBrowserState());app.post("/api/agent/browser/viewport", (c) => handleBrowserViewport(c.req.raw));app.post("/api/agent/browser/:verb", (c) => handleBrowserVerb(c.req.raw, c.req.param("verb"))); const port = Number(process.env.PORT) > 0 ? Number(process.env.PORT) : 8081; serve({ fetch: app.fetch, port, hostname: "127.0.0.1" }, (info) => {  console.log(    `[agent-runtime] listening on http://127.0.0.1:${info.port} (pid ${process.pid}, node ${process.version})`,  );});