UNPKG

trellis

Version:

Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications

984 lines (974 loc) 30.6 kB
import { init_resolve_runtime_theme_css, resolveRuntimeThemeCss } from "./chunk-WJ5T7N6J.js"; import { init_node_adapter, startNodeServer } from "./chunk-KO2LIUUR.js"; import { TrellisVcsEngine, init_engine } from "./chunk-O56VT7VP.js"; import "./chunk-QC5OHKIJ.js"; import { getPromoteLockStatus, init_promote_lock } from "./chunk-DIHRG6LA.js"; import "./chunk-2DFWNMEW.js"; import "./chunk-G3XIHPSQ.js"; import { init_lane, loadLaneMeta, saveLaneMeta } from "./chunk-Q4FKTPX4.js"; import "./chunk-KFJMKL4Y.js"; import "./chunk-LNCBUJNO.js"; import "./chunk-PBH357QR.js"; import "./chunk-E2CFJKLU.js"; import "./chunk-MFZ22U6M.js"; import { PROVENANCE, init_canonical_op } from "./chunk-RUMOVKR4.js"; import "./chunk-GRWQPKYK.js"; import "./chunk-2ESYSVXG.js"; // src/ui/lanes-dashboard.ts init_engine(); init_lane(); init_node_adapter(); import { existsSync as existsSync2, readFileSync } from "fs"; import { join as join3, dirname as dirname2, basename } from "path"; import { fileURLToPath as fileURLToPath2 } from "url"; // src/ui/lanes-snapshot.ts init_promote_lock(); import { join } from "path"; function buildLanesSnapshot(engine, rootPath, extras = {}) { const trellisDir = join(rootPath, ".trellis"); const lock = getPromoteLockStatus(trellisDir); const activeLaneId = engine.getActiveLaneId(); const lanes = engine.listLanes().map((meta) => { const summary = safeSummarize(engine, meta.id); const issuePlain = meta.issueId?.replace(/^issue:/, ""); const issue = issuePlain ? engine.getIssue(issuePlain) : null; return { id: meta.id, status: meta.status, issueId: meta.issueId, issueTitle: issue?.title, claimedSessionId: issue?.claimedSessionId, sessionId: meta.sessionId, agentId: meta.agentId, opCount: engine.getLaneOpCount(meta.id), fileCount: summary.fileCount, worktreePath: meta.worktreePath, baseBranch: meta.baseBranch, targetBranch: meta.targetBranch, isActive: activeLaneId === meta.id, createdAt: meta.createdAt, updatedAt: meta.updatedAt }; }); const inProgressIssues = engine.getActiveIssues().map((issue) => ({ id: issue.id, title: issue.title, claimedLaneId: issue.claimedLaneId, claimedSessionId: issue.claimedSessionId })); const savedLaneId = engine.getActiveLaneId(); const savedLaneLog = engine.activeLaneLog; engine.activeLaneId = void 0; engine.activeLaneLog = null; engine.open(); const allIssues = engine.listIssues().map((issue) => { const issueLanes = lanes.filter((l) => l.issueId === issue.id || l.issueId === `issue:${issue.id}`); return { id: issue.id, title: issue.title, status: issue.status, priority: issue.priority, labels: issue.labels || [], createdAt: issue.createdAt, claimedLaneId: issue.claimedLaneId, claimedSessionId: issue.claimedSessionId, laneCount: issueLanes.length, laneIds: issueLanes.map((l) => l.id) }; }); allIssues.sort((a, b) => { const ac = a.createdAt || ""; const bc = b.createdAt || ""; if (ac && bc && ac !== bc) return bc.localeCompare(ac); return b.id.localeCompare(a.id, void 0, { numeric: true }); }); const milestones = engine.listMilestones().map((m) => ({ id: m.id.replace(/^milestone:/, ""), message: m.message, createdAt: m.createdAt, createdBy: m.createdBy, fileCount: m.affectedFiles?.length ?? 0, affectedFiles: m.affectedFiles ?? [] })); milestones.sort((a, b) => { const ac = a.createdAt || ""; const bc = b.createdAt || ""; if (ac && bc && ac !== bc) return bc.localeCompare(ac); return b.id.localeCompare(a.id, void 0, { numeric: true }); }); engine.activeLaneId = savedLaneId; engine.activeLaneLog = savedLaneLog; if (savedLaneId) engine.open(); lanes.sort((a, b) => { if (a.isActive !== b.isActive) return a.isActive ? -1 : 1; if (a.status !== b.status) { const order = ["active", "promoting", "promoted", "dropped"]; return order.indexOf(a.status) - order.indexOf(b.status); } const au = a.updatedAt || a.createdAt || ""; const bu = b.updatedAt || b.createdAt || ""; if (au !== bu) return bu.localeCompare(au); return b.id.localeCompare(a.id, void 0, { numeric: true }); }); const activeAgentIds = new Set( lanes.filter((l) => l.isActive || l.status === "active").map((l) => l.agentId).filter(Boolean) ); return { at: (/* @__PURE__ */ new Date()).toISOString(), rootPath, integrationBranch: engine.getCurrentBranch?.() ?? "main", activeLaneId, port: extras.port, viewers: extras.viewers ?? 0, activeAgents: activeAgentIds.size, promoteLock: { locked: lock.locked, stale: lock.stale, laneId: lock.record?.laneId, pid: lock.record?.pid, acquiredAt: lock.record?.acquiredAt }, lanes, issues: allIssues, milestones, inProgressIssues }; } function safeSummarize(engine, laneId) { try { const { filePaths } = engine.summarizeLane(laneId); return { fileCount: filePaths.length }; } catch { return { fileCount: 0 }; } } // src/ui/causal-graph-snapshot.ts function shortHash(hash) { if (!hash) return "\u2014"; const hex = hash.match(/[a-f0-9]{7,}/i); if (hex) return hex[0].slice(0, 7); return hash.slice(0, 7); } function laneIssueTitle(lane, issueTitles) { const plain = lane.issueId?.replace(/^issue:/, ""); if (!plain) return void 0; return issueTitles?.[plain] ?? issueTitles?.[`issue:${plain}`]; } function assignLaneIndices(lanes) { const map = /* @__PURE__ */ new Map(); const sorted = [...lanes].sort((a, b) => { const ac = a.createdAt || ""; const bc = b.createdAt || ""; if (ac !== bc) return ac.localeCompare(bc); return a.id.localeCompare(b.id); }); let nextRoot = 1; for (const lane of sorted) { if (lane.parentLaneId && map.has(lane.parentLaneId)) { const parentCol = map.get(lane.parentLaneId); if (lane.forkKind === "child") { map.set(lane.id, Math.min(parentCol + 1, 3)); } else { map.set(lane.id, Math.min(nextRoot, 3)); nextRoot = Math.min(nextRoot + 1, 3); } } else { map.set(lane.id, Math.min(nextRoot, 3)); nextRoot = Math.min(nextRoot + 1, 3); } } return map; } function buildCausalGraphFromInputs(input) { const laneCols = assignLaneIndices(input.lanes); const events = []; for (const m of input.milestones) { const mid = m.id.replace(/^milestone:/, ""); events.push({ id: `milestone:${mid}`, kind: "milestone", lane: 0, time: m.createdAt || "", message: m.message || mid, hash: shortHash(m.toOpHash), author: m.createdBy, tags: [mid] }); } for (const lane of input.lanes) { const col = laneCols.get(lane.id) ?? 1; const title = laneIssueTitle(lane, input.issueTitles); const shortId = lane.id.slice(0, 13) + (lane.id.length > 13 ? "\u2026" : ""); if (lane.status === "promoted") { events.push({ id: `fork:${lane.id}`, kind: "fork", lane: col, laneId: lane.id, time: lane.createdAt, message: title || lane.name || `Fork ${shortId}`, hash: shortHash(lane.baseOpHash), author: lane.agentId }); events.push({ id: `head:${lane.id}`, kind: "head", lane: col, laneId: lane.id, time: lane.updatedAt || lane.createdAt, message: title || lane.name || `Lane ${shortId}`, hash: shortHash(lane.headOpHash), author: lane.agentId, branches: [lane.targetBranch || shortId] }); events.push({ id: `promote:${lane.id}`, kind: "promote", lane: 0, laneId: lane.id, forkLaneCol: col, time: lane.updatedAt || lane.createdAt, message: `Promote ${shortId}${title ? ` \u2014 ${title}` : ""}`, hash: shortHash(lane.headOpHash), author: lane.agentId, branches: [input.integrationBranch] }); } else if (lane.status === "active") { events.push({ id: `head:${lane.id}`, kind: "head", lane: col, laneId: lane.id, time: lane.updatedAt || lane.createdAt, message: title || lane.name || `Lane ${shortId}`, hash: shortHash(lane.headOpHash), author: lane.agentId, branches: [lane.targetBranch || shortId] }); } else { events.push({ id: `fork:${lane.id}`, kind: "fork", lane: col, laneId: lane.id, time: lane.createdAt, message: title || lane.name || `Fork ${shortId}`, hash: shortHash(lane.baseOpHash), author: lane.agentId }); } } events.sort((a, b) => { if (a.time !== b.time) return a.time.localeCompare(b.time); return a.id.localeCompare(b.id); }); let lastTrunk = null; const lastOnLaneCol = /* @__PURE__ */ new Map(); const wired = []; for (const ev of events) { const parents = []; if (ev.kind === "promote") { if (lastTrunk) parents.push(lastTrunk); const forkCol = ev.forkLaneCol ?? 1; const forkHead = lastOnLaneCol.get(forkCol); if (forkHead) parents.push(forkHead); else if (ev.laneId) { const forkId = `head:${ev.laneId}`; if (wired.some((n) => n.id === forkId)) parents.push(forkId); else { const forkEvId = `fork:${ev.laneId}`; if (wired.some((n) => n.id === forkEvId)) parents.push(forkEvId); } } } else if (ev.lane === 0) { if (lastTrunk) parents.push(lastTrunk); lastTrunk = ev.id; } else { if (lastTrunk) parents.push(lastTrunk); const prev = lastOnLaneCol.get(ev.lane); if (prev && ev.kind === "head") { parents.length = 0; parents.push(prev); } lastOnLaneCol.set(ev.lane, ev.id); } const laneMeta = ev.laneId ? input.lanes.find((l) => l.id === ev.laneId) : void 0; wired.push({ id: ev.id, hash: ev.hash, message: ev.message, lane: ev.lane, parents, branches: ev.branches, tags: ev.tags, author: ev.author, date: ev.time, kind: ev.kind, laneId: ev.laneId, active: laneMeta?.status === "active" }); } const commits = wired.reverse(); const activeLanes = input.lanes.filter((l) => l.status === "active"); const activeForkCount = activeLanes.length; return { at: (/* @__PURE__ */ new Date()).toISOString(), integrationBranch: input.integrationBranch, commits, activeLaneIds: activeLanes.map((l) => l.id), stats: { eventCount: commits.length, activeForkCount } }; } function buildCausalGraphSnapshot(engine) { const savedLaneId = engine.getActiveLaneId(); const savedLaneLog = engine.activeLaneLog; engine.activeLaneId = void 0; engine.activeLaneLog = null; engine.open(); const lanes = engine.listLanes(); const milestones = engine.listMilestones(); const issueTitles = {}; for (const issue of engine.listIssues()) { if (issue.title) issueTitles[issue.id] = issue.title; } if (savedLaneId) { engine.activeLaneId = savedLaneId; engine.activeLaneLog = savedLaneLog; engine.open(); } const integrationBranch = engine.getCurrentBranch?.() ?? "main"; return buildCausalGraphFromInputs({ integrationBranch, lanes, milestones, issueTitles }); } // src/ui/lanes-dashboard.ts init_canonical_op(); init_resolve_runtime_theme_css(); // src/ui/ui-dev.ts import { existsSync, mkdirSync, watch as fsWatch } from "fs"; import { join as join2, dirname } from "path"; import { fileURLToPath } from "url"; var UI_DEV_SUBDIR = "ui-dev"; var UI_DEV_ENTRIES = ["tml-runtime.ts", "admin-datatable.ts", "admin-shell.ts"]; var LIVE_RELOAD_CLIENT = `(() => { const es = new EventSource('/__dev/reload'); es.addEventListener('css', () => { for (const link of document.querySelectorAll('link[rel="stylesheet"]')) { const href = link.getAttribute('href'); if (!href || href.startsWith('http')) continue; const base = href.split('?')[0]; link.setAttribute('href', base + '?v=' + Date.now()); } }); es.addEventListener('reload', () => location.reload()); es.addEventListener('html', () => location.reload()); es.onerror = () => {}; })(); `; function liveReloadClientSource() { return LIVE_RELOAD_CLIENT; } function uiDevOutDir(rootPath) { return join2(rootPath, ".trellis", UI_DEV_SUBDIR); } function findUiAsset(name) { const candidates = []; try { const moduleDir = dirname(fileURLToPath(import.meta.url)); candidates.push(join2(moduleDir, name)); candidates.push(join2(moduleDir, "..", "ui", name)); } catch { } let cwd = process.cwd(); for (let i = 0; i < 8; i++) { candidates.push(join2(cwd, "src", "ui", name)); candidates.push(join2(cwd, "dist", "ui", name)); const parent = dirname(cwd); if (parent === cwd) break; cwd = parent; } for (const p of candidates) { if (existsSync(p)) return p; } return null; } async function startUiDevWatch(rootPath, onReload) { const outDir = uiDevOutDir(rootPath); mkdirSync(outDir, { recursive: true }); const entryPoints = []; for (const name of UI_DEV_ENTRIES) { const tsPath = findUiAsset(name); if (tsPath) entryPoints.push(tsPath); } if (entryPoints.length === 0) { throw new Error("ui-dev: no TS entry points found under src/ui/"); } const { context } = await import("./main-IB7F2LHH.js"); let debounce; const notifyDebounced = (reason) => { clearTimeout(debounce); debounce = setTimeout(() => onReload(reason), 80); }; const ctx = await context({ entryPoints, bundle: true, format: "esm", target: "es2020", platform: "browser", sourcemap: "inline", outdir: outDir, logLevel: "warning", plugins: [ { name: "ui-dev-reload", setup(build) { build.onEnd((result) => { if (result.errors.length === 0) notifyDebounced("reload"); }); } } ] }); await ctx.watch(); await ctx.rebuild(); const watchers = []; const watchFile = (path, reason) => { if (!existsSync(path)) return; watchers.push(fsWatch(path, () => notifyDebounced(reason))); }; const adminHtml = findUiAsset("admin.html"); if (adminHtml) watchFile(adminHtml, "html"); const datatableCss = findUiAsset("admin-datatable.css"); if (datatableCss) watchFile(datatableCss, "css"); const themeCss = findUiAsset("theme/runtime-theme.css"); if (themeCss) watchFile(themeCss, "css"); return { outDir, notify: onReload, stop: async () => { clearTimeout(debounce); for (const w of watchers) w.close(); await ctx.dispose(); } }; } // src/ui/lanes-dashboard.ts function findLanesHtml() { const candidates = []; const push = (p) => { if (!candidates.includes(p)) candidates.push(p); }; try { const moduleDir = dirname2(fileURLToPath2(import.meta.url)); push(join3(moduleDir, "lanes.html")); push(join3(moduleDir, "..", "ui", "lanes.html")); } catch { } let cwd = process.cwd(); for (let i = 0; i < 8; i++) { push(join3(cwd, "src", "ui", "lanes.html")); push(join3(cwd, "dist", "ui", "lanes.html")); const parent = dirname2(cwd); if (parent === cwd) break; cwd = parent; } for (const p of candidates) { if (existsSync2(p)) return p; } throw new Error("Could not find lanes.html \u2014 run from trellis-node or build dist."); } function findUiAsset2(name) { const candidates = []; try { const moduleDir = dirname2(fileURLToPath2(import.meta.url)); candidates.push(join3(moduleDir, name)); candidates.push(join3(moduleDir, "..", "ui", name)); } catch { } let cwd = process.cwd(); for (let i = 0; i < 8; i++) { candidates.push(join3(cwd, "src", "ui", name)); candidates.push(join3(cwd, "dist", "ui", name)); const parent = dirname2(cwd); if (parent === cwd) break; cwd = parent; } for (const p of candidates) { if (existsSync2(p)) return p; } return null; } function resolveDevMode(opts) { if (opts.dev === true) return true; if (opts.dev === false) return false; const env = process.env.TRELLIS_UI_DEV?.trim().toLowerCase(); return env === "1" || env === "true" || env === "yes"; } async function bundleUiModule(tsPath) { const { build } = await import("./main-IB7F2LHH.js"); const out = await build({ entryPoints: [tsPath], bundle: true, write: false, format: "esm", target: "es2020", platform: "browser", minify: false }); return out.outputFiles[0].text; } function readDevBundle(outDir, jsName) { const p = join3(outDir, jsName); if (!existsSync2(p)) return null; return readFileSync(p, "utf-8"); } function injectDevLiveReload(html) { const tag = '<script type="module" src="/__dev/live-reload.js"></script>'; if (html.includes("/__dev/live-reload.js")) return html; return html.replace("</body>", ` ${tag} </body>`); } async function startLanesDashboard(opts) { const engine = new TrellisVcsEngine({ rootPath: opts.rootPath, provenance: PROVENANCE.http }); engine.open(); const trellisDir = join3(opts.rootPath, ".trellis"); const uiDev = resolveDevMode(opts); const devOutDir = uiDevOutDir(opts.rootPath); const reloadClients = /* @__PURE__ */ new Set(); const broadcastReload = (reason) => { for (const send of reloadClients) { try { send(reason); } catch { reloadClients.delete(send); } } }; let uiDevHandle = null; if (uiDev) { uiDevHandle = await startUiDevWatch(opts.rootPath, broadcastReload); } const lanesHtmlPath = findLanesHtml(); const readHtml = () => readFileSync(lanesHtmlPath, "utf-8"); const pollMs = opts.pollMs ?? 1e3; const requestedPort = opts.port ?? 3939; let boundPort = requestedPort; let viewers = 0; const headers = { "Access-Control-Allow-Origin": "*", "Cache-Control": "no-store" }; const snapshot = () => buildLanesSnapshot(engine, opts.rootPath, { port: boundPort, viewers }); const serveBundledJs = async (jsName, tsName) => { if (uiDev) { const cached = readDevBundle(devOutDir, jsName); if (cached != null) { return new Response(cached, { headers: { ...headers, "Content-Type": "text/javascript; charset=utf-8", "Cache-Control": "no-cache" } }); } } const tsPath = findUiAsset2(tsName); if (!tsPath) { return new Response(`${tsName} not found \u2014 run from trellis-node.`, { status: 404, headers }); } try { const text = await bundleUiModule(tsPath); return new Response(text, { headers: { ...headers, "Content-Type": "text/javascript; charset=utf-8", "Cache-Control": "no-cache" } }); } catch (err) { return new Response(`${basename(tsName, ".ts")} build failed: ${String(err)}`, { status: 500, headers }); } }; const fetchHandler = async (req) => { const url = new URL(req.url); const path = url.pathname; if (req.method === "OPTIONS") { return new Response(null, { status: 204, headers }); } if (uiDev && path === "/__dev/live-reload.js") { return new Response(liveReloadClientSource(), { headers: { ...headers, "Content-Type": "text/javascript; charset=utf-8", "Cache-Control": "no-cache" } }); } if (uiDev && path === "/__dev/reload") { const stream = new ReadableStream({ start(controller) { const enc = new TextEncoder(); const send = (reason) => { controller.enqueue(enc.encode(`event: ${reason} data: ${reason} `)); }; reloadClients.add(send); req.signal.addEventListener("abort", () => { reloadClients.delete(send); try { controller.close(); } catch { } }); } }); return new Response(stream, { headers: { ...headers, "Content-Type": "text/event-stream", Connection: "keep-alive", "Cache-Control": "no-cache" } }); } if (!uiDev && path.startsWith("/__dev/")) { return new Response("Not Found", { status: 404, headers }); } if (path === "/api/lanes") { engine.open(); return Response.json(snapshot(), { headers }); } if (path === "/api/causal-graph") { engine.open(); return Response.json(buildCausalGraphSnapshot(engine), { headers }); } const laneOpsMatch = path.match(/^\/api\/lanes\/([^/]+)\/ops$/); if (laneOpsMatch) { engine.open(); try { const { ops, meta } = engine.summarizeLane(decodeURIComponent(laneOpsMatch[1])); return Response.json( { laneId: meta.id, agentId: meta.agentId, issueId: meta.issueId, ops: ops.map((op) => ({ hash: op.hash, type: op.kind, at: op.timestamp, agentId: op.agentId, laneId: op.laneId, message: op.vcs?.message, path: op.vcs?.filePath ?? op.vcs?.oldFilePath, issueId: op.vcs?.issueId })) }, { headers } ); } catch { return new Response("Not Found", { status: 404, headers }); } } if (path === "/api/lanes/stream") { const since = url.searchParams.get("since") ?? req.headers.get("last-event-id") ?? void 0; const wantOps = url.searchParams.get("events") !== "snapshot"; const countAsViewer = !wantOps; const stream = new ReadableStream({ start(controller) { const enc = new TextEncoder(); let closed = false; let lastOpHash = since; let sentInitial = false; if (countAsViewer) viewers += 1; const send = (event, data, id) => { const idLine = id ? `id: ${id} ` : ""; controller.enqueue( enc.encode( `${idLine}event: ${event} data: ${JSON.stringify(data)} ` ) ); }; const opsSince = (all) => { if (!lastOpHash) return all; const idx = all.findIndex((o) => o.hash === lastOpHash); return idx >= 0 ? all.slice(idx + 1) : all; }; const push = () => { if (closed) return; try { engine.open(); const all = engine.getOps(); const fresh = opsSince(all); if (!fresh.length && sentInitial) return; send("snapshot", snapshot()); if (wantOps) for (const op of fresh) send("op", op, op.hash); if (all.length) lastOpHash = all[all.length - 1].hash; sentInitial = true; } catch (err) { controller.enqueue( enc.encode( `event: error data: ${JSON.stringify({ message: String(err) })} ` ) ); } }; push(); const timer = setInterval(push, pollMs); req.signal.addEventListener("abort", () => { closed = true; if (countAsViewer) viewers = Math.max(0, viewers - 1); clearInterval(timer); try { controller.close(); } catch { } }); } }); return new Response(stream, { headers: { ...headers, "Content-Type": "text/event-stream", Connection: "keep-alive" } }); } if (path === "/lanes" || path === "/lanes.html") { return new Response(readHtml(), { headers: { ...headers, "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" } }); } if (path === "/admin" || path === "/admin.html") { return new Response(null, { status: 302, headers: { ...headers, Location: `/${url.search}` } }); } if (path === "/") { const htmlPath = findUiAsset2("admin.html"); if (!htmlPath) { return new Response("admin.html not found \u2014 run from trellis-node.", { status: 404, headers }); } let html = readFileSync(htmlPath, "utf-8"); if (uiDev) html = injectDevLiveReload(html); return new Response(html, { headers: { ...headers, "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" } }); } if (path === "/logo.png") { const logoPath = findUiAsset2("logo.png"); if (!logoPath) { return new Response("logo.png not found", { status: 404, headers }); } return new Response(readFileSync(logoPath), { headers: { ...headers, "Content-Type": "image/png", "Cache-Control": "public, max-age=86400" } }); } if (path === "/client" || path === "/client.html") { const htmlPath = findUiAsset2("client.html"); if (!htmlPath) { return new Response("client.html not found \u2014 run from trellis-node.", { status: 404, headers }); } return new Response(readFileSync(htmlPath, "utf-8"), { headers: { ...headers, "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" } }); } if (path === "/theme/runtime-theme.css") { const cssPath = resolveRuntimeThemeCss(opts.rootPath); if (!cssPath) { return new Response("runtime-theme.css not found \u2014 run from trellis-node.", { status: 404, headers }); } return new Response(readFileSync(cssPath, "utf-8"), { headers: { ...headers, "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" } }); } if (path === "/tml-lanes") { const htmlPath = findUiAsset2("tml-lanes.html"); if (!htmlPath) { return new Response("TML test page not found \u2014 build or run from trellis-node.", { status: 404, headers }); } return new Response(readFileSync(htmlPath, "utf-8"), { headers: { ...headers, "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" } }); } if (path === "/tml-runtime.js") { return serveBundledJs("tml-runtime.js", "tml-runtime.ts"); } if (path === "/admin-datatable.js") { return serveBundledJs("admin-datatable.js", "admin-datatable.ts"); } if (path === "/admin-shell.js") { return serveBundledJs("admin-shell.js", "admin-shell.ts"); } if (path === "/admin-causal-graph.js") { return serveBundledJs("admin-causal-graph.js", "admin-causal-graph.ts"); } if (path === "/admin-datatable.css") { const cssPath = findUiAsset2("admin-datatable.css"); if (!cssPath) { return new Response("admin-datatable.css not found \u2014 run from trellis-node.", { status: 404, headers }); } return new Response(readFileSync(cssPath, "utf-8"), { headers: { ...headers, "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" } }); } if (path === "/api/tml-mutations" && req.method === "POST") { let body; try { body = await req.json(); } catch { return new Response(JSON.stringify({ error: "invalid json" }), { status: 400, headers }); } const { action, args } = body; try { if (action === "promote") { await engine.promoteLane(String(args?.id), { dryRun: false }); } else if (action === "updateLaneMeta") { const id = String(args?.id || ""); if (!id) { return new Response(JSON.stringify({ error: "id required" }), { status: 400, headers }); } const meta = loadLaneMeta(trellisDir, id); if (!meta) { return new Response(JSON.stringify({ error: `unknown lane: ${id}` }), { status: 400, headers }); } if ("targetBranch" in (args || {})) { const tb = args?.targetBranch; if (typeof tb !== "string" || !tb.trim()) { return new Response(JSON.stringify({ error: "targetBranch required" }), { status: 400, headers }); } meta.targetBranch = tb.trim(); } if ("issueId" in (args || {})) { const raw = args?.issueId; if (raw == null || raw === "") { delete meta.issueId; } else { const plain = String(raw).replace(/^issue:/, "").trim(); if (!/^TRL-\d+$/i.test(plain)) { return new Response(JSON.stringify({ error: "invalid issueId" }), { status: 400, headers }); } const m = plain.match(/^trl-(\d+)$/i); meta.issueId = m ? `issue:TRL-${m[1]}` : `issue:${plain}`; } } meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString(); saveLaneMeta(trellisDir, meta); } else { return new Response(JSON.stringify({ error: `unknown action: ${action}` }), { status: 400, headers }); } return new Response(JSON.stringify({ ok: true }), { headers }); } catch (err) { return new Response(JSON.stringify({ error: String(err) }), { status: 400, headers }); } } return new Response("Not Found", { status: 404, headers }); }; const server = await startNodeServer({ port: requestedPort, fetch: fetchHandler, websocket: { open: () => { }, message: () => { }, close: () => { } } }); boundPort = server.port; return { port: server.port, stop: () => { void uiDevHandle?.stop(); server.stop(); } }; } export { startLanesDashboard };