openclaw/tsdown.config.ts
Peter Steinberger b3cb94506b
fix(runtime): close Bun API cluster loader and fixture gaps (#163052)
Preserve plugin capture conditions, observed missing inputs, and lazy dependency acquisition under Bun. Target portable worker and recovery bundles at Node independently of the build runtime, and migrate runtime fixtures to owned cleanup channels.

Validation: paired Node24/Bun17c9 covering suites; 54 Node plugin cases and 46 Bun cases with eight exact-pin skips; full build, changed-file checks, and import-cycle checks; green exact-head CI. The published 2026.9.7 updater installed the verified candidate, replaced the managed service, completed its durable update run, and passed readiness and build-identity checks.
2026-10-01 16:40:33 -07:00

1027 lines
39 KiB
TypeScript

// tsdown config defines package build entrypoints and output options.
import fs from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";
import type { DtsOptions, TsdownPlugin, UserConfig } from "tsdown";
import {
collectBundledPluginBuildEntries,
collectChannelConfigDoctorBuildEntries,
collectPluginDeclarationSourceEntries,
collectSourceCheckoutPluginBuildEntries,
createBundledPluginBuildInventory,
} from "./scripts/lib/bundled-plugin-build-entries.mjs";
import { createGatewayRunChunkMetadataPlugin } from "./scripts/lib/gateway-run-chunk-metadata.mts";
import { createManagedHandoffBuildConfigs } from "./scripts/lib/managed-handoff-build-config.mts";
import { createPluginInventoryModuleRefsPlugin } from "./scripts/lib/plugin-inventory-module-refs.mts";
import {
buildPluginSdkEntrySources,
pluginSdkEntrypoints,
privateQaPluginSdkEntrypoints,
productionPluginSdkEntrypoints,
publicPluginSdkEntrypoints,
} from "./scripts/lib/plugin-sdk-entries.mts";
import { createRuntimeDependencyOwnershipBuildPlugin } from "./scripts/lib/runtime-dependency-ownership-build-plugin.mts";
import { runtimeProcessBuildEntries } from "./scripts/lib/runtime-process-build-entries.mts";
import {
sharedRuntimeProcessBuildEntries,
shouldBundleRuntimeSqliteDependency,
standaloneRuntimeProcessBuildEntries,
} from "./scripts/lib/runtime-process-core-build-entries.mts";
import {
createStateSchemaInlinePlugin,
STATE_SCHEMA_INLINE_PLUGIN_NAME,
} from "./scripts/lib/state-schema-inline-plugin.mts";
import {
TSDOWN_PACKAGE_CONFIG_GROUP,
TSDOWN_UNIFIED_CONFIG_GROUP,
TSDOWN_UNIFIED_DTS_CONFIG_GROUPS,
} from "./scripts/lib/tsdown-config-groups.mts";
import { createDeclarationBoundaryHooks } from "./scripts/lib/tsdown-declaration-boundary.mts";
import { createDeclarationInputCapture } from "./scripts/lib/tsdown-declaration-inputs.mts";
import { tsdownPackageOutputRoot } from "./scripts/lib/tsdown-output-roots.mts";
import { runtimeProcessDeclarationEntries } from "./scripts/lib/vitest-worker-declarations.mts";
import {
createWorkerDeployBuildPlugin,
WORKER_DEPLOY_OPTIONAL_NATIVE_MODULE_ID,
} from "./scripts/lib/worker-deploy-build-plugin.mts";
import { buildPackageDistEntriesFromExports } from "./scripts/lib/workspace-package-entries.mts";
type InputOptionsFactory = Extract<NonNullable<UserConfig["inputOptions"]>, Function>;
type InputOptionsArg = InputOptionsFactory extends (
options: infer Options,
format: infer _Format,
context: infer _Context,
) => infer _Return
? Options
: never;
type InputOptionsReturn = InputOptionsFactory extends (
options: infer _Options,
format: infer _Format,
context: infer _Context,
) => infer Return
? Return
: never;
type OnLogFunction = InputOptionsArg extends { onLog?: infer OnLog } ? NonNullable<OnLog> : never;
type ExternalOptionFunction = (
id: string,
parentId: string | undefined,
isResolved: boolean,
) => boolean | null | undefined;
const env = {
NODE_ENV: "production",
};
const workerDeployVersion = (
JSON.parse(fs.readFileSync("package.json", "utf8")) as { version: string }
).version;
const OUTPUT_SOURCE_MAPS = process.env.OUTPUT_SOURCE_MAPS === "1";
const RUN_NODE_SKIP_DTS_BUILD = process.env.OPENCLAW_RUN_NODE_SKIP_DTS_BUILD === "1";
const TSDOWN_DECLARATIONS = !RUN_NODE_SKIP_DTS_BUILD;
export { createStateSchemaInlinePlugin, STATE_SCHEMA_INLINE_PLUGIN_NAME };
const SUPPRESSED_EVAL_WARNING_PATHS = [
"@protobufjs/inquire/index.js",
"bottleneck/lib/IORedisConnection.js",
"bottleneck/lib/RedisConnection.js",
] as const;
function normalizedLogHaystack(log: { message?: string; id?: string; importer?: string }): string {
return [log.message, log.id, log.importer].filter(Boolean).join("\n").replaceAll("\\", "/");
}
function matchesExternalOption(
option: unknown,
id: string,
parentId: string | undefined,
isResolved: boolean,
): boolean {
if (!option) {
return false;
}
if (typeof option === "function") {
return (option as ExternalOptionFunction)(id, parentId, isResolved) === true;
}
if (typeof option === "string") {
return option === id;
}
if (option instanceof RegExp) {
return option.test(id);
}
if (Array.isArray(option)) {
return option.some((entry) => matchesExternalOption(entry, id, parentId, isResolved));
}
return false;
}
function buildInputOptions(
options: InputOptionsArg,
build?: { bundleAllDependencies?: boolean },
): InputOptionsReturn {
if (process.env.OPENCLAW_BUILD_VERBOSE === "1") {
return undefined;
}
const previousOnLog = typeof options.onLog === "function" ? options.onLog : undefined;
const previousExternal = (options as { external?: unknown }).external;
function isSuppressedLog(log: {
code?: string;
message?: string;
id?: string;
importer?: string;
plugin?: string;
}) {
if (log.code === "PLUGIN_TIMINGS") {
return true;
}
if (log.code === "UNRESOLVED_IMPORT") {
return normalizedLogHaystack(log).includes("extensions/");
}
if (
log.code === "PLUGIN_WARNING" &&
log.plugin === "rolldown-plugin-dts:fake-js" &&
typeof log.message === "string" &&
log.message.includes("uses CommonJS dts syntax")
) {
return true;
}
if (log.code !== "EVAL") {
return false;
}
const haystack = normalizedLogHaystack(log);
return SUPPRESSED_EVAL_WARNING_PATHS.some((pathLocal) => haystack.includes(pathLocal));
}
return {
...options,
external(id: string, parentId: string | undefined, isResolved: boolean) {
return (
(!build?.bundleAllDependencies && shouldNeverBundleDependency(id)) ||
matchesExternalOption(previousExternal, id, parentId, isResolved)
);
},
onLog(...args: Parameters<OnLogFunction>) {
const [level, log, defaultHandler] = args;
if (isSuppressedLog(log)) {
return;
}
if (typeof previousOnLog === "function") {
previousOnLog(level, log, defaultHandler);
return;
}
defaultHandler(level, log);
},
};
}
function createBashParserAssetsPlugin(): TsdownPlugin {
const runtimePath = fs.realpathSync(
path.resolve("src/infra/command-explainer/tree-sitter-runtime.ts"),
);
const grammarResolution = 'require.resolve("tree-sitter-bash/tree-sitter-bash.wasm")';
return {
name: "openclaw:bash-parser-assets",
transform(code, id) {
if (path.normalize(id) !== runtimePath) {
return undefined;
}
if (!code.includes(grammarResolution)) {
this.error("tree-sitter bootstrap changed; update the packaged grammar transform");
}
const require = createRequire(import.meta.url);
const grammarPath = require.resolve("tree-sitter-bash/tree-sitter-bash.wasm");
const licensePath = path.join(path.dirname(grammarPath), "LICENSE");
this.addWatchFile(grammarPath);
this.addWatchFile(licensePath);
const grammar = this.emitFile({
type: "asset",
fileName: "tree-sitter-bash.wasm",
source: fs.readFileSync(grammarPath),
});
this.emitFile({
type: "asset",
fileName: "tree-sitter-bash.LICENSE",
source: fs.readFileSync(licensePath),
});
// Let the bundler relocate the asset for root and nested chunks. Source
// checkouts still resolve the dev dependency; sealed workers inline it.
return {
code: code.replace(grammarResolution, `new URL(import.meta.ROLLUP_FILE_URL_${grammar})`),
map: null,
};
},
};
}
function nodeBuildConfig(
config: UserConfig,
declarations: UserConfig["dts"] = TSDOWN_DECLARATIONS,
): UserConfig {
return {
...config,
// Recovery diagnostics must parse on Node 22; runtime admission still guards live writers.
target: "node22",
dts: declarations,
hooks: createDeclarationBoundaryHooks(config.hooks),
plugins: [
typeof declarations === "object" && declarations.emitDtsOnly
? undefined
: createBashParserAssetsPlugin(),
config.plugins,
],
env,
define: { WORKER_DEPLOY_BUILD: "false", ...config.define },
outExtensions: () => ({ js: ".js", dts: ".d.ts" }),
fixedExtension: false,
sourcemap: OUTPUT_SOURCE_MAPS,
inputOptions: (options) => buildInputOptions(options),
};
}
function workerDeployBuildConfig(entry: Record<string, string>): UserConfig {
return {
name: TSDOWN_UNIFIED_CONFIG_GROUP,
entry,
outDir: "dist",
platform: "node",
dts: false,
env,
define: {
WORKER_DEPLOY_BUILD: "true",
SEALED_RUNTIME_BUILD: "true",
WORKER_DEPLOY_VERSION: JSON.stringify(workerDeployVersion),
},
alias: {
bufferutil: WORKER_DEPLOY_OPTIONAL_NATIVE_MODULE_ID,
"chromium-bidi/lib/cjs/bidiMapper/BidiMapper": WORKER_DEPLOY_OPTIONAL_NATIVE_MODULE_ID,
"chromium-bidi/lib/cjs/cdp/CdpConnection": WORKER_DEPLOY_OPTIONAL_NATIVE_MODULE_ID,
"electron/index.js": WORKER_DEPLOY_OPTIONAL_NATIVE_MODULE_ID,
fsevents: WORKER_DEPLOY_OPTIONAL_NATIVE_MODULE_ID,
kerberos: WORKER_DEPLOY_OPTIONAL_NATIVE_MODULE_ID,
"utf-8-validate": WORKER_DEPLOY_OPTIONAL_NATIVE_MODULE_ID,
},
deps: {
// Rolldown's Node target owns builtin resolution, independently of the build host.
alwaysBundle: () => true,
onlyBundle: false,
},
fixedExtension: false,
minify: { codegen: true, compress: true, mangle: { keepNames: true } },
outExtensions: () => ({ js: ".mjs", dts: ".d.ts" }),
outputOptions: { codeSplitting: false, assetFileNames: "worker/[name][extname]" },
plugins: [createStateSchemaInlinePlugin(), createWorkerDeployBuildPlugin()],
shims: true,
sourcemap: OUTPUT_SOURCE_MAPS,
inputOptions: (options) => buildInputOptions(options, { bundleAllDependencies: true }),
};
}
function workerHelperBuildConfig(
entry: Record<string, string>,
define?: UserConfig["define"],
): UserConfig {
return {
name: TSDOWN_UNIFIED_CONFIG_GROUP,
entry,
outDir: "dist",
platform: "node",
dts: false,
env,
define,
deps: {
alwaysBundle: () => true,
onlyBundle: false,
},
fixedExtension: false,
outExtensions: () => ({ js: ".mjs", dts: ".d.ts" }),
outputOptions: { codeSplitting: false },
shims: true,
sourcemap: OUTPUT_SOURCE_MAPS,
inputOptions: (options) => buildInputOptions(options, { bundleAllDependencies: true }),
};
}
function nodeWorkspacePackageBuildConfig(packageDir: string, config: UserConfig = {}): UserConfig {
return {
...config,
dts: TSDOWN_DECLARATIONS,
hooks: createDeclarationBoundaryHooks(config.hooks),
entry: config.entry ?? buildPackageDistEntriesFromExports(packageDir),
env,
name: config.name ?? TSDOWN_PACKAGE_CONFIG_GROUP,
outDir: config.outDir ?? tsdownPackageOutputRoot(packageDir),
sourcemap: OUTPUT_SOURCE_MAPS,
inputOptions: (options) => buildInputOptions(options),
};
}
const bundledPluginBuildInventory = createBundledPluginBuildInventory();
const bundledPluginBuildEntries = collectBundledPluginBuildEntries(bundledPluginBuildInventory);
const shouldBuildPrivateQaEntries = process.env.OPENCLAW_BUILD_PRIVATE_QA === "1";
const selectedPluginSdkEntrypoints = shouldBuildPrivateQaEntries
? [...pluginSdkEntrypoints, ...privateQaPluginSdkEntrypoints]
: productionPluginSdkEntrypoints;
function buildBundledHookEntries(): Record<string, string> {
const hooksRoot = path.join(process.cwd(), "src", "hooks", "bundled");
const entries: Record<string, string> = {};
if (!fs.existsSync(hooksRoot)) {
return entries;
}
for (const dirent of fs.readdirSync(hooksRoot, { withFileTypes: true })) {
if (!dirent.isDirectory()) {
continue;
}
const hookName = dirent.name;
const handlerPath = path.join(hooksRoot, hookName, "handler.ts");
if (!fs.existsSync(handlerPath)) {
continue;
}
entries[`bundled/${hookName}/handler`] = handlerPath;
}
return entries;
}
const bundledHookEntries = buildBundledHookEntries();
const bundledPluginRoot = (pluginId: string) => ["extensions", pluginId].join("/");
const bundledPluginFile = (pluginId: string, relativePath: string) =>
`${bundledPluginRoot(pluginId)}/${relativePath}`;
function withExternalPackageSubpaths(options: { neverBundle: string[] }) {
return {
neverBundle: options.neverBundle.flatMap((dependency) => [
dependency,
new RegExp(`^${dependency.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/`, "u"),
]),
} satisfies NonNullable<UserConfig["deps"]>;
}
const rootDependencyOptions = withExternalPackageSubpaths({
neverBundle: [
"@anthropic-ai/vertex-sdk",
"@discordjs/voice",
"@larksuiteoapi/node-sdk",
"@matrix-org/matrix-sdk-crypto-nodejs",
"@openclaw/ai",
// Its native loader resolves optional platform packages from the package scope.
"@openclaw/fs-safe",
"@slack/bolt",
"@slack/web-api",
"@vitest/expect",
// LanceDB's serializer and plugin schemas must share Arrow's CJS type identity.
"apache-arrow",
"jimp",
"matrix-js-sdk",
"prism-media",
// Extensions and external tool validation must share Format and Settings registries.
"typebox",
"typescript",
"vitest",
// Selected plugin distributions install platform optionals beside bundled JavaScript.
...bundledPluginBuildEntries.flatMap(({ packageJson }) =>
Object.keys(packageJson?.optionalDependencies ?? {}),
),
],
});
function shouldNeverBundleDependency(id: string): boolean {
return matchesExternalOption(rootDependencyOptions.neverBundle, id, undefined, false);
}
function shouldNeverBundleDeclarationDependency(id: string): boolean {
// Keep dependency declarations beside their package modules.
return (
shouldNeverBundleDependency(id) ||
["zod", "kysely"].some((name) => id === name || id.startsWith(`${name}/`))
);
}
function shouldAlwaysBundleDependency(id: string): boolean {
return (
// Keep htmlparser2's decoder version intact instead of binding it to the root major.
id === "entities/decode" ||
id === "openclaw/plugin-sdk/ssrf-runtime-internal" ||
id === "@openclaw/normalization-core" ||
id.startsWith("@openclaw/normalization-core/") ||
id === "@openclaw/retry" ||
id === "@openclaw/media-core" ||
id.startsWith("@openclaw/media-core/") ||
[
"@openclaw/acp-core",
"@openclaw/session-url-contract",
"@openclaw/workboard-contract",
].includes(id) ||
id.startsWith("@openclaw/acp-core/") ||
id === "zod" ||
id.startsWith("zod/")
);
}
function listBundledPluginEntrySources(
entries: Array<{
id: string;
sourceEntries: string[];
}>,
): Record<string, string> {
const sources: Record<string, string> = {};
for (const { id, sourceEntries } of entries) {
for (const entry of sourceEntries) {
const normalizedEntry = entry.replace(/^\.\//u, "");
const entryKey = bundledPluginFile(id, normalizedEntry.replace(/\.[^.]+$/u, ""));
const source = normalizedEntry ? `extensions/${id}/${normalizedEntry}` : `extensions/${id}`;
if (sources[entryKey] && sources[entryKey] !== source) {
throw new Error(
`Plugin build entries share output ${entryKey}: ${sources[entryKey]}, ${source}`,
);
}
sources[entryKey] = source;
}
}
return sources;
}
function buildCoreDistEntries(): Record<string, string> {
return {
index: "src/index.ts",
entry: "src/entry.ts",
"infra/package-lifecycle": "src/infra/package-lifecycle.ts",
"commands/doctor-update-schema-guard": "src/commands/doctor-update-schema-guard.ts",
"crabbox-wrapper": "scripts/crabbox-wrapper.mts",
"docker-healthcheck": "src/docker-healthcheck.ts",
// Ensure this module is bundled as an entry so legacy CLI shims can resolve its exports.
"cli/daemon-cli": "src/cli/daemon-cli.ts",
// Keep recorded post-swap imports of this binding out of the shared updater graph.
"cli/update-cli/node-runner": "src/cli/update-cli/node-runner.ts",
// Keep long-lived lazy runtime boundaries on stable filenames so rebuilt
// dist/ trees do not strand already-running gateways on stale hashed chunks.
"agents/agent-bundle-mcp-runtime": "src/agents/agent-bundle-mcp-runtime.ts",
// Published builds lazily import these lifecycle facts from a hashed chunk; update
// compatibility bridges need a current chunk that still exports them.
"agents/provider-runtime-lifecycle": "src/agents/provider-runtime-lifecycle.ts",
"agents/mcp-auth-profile.runtime": "src/agents/mcp-auth-profile.runtime.ts",
"agents/auth-profiles.runtime": "src/agents/auth-profiles.runtime.ts",
"agents/model-catalog.runtime": "src/agents/model-catalog.runtime.ts",
"agents/models-config.runtime": "src/agents/models-config.runtime.ts",
"agents/tool-images.runtime": "src/agents/tool-images.runtime.ts",
"agents/compaction-planning.worker": "src/agents/compaction-planning.worker.ts",
"config/sessions/disk-budget.worker": "src/config/sessions/disk-budget.worker.ts",
"config/sessions/session-transcript-reconcile":
"src/config/sessions/session-transcript-reconcile.ts",
...runtimeProcessBuildEntries,
...runtimeProcessDeclarationEntries,
"acp/control-plane/manager": "src/acp/control-plane/manager.ts",
"cli/gateway-lifecycle.runtime": "src/cli/gateway-cli/lifecycle.runtime.ts",
"provider-dispatcher.runtime": "src/auto-reply/reply/provider-dispatcher.runtime.ts",
"server-close.runtime": "src/gateway/server-close.runtime.ts",
"gateway/plugin-channel-reload-targets": "src/gateway/plugin-channel-reload-targets.ts",
"gateway/worker-environments/runtime": "src/gateway/worker-environments/runtime.ts",
"plugins/hook-runner-global": "src/plugins/hook-runner-global.ts",
"plugins/memory-state": "src/plugins/memory-state.ts",
"plugins/synthetic-auth.runtime": "src/plugins/synthetic-auth.runtime.ts",
"subagent-registry.runtime": "src/agents/subagents/registry/subagent-registry.runtime.ts",
"link-understanding/apply.runtime": "src/link-understanding/apply.runtime.ts",
"media-understanding/apply.runtime": "src/media-understanding/apply.runtime.ts",
"commands/status.summary.runtime": "src/status/summary.runtime.ts",
"infra/boundary-file-read": "src/infra/boundary-file-read.ts",
"plugins/provider-discovery.runtime": "src/plugins/provider-discovery.runtime.ts",
"plugins/provider-runtime.runtime": "src/plugins/provider-runtime.runtime.ts",
"web-fetch/runtime": "src/web-fetch/runtime.ts",
"plugins/public-surface-runtime": "src/plugins/public-surface-runtime.ts",
"plugins/loader": "src/plugins/loader.ts",
"plugins/sdk-alias": "src/plugins/sdk-alias.ts",
"facade-activation-check.runtime": "src/plugin-sdk/facade-activation-check.runtime.ts",
"plugin-metadata-readers.runtime": "src/plugins/plugin-metadata-readers.runtime.ts",
"legacy-config-binding-repair.runtime":
"src/commands/doctor/shared/legacy-config-binding-repair.runtime.ts",
"infra/warning-filter": "src/infra/warning-filter.ts",
"telegram-ingress-worker.runtime": bundledPluginFile(
"telegram",
"src/telegram-ingress-worker.runtime.ts",
),
"telegram/audit": bundledPluginFile("telegram", "src/audit.ts"),
"telegram/token": bundledPluginFile("telegram", "src/token.ts"),
"plugins/build-smoke-entry": "src/plugins/build-smoke-entry.ts",
"plugins/runtime/index": "src/plugins/runtime/index.ts",
"llm-slug-generator": "src/hooks/llm-slug-generator.ts",
"mcp/plugin-tools-serve": "src/mcp/plugin-tools-serve.ts",
"mcp/openclaw-tools-serve": "src/mcp/openclaw-tools-serve.ts",
};
}
function buildDockerE2eHarnessEntries(): Record<string, string> {
return {
// Mounted Docker harnesses need stable package dist entries for asserted internal modules.
"agents/agent-bundle-mcp-manager-api": "src/agents/agent-bundle-mcp-manager-api.ts",
"agents/agent-bundle-mcp-materialize": "src/agents/agent-bundle-mcp-materialize.ts",
"agents/agent-tool-definition-adapter": "src/agents/agent-tool-definition-adapter.ts",
"agents/conversation-capability-profile": "src/agents/conversation-capability-profile.ts",
"agents/embedded-agent-runner/effective-tool-policy":
"src/agents/embedded-agent-runner/effective-tool-policy.ts",
"agents/embedded-agent-runner/run/runtime-context-prompt":
"src/agents/embedded-agent-runner/run/runtime-context-prompt.ts",
"auto-reply/reply/commands-system-agent": "src/auto-reply/reply/commands-system-agent.ts",
"cli/run-main": "src/cli/run-main.ts",
"commands/onboard-guided": "src/commands/onboard-guided.ts",
"config/config": "src/config/config.ts",
"infra/gateway-scheduler": "src/infra/gateway-scheduler.ts",
"infra/sqlite-audit-record-store": "src/infra/sqlite-audit-record-store.ts",
"state/local-onboarding-state": "src/state/local-onboarding-state.ts",
"system-agent/audit": "src/system-agent/audit.ts",
"system-agent/system-agent": "src/system-agent/system-agent.ts",
"system-agent/rescue-message": "src/system-agent/rescue-message.ts",
"system-agent/setup-inference": "src/system-agent/setup-inference.ts",
"gateway/protocol/index": "packages/gateway-protocol/src/index.ts",
"infra/errors": "src/infra/errors.ts",
"infra/ws": "src/infra/ws.ts",
"plugin-sdk/provider-onboard": "src/plugin-sdk/provider-onboard.ts",
"plugins/tool-metadata": "src/plugins/tool-metadata.ts",
"normalization-core/string-coerce": "packages/normalization-core/src/string-coerce.ts",
};
}
function buildAgentCoreDistEntries(): Record<string, string> {
return {
index: "packages/agent-core/src/index.ts",
agent: "packages/agent-core/src/agent.ts",
"agent-loop": "packages/agent-core/src/agent-loop.ts",
llm: "packages/agent-core/src/llm.ts",
"runtime-deps": "packages/agent-core/src/runtime-deps.ts",
types: "packages/agent-core/src/types.ts",
validation: "packages/agent-core/src/validation.ts",
"harness/messages": "packages/agent-core/src/harness/messages.ts",
"harness/env/kill-tree": "packages/agent-core/src/harness/env/kill-tree.ts",
"harness/compaction": "packages/agent-core/src/harness/compaction/compaction.ts",
"harness/branch-summarization":
"packages/agent-core/src/harness/compaction/branch-summarization.ts",
"harness/prompt-template-arguments":
"packages/agent-core/src/harness/prompt-template-arguments.ts",
"harness/utils/truncate": "packages/agent-core/src/harness/utils/truncate.ts",
};
}
function buildLlmCoreDistEntries(): Record<string, string> {
return {
index: "packages/llm-core/src/index.ts",
"model-contracts/anthropic": "packages/llm-core/src/model-contracts/anthropic.ts",
types: "packages/llm-core/src/types.ts",
"utils/diagnostics": "packages/llm-core/src/utils/diagnostics.ts",
"utils/event-stream": "packages/llm-core/src/utils/event-stream.ts",
validation: "packages/llm-core/src/validation.ts",
};
}
function shouldExternalizeAgentCoreDependency(id: string): boolean {
return (
id === "@openclaw/ai" ||
id.startsWith("@openclaw/ai/") ||
id === "@openclaw/llm-core" ||
id.startsWith("@openclaw/llm-core/") ||
id === "ignore" ||
id === "openclaw" ||
id.startsWith("openclaw/") ||
id === "typebox" ||
id.startsWith("typebox/") ||
id === "yaml" ||
id.startsWith("yaml/")
);
}
function shouldExternalizeGatewayProtocolDependency(id: string): boolean {
return id === "typebox" || id.startsWith("typebox/");
}
function shouldExternalizeGatewayClientDependency(id: string): boolean {
return ["ws", "@openclaw/gateway-protocol", "ipaddr.js"].some(
(dependency) => id === dependency || id.startsWith(`${dependency}/`),
);
}
function shouldExternalizeNetPolicyDependency(id: string): boolean {
return id === "ipaddr.js" || id.startsWith("ipaddr.js/");
}
function shouldExternalizeLlmCoreDependency(id: string): boolean {
return id === "typebox" || id.startsWith("typebox/");
}
function shouldExternalizeMarkdownCoreDependency(id: string): boolean {
return (
id === "markdown-it" || id.startsWith("markdown-it/") || id === "yaml" || id.startsWith("yaml/")
);
}
function shouldExternalizeTerminalCoreDependency(id: string): boolean {
return id === "@clack/prompts" || id.startsWith("@clack/prompts/") || id === "chalk";
}
const coreDistEntries = buildCoreDistEntries();
const dockerE2eHarnessEntries = buildDockerE2eHarnessEntries();
const rootBundledPluginBuildEntries = collectSourceCheckoutPluginBuildEntries(
bundledPluginBuildInventory,
).filter(({ isolated }) => !isolated);
const bundledInventoryEntries = rootBundledPluginBuildEntries.flatMap((plugin) => {
const sourceEntries = plugin.sourceEntries.filter(
(source) =>
source === plugin.packageJson?.openclaw?.setupEntry ||
plugin.catalogSourceEntries.includes(source) ||
/^\.\/setup-api\.[cm]?[jt]s$/u.test(source),
);
if (!sourceEntries.length) {
return [];
}
const entries = [{ id: plugin.id, sourceEntries }];
const runtime = listBundledPluginEntrySources([
{
id: plugin.id,
sourceEntries: plugin.packageJson?.openclaw?.extensions?.length
? plugin.packageJson.openclaw.extensions
: ["./index.ts"],
},
]);
const runtimeSources = new Set(Object.values(runtime).map((source) => fs.realpathSync(source)));
for (const [name, source] of Object.entries(listBundledPluginEntrySources(entries))) {
// One public artifact cannot carry both native runtime and reloadable inventory ownership.
if (Object.hasOwn(runtime, name) || runtimeSources.has(fs.realpathSync(source))) {
throw new Error(`Plugin ${plugin.id} inventory entry overlaps its runtime: ${source}`);
}
}
return entries;
});
const bundledInventoryEntryNames = new Set(
Object.keys(listBundledPluginEntrySources(bundledInventoryEntries)),
);
function buildUnifiedDistEntries(): Record<string, string> {
return {
...coreDistEntries,
...dockerE2eHarnessEntries,
// Private app protocol entry shares chunks with the SDK needed by node-host plugins.
"mac-node-worker": "src/node-host/mac-worker-entry.ts",
...Object.fromEntries(
Object.entries(buildPackageDistEntriesFromExports("normalization-core")).map(
([entry, source]) => [`normalization-core/${entry}`, source],
),
),
...Object.fromEntries(
Object.entries(buildPackageDistEntriesFromExports("retry")).map(([entry, source]) => [
`retry/${entry}`,
source,
]),
),
...Object.fromEntries(
Object.entries(buildPackageDistEntriesFromExports("media-core")).map(([entry, source]) => [
`media-core/${entry}`,
source,
]),
),
...Object.fromEntries(
Object.entries(buildPackageDistEntriesFromExports("acp-core")).map(([entry, source]) => [
`acp-core/${entry}`,
source,
]),
),
...Object.fromEntries(
Object.entries(buildPackageDistEntriesFromExports("terminal-core")).map(([entry, source]) => [
`terminal-core/${entry}`,
source,
]),
),
// Private bundled Codex helper for app-server user MCP config projection.
"plugin-sdk/codex-mcp-projection": "src/plugin-sdk/codex-mcp-projection.ts",
// Private bundled Codex helper for app-server transcript mirroring.
"plugin-sdk/codex-session-transcript-runtime":
"src/plugin-sdk/codex-session-transcript-runtime.ts",
...Object.fromEntries(
Object.entries(buildPluginSdkEntrySources(selectedPluginSdkEntrypoints)).map(
([entry, source]) => [`plugin-sdk/${entry}`, source],
),
),
...listBundledPluginEntrySources(rootBundledPluginBuildEntries),
"extensions/browser/native-host-entry": "extensions/browser/native-host-entry.ts",
"extensions/browser/relay-daemon-entry": "extensions/browser/relay-daemon-entry.ts",
"extensions/browser/setup-entry": "extensions/browser/setup-entry.ts",
...bundledHookEntries,
};
}
type UnifiedEntry = [name: string, source: string];
function partitionUnifiedEntryGroups(
entryGroups: UnifiedEntry[][],
partitionCount: number,
): UnifiedEntry[][] {
const partitions = Array.from({ length: partitionCount }, () => [] as UnifiedEntry[]);
for (const entryGroup of entryGroups) {
let targetIndex = 0;
for (let index = 1; index < partitions.length; index += 1) {
const candidate = partitions[index];
const target = partitions[targetIndex];
if (candidate && target && candidate.length < target.length) {
targetIndex = index;
}
}
const target = partitions[targetIndex];
if (!target) {
throw new Error("unified declaration partition count must be positive");
}
target.push(...entryGroup);
}
return partitions;
}
function normalizeDeclarationEntrySource(source: string): string {
const relativeSource = path.isAbsolute(source) ? path.relative(process.cwd(), source) : source;
return relativeSource.replaceAll(path.sep, "/");
}
function buildUnifiedDeclarationPartitions(
entries: Record<string, string>,
): Array<{ name: string; sources: string[] }> {
const publicPluginSdkEntryNames = new Set(
publicPluginSdkEntrypoints.map((entry) => `plugin-sdk/${entry}`),
);
const pluginContracts = listBundledPluginEntrySources(
rootBundledPluginBuildEntries.map((plugin) => ({
id: plugin.id,
sourceEntries: collectPluginDeclarationSourceEntries(
plugin.packageJson,
plugin.sourceEntries,
),
})),
);
// Runtime entrypoints include workers, lazy loaders, and private implementation
// sidecars. Only the library root, typed SDK, and plugin contracts own declarations.
const sortedEntries = Object.entries(entries)
.filter(([name]) =>
name.startsWith("plugin-sdk/")
? shouldBuildPrivateQaEntries || publicPluginSdkEntryNames.has(name)
: name === "index" || Object.hasOwn(pluginContracts, name),
)
.toSorted(([left], [right]) => left.localeCompare(right));
const baseEntries = sortedEntries.filter(([name]) => name === "index");
const pluginSdkEntries = sortedEntries.filter(([name]) => name.startsWith("plugin-sdk/"));
const extensionEntriesById = new Map<string, UnifiedEntry[]>();
for (const entry of sortedEntries) {
const [name] = entry;
if (!name.startsWith("extensions/")) {
continue;
}
const extensionId = name.split("/", 3)[1];
if (!extensionId) {
continue;
}
const extensionEntries = extensionEntriesById.get(extensionId) ?? [];
extensionEntries.push(entry);
extensionEntriesById.set(extensionId, extensionEntries);
}
// Keep memory-bounded partitions. Outside private QA the private SDK partition
// has no emit roots, so the declaration plugin performs no compiler work for it.
const pluginSdkPartitions = [
pluginSdkEntries.filter(([name]) => publicPluginSdkEntryNames.has(name)),
pluginSdkEntries.filter(([name]) => !publicPluginSdkEntryNames.has(name)),
];
const extensionPartitions = partitionUnifiedEntryGroups(
[...extensionEntriesById.entries()]
.toSorted(([left], [right]) => left.localeCompare(right))
.map(([, extensionEntries]) => extensionEntries),
5,
);
const partitions = [baseEntries, ...pluginSdkPartitions, ...extensionPartitions];
return TSDOWN_UNIFIED_DTS_CONFIG_GROUPS.map((name, index) => {
const partition = partitions[index];
if (!partition) {
throw new Error(`missing unified declaration partition for ${name}`);
}
return {
name,
// The compiler's TypeScript-only policy leaves JavaScript runtime assets
// without declarations; they remain in the unified runtime entry graph.
sources: partition
.filter(([, source]) => /\.[cm]?tsx?$/u.test(source))
.map(([, source]) => normalizeDeclarationEntrySource(source)),
};
});
}
const unifiedDistEntries = buildUnifiedDistEntries();
const unifiedDeps = {
...rootDependencyOptions,
alwaysBundle: shouldAlwaysBundleDependency,
// Keep dependency-owned types canonical across independently emitted declaration graphs.
dts: { neverBundle: shouldNeverBundleDeclarationDependency },
};
// TypeScript supports this hidden flag before get-tsconfig's option type does.
const unifiedDeclarationCompilerOptions: NonNullable<DtsOptions["compilerOptions"]> & {
stableTypeOrdering: true;
} = { stableTypeOrdering: true };
const configs: UserConfig[] = [
nodeBuildConfig({
name: TSDOWN_PACKAGE_CONFIG_GROUP,
entry: buildAgentCoreDistEntries(),
outDir: tsdownPackageOutputRoot("agent-core"),
deps: {
neverBundle: shouldExternalizeAgentCoreDependency,
},
}),
nodeWorkspacePackageBuildConfig("gateway-protocol", {
deps: {
neverBundle: shouldExternalizeGatewayProtocolDependency,
},
}),
nodeWorkspacePackageBuildConfig("gateway-client", {
deps: {
neverBundle: shouldExternalizeGatewayClientDependency,
},
}),
nodeWorkspacePackageBuildConfig("net-policy", {
deps: {
neverBundle: shouldExternalizeNetPolicyDependency,
},
}),
nodeWorkspacePackageBuildConfig("media-generation-core"),
nodeWorkspacePackageBuildConfig("media-understanding-common"),
nodeWorkspacePackageBuildConfig("markdown-core", {
deps: {
neverBundle: shouldExternalizeMarkdownCoreDependency,
},
}),
nodeWorkspacePackageBuildConfig("normalization-core"),
nodeWorkspacePackageBuildConfig("retry"),
nodeWorkspacePackageBuildConfig("sdk", {
deps: withExternalPackageSubpaths({
neverBundle: [
"@openclaw/gateway-client",
"@openclaw/gateway-protocol",
"@openclaw/normalization-core",
],
}),
}),
nodeWorkspacePackageBuildConfig("media-core"),
nodeWorkspacePackageBuildConfig("acp-core", {
entry: {
...buildPackageDistEntriesFromExports("acp-core"),
// Preserve the standalone package build's non-exported redactor artifact.
"error-format": "packages/acp-core/src/error-format.ts",
},
}),
nodeWorkspacePackageBuildConfig("terminal-core", {
deps: {
neverBundle: shouldExternalizeTerminalCoreDependency,
},
}),
nodeWorkspacePackageBuildConfig("llm-core", {
entry: buildLlmCoreDistEntries(),
deps: {
neverBundle: shouldExternalizeLlmCoreDependency,
},
}),
nodeWorkspacePackageBuildConfig("model-catalog-core"),
nodeBuildConfig(
{
name: TSDOWN_UNIFIED_CONFIG_GROUP,
// Build core entrypoints, plugin-sdk subpaths, bundled plugin entrypoints,
// and bundled hooks in one graph so runtime singletons are emitted once.
entry: Object.fromEntries(
Object.entries(sharedRuntimeProcessBuildEntries(unifiedDistEntries)).filter(
([name]) => !bundledInventoryEntryNames.has(name),
),
),
deps: {
...unifiedDeps,
alwaysBundle: (id) =>
shouldAlwaysBundleDependency(id) || shouldBundleRuntimeSqliteDependency(id),
},
// Explicit ESM chunks avoid repeated package-format parsing in Node;
// named entrypoints retain their public .js paths.
outputOptions: { chunkFileNames: "[name]-[hash].mjs" },
plugins: [
createStateSchemaInlinePlugin(),
createGatewayRunChunkMetadataPlugin(),
createRuntimeDependencyOwnershipBuildPlugin(),
],
},
false,
),
nodeBuildConfig(
{
name: TSDOWN_UNIFIED_CONFIG_GROUP,
// One-shot relays must not load shared Gateway/SDK chunks just to read a locator.
// Keep splitting enabled so the existing Gateway fallback stays lazy.
entry: { "native-hook-relay/entry": "src/cli/native-hook-relay-entry.ts" },
deps: unifiedDeps,
outputOptions: { chunkFileNames: "native-hook-relay/[name]-[hash].mjs" },
plugins: [createStateSchemaInlinePlugin()],
},
false,
),
...bundledInventoryEntries.map((plugin) => {
const entry = listBundledPluginEntrySources([plugin]);
const privateChunks = `${bundledPluginRoot(plugin.id)}/.setup/[name]-[hash].mjs`;
return nodeBuildConfig(
{
name: TSDOWN_UNIFIED_CONFIG_GROUP,
// Inventory generations own their lazy chunks; host SDK singletons stay native.
entry,
plugins: [createPluginInventoryModuleRefsPlugin(bundledPluginRoot(plugin.id))],
deps: {
...unifiedDeps,
neverBundle: [...rootDependencyOptions.neverBundle, /^openclaw(?:\/|$)/u],
alwaysBundle: (id) => !/^openclaw(?:\/|$)/u.test(id) && shouldAlwaysBundleDependency(id),
},
outputOptions: {
entryFileNames: (chunk) =>
Object.hasOwn(entry, chunk.name) ? "[name].js" : privateChunks,
chunkFileNames: privateChunks,
assetFileNames: `${bundledPluginRoot(plugin.id)}/.setup/[name]-[hash][extname]`,
},
},
false,
);
}),
...Object.entries(standaloneRuntimeProcessBuildEntries).map(([name, source]) =>
nodeBuildConfig(
{
name: TSDOWN_UNIFIED_CONFIG_GROUP,
entry: { [name]: source },
deps: {
...unifiedDeps,
alwaysBundle: (id) =>
shouldAlwaysBundleDependency(id) || shouldBundleRuntimeSqliteDependency(id),
},
outputOptions: { codeSplitting: false },
plugins: [createStateSchemaInlinePlugin()],
},
false,
),
),
nodeBuildConfig(
{
name: TSDOWN_UNIFIED_CONFIG_GROUP,
entry: { "node-host-launcher-bootstrap": "src/node-host/launcher-bootstrap.ts" },
deps: unifiedDeps,
outputOptions: { codeSplitting: false },
},
false,
),
workerDeployBuildConfig({ "worker/worker": "src/worker/worker-deploy-entry.ts" }),
workerDeployBuildConfig({
"worker/file-tool-planning.worker": "src/worker/worker-deploy-file-tool-planning.ts",
}),
workerDeployBuildConfig({
"worker/image-processor.worker": "src/worker/worker-deploy-image-processor.ts",
}),
workerDeployBuildConfig({
"worker/sqlite-store.worker": "src/worker/worker-deploy-sqlite-store.ts",
}),
...createManagedHandoffBuildConfigs().map((config) =>
Object.assign(config, { name: TSDOWN_UNIFIED_CONFIG_GROUP, env }),
),
nodeBuildConfig(
{
name: TSDOWN_UNIFIED_CONFIG_GROUP,
// Keep retained config repairs in their own graph: shared public SDK chunks
// otherwise pull state-migration exports into these pre-install artifacts.
entry: collectChannelConfigDoctorBuildEntries(bundledPluginBuildInventory),
outDir: "dist/config-doctor",
deps: unifiedDeps,
},
false,
),
workerHelperBuildConfig({
"worker/workspace-rsync-receiver": "src/worker/workspace-rsync-receiver.ts",
}),
workerHelperBuildConfig({ "worker/github-exec-launcher": "src/agents/github-exec-launcher.ts" }),
...["service-child-relay", "service-child-group-anchor"].map((name) =>
workerHelperBuildConfig(
{ [`worker/${name}`]: `src/process/supervisor/${name}.ts` },
{ WORKER_DEPLOY_BUILD: "true", SEALED_RUNTIME_BUILD: "true" },
),
),
...(TSDOWN_DECLARATIONS
? buildUnifiedDeclarationPartitions(unifiedDistEntries).map(({ name, sources }) =>
nodeBuildConfig(
{
name,
entry: unifiedDistEntries,
deps: unifiedDeps,
hooks: { "build:done": createDeclarationInputCapture(name) },
},
{
emitDtsOnly: true,
entry: sources,
compilerOptions: unifiedDeclarationCompilerOptions,
},
),
)
: []),
];
export default configs;