openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
576 lines (519 loc) • 18.9 kB
HTML
<html data-openclaw-control-ui-build-id="2026.9.2-release-3928bad9badf-2026-09-05T15-22-41.651Z-71b2645fc29849d3cc27b9ef88a1c066f48136ed05c7478b4dc737076b890f9a" lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content"
/>
<title>OpenClaw Control</title>
<meta name="color-scheme" content="dark light" />
<!-- These mirror the claw theme's --bg tokens for first paint; runtime theme application keeps them synchronized. -->
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#faf9f7" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0e1015" />
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
<link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon.png" />
<link rel="manifest" href="./manifest.webmanifest" crossorigin="use-credentials" />
<script data-cfasync="false">
// Configure Zod before module evaluation so its JIT probe cannot violate strict CSP.
globalThis.__zod_globalConfig ??= {};
globalThis.__zod_globalConfig.jitless = true;
(function () {
var THEMES = {
claw: 1,
knot: 1,
dash: 1,
absolutely: 1,
tide: 1,
beacon: 1,
phosphor: 1,
crt: 1,
manuscript: 1,
rose: 1,
miami: 1,
};
var MODES = { system: 1, light: 1, dark: 1 };
var LEGACY = {
dark: "claw:dark",
light: "claw:light",
openknot: "knot:dark",
fieldmanual: "dash:dark",
clawdash: "dash:light",
system: "claw:system",
};
try {
var keys = Object.keys(localStorage);
var raw;
for (var i = 0; i < keys.length; i++) {
if (keys[i].indexOf("openclaw.control.settings.v1") === 0) {
raw = localStorage.getItem(keys[i]);
if (raw) break;
}
}
var s = raw ? JSON.parse(raw) : null;
var t = s && s.theme;
var m = s && s.themeMode;
if (typeof t !== "string") t = "";
if (typeof m !== "string") m = "";
var legacy = LEGACY[t];
var theme = THEMES[t] ? t : legacy ? legacy.split(":")[0] : "claw";
var mode = MODES[m] ? m : legacy ? legacy.split(":")[1] : "system";
if (mode === "system") {
mode = window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
}
// family -> [dark, light] resolved names. Kept in lockstep with
// resolveTheme() in app/theme.ts and the files in public/themes.
var FAMILIES = {
knot: ["openknot", "openknot-light"],
dash: ["dash", "dash-light"],
absolutely: ["absolutely", "absolutely-light"],
tide: ["tide", "tide-light"],
beacon: ["beacon", "beacon-light"],
phosphor: ["phosphor", "phosphor-light"],
crt: ["crt", "crt-light"],
manuscript: ["manuscript", "manuscript-light"],
rose: ["rose", "rose-light"],
miami: ["miami", "miami-light"],
};
var pair = FAMILIES[theme];
var resolved = pair
? pair[mode === "light" ? 1 : 0]
: mode === "light"
? "light"
: "dark";
if (pair) {
// Script-created links need explicit render blocking before first paint.
// The gateway stamps the mount prefix and asset version before this runs.
var base =
document.documentElement.getAttribute("data-openclaw-control-ui-base-path") || "";
var buildId = document.documentElement.getAttribute(
"data-openclaw-control-ui-build-id",
);
var version = buildId ? "?v=" + encodeURIComponent(buildId) : "";
var palette = document.createElement("link");
palette.rel = "stylesheet";
palette.setAttribute("blocking", "render");
palette.id = "openclaw-theme-palette-" + theme;
palette.onerror = function () {
palette.remove();
};
palette.href = base + "/themes/" + theme + ".css" + version;
document.head.appendChild(palette);
}
document.documentElement.setAttribute("data-theme", resolved);
var resolvedMode = resolved.includes("light") ? "light" : "dark";
document.documentElement.setAttribute("data-theme-mode", resolvedMode);
// Carapace CSS selects on [data-theme-resolved]; keep in lockstep.
document.documentElement.setAttribute("data-theme-resolved", resolvedMode);
} catch (e) {}
})();
</script>
<style>
/* Paint before the app stylesheet arrives so WebKit never exposes a white
document between its native dark canvas and the persisted UI theme. */
html,
body {
min-height: 100%;
}
html {
background: #0e1015;
color-scheme: dark;
}
html[data-theme-mode="light"] {
background: #faf9f7;
color-scheme: light;
}
html[data-theme="openknot"] {
background: #080808;
}
html[data-theme="openknot-light"] {
background: #f9f9fb;
}
html[data-theme="dash"] {
background: #1a1210;
}
html[data-theme="dash-light"] {
background: #f7f2ec;
}
html[data-theme="absolutely"] {
background: #1c1c1a;
}
html[data-theme="absolutely-light"] {
background: #faf9f5;
}
html[data-theme="tide"] {
background: #10151b;
}
html[data-theme="tide-light"] {
background: #f7f9fb;
}
html[data-theme="beacon"] {
background: #000000;
}
html[data-theme="beacon-light"] {
background: #ffffff;
}
html[data-theme="phosphor"] {
background: #0a0f0a;
}
html[data-theme="phosphor-light"] {
background: #f4f7f4;
}
html[data-theme="crt"] {
background: #090a09;
}
html[data-theme="crt-light"] {
background: #f5f5f4;
}
html[data-theme="manuscript"] {
background: #211e18;
}
html[data-theme="manuscript-light"] {
background: #f6f1e4;
}
html[data-theme="rose"] {
background: #191724;
}
html[data-theme="rose-light"] {
background: #faf4ed;
}
html[data-theme="miami"] {
background: #140f1e;
}
html[data-theme="miami-light"] {
background: #f7f3f6;
}
body {
margin: 0;
min-height: 100%;
background: inherit;
color-scheme: inherit;
}
body.openclaw-mount-fallback-active {
margin: 0;
min-width: 320px;
color: #eef4f8;
background: #101418;
font-family:
Inter,
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
}
body.openclaw-mount-fallback-active openclaw-app {
display: none;
}
.mount-fallback {
box-sizing: border-box;
/* The app stylesheet clips the root scrollers, so this pre-app error
surface must own its scrolling on short windows. dvh keeps the
scroll range inside the visible mobile viewport; vh is the fallback. */
height: 100vh;
height: 100dvh;
overflow-y: auto;
padding: 24px;
place-items: center;
place-items: safe center;
}
.mount-fallback:not([hidden]) {
display: grid;
}
.mount-fallback__panel {
box-sizing: border-box;
width: min(100%, 640px);
border: 1px solid rgba(148, 163, 184, 0.28);
border-radius: 8px;
background: #151b21;
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.36);
padding: 28px;
}
.mount-fallback__eyebrow {
margin: 0 0 10px;
color: #9fb0bd;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.mount-fallback__panel h1 {
margin: 0;
color: inherit;
font-size: clamp(1.5rem, 3vw, 2rem);
line-height: 1.15;
}
.mount-fallback__panel p {
margin: 16px 0 0;
color: #c8d3da;
font-size: 1rem;
line-height: 1.6;
}
.mount-fallback__panel ul {
margin: 18px 0 0;
padding-left: 1.2rem;
color: #c8d3da;
line-height: 1.6;
}
.mount-fallback__panel a {
color: #8bd3ff;
text-decoration-thickness: 0.08em;
text-underline-offset: 0.18em;
}
.mount-fallback__actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 24px;
}
.mount-fallback__button {
min-height: 42px;
border: 1px solid rgba(148, 163, 184, 0.36);
border-radius: 6px;
padding: 0 16px;
color: inherit;
background: transparent;
font: inherit;
font-weight: 700;
}
.mount-fallback__button--primary {
border-color: #66c2ff;
color: #061019;
background: #8bd3ff;
}
.mount-fallback__panel:focus {
outline: none;
}
.mount-fallback__button:focus-visible,
.mount-fallback__panel a:focus-visible {
outline: 3px solid #f9c74f;
outline-offset: 3px;
}
html[data-theme-mode="light"] body.openclaw-mount-fallback-active {
color: #151b21;
background: #f5f7fa;
}
html[data-theme-mode="light"] .mount-fallback__panel {
border-color: rgba(71, 85, 105, 0.22);
background: #ffffff;
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.14);
}
html[data-theme-mode="light"] .mount-fallback__eyebrow,
html[data-theme-mode="light"] .mount-fallback__panel p,
html[data-theme-mode="light"] .mount-fallback__panel ul {
color: #4b5963;
}
html[data-theme-mode="light"] .mount-fallback__panel a {
color: #0369a1;
}
</style>
<script data-cfasync="false" type="module" crossorigin src="./assets/index-1fvEwqai.js"></script>
<link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-DkW27tQK.js">
<link rel="modulepreload" crossorigin href="./assets/lit-runtime-CV_MH99K.js">
<link rel="modulepreload" crossorigin href="./assets/control-ui-core-vvcQQV2m.js">
<link rel="modulepreload" crossorigin href="./assets/gateway-runtime-Bqqcp9-I.js">
<link rel="modulepreload" crossorigin href="./assets/control-ui-core-YphAl4aM.js">
<link rel="modulepreload" crossorigin href="./assets/control-ui-core-Rwzhj6nO.js">
<link rel="modulepreload" crossorigin href="./assets/control-ui-foundation-Cydpkyqa.js">
<link rel="stylesheet" crossorigin href="./assets/control-ui-core-ClxHIvNM.css">
</head>
<body>
<openclaw-app></openclaw-app>
<section
id="openclaw-mount-fallback"
class="mount-fallback"
data-openclaw-mount-timeout-ms="12000"
role="alert"
aria-labelledby="openclaw-mount-fallback-title"
hidden
>
<div class="mount-fallback__panel" tabindex="-1">
<p class="mount-fallback__eyebrow">OpenClaw Control UI</p>
<h1 id="openclaw-mount-fallback-title">Control UI did not start</h1>
<p id="openclaw-mount-fallback-summary">
The browser loaded the static page, but the app bundle did not start. The gateway may be
restarting, or this page may reference assets from a different OpenClaw version.
</p>
<ul>
<li>OpenClaw will retry the current app bundle automatically.</li>
<li>If this persists, reload or try a clean browser profile.</li>
<li>
See
<a
href="https://docs.openclaw.ai/web/control-ui#blank-control-ui-page"
target="_blank"
rel="noopener noreferrer"
>Control UI troubleshooting</a
>.
</li>
</ul>
<div class="mount-fallback__actions">
<button
type="button"
id="openclaw-mount-retry"
class="mount-fallback__button mount-fallback__button--primary"
>
Try again
</button>
<button type="button" id="openclaw-mount-wait" class="mount-fallback__button">
Keep waiting
</button>
</div>
</div>
</section>
<script data-cfasync="false">
(function () {
var app = document.querySelector("openclaw-app");
var fallback = document.getElementById("openclaw-mount-fallback");
if (!app || !fallback) return;
var panel = fallback.querySelector(".mount-fallback__panel");
var summary = document.getElementById("openclaw-mount-fallback-summary");
var retry = document.getElementById("openclaw-mount-retry");
var wait = document.getElementById("openclaw-mount-wait");
var rawDelay = Number(fallback.getAttribute("data-openclaw-mount-timeout-ms"));
var delay = Number.isFinite(rawDelay) && rawDelay > 0 ? rawDelay : 12000;
var maxRecoveryAttempts = 6;
var timer;
var recoveryTimer;
var recoveryAttempt = 0;
var recoveryInFlight = false;
var recoveryNavigation = false;
var appStarted = false;
try {
var initialUrl = new URL(window.location.href);
recoveryNavigation = initialUrl.searchParams.has("openclaw_mount_recovery");
if (recoveryNavigation) {
initialUrl.searchParams.delete("openclaw_mount_recovery");
window.history.replaceState(null, "", initialUrl.href);
}
} catch (e) {}
function hideFallback() {
fallback.hidden = true;
document.body.classList.remove("openclaw-mount-fallback-active");
}
function setSummary(text) {
if (summary) summary.textContent = text;
}
function scheduleRecovery() {
window.clearTimeout(recoveryTimer);
if (appStarted || recoveryAttempt >= maxRecoveryAttempts) return;
var retryDelay = Math.min(delay, 1000 * Math.pow(2, recoveryAttempt));
recoveryTimer = window.setTimeout(retryCurrentDocument, retryDelay);
}
function finishRecoveryAttempt() {
recoveryInFlight = false;
if (appStarted) return;
if (recoveryAttempt >= maxRecoveryAttempts) {
setSummary(
"The gateway is still unavailable. Try again, then check the troubleshooting guide if the problem persists.",
);
return;
}
setSummary(
"The gateway is not reachable yet. OpenClaw will keep retrying while it restarts.",
);
scheduleRecovery();
}
function unregisterServiceWorkers() {
if (!navigator.serviceWorker?.getRegistrations) return;
// Recovery must not depend on a possibly broken worker. Navigations bypass it,
// so a reload can self-heal after the registrations are removed.
void navigator.serviceWorker
.getRegistrations()
.then(function (registrations) {
return Promise.all(
registrations.map(function (registration) {
return registration.unregister();
}),
);
})
.catch(function () {});
}
function retryCurrentDocument() {
if (
appStarted ||
recoveryInFlight ||
recoveryAttempt >= maxRecoveryAttempts ||
typeof window.fetch !== "function"
)
return;
var documentUrl = new URL(window.location.href);
if (recoveryNavigation) {
setSummary(
"A fresh page still could not start the Control UI. Try again, then check the troubleshooting guide if the problem persists.",
);
return;
}
recoveryInFlight = true;
recoveryAttempt += 1;
documentUrl.searchParams.set("openclaw_mount_recovery", String(Date.now()));
var recoveryController = new AbortController();
var requestTimer = window.setTimeout(function () {
recoveryController.abort();
}, delay);
window
.fetch(documentUrl.href, {
cache: "no-store",
credentials: "same-origin",
signal: recoveryController.signal,
})
.then(function (response) {
if (!response.ok) throw new Error("gateway unavailable");
if (!appStarted) window.location.replace(documentUrl.href);
})
.catch(function () {
unregisterServiceWorkers();
finishRecoveryAttempt();
})
.finally(function () {
window.clearTimeout(requestTimer);
});
}
function showFallback() {
if (appStarted) return;
retryCurrentDocument();
fallback.hidden = false;
document.body.classList.add("openclaw-mount-fallback-active");
if (panel && typeof panel.focus === "function") {
try {
panel.focus({ preventScroll: true });
} catch (e) {
panel.focus();
}
}
}
function armFallbackTimer() {
window.clearTimeout(timer);
timer = window.setTimeout(showFallback, delay);
}
armFallbackTimer();
window.addEventListener(
"openclaw-control-ui-rendered",
function () {
appStarted = true;
window.clearTimeout(timer);
window.clearTimeout(recoveryTimer);
hideFallback();
},
{ once: true },
);
if (retry) {
retry.addEventListener("click", function () {
unregisterServiceWorkers();
window.location.reload();
});
}
if (wait) {
wait.addEventListener("click", function () {
hideFallback();
recoveryAttempt = 0;
retryCurrentDocument();
armFallbackTimer();
});
}
})();
</script>
</body>
</html>