2026-03-15 21:16:27 +02:00
|
|
|
import fs from "node:fs";
|
|
|
|
|
import path from "node:path";
|
chore: Migrate to tsdown, speed up JS bundling by ~10x (thanks @hyf0).
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.
2026-02-02 17:20:24 +09:00
|
|
|
import { defineConfig } from "tsdown";
|
|
|
|
|
|
|
|
|
|
const env = {
|
|
|
|
|
NODE_ENV: "production",
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-08 04:12:32 +00:00
|
|
|
function buildInputOptions(options: { onLog?: unknown; [key: string]: unknown }) {
|
|
|
|
|
if (process.env.OPENCLAW_BUILD_VERBOSE === "1") {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const previousOnLog = typeof options.onLog === "function" ? options.onLog : undefined;
|
|
|
|
|
|
2026-03-16 00:29:33 +00:00
|
|
|
function isSuppressedLog(log: {
|
|
|
|
|
code?: string;
|
|
|
|
|
message?: string;
|
|
|
|
|
id?: string;
|
|
|
|
|
importer?: string;
|
|
|
|
|
}) {
|
|
|
|
|
if (log.code === "PLUGIN_TIMINGS") {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (log.code !== "EVAL") {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const haystack = [log.message, log.id, log.importer].filter(Boolean).join("\n");
|
|
|
|
|
return haystack.includes("@protobufjs/inquire/index.js");
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-08 04:12:32 +00:00
|
|
|
return {
|
|
|
|
|
...options,
|
|
|
|
|
onLog(
|
|
|
|
|
level: string,
|
2026-03-16 00:29:33 +00:00
|
|
|
log: { code?: string; message?: string; id?: string; importer?: string },
|
2026-03-08 04:12:32 +00:00
|
|
|
defaultHandler: (level: string, log: { code?: string }) => void,
|
|
|
|
|
) {
|
2026-03-16 00:29:33 +00:00
|
|
|
if (isSuppressedLog(log)) {
|
2026-03-08 04:12:32 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (typeof previousOnLog === "function") {
|
|
|
|
|
previousOnLog(level, log, defaultHandler);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
defaultHandler(level, log);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function nodeBuildConfig(config: Record<string, unknown>) {
|
|
|
|
|
return {
|
|
|
|
|
...config,
|
|
|
|
|
env,
|
|
|
|
|
fixedExtension: false,
|
|
|
|
|
platform: "node",
|
|
|
|
|
inputOptions: buildInputOptions,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 02:31:44 -05:00
|
|
|
const pluginSdkEntrypoints = [
|
|
|
|
|
"index",
|
|
|
|
|
"core",
|
|
|
|
|
"compat",
|
|
|
|
|
"telegram",
|
|
|
|
|
"discord",
|
|
|
|
|
"slack",
|
|
|
|
|
"signal",
|
|
|
|
|
"imessage",
|
|
|
|
|
"whatsapp",
|
|
|
|
|
"line",
|
|
|
|
|
"msteams",
|
|
|
|
|
"acpx",
|
|
|
|
|
"bluebubbles",
|
|
|
|
|
"copilot-proxy",
|
|
|
|
|
"device-pair",
|
|
|
|
|
"diagnostics-otel",
|
|
|
|
|
"diffs",
|
|
|
|
|
"feishu",
|
|
|
|
|
"googlechat",
|
|
|
|
|
"irc",
|
|
|
|
|
"llm-task",
|
|
|
|
|
"lobster",
|
|
|
|
|
"matrix",
|
|
|
|
|
"mattermost",
|
|
|
|
|
"memory-core",
|
|
|
|
|
"memory-lancedb",
|
|
|
|
|
"minimax-portal-auth",
|
|
|
|
|
"nextcloud-talk",
|
|
|
|
|
"nostr",
|
|
|
|
|
"open-prose",
|
|
|
|
|
"phone-control",
|
|
|
|
|
"qwen-portal-auth",
|
|
|
|
|
"synology-chat",
|
|
|
|
|
"talk-voice",
|
|
|
|
|
"test-utils",
|
|
|
|
|
"thread-ownership",
|
|
|
|
|
"tlon",
|
|
|
|
|
"twitch",
|
|
|
|
|
"voice-call",
|
|
|
|
|
"zalo",
|
|
|
|
|
"zalouser",
|
|
|
|
|
"account-id",
|
|
|
|
|
"keyed-async-queue",
|
|
|
|
|
] as const;
|
|
|
|
|
|
2026-03-15 21:16:27 +02:00
|
|
|
function listBundledPluginBuildEntries(): Record<string, string> {
|
|
|
|
|
const extensionsRoot = path.join(process.cwd(), "extensions");
|
|
|
|
|
const entries: Record<string, string> = {};
|
|
|
|
|
|
|
|
|
|
for (const dirent of fs.readdirSync(extensionsRoot, { withFileTypes: true })) {
|
|
|
|
|
if (!dirent.isDirectory()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pluginDir = path.join(extensionsRoot, dirent.name);
|
|
|
|
|
const manifestPath = path.join(pluginDir, "openclaw.plugin.json");
|
|
|
|
|
if (!fs.existsSync(manifestPath)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const packageJsonPath = path.join(pluginDir, "package.json");
|
|
|
|
|
let packageEntries: string[] = [];
|
|
|
|
|
if (fs.existsSync(packageJsonPath)) {
|
|
|
|
|
try {
|
|
|
|
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
|
2026-03-15 18:46:22 -07:00
|
|
|
openclaw?: { extensions?: unknown; setupEntry?: unknown };
|
2026-03-15 21:16:27 +02:00
|
|
|
};
|
|
|
|
|
packageEntries = Array.isArray(packageJson.openclaw?.extensions)
|
|
|
|
|
? packageJson.openclaw.extensions.filter(
|
|
|
|
|
(entry): entry is string => typeof entry === "string" && entry.trim().length > 0,
|
|
|
|
|
)
|
|
|
|
|
: [];
|
2026-03-15 18:46:22 -07:00
|
|
|
const setupEntry =
|
|
|
|
|
typeof packageJson.openclaw?.setupEntry === "string" &&
|
|
|
|
|
packageJson.openclaw.setupEntry.trim().length > 0
|
|
|
|
|
? packageJson.openclaw.setupEntry
|
|
|
|
|
: undefined;
|
|
|
|
|
if (setupEntry) {
|
|
|
|
|
packageEntries = Array.from(new Set([...packageEntries, setupEntry]));
|
|
|
|
|
}
|
2026-03-15 21:16:27 +02:00
|
|
|
} catch {
|
|
|
|
|
packageEntries = [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sourceEntries = packageEntries.length > 0 ? packageEntries : ["./index.ts"];
|
|
|
|
|
for (const entry of sourceEntries) {
|
|
|
|
|
const normalizedEntry = entry.replace(/^\.\//, "");
|
|
|
|
|
const entryKey = `extensions/${dirent.name}/${normalizedEntry.replace(/\.[^.]+$/u, "")}`;
|
|
|
|
|
entries[entryKey] = path.join("extensions", dirent.name, normalizedEntry);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return entries;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bundledPluginBuildEntries = listBundledPluginBuildEntries();
|
|
|
|
|
|
chore: Migrate to tsdown, speed up JS bundling by ~10x (thanks @hyf0).
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.
2026-02-02 17:20:24 +09:00
|
|
|
export default defineConfig([
|
2026-03-08 04:12:32 +00:00
|
|
|
nodeBuildConfig({
|
chore: Migrate to tsdown, speed up JS bundling by ~10x (thanks @hyf0).
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.
2026-02-02 17:20:24 +09:00
|
|
|
entry: "src/index.ts",
|
2026-03-08 04:12:32 +00:00
|
|
|
}),
|
|
|
|
|
nodeBuildConfig({
|
chore: Migrate to tsdown, speed up JS bundling by ~10x (thanks @hyf0).
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.
2026-02-02 17:20:24 +09:00
|
|
|
entry: "src/entry.ts",
|
2026-03-08 04:12:32 +00:00
|
|
|
}),
|
|
|
|
|
nodeBuildConfig({
|
2026-02-15 04:52:51 +00:00
|
|
|
// Ensure this module is bundled as an entry so legacy CLI shims can resolve its exports.
|
|
|
|
|
entry: "src/cli/daemon-cli.ts",
|
2026-03-08 04:12:32 +00:00
|
|
|
}),
|
|
|
|
|
nodeBuildConfig({
|
2026-02-08 05:17:59 -05:00
|
|
|
entry: "src/infra/warning-filter.ts",
|
2026-03-08 04:12:32 +00:00
|
|
|
}),
|
|
|
|
|
nodeBuildConfig({
|
2026-03-04 02:58:48 +02:00
|
|
|
// Keep sync lazy-runtime channel modules as concrete dist files.
|
|
|
|
|
entry: {
|
|
|
|
|
"channels/plugins/agent-tools/whatsapp-login":
|
|
|
|
|
"src/channels/plugins/agent-tools/whatsapp-login.ts",
|
|
|
|
|
"channels/plugins/actions/discord": "src/channels/plugins/actions/discord.ts",
|
|
|
|
|
"channels/plugins/actions/signal": "src/channels/plugins/actions/signal.ts",
|
|
|
|
|
"channels/plugins/actions/telegram": "src/channels/plugins/actions/telegram.ts",
|
refactor: remove channel shim directories, point all imports to extensions (#45967)
* refactor: remove channel shim directories, point all imports to extensions
Delete the 6 backward-compat shim directories (src/telegram, src/discord,
src/slack, src/signal, src/imessage, src/web) that were re-exporting from
extensions. Update all 112+ source files to import directly from
extensions/{channel}/src/ instead of through the shims.
Also:
- Move src/channels/telegram/ (allow-from, api) to extensions/telegram/src/
- Fix outbound adapters to use resolveOutboundSendDep (fixes 5 pre-existing TS errors)
- Update cross-extension imports (src/web/media.js → extensions/whatsapp/src/media.js)
- Update vitest, tsdown, knip, labeler, and script configs for new paths
- Update guard test allowlists for extension paths
After this, src/ has zero channel-specific implementation code — only the
generic plugin framework remains.
* fix: update raw-fetch guard allowlist line numbers after shim removal
* refactor: document direct extension channel imports
* test: mock transcript module in delivery helpers
2026-03-14 03:43:07 -07:00
|
|
|
"telegram/audit": "extensions/telegram/src/audit.ts",
|
|
|
|
|
"telegram/token": "extensions/telegram/src/token.ts",
|
2026-03-04 02:58:48 +02:00
|
|
|
"line/accounts": "src/line/accounts.ts",
|
|
|
|
|
"line/send": "src/line/send.ts",
|
|
|
|
|
"line/template-messages": "src/line/template-messages.ts",
|
|
|
|
|
},
|
2026-03-08 04:12:32 +00:00
|
|
|
}),
|
2026-03-13 20:55:20 +01:00
|
|
|
nodeBuildConfig({
|
|
|
|
|
// Bundle all plugin-sdk entries in a single build so the bundler can share
|
|
|
|
|
// common chunks instead of duplicating them per entry (~712MB heap saved).
|
|
|
|
|
entry: Object.fromEntries(pluginSdkEntrypoints.map((e) => [e, `src/plugin-sdk/${e}.ts`])),
|
|
|
|
|
outDir: "dist/plugin-sdk",
|
|
|
|
|
}),
|
2026-03-15 21:16:27 +02:00
|
|
|
nodeBuildConfig({
|
|
|
|
|
// Bundle bundled plugin entrypoints so built gateway startup can load JS
|
|
|
|
|
// directly from dist/extensions instead of transpiling extensions/*.ts via Jiti.
|
|
|
|
|
entry: bundledPluginBuildEntries,
|
|
|
|
|
outDir: "dist",
|
|
|
|
|
}),
|
2026-03-08 04:12:32 +00:00
|
|
|
nodeBuildConfig({
|
chore: Migrate to tsdown, speed up JS bundling by ~10x (thanks @hyf0).
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.
2026-02-02 17:20:24 +09:00
|
|
|
entry: "src/extensionAPI.ts",
|
2026-03-08 04:12:32 +00:00
|
|
|
}),
|
|
|
|
|
nodeBuildConfig({
|
2026-02-08 18:35:47 -08:00
|
|
|
entry: ["src/hooks/bundled/*/handler.ts", "src/hooks/llm-slug-generator.ts"],
|
2026-03-08 04:12:32 +00:00
|
|
|
}),
|
chore: Migrate to tsdown, speed up JS bundling by ~10x (thanks @hyf0).
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.
2026-02-02 17:20:24 +09:00
|
|
|
]);
|