openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
307 lines (306 loc) • 12 kB
JavaScript
import { n as resolveOpenClawPackageRootSync, t as resolveOpenClawPackageRoot } from "./openclaw-root-CfYY-fyD.js";
import { d as normalizeStringEntries } from "./string-normalization-DsCfAx8q.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { r as defaultRuntime } from "./runtime-CF2WjnNZ.js";
import { r as runCommandWithTimeout } from "./exec-BIE-3oLG.js";
import { n as quotePowerShellArg, t as quoteCliArg } from "./quote-cli-arg-BEt71TUh.js";
import fs from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
//#region src/infra/control-ui-assets.fs.runtime.ts
const existsSync$1 = fs.existsSync.bind(fs);
const readFileSync$1 = fs.readFileSync.bind(fs);
const statSync$1 = fs.statSync.bind(fs);
const realpathSync$1 = fs.realpathSync.bind(fs);
//#endregion
//#region src/infra/control-ui-assets.ts
function formatControlUiSourceCommand(root, action) {
return `pnpm --dir ${process.platform === "win32" ? quotePowerShellArg(root) : quoteCliArg(root)} ui:${action}`;
}
function resolveControlUiDistIndexPathForRoot(root) {
return path.join(root, "dist", "control-ui", "index.html");
}
async function resolveControlUiAssetHealth(opts = {}) {
return inspectControlUiAssetHealth(opts.root ? resolveControlUiDistIndexPathForRoot(opts.root) : await resolveControlUiDistIndexPath({
argv1: opts.argv1 ?? process.argv[1],
moduleUrl: opts.moduleUrl
}));
}
function resolveControlUiRepoRoot(opts) {
const cwd = opts.cwd ?? process.cwd();
return (opts.root ? [path.resolve(opts.root)] : [resolveOpenClawPackageRootSync({
argv1: opts.argv1 ?? process.argv[1],
moduleUrl: opts.moduleUrl ?? import.meta.url,
cwd
}), resolveOpenClawPackageRootSync({ cwd })]).find((root) => root !== null && existsSync$1(path.join(root, "ui", "vite.config.ts"))) ?? null;
}
async function resolveControlUiDistIndexPath(argv1OrOpts) {
const argv1 = typeof argv1OrOpts === "string" ? argv1OrOpts : argv1OrOpts?.argv1 ?? process.argv[1];
const moduleUrl = typeof argv1OrOpts === "object" ? argv1OrOpts?.moduleUrl : void 0;
if (!argv1) return null;
const normalized = path.resolve(argv1);
const entrypointCandidates = [normalized];
try {
const realpathEntrypoint = realpathSync$1(normalized);
if (realpathEntrypoint !== normalized) entrypointCandidates.push(realpathEntrypoint);
} catch {}
for (const entrypoint of entrypointCandidates) {
const distDir = path.dirname(entrypoint);
if (path.basename(distDir) === "dist") return path.join(distDir, "control-ui", "index.html");
}
const packageRoot = await resolveOpenClawPackageRoot({
argv1: normalized,
moduleUrl
});
if (packageRoot) return path.join(packageRoot, "dist", "control-ui", "index.html");
const fallbackStartDirs = new Set(entrypointCandidates.map((candidate) => path.dirname(candidate)));
for (const startDir of fallbackStartDirs) {
let dir = startDir;
for (let i = 0; i < 8; i++) {
const pkgJsonPath = path.join(dir, "package.json");
const indexPath = path.join(dir, "dist", "control-ui", "index.html");
if (existsSync$1(pkgJsonPath)) try {
const raw = readFileSync$1(pkgJsonPath, "utf-8");
if (JSON.parse(raw).name === "openclaw") return existsSync$1(indexPath) ? indexPath : null;
break;
} catch {
break;
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
}
return null;
}
function pathsMatchByRealpathOrResolve(left, right) {
let realLeft;
let realRight;
try {
realLeft = realpathSync$1(left);
} catch {
realLeft = path.resolve(left);
}
try {
realRight = realpathSync$1(right);
} catch {
realRight = path.resolve(right);
}
return realLeft === realRight;
}
function addCandidate(candidates, value) {
if (!value) return;
candidates.add(path.resolve(value));
}
function resolveControlUiRootOverrideSync(rootOverride) {
const resolved = path.resolve(rootOverride);
try {
const stats = statSync$1(resolved);
if (stats.isFile()) return path.basename(resolved) === "index.html" ? path.dirname(resolved) : null;
if (stats.isDirectory()) {
const indexPath = path.join(resolved, "index.html");
return existsSync$1(indexPath) ? resolved : null;
}
} catch {
return null;
}
return null;
}
function resolveControlUiRootSync(opts = {}) {
const candidates = /* @__PURE__ */ new Set();
const argv1 = opts.argv1 ?? process.argv[1];
const cwd = opts.cwd ?? process.cwd();
const moduleDir = opts.moduleUrl ? path.dirname(fileURLToPath(opts.moduleUrl)) : null;
const argv1Dir = argv1 ? path.dirname(path.resolve(argv1)) : null;
const argv1RealpathDir = (() => {
if (!argv1) return null;
try {
return path.dirname(realpathSync$1(path.resolve(argv1)));
} catch {
return null;
}
})();
const execDir = (() => {
try {
const execPath = opts.execPath ?? process.execPath;
return path.dirname(realpathSync$1(execPath));
} catch {
return null;
}
})();
const packageRoot = resolveOpenClawPackageRootSync({
argv1,
moduleUrl: opts.moduleUrl,
cwd
});
addCandidate(candidates, execDir ? path.join(execDir, "../Resources/control-ui") : null);
addCandidate(candidates, execDir ? path.join(execDir, "control-ui") : null);
if (moduleDir) {
addCandidate(candidates, path.join(moduleDir, "control-ui"));
addCandidate(candidates, path.join(moduleDir, "../control-ui"));
addCandidate(candidates, path.join(moduleDir, "../../dist/control-ui"));
}
if (argv1Dir) {
addCandidate(candidates, path.join(argv1Dir, "dist", "control-ui"));
addCandidate(candidates, path.join(argv1Dir, "control-ui"));
}
if (argv1RealpathDir && argv1RealpathDir !== argv1Dir) {
addCandidate(candidates, path.join(argv1RealpathDir, "dist", "control-ui"));
addCandidate(candidates, path.join(argv1RealpathDir, "control-ui"));
}
if (packageRoot) addCandidate(candidates, path.join(packageRoot, "dist", "control-ui"));
addCandidate(candidates, path.join(cwd, "dist", "control-ui"));
for (const dir of candidates) {
const indexPath = path.join(dir, "index.html");
if (existsSync$1(indexPath)) return dir;
}
return null;
}
function isPackageProvenControlUiRootSync(root, opts = {}) {
const argv1 = opts.argv1 ?? process.argv[1];
const cwd = opts.cwd ?? process.cwd();
const packageRoot = resolveOpenClawPackageRootSync({
argv1,
moduleUrl: opts.moduleUrl,
cwd
});
if (!packageRoot) return false;
return pathsMatchByRealpathOrResolve(root, path.join(packageRoot, "dist", "control-ui"));
}
const CONTROL_UI_ASSETS_BUILD_TIMEOUT_MS = 6e5;
function controlUiAssetsFailure(message, built = false) {
return {
ok: false,
built,
message
};
}
function inspectControlUiAssetHealth(indexPath) {
if (!indexPath) return {
kind: "missing-index",
indexPath
};
let html;
try {
if (statSync$1(indexPath).size > 262144) return {
kind: "incomplete",
indexPath,
missingAsset: "index.html exceeds its size limit"
};
html = readFileSync$1(indexPath, "utf8");
} catch {
return {
kind: "missing-index",
indexPath
};
}
let references = 0;
for (const tag of html.matchAll(/<(?:link|script)\b[^>]*>/giu)) {
const reference = tag[0].match(/\s(?:href|src)\s*=\s*["']([^"']+)["']/iu)?.[1]?.split(/[?#]/u, 1)[0]?.replace(/\\/gu, "/");
if (!reference || /^(?:[a-z][a-z\d+.-]*:|\/\/|#)/iu.test(reference)) continue;
const marker = reference.lastIndexOf("assets/");
if (marker === -1 || !/\.(?:css|js)$/iu.test(reference)) continue;
const asset = reference.slice(marker);
if (++references > 128 || reference.split("/").includes("..")) return {
kind: "incomplete",
indexPath,
missingAsset: references > 128 ? "too many startup assets" : asset
};
if (!existsSync$1(path.join(path.dirname(indexPath), asset))) return {
kind: "incomplete",
indexPath,
missingAsset: asset
};
}
return {
kind: "ready",
indexPath
};
}
function isControlUiStartupAssetsReady(root) {
return inspectControlUiAssetHealth(path.join(root, "index.html")).kind === "ready";
}
function summarizeCommandOutput(text) {
const lines = normalizeStringEntries(text.split(/\r?\n/g));
if (!lines.length) return;
const last = lines.at(-1);
if (!last) return;
return last.length > 240 ? `${truncateUtf16Safe(last, 239)}…` : last;
}
async function ensureControlUiAssetsBuilt(runtime = defaultRuntime, opts = {}) {
const argv1 = opts.argv1 ?? process.argv[1];
const health = await resolveControlUiAssetHealth({
...opts.root ? { root: opts.root } : {},
argv1,
moduleUrl: opts.moduleUrl
});
const indexFromDist = health.indexPath;
let missingStartupAsset = health.kind === "incomplete" ? health.missingAsset : void 0;
if (!opts.force && health.kind === "ready") return {
ok: true,
built: false
};
if (!opts.force && !opts.root) {
const detectedRoot = resolveControlUiRootSync({
argv1,
moduleUrl: opts.moduleUrl,
cwd: opts.cwd,
execPath: opts.execPath
});
if (detectedRoot) {
const detectedHealth = inspectControlUiAssetHealth(path.join(detectedRoot, "index.html"));
missingStartupAsset = detectedHealth.kind === "incomplete" ? detectedHealth.missingAsset : void 0;
if (detectedHealth.kind === "ready") return {
ok: true,
built: false
};
}
}
const repoRoot = resolveControlUiRepoRoot({
root: opts.root,
argv1,
moduleUrl: opts.moduleUrl,
cwd: opts.cwd
});
if (!repoRoot) return controlUiAssetsFailure(`${missingStartupAsset ? `Incomplete Control UI assets${indexFromDist ? ` at ${indexFromDist}` : ""} (missing ${missingStartupAsset})` : indexFromDist ? `Missing Control UI assets at ${indexFromDist}` : "Missing Control UI assets"}. Reinstall OpenClaw to restore bundled Control UI assets.`);
const indexPath = resolveControlUiDistIndexPathForRoot(repoRoot);
if (!opts.force && inspectControlUiAssetHealth(indexPath).kind === "ready") return {
ok: true,
built: false
};
const uiScript = path.join(repoRoot, "scripts", "ui.js");
if (!existsSync$1(uiScript)) return controlUiAssetsFailure(`Control UI assets missing but ${uiScript} is unavailable.`);
if (opts.signal?.aborted) return controlUiAssetsFailure("Control UI build canceled.");
if (opts.onBuildStart) opts.onBuildStart();
else {
const buildCommand = formatControlUiSourceCommand(repoRoot, "build");
const devCommand = formatControlUiSourceCommand(repoRoot, "dev");
runtime.log(`Control UI assets missing; building them now (rerun \`${buildCommand}\` after UI changes, or use \`${devCommand}\` while developing the Control UI)…`);
}
let build;
try {
build = await runCommandWithTimeout([
process.execPath,
uiScript,
"build"
], {
cwd: repoRoot,
timeoutMs: opts.timeoutMs ?? 6e5,
...opts.signal ? { signal: opts.signal } : {}
});
} catch (error) {
return controlUiAssetsFailure(`Control UI build failed: ${summarizeCommandOutput(error instanceof Error ? error.message : String(error)) ?? "unknown error"}`);
}
if (build.termination === "signal") return controlUiAssetsFailure("Control UI build canceled.");
if (build.termination === "timeout" || build.termination === "no-output-timeout") return controlUiAssetsFailure("Control UI build timed out.");
if (build.code !== 0) return controlUiAssetsFailure(`Control UI build failed: ${summarizeCommandOutput(build.stderr) ?? `exit ${build.code}`}`);
const builtHealth = inspectControlUiAssetHealth(indexPath);
if (builtHealth.kind === "missing-index") return controlUiAssetsFailure(`Control UI build completed but ${indexPath} is still missing.`, true);
if (builtHealth.kind === "incomplete") return controlUiAssetsFailure(`Control UI build completed but startup asset ${builtHealth.missingAsset} is missing.`, true);
return {
ok: true,
built: true
};
}
//#endregion
export { isPackageProvenControlUiRootSync as a, resolveControlUiRootOverrideSync as c, isControlUiStartupAssetsReady as i, resolveControlUiRootSync as l, ensureControlUiAssetsBuilt as n, resolveControlUiAssetHealth as o, formatControlUiSourceCommand as r, resolveControlUiDistIndexPathForRoot as s, CONTROL_UI_ASSETS_BUILD_TIMEOUT_MS as t };