refactor: rename clawdbot to moltbot with legacy compat

This commit is contained in:
Peter Steinberger
2026-01-27 12:19:58 +00:00
parent 83460df96f
commit 6d16a658e5
1839 changed files with 11250 additions and 11199 deletions

View File

@@ -3,11 +3,12 @@ import type { IncomingMessage, ServerResponse } from "node:http";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { LEGACY_CANVAS_HANDLER_NAME } from "../compat/legacy-names.js";
import { detectMime } from "../media/mime.js";
export const A2UI_PATH = "/__clawdbot__/a2ui";
export const CANVAS_HOST_PATH = "/__clawdbot__/canvas";
export const CANVAS_WS_PATH = "/__clawdbot/ws";
export const A2UI_PATH = "/__moltbot__/a2ui";
export const CANVAS_HOST_PATH = "/__moltbot__/canvas";
export const CANVAS_WS_PATH = "/__moltbot/ws";
let cachedA2uiRootReal: string | null | undefined;
let resolvingA2uiRoot: Promise<string | null> | null = null;
@@ -91,27 +92,30 @@ async function resolveA2uiFilePath(rootReal: string, urlPath: string) {
}
export function injectCanvasLiveReload(html: string): string {
const legacyHandlerName = LEGACY_CANVAS_HANDLER_NAME;
const snippet = `
<script>
(() => {
// Cross-platform action bridge helper.
// Works on:
// - iOS: window.webkit.messageHandlers.clawdbotCanvasA2UIAction.postMessage(...)
// - Android: window.clawdbotCanvasA2UIAction.postMessage(...)
const actionHandlerName = "clawdbotCanvasA2UIAction";
// - iOS: window.webkit.messageHandlers.(current|legacy)CanvasA2UIAction.postMessage(...)
// - Android: window.(current|legacy)CanvasA2UIAction.postMessage(...)
const handlerNames = ["moltbotCanvasA2UIAction", "${legacyHandlerName}"];
function postToNode(payload) {
try {
const raw = typeof payload === "string" ? payload : JSON.stringify(payload);
const iosHandler = globalThis.webkit?.messageHandlers?.[actionHandlerName];
if (iosHandler && typeof iosHandler.postMessage === "function") {
iosHandler.postMessage(raw);
return true;
}
const androidHandler = globalThis[actionHandlerName];
if (androidHandler && typeof androidHandler.postMessage === "function") {
// Important: call as a method on the interface object (binding matters on Android WebView).
androidHandler.postMessage(raw);
return true;
for (const name of handlerNames) {
const iosHandler = globalThis.webkit?.messageHandlers?.[name];
if (iosHandler && typeof iosHandler.postMessage === "function") {
iosHandler.postMessage(raw);
return true;
}
const androidHandler = globalThis[name];
if (androidHandler && typeof androidHandler.postMessage === "function") {
// Important: call as a method on the interface object (binding matters on Android WebView).
androidHandler.postMessage(raw);
return true;
}
}
} catch {}
return false;
@@ -123,9 +127,11 @@ export function injectCanvasLiveReload(html: string): string {
const action = { ...userAction, id };
return postToNode({ userAction: action });
}
globalThis.Clawdbot = globalThis.Clawdbot ?? {};
globalThis.Clawdbot.postMessage = postToNode;
globalThis.Clawdbot.sendUserAction = sendUserAction;
globalThis.Moltbot = globalThis.Moltbot ?? {};
globalThis.Moltbot.postMessage = postToNode;
globalThis.Moltbot.sendUserAction = sendUserAction;
globalThis.moltbotPostMessage = postToNode;
globalThis.moltbotSendUserAction = sendUserAction;
globalThis.clawdbotPostMessage = postToNode;
globalThis.clawdbotSendUserAction = sendUserAction;

View File

@@ -1 +1 @@
b3c955e808e8d11cdbb6f716a038f26ccdd4b69228ad0c4ce76fd81e98496d56
1376c2e99ad07193d9ab1719200675d84ffb40db417d05128cf07c3b8283581e

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Clawdbot Canvas</title>
<title>Moltbot Canvas</title>
<script>
(() => {
try {
@@ -81,7 +81,7 @@
backface-visibility: hidden;
opacity: 0.45;
pointer-events: none;
animation: clawdbot-grid-drift 140s ease-in-out infinite alternate;
animation: moltbot-grid-drift 140s ease-in-out infinite alternate;
}
:root[data-platform="android"] body::before {
opacity: 0.8;
@@ -101,7 +101,7 @@
backface-visibility: hidden;
transform: translate3d(0, 0, 0);
pointer-events: none;
animation: clawdbot-glow-drift 110s ease-in-out infinite alternate;
animation: moltbot-glow-drift 110s ease-in-out infinite alternate;
}
:root[data-platform="android"] body::after {
opacity: 0.85;
@@ -116,7 +116,7 @@
opacity: 0.7;
}
}
@keyframes clawdbot-grid-drift {
@keyframes moltbot-grid-drift {
0% {
transform: translate3d(-12px, 8px, 0) rotate(-7deg);
opacity: 0.4;
@@ -130,7 +130,7 @@
opacity: 0.42;
}
}
@keyframes clawdbot-glow-drift {
@keyframes moltbot-glow-drift {
0% {
transform: translate3d(-18px, 12px, 0) scale(1.02);
opacity: 0.4;
@@ -153,14 +153,14 @@
touch-action: none;
z-index: 1;
}
:root[data-platform="android"] #clawdbot-canvas {
:root[data-platform="android"] #moltbot-canvas {
background:
radial-gradient(1100px 800px at 20% 15%, rgba(42, 113, 255, 0.78), rgba(0, 0, 0, 0) 58%),
radial-gradient(900px 650px at 82% 28%, rgba(255, 0, 138, 0.66), rgba(0, 0, 0, 0) 62%),
radial-gradient(1000px 900px at 60% 88%, rgba(0, 209, 255, 0.58), rgba(0, 0, 0, 0) 62%),
#141c33;
}
#clawdbot-status {
#moltbot-status {
position: fixed;
inset: 0;
display: none;
@@ -172,7 +172,7 @@
pointer-events: none;
z-index: 3;
}
#clawdbot-status .card {
#moltbot-status .card {
width: min(560px, 88vw);
text-align: left;
padding: 14px 16px 12px;
@@ -185,7 +185,7 @@
-webkit-backdrop-filter: blur(18px) saturate(140%);
backdrop-filter: blur(18px) saturate(140%);
}
#clawdbot-status .title {
#moltbot-status .title {
font:
600 12px/1.2 -apple-system,
BlinkMacSystemFont,
@@ -196,7 +196,7 @@
text-transform: uppercase;
color: rgba(255, 255, 255, 0.7);
}
#clawdbot-status .subtitle {
#moltbot-status .subtitle {
margin-top: 8px;
font:
500 13px/1.45 -apple-system,
@@ -208,39 +208,39 @@
white-space: pre-wrap;
overflow-wrap: anywhere;
}
clawdbot-a2ui-host {
moltbot-a2ui-host {
display: block;
height: 100%;
position: fixed;
inset: 0;
z-index: 4;
--clawdbot-a2ui-inset-top: 28px;
--clawdbot-a2ui-inset-right: 0px;
--clawdbot-a2ui-inset-bottom: 0px;
--clawdbot-a2ui-inset-left: 0px;
--clawdbot-a2ui-scroll-pad-bottom: 0px;
--clawdbot-a2ui-status-top: calc(50% - 18px);
--clawdbot-a2ui-empty-top: 18px;
--moltbot-a2ui-inset-top: 28px;
--moltbot-a2ui-inset-right: 0px;
--moltbot-a2ui-inset-bottom: 0px;
--moltbot-a2ui-inset-left: 0px;
--moltbot-a2ui-scroll-pad-bottom: 0px;
--moltbot-a2ui-status-top: calc(50% - 18px);
--moltbot-a2ui-empty-top: 18px;
}
</style>
</head>
<body>
<canvas id="clawdbot-canvas"></canvas>
<div id="clawdbot-status">
<canvas id="moltbot-canvas"></canvas>
<div id="moltbot-status">
<div class="card">
<div class="title" id="clawdbot-status-title">Ready</div>
<div class="subtitle" id="clawdbot-status-subtitle">Waiting for agent</div>
<div class="title" id="moltbot-status-title">Ready</div>
<div class="subtitle" id="moltbot-status-subtitle">Waiting for agent</div>
</div>
</div>
<clawdbot-a2ui-host></clawdbot-a2ui-host>
<moltbot-a2ui-host></moltbot-a2ui-host>
<script src="a2ui.bundle.js"></script>
<script>
(() => {
const canvas = document.getElementById("clawdbot-canvas");
const canvas = document.getElementById("moltbot-canvas");
const ctx = canvas.getContext("2d");
const statusEl = document.getElementById("clawdbot-status");
const titleEl = document.getElementById("clawdbot-status-title");
const subtitleEl = document.getElementById("clawdbot-status-subtitle");
const statusEl = document.getElementById("moltbot-status");
const titleEl = document.getElementById("moltbot-status-title");
const subtitleEl = document.getElementById("moltbot-status-subtitle");
const debugStatusEnabledByQuery = (() => {
try {
const params = new URLSearchParams(window.location.search);
@@ -278,7 +278,7 @@
statusEl.style.display = "none";
}
window.__clawdbot = {
window.__moltbot = {
canvas,
ctx,
setDebugStatusEnabled,

View File

@@ -15,12 +15,12 @@ describe("canvas host", () => {
const out = injectCanvasLiveReload("<html><body>Hello</body></html>");
expect(out).toContain(CANVAS_WS_PATH);
expect(out).toContain("location.reload");
expect(out).toContain("clawdbotCanvasA2UIAction");
expect(out).toContain("clawdbotSendUserAction");
expect(out).toContain("moltbotCanvasA2UIAction");
expect(out).toContain("moltbotSendUserAction");
});
it("creates a default index.html when missing", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-canvas-"));
const server = await startCanvasHost({
runtime: defaultRuntime,
@@ -35,7 +35,7 @@ describe("canvas host", () => {
const html = await res.text();
expect(res.status).toBe(200);
expect(html).toContain("Interactive test page");
expect(html).toContain("clawdbotSendUserAction");
expect(html).toContain("moltbotSendUserAction");
expect(html).toContain(CANVAS_WS_PATH);
} finally {
await server.close();
@@ -44,7 +44,7 @@ describe("canvas host", () => {
});
it("skips live reload injection when disabled", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-canvas-"));
await fs.writeFile(path.join(dir, "index.html"), "<html><body>no-reload</body></html>", "utf8");
const server = await startCanvasHost({
@@ -72,7 +72,7 @@ describe("canvas host", () => {
});
it("serves canvas content from the mounted base path", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-canvas-"));
await fs.writeFile(path.join(dir, "index.html"), "<html><body>v1</body></html>", "utf8");
const handler = await createCanvasHostHandler({
@@ -117,7 +117,7 @@ describe("canvas host", () => {
});
it("reuses a handler without closing it twice", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-canvas-"));
await fs.writeFile(path.join(dir, "index.html"), "<html><body>v1</body></html>", "utf8");
const handler = await createCanvasHostHandler({
@@ -150,7 +150,7 @@ describe("canvas host", () => {
});
it("serves HTML with injection and broadcasts reload on file changes", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-canvas-"));
const index = path.join(dir, "index.html");
await fs.writeFile(index, "<html><body>v1</body></html>", "utf8");
@@ -201,7 +201,7 @@ describe("canvas host", () => {
}, 20_000);
it("serves the gateway-hosted A2UI scaffold", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-canvas-"));
const server = await startCanvasHost({
runtime: defaultRuntime,
@@ -212,18 +212,18 @@ describe("canvas host", () => {
});
try {
const res = await fetch(`http://127.0.0.1:${server.port}/__clawdbot__/a2ui/`);
const res = await fetch(`http://127.0.0.1:${server.port}/__moltbot__/a2ui/`);
const html = await res.text();
expect(res.status).toBe(200);
expect(html).toContain("clawdbot-a2ui-host");
expect(html).toContain("clawdbotCanvasA2UIAction");
expect(html).toContain("moltbot-a2ui-host");
expect(html).toContain("moltbotCanvasA2UIAction");
const bundleRes = await fetch(
`http://127.0.0.1:${server.port}/__clawdbot__/a2ui/a2ui.bundle.js`,
`http://127.0.0.1:${server.port}/__moltbot__/a2ui/a2ui.bundle.js`,
);
const js = await bundleRes.text();
expect(bundleRes.status).toBe(200);
expect(js).toContain("clawdbotA2UI");
expect(js).toContain("moltbotA2UI");
} finally {
await server.close();
await fs.rm(dir, { recursive: true, force: true });

View File

@@ -59,7 +59,7 @@ function defaultIndexHTML() {
return `<!doctype html>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Clawdbot Canvas</title>
<title>Moltbot Canvas</title>
<style>
html, body { height: 100%; margin: 0; background: #000; color: #fff; font: 16px/1.4 -apple-system, BlinkMacSystemFont, system-ui, Segoe UI, Roboto, Helvetica, Arial, sans-serif; }
.wrap { min-height: 100%; display: grid; place-items: center; padding: 24px; }
@@ -77,7 +77,7 @@ function defaultIndexHTML() {
<div class="wrap">
<div class="card">
<div class="title">
<h1>Clawdbot Canvas</h1>
<h1>Moltbot Canvas</h1>
<div class="sub">Interactive test page (auto-reload enabled)</div>
</div>
@@ -98,26 +98,51 @@ function defaultIndexHTML() {
const statusEl = document.getElementById("status");
const log = (msg) => { logEl.textContent = String(msg); };
const hasIOS = () => !!(window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.clawdbotCanvasA2UIAction);
const hasAndroid = () => !!(window.clawdbotCanvasA2UIAction && typeof window.clawdbotCanvasA2UIAction.postMessage === "function");
const hasHelper = () => typeof window.clawdbotSendUserAction === "function";
const hasIOS = () =>
!!(
window.webkit &&
window.webkit.messageHandlers &&
(window.webkit.messageHandlers.moltbotCanvasA2UIAction ||
window.webkit.messageHandlers.clawdbotCanvasA2UIAction)
);
const hasAndroid = () =>
!!(
(window.moltbotCanvasA2UIAction &&
typeof window.moltbotCanvasA2UIAction.postMessage === "function") ||
(window.clawdbotCanvasA2UIAction &&
typeof window.clawdbotCanvasA2UIAction.postMessage === "function")
);
const legacySend = typeof window.clawdbotSendUserAction === "function" ? window.clawdbotSendUserAction : undefined;
if (!window.moltbotSendUserAction && legacySend) {
window.moltbotSendUserAction = legacySend;
}
if (!window.clawdbotSendUserAction && typeof window.moltbotSendUserAction === "function") {
window.clawdbotSendUserAction = window.moltbotSendUserAction;
}
const hasHelper = () =>
typeof window.moltbotSendUserAction === "function" ||
typeof window.clawdbotSendUserAction === "function";
statusEl.innerHTML =
"Bridge: " +
(hasHelper() ? "<span class='ok'>ready</span>" : "<span class='bad'>missing</span>") +
" · iOS=" + (hasIOS() ? "yes" : "no") +
" · Android=" + (hasAndroid() ? "yes" : "no");
window.addEventListener("clawdbot:a2ui-action-status", (ev) => {
window.addEventListener("moltbot:a2ui-action-status", (ev) => {
const d = ev && ev.detail || {};
log("Action status: id=" + (d.id || "?") + " ok=" + String(!!d.ok) + (d.error ? (" error=" + d.error) : ""));
});
function send(name, sourceComponentId) {
if (!hasHelper()) {
log("No action bridge found. Ensure you're viewing this on an iOS/Android Clawdbot node canvas.");
log("No action bridge found. Ensure you're viewing this on an iOS/Android Moltbot node canvas.");
return;
}
const ok = window.clawdbotSendUserAction({
const sendUserAction =
typeof window.moltbotSendUserAction === "function"
? window.moltbotSendUserAction
: window.clawdbotSendUserAction;
const ok = sendUserAction({
name,
surfaceId: "main",
sourceComponentId,
@@ -319,7 +344,7 @@ export async function createCanvasHostHandler(
res.statusCode = 404;
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(
`<!doctype html><meta charset="utf-8" /><title>Clawdbot Canvas</title><pre>Missing file.\nCreate ${rootDir}/index.html</pre>`,
`<!doctype html><meta charset="utf-8" /><title>Moltbot Canvas</title><pre>Missing file.\nCreate ${rootDir}/index.html</pre>`,
);
return true;
}