fix(cli): bound diagnostics and add swap management

This commit is contained in:
Codex
2026-05-17 08:07:32 +00:00
parent c8e291f5fd
commit 57402f28c0
13 changed files with 618 additions and 50 deletions
+53 -7
View File
@@ -27,6 +27,9 @@ interface FetchJsonResult {
status?: number;
body?: unknown;
error?: string;
responseTruncated?: boolean;
responseBytesRead?: number;
responseContentLength?: string | null;
}
const hostOptions = new Set(["--main-server-ip", "--main-server", "--server"]);
@@ -172,19 +175,54 @@ function frontendBaseUrl(host: string, config: UniDeskConfig): string {
return `http://${host}:${config.network.frontend.port}`;
}
async function readJson(url: string, init?: RequestInit, timeoutMs = 8000): Promise<FetchJsonResult> {
async function readJson(url: string, init?: RequestInit, timeoutMs = 8000, maxResponseBytes = 5_000_000): Promise<FetchJsonResult> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(url, { ...init, signal: controller.signal });
const text = await res.text();
const reader = res.body?.getReader();
const chunks: Uint8Array[] = [];
let bytes = 0;
let responseTruncated = false;
if (reader !== undefined) {
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (bytes + value.byteLength > maxResponseBytes) {
const keep = Math.max(0, maxResponseBytes - bytes);
if (keep > 0) {
chunks.push(value.slice(0, keep));
bytes += keep;
}
responseTruncated = true;
try {
await reader.cancel();
} catch {
// Ignore cancel failures after the bounded preview has been collected.
}
break;
}
chunks.push(value);
bytes += value.byteLength;
}
}
const buffer = new Uint8Array(bytes);
let offset = 0;
for (const chunk of chunks) {
buffer.set(chunk, offset);
offset += chunk.byteLength;
}
const text = new TextDecoder().decode(buffer);
let body: unknown = null;
try {
body = text.length > 0 ? JSON.parse(text) : null;
body = text.length > 0 && !responseTruncated ? JSON.parse(text) : null;
} catch {
body = { text };
}
return { ok: res.ok, status: res.status, body };
if (responseTruncated) {
body = { _unideskResponseTruncated: true, maxResponseBytes, bytesRead: bytes, contentLength: res.headers.get("content-length"), textPreview: text };
}
return { ok: res.ok, status: res.status, body, responseTruncated, responseBytesRead: bytes, responseContentLength: res.headers.get("content-length") };
} catch (error) {
return { ok: false, error: error instanceof Error ? error.message : String(error) };
} finally {
@@ -208,11 +246,11 @@ async function loginFrontend(host: string, config: UniDeskConfig): Promise<Front
return { baseUrl, cookie };
}
async function frontendJson(session: FrontendSession, path: string, init?: RequestInit, timeoutMs = 8000): Promise<FetchJsonResult> {
async function frontendJson(session: FrontendSession, path: string, init?: RequestInit, timeoutMs = 8000, maxResponseBytes = 5_000_000): Promise<FetchJsonResult> {
const headers = new Headers(init?.headers);
headers.set("cookie", session.cookie);
if (init?.body !== undefined && !headers.has("content-type")) headers.set("content-type", "application/json");
return readJson(`${session.baseUrl}${path}`, { ...init, headers }, timeoutMs);
return readJson(`${session.baseUrl}${path}`, { ...init, headers }, timeoutMs, maxResponseBytes);
}
function stringOption(args: string[], name: string): string | undefined {
@@ -231,6 +269,10 @@ function numberOption(args: string[], name: string, defaultValue: number): numbe
return value;
}
function cappedNumberOption(args: string[], name: string, defaultValue: number, maxValue: number): number {
return Math.min(numberOption(args, name, defaultValue), maxValue);
}
function jsonOption(args: string[], name: string): Record<string, unknown> | undefined {
const raw = stringOption(args, name);
if (raw === undefined) return undefined;
@@ -462,7 +504,11 @@ async function remoteMicroservice(session: FrontendSession, args: string[]): Pro
};
}
if (action === "proxy" && id !== undefined && path !== undefined && path.startsWith("/")) {
const response = await frontendJson(session, `/api/microservices/${encodeURIComponent(id)}/proxy${path}`, undefined, 24_000);
const full = args.includes("--full");
const raw = args.includes("--raw");
const maxBodyBytes = full ? numberOption(args, "--max-body-bytes", 5_000_000) : cappedNumberOption(args, "--max-body-bytes", raw ? 120_000 : 60_000, 500_000);
const maxResponseBytes = full ? Math.min(Math.max(maxBodyBytes, 120_000), 5_000_000) : Math.min(Math.max(maxBodyBytes * 3, 240_000), 1_500_000);
const response = await frontendJson(session, `/api/microservices/${encodeURIComponent(id)}/proxy${path}`, undefined, 24_000, maxResponseBytes);
return {
transport: "frontend",
response: summarizeMicroserviceProxyResponse(response, args),