fix(skills): prevent skills loading crash
This commit is contained in:
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
||||
import http, { type Server } from "node:http";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import chokidar from "chokidar";
|
||||
import express from "express";
|
||||
@@ -25,6 +26,36 @@ export type CanvasHostServer = {
|
||||
};
|
||||
|
||||
const WS_PATH = "/__clawdis/ws";
|
||||
const A2UI_PATH = "/__clawdis__/a2ui";
|
||||
|
||||
async function resolveA2uiRoot(): Promise<string | null> {
|
||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||
const candidates = [
|
||||
// Running from source (tsx) or dist (tsc + copied assets).
|
||||
path.resolve(here, "a2ui"),
|
||||
// Running from dist without copied assets (fallback to source).
|
||||
path.resolve(here, "../../src/canvas-host/a2ui"),
|
||||
// Running from repo root.
|
||||
path.resolve(process.cwd(), "src/canvas-host/a2ui"),
|
||||
path.resolve(process.cwd(), "dist/canvas-host/a2ui"),
|
||||
];
|
||||
if (process.execPath) {
|
||||
candidates.unshift(path.resolve(path.dirname(process.execPath), "a2ui"));
|
||||
}
|
||||
|
||||
for (const dir of candidates) {
|
||||
try {
|
||||
const indexPath = path.join(dir, "index.html");
|
||||
const bundlePath = path.join(dir, "a2ui.bundle.js");
|
||||
await fs.stat(indexPath);
|
||||
await fs.stat(bundlePath);
|
||||
return dir;
|
||||
} catch {
|
||||
// try next
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function injectCanvasLiveReload(html: string): string {
|
||||
const snippet = `
|
||||
@@ -237,6 +268,8 @@ export async function startCanvasHost(
|
||||
}
|
||||
|
||||
const bindHost = opts.listenHost?.trim() || "0.0.0.0";
|
||||
const a2uiRoot = await resolveA2uiRoot();
|
||||
const a2uiRootReal = a2uiRoot ? await fs.realpath(a2uiRoot) : null;
|
||||
|
||||
const app = express();
|
||||
app.disable("x-powered-by");
|
||||
@@ -249,6 +282,40 @@ export async function startCanvasHost(
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
url.pathname === A2UI_PATH ||
|
||||
url.pathname.startsWith(`${A2UI_PATH}/`)
|
||||
) {
|
||||
if (!a2uiRootReal) {
|
||||
res
|
||||
.status(503)
|
||||
.type("text/plain; charset=utf-8")
|
||||
.send("A2UI assets not found");
|
||||
return;
|
||||
}
|
||||
const rel = url.pathname.slice(A2UI_PATH.length);
|
||||
const filePath = await resolveFilePath(a2uiRootReal, rel || "/");
|
||||
if (!filePath) {
|
||||
res.status(404).send("not found");
|
||||
return;
|
||||
}
|
||||
const lower = filePath.toLowerCase();
|
||||
const mime =
|
||||
lower.endsWith(".html") || lower.endsWith(".htm")
|
||||
? "text/html"
|
||||
: (detectMime({ filePath }) ?? "application/octet-stream");
|
||||
res.setHeader("Cache-Control", "no-store");
|
||||
if (mime === "text/html") {
|
||||
const html = await fs.readFile(filePath, "utf8");
|
||||
res
|
||||
.type("text/html; charset=utf-8")
|
||||
.send(injectCanvasLiveReload(html));
|
||||
return;
|
||||
}
|
||||
res.type(mime).send(await fs.readFile(filePath));
|
||||
return;
|
||||
}
|
||||
|
||||
const filePath = await resolveFilePath(rootReal, url.pathname);
|
||||
if (!filePath) {
|
||||
if (url.pathname === "/" || url.pathname.endsWith("/")) {
|
||||
|
||||
Reference in New Issue
Block a user