fix(ui): fix web UI after tsdown migration and typing changes

This commit is contained in:
Gustavo Madeira Santana
2026-02-03 13:56:20 -05:00
parent 1c4db91593
commit 5935c4d23d
24 changed files with 499 additions and 43 deletions

View File

@@ -0,0 +1,24 @@
import { loadSkillsFromDir } from "@mariozechner/pi-coding-agent";
import { resolveBundledSkillsDir, type BundledSkillsResolveOptions } from "./bundled-dir.js";
export type BundledSkillsContext = {
dir?: string;
names: Set<string>;
};
export function resolveBundledSkillsContext(
opts: BundledSkillsResolveOptions = {},
): BundledSkillsContext {
const dir = resolveBundledSkillsDir(opts);
const names = new Set<string>();
if (!dir) {
return { dir, names };
}
const result = loadSkillsFromDir({ dir, source: "openclaw-bundled" });
for (const skill of result.skills) {
if (skill.name.trim()) {
names.add(skill.name);
}
}
return { dir, names };
}

View File

@@ -0,0 +1,54 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import { resolveBundledSkillsDir } from "./bundled-dir.js";
async function writeSkill(dir: string, name: string) {
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(
path.join(dir, "SKILL.md"),
`---\nname: ${name}\ndescription: ${name}\n---\n\n# ${name}\n`,
"utf-8",
);
}
describe("resolveBundledSkillsDir", () => {
const originalOverride = process.env.OPENCLAW_BUNDLED_SKILLS_DIR;
afterEach(() => {
if (originalOverride === undefined) {
delete process.env.OPENCLAW_BUNDLED_SKILLS_DIR;
} else {
process.env.OPENCLAW_BUNDLED_SKILLS_DIR = originalOverride;
}
});
it("resolves bundled skills under a flattened dist layout", async () => {
delete process.env.OPENCLAW_BUNDLED_SKILLS_DIR;
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-bundled-"));
await fs.writeFile(path.join(root, "package.json"), JSON.stringify({ name: "openclaw" }));
await writeSkill(path.join(root, "skills", "peekaboo"), "peekaboo");
const distDir = path.join(root, "dist");
await fs.mkdir(distDir, { recursive: true });
const argv1 = path.join(distDir, "index.js");
await fs.writeFile(argv1, "// stub", "utf-8");
const moduleUrl = pathToFileURL(path.join(distDir, "skills.js")).href;
const execPath = path.join(root, "bin", "node");
await fs.mkdir(path.dirname(execPath), { recursive: true });
const resolved = resolveBundledSkillsDir({
argv1,
moduleUrl,
cwd: distDir,
execPath,
});
expect(resolved).toBe(path.join(root, "skills"));
});
});

View File

@@ -1,8 +1,41 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { resolveOpenClawPackageRootSync } from "../../infra/openclaw-root.js";
export function resolveBundledSkillsDir(): string | undefined {
function looksLikeSkillsDir(dir: string): boolean {
try {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.name.startsWith(".")) {
continue;
}
const fullPath = path.join(dir, entry.name);
if (entry.isFile() && entry.name.endsWith(".md")) {
return true;
}
if (entry.isDirectory()) {
if (fs.existsSync(path.join(fullPath, "SKILL.md"))) {
return true;
}
}
}
} catch {
return false;
}
return false;
}
export type BundledSkillsResolveOptions = {
argv1?: string;
moduleUrl?: string;
cwd?: string;
execPath?: string;
};
export function resolveBundledSkillsDir(
opts: BundledSkillsResolveOptions = {},
): string | undefined {
const override = process.env.OPENCLAW_BUNDLED_SKILLS_DIR?.trim();
if (override) {
return override;
@@ -10,7 +43,8 @@ export function resolveBundledSkillsDir(): string | undefined {
// bun --compile: ship a sibling `skills/` next to the executable.
try {
const execDir = path.dirname(process.execPath);
const execPath = opts.execPath ?? process.execPath;
const execDir = path.dirname(execPath);
const sibling = path.join(execDir, "skills");
if (fs.existsSync(sibling)) {
return sibling;
@@ -21,11 +55,32 @@ export function resolveBundledSkillsDir(): string | undefined {
// npm/dev: resolve `<packageRoot>/skills` relative to this module.
try {
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(moduleDir, "..", "..", "..");
const candidate = path.join(root, "skills");
if (fs.existsSync(candidate)) {
return candidate;
const moduleUrl = opts.moduleUrl ?? import.meta.url;
const moduleDir = path.dirname(fileURLToPath(moduleUrl));
const argv1 = opts.argv1 ?? process.argv[1];
const cwd = opts.cwd ?? process.cwd();
const packageRoot = resolveOpenClawPackageRootSync({
argv1,
moduleUrl,
cwd,
});
if (packageRoot) {
const candidate = path.join(packageRoot, "skills");
if (looksLikeSkillsDir(candidate)) {
return candidate;
}
}
let current = moduleDir;
for (let depth = 0; depth < 6; depth += 1) {
const candidate = path.join(current, "skills");
if (looksLikeSkillsDir(candidate)) {
return candidate;
}
const next = path.dirname(current);
if (next === current) {
break;
}
current = next;
}
} catch {
// ignore