The previous migration to tsdown was reverted because it caused a ~20x slowdown when running OpenClaw from the repo. @hyf0 investigated and found that simply renaming the `dist` folder also caused the same slowdown. It turns out the Plugin script loader has a bunch of voodoo vibe logic to determine if it should load files from source and compile them, or if it should load them from dist. When building with tsdown, the filesystem layout is different (bundled), and so some files weren't in the right location, and the Plugin script loader decided to compile source files from scratch using Jiti. The new implementation uses tsdown to embed `NODE_ENV: 'production'`, which we now use to determine if we are running OpenClaw from a "production environmen" (ie. from dist). This removes the slop in favor of a deterministic toggle, and doesn't rely on directory names or similar. There is some code reaching into `dist` to load specific modules, primarily in the voice-call extension, which I simplified into loading an "officially" exported `extensionAPI.js` file. With tsdown, entry points need to be explicitly configured, so we should be able to avoid sloppy code reaching into internals from now on. This might break some existing users, but if it does, it's because they were using "private" APIs.
160 lines
4.6 KiB
TypeScript
160 lines
4.6 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
import type { VoiceCallTtsConfig } from "./config.js";
|
|
|
|
export type CoreConfig = {
|
|
session?: {
|
|
store?: string;
|
|
};
|
|
messages?: {
|
|
tts?: VoiceCallTtsConfig;
|
|
};
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
type CoreAgentDeps = {
|
|
resolveAgentDir: (cfg: CoreConfig, agentId: string) => string;
|
|
resolveAgentWorkspaceDir: (cfg: CoreConfig, agentId: string) => string;
|
|
resolveAgentIdentity: (
|
|
cfg: CoreConfig,
|
|
agentId: string,
|
|
) => { name?: string | null } | null | undefined;
|
|
resolveThinkingDefault: (params: {
|
|
cfg: CoreConfig;
|
|
provider?: string;
|
|
model?: string;
|
|
}) => string;
|
|
runEmbeddedPiAgent: (params: {
|
|
sessionId: string;
|
|
sessionKey?: string;
|
|
messageProvider?: string;
|
|
sessionFile: string;
|
|
workspaceDir: string;
|
|
config?: CoreConfig;
|
|
prompt: string;
|
|
provider?: string;
|
|
model?: string;
|
|
thinkLevel?: string;
|
|
verboseLevel?: string;
|
|
timeoutMs: number;
|
|
runId: string;
|
|
lane?: string;
|
|
extraSystemPrompt?: string;
|
|
agentDir?: string;
|
|
}) => Promise<{
|
|
payloads?: Array<{ text?: string; isError?: boolean }>;
|
|
meta?: { aborted?: boolean };
|
|
}>;
|
|
resolveAgentTimeoutMs: (opts: { cfg: CoreConfig }) => number;
|
|
ensureAgentWorkspace: (params?: { dir: string }) => Promise<void>;
|
|
resolveStorePath: (store?: string, opts?: { agentId?: string }) => string;
|
|
loadSessionStore: (storePath: string) => Record<string, unknown>;
|
|
saveSessionStore: (storePath: string, store: Record<string, unknown>) => Promise<void>;
|
|
resolveSessionFilePath: (
|
|
sessionId: string,
|
|
entry: unknown,
|
|
opts?: { agentId?: string },
|
|
) => string;
|
|
DEFAULT_MODEL: string;
|
|
DEFAULT_PROVIDER: string;
|
|
};
|
|
|
|
let coreRootCache: string | null = null;
|
|
let coreDepsPromise: Promise<CoreAgentDeps> | null = null;
|
|
|
|
function findPackageRoot(startDir: string, name: string): string | null {
|
|
let dir = startDir;
|
|
for (;;) {
|
|
const pkgPath = path.join(dir, "package.json");
|
|
try {
|
|
if (fs.existsSync(pkgPath)) {
|
|
const raw = fs.readFileSync(pkgPath, "utf8");
|
|
const pkg = JSON.parse(raw) as { name?: string };
|
|
if (pkg.name === name) {
|
|
return dir;
|
|
}
|
|
}
|
|
} catch {
|
|
// ignore parse errors and keep walking
|
|
}
|
|
const parent = path.dirname(dir);
|
|
if (parent === dir) {
|
|
return null;
|
|
}
|
|
dir = parent;
|
|
}
|
|
}
|
|
|
|
function resolveOpenClawRoot(): string {
|
|
if (coreRootCache) {
|
|
return coreRootCache;
|
|
}
|
|
const override = process.env.OPENCLAW_ROOT?.trim();
|
|
if (override) {
|
|
coreRootCache = override;
|
|
return override;
|
|
}
|
|
|
|
const candidates = new Set<string>();
|
|
if (process.argv[1]) {
|
|
candidates.add(path.dirname(process.argv[1]));
|
|
}
|
|
candidates.add(process.cwd());
|
|
try {
|
|
const urlPath = fileURLToPath(import.meta.url);
|
|
candidates.add(path.dirname(urlPath));
|
|
} catch {
|
|
// ignore
|
|
}
|
|
|
|
for (const start of candidates) {
|
|
for (const name of ["openclaw"]) {
|
|
const found = findPackageRoot(start, name);
|
|
if (found) {
|
|
coreRootCache = found;
|
|
return found;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Error("Unable to resolve core root. Set OPENCLAW_ROOT to the package root.");
|
|
}
|
|
|
|
async function importCoreExtensionAPI(): Promise<{
|
|
resolveAgentDir: CoreAgentDeps["resolveAgentDir"];
|
|
resolveAgentWorkspaceDir: CoreAgentDeps["resolveAgentWorkspaceDir"];
|
|
DEFAULT_MODEL: string;
|
|
DEFAULT_PROVIDER: string;
|
|
resolveAgentIdentity: CoreAgentDeps["resolveAgentIdentity"];
|
|
resolveThinkingDefault: CoreAgentDeps["resolveThinkingDefault"];
|
|
runEmbeddedPiAgent: CoreAgentDeps["runEmbeddedPiAgent"];
|
|
resolveAgentTimeoutMs: CoreAgentDeps["resolveAgentTimeoutMs"];
|
|
ensureAgentWorkspace: CoreAgentDeps["ensureAgentWorkspace"];
|
|
resolveStorePath: CoreAgentDeps["resolveStorePath"];
|
|
loadSessionStore: CoreAgentDeps["loadSessionStore"];
|
|
saveSessionStore: CoreAgentDeps["saveSessionStore"];
|
|
resolveSessionFilePath: CoreAgentDeps["resolveSessionFilePath"];
|
|
}> {
|
|
// Do not import any other module. You can't touch this or you will be fired.
|
|
const distPath = path.join(resolveOpenClawRoot(), "dist", "extensionAPI.js");
|
|
if (!fs.existsSync(distPath)) {
|
|
throw new Error(
|
|
`Missing core module at ${distPath}. Run \`pnpm build\` or install the official package.`,
|
|
);
|
|
}
|
|
return await import(pathToFileURL(distPath).href);
|
|
}
|
|
|
|
export async function loadCoreAgentDeps(): Promise<CoreAgentDeps> {
|
|
if (coreDepsPromise) {
|
|
return coreDepsPromise;
|
|
}
|
|
|
|
coreDepsPromise = (async () => {
|
|
return await importCoreExtensionAPI();
|
|
})();
|
|
|
|
return coreDepsPromise;
|
|
}
|