openclaw/tsdown.config.ts
RoboClaw bb2d479926
feat(browser): unify local Chrome setup across desktop and terminal (#152057)
* feat(browser): unify local Chrome setup across desktop and terminal

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>

* feat(browser): unify local Chrome setup across desktop and terminal

OpenClaw-Publication: d19e865e-b5a0-4c70-8876-c1662f6e7ef2

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>

* chore(linux): format Chrome setup fixture

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>

* feat(browser): keep desktop Chrome setup local and preserve pairing

Delegate Windows registration to the shared native management owner, preserve
released native bridge compatibility and saved launcher profiles, and integrate
serialized desktop setup through isolated local runtimes.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* fix(browser): repair native setup CI contracts

Keep Windows installer dependencies acyclic, validate Unicode within the
package library target, and remove unused private exports. Require all
eight packaged native-host proof cases and update lazy CLI inventory.
Apply native Swift formatter diagnostics without changing behavior.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* test(cli): account for plugin-owned browser extension catalog

Keep the core-only registration invariant aligned with the Browser plugin
owner already exercised by its lazy registration tests.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* test(macos): retain released Chrome bridge request expectation

Align the native bridge test with the shipped contract1 request retained
by the canonical setup owner, and reject extra legacy payload fields.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* test(tui): give command handler harness a unique export

Rename the shared TUI test helper and both consumers to avoid the
Gateway placement harness export collision. No alias or guard waiver.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* chore(sdk): allow canonical browser config path resolver

Apply the approved single public-export and callable allowance for
resolveConfigPath. Preserve canonical pre-config path ownership and
all other SDK surface checks.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* fix(browser): preserve desktop setup selection and supported actions

Keep native automatic setup selector-free and resolve saved local browser
selection through the canonical setup owner before installation. Respect
Mac action advertisements and the released legacy install projection in
the Apps card, and document the public config-path resolver contract.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* fix(auth): retry model selection after concurrent credential refresh

Adopt upstream PR #152426, commit 32298b10f6, without changing its five source files.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* test: preserve native setup selection and await dashboard document

Match the selector-free native CLI arguments exactly and preserve a saved work-profile result. Wait through the existing document-readiness owner only at the quota test browser-proof boundary, after auth assertions.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* fix(ui): avoid preloading already imported modules

Remove exact direct static JavaScript imports from lazy preload tables using the emitted build graph. Preserve HTML, lazy-only JavaScript, CSS, and locale hints. Source-exact CI merge reproduction drops startup gzip from 363283 to 362968 bytes without changing budgets. Add a real emitted-bundle regression.

Apply rustfmt layout to the native Chrome selector-free expected arguments.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* fix: preserve Chrome profiles and attachment follow-up branch binding

Let the TUI canonical setup controller retain its saved browser profile and project only a bounded returned name. Align both native first-run fixture expectations with selector-free setup.

Join pending chat history before the composer task handoff can expose an admitted attachment to restored-outbox delivery. Preserve idempotency, attachment custody, restored delivery semantics, and all existing assertions and timeouts. Add a deterministic regression reproduced on the exact failed CI merge and its main parent.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* test(state): adopt canonical worker-custody fixture repair

Adopt src/plugin-state/plugin-state-worker.test.ts byte-for-byte from upstream bfec65a2a0 (#152456).

The former fixture held its late competing owner until after awaiting off-thread acquisition. Preserve that overlap, assert continued host authority checks and noncompletion, release custody, then assert the original result and persisted state. No production locking, guard, deadline or outcome assertion is relaxed.

Both prior failures reproduced on the exact CI main parent with independently installed frozen dependencies and Node 24.19.0. All 12 repaired file tests, selected state-logging types, scoped typed lint and fresh P0-P2 review passed.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* fix(browser): retain saved Windows setup profiles

Recover configured extension profiles through bounded serial read-only C# inspection. Select only independently validated current matching descriptors, confirm the selected generation before effects, and leave the single mutation under the existing C# owner. Preserve POSIX behavior, existing manual relay verification and explicit same-profile repair.

Missing descriptors, runtime/origin drift and unknown or changing observations fail closed without automatic mutation. Keep raw management facts private and populate the existing browserProfile field only from validated binding metadata. No ABI, schema, SDK, configuration flag or registry/activation owner change.

29 actual CLI/controller/Windows-adapter boundary cases plus sibling coverage: 94 tests pass. Canonical changed checks, full production build and fresh independent P0-P2 review passed. Actual C# native proof remains separately coordinated.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* fix(browser): preserve saved profiles after POSIX bundle relocation

Separate validated native registration ownership from supported origin-migration readiness. Recover the profile only after full private manifest and exact launcher validation; preserve the existing one-slot migration rule and all unsupported-origin, ACL and foreign-host refusals. Fail closed before selector-free installation when the saved selection cannot be proved.

Extract the unchanged shared origin helpers into a cohesive sibling to satisfy the existing line-cap guard without waivers. Windows admission, ABI and selector behavior remain unchanged.

Actual Linux/Darwin CLI-to-filesystem relocation regressions: 18 failures on original production, all 22 cases repaired. Preserve the private relay key and inode, config, Chrome preferences, work relay19444 and explicit-profile intent. 137 focused tests, eight real POSIX native-host E2E cases, canonical changed checks, full production build and fresh P0-P2 review passed.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* fix(ui): retain input handoff through the shared outbox owner

Remove the superseded pending-history no-yield workaround after main introduced foreground submission custody in the shared outbox owner. Restore chat-submit-guard.ts exactly to pinned main cc7 rather than retaining competing timing policies. Keep passive drains fenced while the input task yields.

Preserve the retained history regression with explicit MessageChannel admission, no passive send before resume, and the same terminal leaf, idempotency key, attachment bytes and exactly-once assertions after completion. Original composed source fails all five focused cases; the repair passes 67 handoff/attachment cases and 20 real Chromium cases in the canonical secretless network-none runner. Canonical checks, UI build/performance and fresh P0-P2 review pass. No assertion, timeout, origin or proxy-policy weakening.

Browser POSIX/Windows repairs remain byte-identical to accepted255f. The failed e40e CI receipts remain preserved; fresh exact-head CI and parent handoff are still required.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* fix(tasks): preserve reads across native event finalization

Hand joined event publication to its exact native successor after the native
flow and observer publication frame completes. Keep worker settlement and
cleanup, reversible claim transfer, current-authority and ABA checks, and
post-commit delivery in their existing owners without replaying writes.

Cover pre-result and readback finalization, native and reentrant successor
chains, rollback, failed publication, delivery, and terminal activity cleanup.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* test(ui): retain rail baseline geometry on readiness failure

Keep the exact existing readiness predicate, fixtures, case inventory, assertion and timeout. When the predicate is false, retain synthetic marker identity and numeric geometry so hosted CI can distinguish scroll, visibility and viewport failures.

This is diagnostic evidence, not a repair or waiver of the unresolved rail failure. Local rootless browser infrastructure is unavailable; the existing hosted CI lane will verify the reviewed task-publication repair and collect meaningful rail evidence. Canonical changed checks and P0-P2 diagnostic review pass.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* docs(linux): describe saved-profile Chrome setup selection

Match the selector-free adapter argument vector and its regression test. Address the fresh P3 review finding without changing runtime behavior. Markdown syntax and diff checks pass; the generic formatter excludes this subtree.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* test(transcripts): join configured startup before cleanup faults

Observe and await the real startTranscripts promise through a narrow call-through spy while retaining the configured service entry point and real SQLite/provider work. Bind the await to the existing test lifetime instead of charging startup to the subsequent short active-map poll.

Gate provider return after persisted utterance to prove readiness does not settle early; retain both missing/unreadable row injections and all cleanup, private-source, lifecycle-token and summary assertions. Cover real startup rejection explicitly. No production change, timeout increase, retries or broad module/storage mocks.

The deterministic ordering boundary fails with the old fire-and-forget readiness and passes with the real promise join. Final 39 tests across 3 files, canonical changed checks and full-owner P0-P2 review pass. This does not recover whether the historical CI startup was late or rejected.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* fix(browser): preserve automatic desktop status inspection

Restore read-only Device-page inspection for current and released Mac bridges while keeping installation and verification explicit. Preserve the native filesystem prerequisite proof, split installer repair tests within the existing line cap, and remove the superseded constant export.

* fix(browser): preserve registered setup configuration

Require canonical setup to match an owned launcher's effective state and
config selection before installation or relay access. Preserve equivalent
implicit/explicit default selections and the saved launch context. Recheck
automatic profile selection before effects and the current manifest before
publication through the existing registration owner. Keep manual install
and relocation repair contracts unchanged.

Cover mismatched configs, legacy selectors, equivalent defaults, selection
drift, and actual bootstrap after refused setup. Restore the missing Command
import in the existing Unix-only companion CLI test.

Focused tests, types, lint, fresh review, clean package build and sealed Mac
ARM64 runtime proof pass. The separate historical clock-jump CI failure has
bounded replay evidence and remains documented without a speculative fix.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>

* fix(browser): keep setup registration types acyclic

Move the private registration status contract beside its context policy and
point both consumers at that owner. Remove the publication-module back-edge
without keeping an unused compatibility export.

The full architecture gate, extension production/test types, typed lint and
fresh independent review pass. Node's transformed JavaScript is byte-identical
for all three affected modules, so the existing runtime proof remains valid.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>

* test(linux): handle Chrome setup in desktop sharing fixture

Recognize the exact automatic Chrome setup invocation and require its native
no-respawn flag. Keep unknown-command rejection, selected-auth validation,
process-group ownership and joined teardown assertions unchanged.

The original fixture reproduces the CI rejection against the real Linux app.
The repaired fixture passes all nine checks against that same binary, with
five Chrome setup calls and five node starts and joined stops. Fresh review
is clean; production app behavior is unchanged.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>

---------

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-09-22 06:28:29 -07:00

1024 lines
39 KiB
TypeScript

// tsdown config defines package build entrypoints and output options.
import fs from "node:fs";
import { createRequire, isBuiltin } 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 { createManagedHandoffBuildConfig } 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";
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",
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: {
alwaysBundle: (id) => !isBuiltin(id),
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",
dts: false,
env,
define,
deps: {
alwaysBundle: (id) => !isBuiltin(id),
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",
"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 {
// Arrow's relative module augmentations must stay beside their package modules.
return (
shouldNeverBundleDependency(id) ||
["zod", "apache-arrow", "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",
"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",
"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",
"task-registry-control.runtime": "src/tasks/task-registry-control.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/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/tool-split": "src/agents/embedded-agent-runner/tool-split.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/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 buildPackageDistEntriesFromExports(packageDir: string): Record<string, string> {
const packageJsonPath = path.join("packages", packageDir, "package.json");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
exports?: Record<string, unknown>;
};
const entries: Record<string, string> = {};
for (const [exportKey, value] of Object.entries(packageJson.exports ?? {})) {
const entry =
exportKey === "." ? "index" : exportKey.startsWith("./") ? exportKey.slice(2) : "";
if (!entry || entry.includes("..")) {
continue;
}
const importPath =
typeof value === "object" && value !== null && !Array.isArray(value)
? (value as Record<string, unknown>).import
: value;
if (typeof importPath !== "string" || !importPath.startsWith("./dist/")) {
continue;
}
const sourcePath = importPath
.replace(/^\.\/dist\//u, `packages/${packageDir}/src/`)
.replace(/\.mjs$/u, ".ts");
entries[entry] = sourcePath;
}
return Object.fromEntries(Object.entries(entries).toSorted(([a], [b]) => a.localeCompare(b)));
}
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("media-core"),
nodeWorkspacePackageBuildConfig("acp-core"),
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/image-processor.worker": "src/worker/worker-deploy-image-processor.ts",
}),
workerDeployBuildConfig({
"worker/sqlite-store.worker": "src/worker/worker-deploy-sqlite-store.ts",
}),
{ ...createManagedHandoffBuildConfig(), 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;