improve(control-ui): compress and cache bundled assets (#105890)

* perf(control-ui): compress bundled assets

* chore(control-ui): remove release-owned changelog edit

* fix(control-ui): release assets before compression

* refactor(control-ui): split static response helpers

* perf(control-ui): precompress bundled assets

* refactor(control-ui): keep encoding internals private

* chore(control-ui): refresh LOC baseline
This commit is contained in:
Peter Steinberger
2026-07-12 23:13:04 -07:00
committed by GitHub
parent 70b3467484
commit 16fa19cd72
10 changed files with 842 additions and 132 deletions

View File

@@ -0,0 +1,352 @@
// Control UI static-response policy: MIME types, caching, encoding, and pinned-file reads.
import fs from "node:fs";
import type { IncomingMessage, ServerResponse } from "node:http";
import path from "node:path";
import { brotliCompress, constants as zlibConstants, gzip } from "node:zlib";
const CONTROL_UI_IMMUTABLE_CACHE_CONTROL = "public, max-age=31536000, immutable";
const CONTROL_UI_HTML_COMPRESSION_CACHE_MAX_ENTRIES = 4;
const CONTROL_UI_COMPRESSIBLE_EXTENSIONS = new Set([
".css",
".html",
".js",
".json",
".svg",
".txt",
".wasm",
".webmanifest",
]);
const CONTROL_UI_PRECOMPRESSED_ASSET_EXTENSIONS = new Set([".br", ".gz"]);
/**
* Missing files with these extensions return 404 instead of the SPA index.
* `.html` stays excluded because client-side routes may use that suffix.
*/
const CONTROL_UI_STATIC_ASSET_EXTENSIONS = new Set([
".js",
".css",
".json",
".map",
".svg",
".png",
".jpg",
".jpeg",
".gif",
".webp",
".ico",
".txt",
".wasm",
".webmanifest",
]);
export function isControlUiStaticAssetExtension(extension: string): boolean {
return CONTROL_UI_STATIC_ASSET_EXTENSIONS.has(extension);
}
function isControlUiCompressibleExtension(extension: string): boolean {
return CONTROL_UI_COMPRESSIBLE_EXTENSIONS.has(extension);
}
export function isControlUiPrecompressedAssetExtension(extension: string): boolean {
return CONTROL_UI_PRECOMPRESSED_ASSET_EXTENSIONS.has(extension);
}
type ControlUiContentEncoding = "br" | "gzip";
type ControlUiEncodingSelection = ControlUiContentEncoding | "identity" | "not-acceptable";
const CONTROL_UI_DYNAMIC_ENCODINGS = new Set<ControlUiContentEncoding>(["br", "gzip"]);
const controlUiHtmlCompressionCache = new Map<string, Promise<Buffer>>();
function contentTypeForExtension(ext: string): string {
switch (ext) {
case ".html":
return "text/html; charset=utf-8";
case ".js":
return "application/javascript; charset=utf-8";
case ".css":
return "text/css; charset=utf-8";
case ".json":
case ".map":
return "application/json; charset=utf-8";
case ".svg":
return "image/svg+xml";
case ".png":
return "image/png";
case ".jpg":
case ".jpeg":
return "image/jpeg";
case ".gif":
return "image/gif";
case ".webp":
return "image/webp";
case ".ico":
return "image/x-icon";
case ".txt":
return "text/plain; charset=utf-8";
case ".wasm":
return "application/wasm";
case ".webmanifest":
return "application/manifest+json; charset=utf-8";
default:
return "application/octet-stream";
}
}
function normalizedAcceptEncoding(req: IncomingMessage): string {
const value = req.headers?.["accept-encoding"];
return Array.isArray(value) ? value.join(",") : (value ?? "");
}
function resolveControlUiContentEncoding(
req: IncomingMessage,
availableEncodings: ReadonlySet<ControlUiContentEncoding>,
): ControlUiEncodingSelection {
const qualities = new Map<string, number>();
for (const entry of normalizedAcceptEncoding(req).split(",")) {
const [rawName, ...rawParams] = entry.split(";");
const name = rawName?.trim().toLowerCase();
if (!name) {
continue;
}
const qualityParam = rawParams.find((param) => param.trim().toLowerCase().startsWith("q="));
const parsedQuality = qualityParam ? Number.parseFloat(qualityParam.trim().slice(2)) : 1;
const quality =
Number.isFinite(parsedQuality) && parsedQuality >= 0 && parsedQuality <= 1
? parsedQuality
: 0;
qualities.set(name, Math.max(qualities.get(name) ?? 0, quality));
}
const hasAcceptEncoding = normalizedAcceptEncoding(req).trim().length > 0;
if (!hasAcceptEncoding) {
return "identity";
}
const wildcardQuality = qualities.get("*");
const qualityFor = (name: ControlUiContentEncoding) =>
qualities.has(name) ? (qualities.get(name) ?? 0) : (wildcardQuality ?? 0);
// RFC 9110 keeps identity acceptable unless identity or a rejecting wildcard
// explicitly disables it. This distinction is required to return 406 rather
// than silently violate identity;q=0.
const identityQuality = qualities.has("identity")
? (qualities.get("identity") ?? 0)
: wildcardQuality === 0
? 0
: 1;
const candidates: Array<{ encoding: ControlUiEncodingSelection; quality: number; rank: number }> =
[{ encoding: "identity", quality: identityQuality, rank: 0 }];
if (availableEncodings.has("gzip")) {
candidates.push({ encoding: "gzip", quality: qualityFor("gzip"), rank: 1 });
}
if (availableEncodings.has("br")) {
candidates.push({ encoding: "br", quality: qualityFor("br"), rank: 2 });
}
const selected = candidates
.filter((candidate) => candidate.quality > 0)
.toSorted((left, right) => right.quality - left.quality || right.rank - left.rank)[0];
return selected?.encoding ?? "not-acceptable";
}
export function resolveControlUiHtmlEncoding(req: IncomingMessage): ControlUiEncodingSelection {
return resolveControlUiContentEncoding(req, CONTROL_UI_DYNAMIC_ENCODINGS);
}
type OpenedControlUiRepresentation = {
bodyFile: { path: string; fd: number };
contentPath: string;
encoding?: ControlUiContentEncoding;
};
export function resolveOpenedControlUiRepresentation(params: {
req: IncomingMessage;
sourceFile: { path: string; fd: number };
precompressed: boolean;
openPrecompressedFile: (filePath: string) => { path: string; fd: number } | null;
}): OpenedControlUiRepresentation | null {
const { req, sourceFile, precompressed, openPrecompressedFile } = params;
const extension = path.extname(sourceFile.path).toLowerCase();
const availableEncodings =
precompressed && isControlUiCompressibleExtension(extension)
? new Set(CONTROL_UI_DYNAMIC_ENCODINGS)
: new Set<ControlUiContentEncoding>();
for (;;) {
const selected = resolveControlUiContentEncoding(req, availableEncodings);
if (selected === "not-acceptable") {
fs.closeSync(sourceFile.fd);
return null;
}
if (selected === "identity") {
return { bodyFile: sourceFile, contentPath: sourceFile.path };
}
const suffix = selected === "br" ? ".br" : ".gz";
let compressedFile: { path: string; fd: number } | null;
try {
compressedFile = openPrecompressedFile(`${sourceFile.path}${suffix}`);
} catch (error) {
fs.closeSync(sourceFile.fd);
throw error;
}
if (compressedFile) {
fs.closeSync(sourceFile.fd);
return { bodyFile: compressedFile, contentPath: sourceFile.path, encoding: selected };
}
// Generated builds have both variants, but a stale or partial local build
// can miss one. Retry the remaining representation before identity/406.
availableEncodings.delete(selected);
}
}
function setControlUiEncodingHeaders(
res: ServerResponse,
extension: string,
encoding: ControlUiContentEncoding | "identity",
) {
res.setHeader("Vary", "Accept-Encoding");
if (!CONTROL_UI_COMPRESSIBLE_EXTENSIONS.has(extension)) {
return;
}
if (encoding !== "identity") {
res.setHeader("Content-Encoding", encoding);
}
}
function setControlUiFileHeaders(
res: ServerResponse,
filePath: string,
options?: { immutable?: boolean; encoding?: ControlUiContentEncoding },
) {
const extension = path.extname(filePath).toLowerCase();
res.setHeader("Content-Type", contentTypeForExtension(extension));
res.setHeader(
"Cache-Control",
options?.immutable ? CONTROL_UI_IMMUTABLE_CACHE_CONTROL : "no-cache",
);
setControlUiEncodingHeaders(res, extension, options?.encoding ?? "identity");
}
export function respondHeadForControlUiFile(
res: ServerResponse,
filePath: string,
options?: { immutable?: boolean; encoding?: ControlUiContentEncoding },
) {
res.statusCode = 200;
setControlUiFileHeaders(res, filePath, options);
res.end();
}
function compressControlUiBody(body: Buffer, encoding: ControlUiContentEncoding): Promise<Buffer> {
return new Promise((resolve, reject) => {
const callback = (error: Error | null, compressed: Buffer) => {
if (error) {
reject(error);
return;
}
resolve(compressed);
};
if (encoding === "br") {
brotliCompress(
body,
{
params: {
[zlibConstants.BROTLI_PARAM_QUALITY]: 4,
},
},
callback,
);
return;
}
gzip(body, { level: 6 }, callback);
});
}
export async function serveControlUiAsset(
res: ServerResponse,
filePath: string,
body: Buffer,
options?: { immutable?: boolean; encoding?: ControlUiContentEncoding },
) {
setControlUiFileHeaders(res, filePath, options);
res.end(body);
}
function cachedCompressedControlUiHtml(
body: string,
encoding: ControlUiContentEncoding,
): Promise<Buffer> {
const key = `${encoding}\0${body}`;
const cached = controlUiHtmlCompressionCache.get(key);
if (cached) {
controlUiHtmlCompressionCache.delete(key);
controlUiHtmlCompressionCache.set(key, cached);
return cached;
}
// Index HTML is process-stable for a configured root. Keep its few rewritten
// variants single-flight and bounded so unauthenticated requests cannot fan
// out zlib work; large hashed assets use build-time sidecars instead.
const compression = compressControlUiBody(Buffer.from(body), encoding);
controlUiHtmlCompressionCache.set(key, compression);
void compression.catch(() => {
if (controlUiHtmlCompressionCache.get(key) === compression) {
controlUiHtmlCompressionCache.delete(key);
}
});
while (controlUiHtmlCompressionCache.size > CONTROL_UI_HTML_COMPRESSION_CACHE_MAX_ENTRIES) {
const oldestKey = controlUiHtmlCompressionCache.keys().next().value;
if (oldestKey === undefined) {
break;
}
controlUiHtmlCompressionCache.delete(oldestKey);
}
return compression;
}
export function respondControlUiNotAcceptable(res: ServerResponse) {
res.statusCode = 406;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.setHeader("Cache-Control", "no-store");
res.setHeader("Vary", "Accept-Encoding");
res.end("Not Acceptable");
}
export async function sendControlUiHtmlBody(
req: IncomingMessage,
res: ServerResponse,
body: string,
) {
const encoding = resolveControlUiHtmlEncoding(req);
if (encoding === "not-acceptable") {
respondControlUiNotAcceptable(res);
return;
}
setControlUiEncodingHeaders(res, ".html", encoding);
res.end(encoding === "identity" ? body : await cachedCompressedControlUiHtml(body, encoding));
}
function readOpenedFile(fd: number): Promise<Buffer> {
return new Promise((resolve, reject) => {
fs.readFile(fd, (error, data) => {
if (error) {
reject(error);
return;
}
resolve(data);
});
});
}
// Compression can wait in zlib's worker queue, so release the pinned file as
// soon as its bytes are loaded instead of retaining descriptors per request.
export async function readAndCloseControlUiFile(fd: number): Promise<Buffer> {
try {
return await readOpenedFile(fd);
} finally {
fs.closeSync(fd);
}
}
export async function readAndCloseControlUiFileText(fd: number): Promise<string> {
return (await readAndCloseControlUiFile(fd)).toString("utf8");
}

View File

@@ -6,6 +6,7 @@ import fs from "node:fs/promises";
import type { IncomingMessage } from "node:http";
import os from "node:os";
import path from "node:path";
import { brotliCompressSync, brotliDecompressSync, gzipSync, gunzipSync } from "node:zlib";
import { afterEach, describe, expect, it, vi } from "vitest";
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
import { normalizeAssistantIdentity } from "../../ui/src/lib/assistant-identity.ts";
@@ -21,6 +22,7 @@ import { AVATAR_MAX_BYTES, AVATAR_MAX_DATA_URL_CHARS } from "../shared/avatar-po
import { withEnvAsync } from "../test-utils/env.js";
import type { ResolvedGatewayAuth } from "./auth.js";
import { CONTROL_UI_BOOTSTRAP_CONFIG_PATH } from "./control-ui-contract.js";
import { resolveOpenedControlUiRepresentation } from "./control-ui-static.js";
import {
handleControlUiAssistantMediaRequest,
handleControlUiAvatarRequest,
@@ -121,17 +123,18 @@ describe("handleControlUiHttpRequest", () => {
rootPath: string;
basePath?: string;
rootKind?: "resolved" | "bundled";
headers?: IncomingMessage["headers"];
}) {
const { res, end } = makeMockHttpResponse();
const { res, end, setHeader } = makeMockHttpResponse();
const handled = await handleControlUiHttpRequest(
{ url: params.url, method: params.method } as IncomingMessage,
{ url: params.url, method: params.method, headers: params.headers ?? {} } as IncomingMessage,
res,
{
...(params.basePath ? { basePath: params.basePath } : {}),
root: { kind: params.rootKind ?? "resolved", path: params.rootPath },
},
);
return { res, end, handled };
return { res, end, setHeader, handled };
}
async function runBootstrapConfigRequest(params: {
@@ -1838,6 +1841,258 @@ describe("handleControlUiHttpRequest", () => {
});
});
it("compresses bundled assets and caches them immutably", async () => {
await withControlUiRoot({
fn: async (tmp) => {
const source = "console.log('compressed');\n".repeat(200);
const { filePath } = await writeAssetFile(tmp, "app-AbCd1234.js", source);
await fs.writeFile(`${filePath}.br`, brotliCompressSync(source));
await fs.writeFile(`${filePath}.gz`, gzipSync(source));
const closeSync = vi.spyOn(fsSync, "closeSync");
try {
const { res, end, setHeader, handled } = await runControlUiRequest({
url: "/assets/app-AbCd1234.js",
method: "GET",
rootPath: tmp,
rootKind: "bundled",
headers: { "accept-encoding": "gzip;q=0.5, br, identity;q=0.1" },
});
expect(handled).toBe(true);
expect(res.statusCode).toBe(200);
expect(setHeader).toHaveBeenCalledWith(
"Cache-Control",
"public, max-age=31536000, immutable",
);
expect(setHeader).toHaveBeenCalledWith("Vary", "Accept-Encoding");
expect(setHeader).toHaveBeenCalledWith("Content-Encoding", "br");
const compressed = end.mock.calls[0]?.[0];
expect(Buffer.isBuffer(compressed)).toBe(true);
expect(brotliDecompressSync(compressed as Buffer).toString()).toBe(source);
expect(closeSync.mock.invocationCallOrder.at(-1)).toBeLessThan(
end.mock.invocationCallOrder[0] ?? Number.POSITIVE_INFINITY,
);
} finally {
closeSync.mockRestore();
}
},
});
});
it("serves build-time gzip variants when they are preferred", async () => {
await withControlUiRoot({
fn: async (tmp) => {
const source = "console.log('gzip');\n".repeat(200);
const { filePath } = await writeAssetFile(tmp, "app-EfGh5678.js", source);
await fs.writeFile(`${filePath}.br`, brotliCompressSync(source));
await fs.writeFile(`${filePath}.gz`, gzipSync(source));
const { end, setHeader } = await runControlUiRequest({
url: "/assets/app-EfGh5678.js",
method: "GET",
rootPath: tmp,
rootKind: "bundled",
headers: { "accept-encoding": "br;q=0.5, gzip, identity;q=0.1" },
});
expect(setHeader).toHaveBeenCalledWith("Content-Encoding", "gzip");
expect(gunzipSync(end.mock.calls[0]?.[0] as Buffer).toString()).toBe(source);
},
});
});
it("falls through to an acceptable sidecar when the preferred variant is missing", async () => {
await withControlUiRoot({
fn: async (tmp) => {
const source = "console.log('partial-build');\n".repeat(200);
const { filePath } = await writeAssetFile(tmp, "app-IjKl9012.js", source);
await fs.writeFile(`${filePath}.gz`, gzipSync(source));
const { end, setHeader } = await runControlUiRequest({
url: "/assets/app-IjKl9012.js",
method: "GET",
rootPath: tmp,
rootKind: "bundled",
headers: { "accept-encoding": "br, gzip;q=0.5, identity;q=0" },
});
expect(setHeader).toHaveBeenCalledWith("Content-Encoding", "gzip");
expect(gunzipSync(end.mock.calls[0]?.[0] as Buffer).toString()).toBe(source);
},
});
});
it("closes the source descriptor when opening a sidecar fails", async () => {
await withControlUiRoot({
fn: async (tmp) => {
const { filePath } = await writeAssetFile(tmp, "app-MnOp3456.js", "source\n");
const fd = fsSync.openSync(filePath, "r");
const openError = Object.assign(new Error("descriptor limit"), { code: "EMFILE" });
expect(() =>
resolveOpenedControlUiRepresentation({
req: {
headers: { "accept-encoding": "br, identity;q=0" },
} as IncomingMessage,
sourceFile: { path: filePath, fd },
precompressed: true,
openPrecompressedFile: () => {
throw openError;
},
}),
).toThrow(openError);
expect(() => fsSync.fstatSync(fd)).toThrow(/bad file descriptor/iu);
},
});
});
it("keeps configured-root assets identity encoded and revalidated", async () => {
await withControlUiRoot({
fn: async (tmp) => {
const source = "console.log('configured');\n".repeat(100);
await writeAssetFile(tmp, "app-settings.js", source);
const { end, setHeader } = await runControlUiRequest({
url: "/assets/app-settings.js",
method: "GET",
rootPath: tmp,
headers: { "accept-encoding": "br;q=0, gzip;q=0.8" },
});
expect(setHeader).toHaveBeenCalledWith("Cache-Control", "no-cache");
expect(setHeader).not.toHaveBeenCalledWith("Content-Encoding", expect.anything());
expect(responseBody(end)).toBe(source);
},
});
});
it("returns 406 when no available asset representation is acceptable", async () => {
await withControlUiRoot({
fn: async (tmp) => {
await writeAssetFile(tmp, "app-settings.js", "console.log('configured');\n");
const { res, end } = await runControlUiRequest({
url: "/assets/app-settings.js",
method: "GET",
rootPath: tmp,
headers: { "accept-encoding": "br;q=0, gzip;q=0, identity;q=0" },
});
expect(res.statusCode).toBe(406);
expect(responseBody(end)).toBe("Not Acceptable");
},
});
});
it("varies identity-only assets on Accept-Encoding", async () => {
await withControlUiRoot({
fn: async (tmp) => {
await writeAssetFile(tmp, "logo.png", "png-bytes");
const { setHeader } = await runControlUiRequest({
url: "/assets/logo.png",
method: "GET",
rootPath: tmp,
rootKind: "bundled",
});
expect(setHeader).toHaveBeenCalledWith("Vary", "Accept-Encoding");
},
});
});
it("does not expose precompressed sidecars as independent assets", async () => {
await withControlUiRoot({
fn: async (tmp) => {
const { filePath } = await writeAssetFile(tmp, "app-AbCd1234.js", "source\n");
await fs.writeFile(`${filePath}.br`, brotliCompressSync("source\n"));
const { res, end, handled } = await runControlUiRequest({
url: "/assets/app-AbCd1234.js.br",
method: "GET",
rootPath: tmp,
rootKind: "bundled",
});
expectNotFoundResponse({ handled, res, end });
},
});
});
it("preserves standalone compressed files in configured roots", async () => {
await withControlUiRoot({
fn: async (tmp) => {
await writeAssetFile(tmp, "data.gz", "configured-compressed-artifact\n");
const { end, handled } = await runControlUiRequest({
url: "/assets/data.gz",
method: "GET",
rootPath: tmp,
});
expect(handled).toBe(true);
expect(responseBody(end)).toBe("configured-compressed-artifact\n");
},
});
});
it.each([
["index", "/"],
["SPA fallback", "/chat"],
])("compresses %s HTML after closing its descriptor", async (_name, url) => {
const html = `<html><body>${"hello ".repeat(200)}</body></html>\n`;
await withControlUiRoot({
indexHtml: html,
fn: async (tmp) => {
const { res, end, setHeader } = makeMockHttpResponse();
const closeSync = vi.spyOn(fsSync, "closeSync");
try {
await handleControlUiHttpRequest(
{
url,
method: "GET",
headers: { "accept-encoding": "gzip" },
} as IncomingMessage,
res,
{ root: { kind: "resolved", path: tmp } },
);
expect(setHeader).toHaveBeenCalledWith("Cache-Control", "no-cache");
expect(setHeader).toHaveBeenCalledWith("Content-Encoding", "gzip");
expect(gunzipSync(end.mock.calls[0]?.[0] as Buffer).toString()).toContain(
'<html data-openclaw-terminal-enabled="false">',
);
expect(closeSync.mock.invocationCallOrder.at(-1)).toBeLessThan(
end.mock.invocationCallOrder[0] ?? Number.POSITIVE_INFINITY,
);
} finally {
closeSync.mockRestore();
}
},
});
});
it("returns 406 when every HTML representation is explicitly rejected", async () => {
await withControlUiRoot({
fn: async (tmp) => {
const { res, end } = makeMockHttpResponse();
await handleControlUiHttpRequest(
{
url: "/",
method: "GET",
headers: { "accept-encoding": "*;q=0" },
} as IncomingMessage,
res,
{ root: { kind: "resolved", path: tmp } },
);
expect(res.statusCode).toBe(406);
expect(responseBody(end)).toBe("Not Acceptable");
},
});
});
it("serves HEAD for in-root assets without writing a body", async () => {
await withControlUiRoot({
fn: async (tmp) => {

View File

@@ -63,6 +63,18 @@ import {
CONTROL_UI_AVATAR_PREFIX,
normalizeControlUiBasePath,
} from "./control-ui-shared.js";
import {
isControlUiPrecompressedAssetExtension,
isControlUiStaticAssetExtension,
readAndCloseControlUiFile,
readAndCloseControlUiFileText,
resolveControlUiHtmlEncoding,
resolveOpenedControlUiRepresentation,
respondControlUiNotAcceptable,
respondHeadForControlUiFile,
sendControlUiHtmlBody,
serveControlUiAsset,
} from "./control-ui-static.js";
import { buildMissingScopeForbiddenBody, sendGatewayAuthFailure } from "./http-common.js";
import {
getBearerToken,
@@ -113,62 +125,6 @@ export type ControlUiRootState =
| { kind: "invalid"; path: string }
| { kind: "missing" };
function contentTypeForExt(ext: string): string {
switch (ext) {
case ".html":
return "text/html; charset=utf-8";
case ".js":
return "application/javascript; charset=utf-8";
case ".css":
return "text/css; charset=utf-8";
case ".json":
case ".map":
return "application/json; charset=utf-8";
case ".svg":
return "image/svg+xml";
case ".png":
return "image/png";
case ".jpg":
case ".jpeg":
return "image/jpeg";
case ".gif":
return "image/gif";
case ".webp":
return "image/webp";
case ".ico":
return "image/x-icon";
case ".txt":
return "text/plain; charset=utf-8";
case ".webmanifest":
return "application/manifest+json; charset=utf-8";
default:
return "application/octet-stream";
}
}
/**
* Extensions recognised as static assets. Missing files with these extensions
* return 404 instead of the SPA index.html fallback. `.html` is intentionally
* excluded — actual HTML files on disk are served earlier, and missing `.html`
* paths should fall through to the SPA router (client-side routers may use
* `.html`-suffixed routes).
*/
const STATIC_ASSET_EXTENSIONS = new Set([
".js",
".css",
".json",
".map",
".svg",
".png",
".jpg",
".jpeg",
".gif",
".webp",
".ico",
".txt",
".webmanifest",
]);
const CONTROL_UI_NAMESPACE_PREFIX = "/__openclaw__/";
const CONTROL_UI_ROOT_PUBLIC_ASSETS = new Set([
"apple-touch-icon.png",
@@ -257,16 +213,6 @@ function respondControlUiAssetsUnavailable(
respondPlainText(res, 503, CONTROL_UI_ASSETS_MISSING_MESSAGE);
}
function respondHeadForFile(req: IncomingMessage, res: ServerResponse, filePath: string): boolean {
if (req.method !== "HEAD") {
return false;
}
res.statusCode = 200;
setStaticFileHeaders(res, filePath);
res.end();
return true;
}
function isValidAgentId(agentId: string): boolean {
return /^[a-z0-9][a-z0-9_-]{0,63}$/i.test(agentId);
}
@@ -794,20 +740,8 @@ export async function handleControlUiAvatarRequest(
}
}
function setStaticFileHeaders(res: ServerResponse, filePath: string) {
const ext = path.extname(filePath).toLowerCase();
res.setHeader("Content-Type", contentTypeForExt(ext));
// Static UI should never be cached aggressively while iterating; allow the
// browser to revalidate.
res.setHeader("Cache-Control", "no-cache");
}
function serveResolvedFile(res: ServerResponse, filePath: string, body: Buffer) {
setStaticFileHeaders(res, filePath);
res.end(body);
}
function serveResolvedIndexHtml(
async function serveResolvedIndexHtml(
req: IncomingMessage,
res: ServerResponse,
body: string,
basePath?: string,
@@ -833,23 +767,7 @@ function serveResolvedIndexHtml(
);
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.setHeader("Cache-Control", "no-cache");
res.end(prepared);
}
function readOpenedFile(fd: number): Promise<Buffer> {
return new Promise((resolve, reject) => {
fs.readFile(fd, (error, data) => {
if (error) {
reject(error);
return;
}
resolve(data);
});
});
}
async function readOpenedFileText(fd: number): Promise<string> {
return (await readOpenedFile(fd)).toString("utf8");
await sendControlUiHtmlBody(req, res, prepared);
}
function isExpectedSafePathError(error: unknown): boolean {
@@ -1116,7 +1034,6 @@ export async function handleControlUiHttpRequest(
respondControlUiNotFound(res);
return true;
}
const filePath = path.resolve(root, fileRel);
if (!isWithinDir(root, filePath)) {
respondControlUiNotFound(res);
@@ -1131,35 +1048,77 @@ export async function handleControlUiHttpRequest(
argv1: process.argv[1],
cwd: process.cwd(),
}));
// Bundled sidecars are implementation artifacts selected through
// Accept-Encoding. Configured roots retain ordinary .br/.gz resources.
if (
isBundledRoot &&
isControlUiPrecompressedAssetExtension(path.extname(fileRel).toLowerCase())
) {
respondControlUiNotFound(res);
return true;
}
const rejectHardlinks = !isBundledRoot;
// Vite fingerprints every file emitted under the bundled assets directory.
// Configured roots remain revalidated because their naming is not our contract.
const immutableAsset = isBundledRoot && fileRel.startsWith("assets/");
const safeFile = resolveSafeControlUiFile(rootReal, filePath, rejectHardlinks);
if (safeFile) {
try {
if (respondHeadForFile(req, res, safeFile.path)) {
return true;
if (path.basename(safeFile.path) === "index.html") {
if (req.method === "HEAD") {
try {
const encoding = resolveControlUiHtmlEncoding(req);
if (encoding === "not-acceptable") {
respondControlUiNotAcceptable(res);
return true;
}
respondHeadForControlUiFile(res, safeFile.path, {
encoding: encoding === "identity" ? undefined : encoding,
});
return true;
} finally {
fs.closeSync(safeFile.fd);
}
}
if (path.basename(safeFile.path) === "index.html") {
serveResolvedIndexHtml(
res,
await readOpenedFileText(safeFile.fd),
basePath,
terminalEnabled,
);
return true;
}
serveResolvedFile(res, safeFile.path, await readOpenedFile(safeFile.fd));
const body = await readAndCloseControlUiFileText(safeFile.fd);
await serveResolvedIndexHtml(req, res, body, basePath, terminalEnabled);
return true;
} finally {
fs.closeSync(safeFile.fd);
}
const representation = resolveOpenedControlUiRepresentation({
req,
sourceFile: safeFile,
precompressed: immutableAsset,
openPrecompressedFile: (compressedPath) =>
resolveSafeControlUiFile(rootReal, compressedPath, false),
});
if (!representation) {
respondControlUiNotAcceptable(res);
return true;
}
if (req.method === "HEAD") {
try {
respondHeadForControlUiFile(res, representation.contentPath, {
immutable: immutableAsset,
encoding: representation.encoding,
});
return true;
} finally {
fs.closeSync(representation.bodyFile.fd);
}
}
const body = await readAndCloseControlUiFile(representation.bodyFile.fd);
await serveControlUiAsset(res, representation.contentPath, body, {
immutable: immutableAsset,
encoding: representation.encoding,
});
return true;
}
// If the requested path looks like a static asset (known extension), return
// 404 rather than falling through to the SPA index.html fallback. We check
// against the same set of extensions that contentTypeForExt() recognises so
// against the same extension set used by the static response helper so
// that dotted SPA routes (e.g. /user/jane.doe, /v2.0) still get the
// client-side router fallback.
if (STATIC_ASSET_EXTENSIONS.has(path.extname(fileRel).toLowerCase())) {
if (isControlUiStaticAssetExtension(path.extname(fileRel).toLowerCase())) {
respondControlUiNotFound(res);
return true;
}
@@ -1168,20 +1127,24 @@ export async function handleControlUiHttpRequest(
const indexPath = path.join(root, "index.html");
const safeIndex = resolveSafeControlUiFile(rootReal, indexPath, rejectHardlinks);
if (safeIndex) {
try {
if (respondHeadForFile(req, res, safeIndex.path)) {
if (req.method === "HEAD") {
try {
const encoding = resolveControlUiHtmlEncoding(req);
if (encoding === "not-acceptable") {
respondControlUiNotAcceptable(res);
return true;
}
respondHeadForControlUiFile(res, safeIndex.path, {
encoding: encoding === "identity" ? undefined : encoding,
});
return true;
} finally {
fs.closeSync(safeIndex.fd);
}
serveResolvedIndexHtml(
res,
await readOpenedFileText(safeIndex.fd),
basePath,
terminalEnabled,
);
return true;
} finally {
fs.closeSync(safeIndex.fd);
}
const body = await readAndCloseControlUiFileText(safeIndex.fd);
await serveResolvedIndexHtml(req, res, body, basePath, terminalEnabled);
return true;
}
respondControlUiNotFound(res);