Files
openclaw/test/scripts/runtime-postbuild.test.ts
Peter Steinberger bb46b79d3c refactor: internalize OpenClaw agent runtime (#85341)
* refactor: extract agent core package

Introduce packages/agent-core as the OpenClaw-owned home for reusable agent loop, harness, session, prompt, and runtime dependency contracts.

* refactor: extract shared llm runtime

Move provider model registries, stream wrappers, OAuth helpers, and LLM utilities into src/llm with plugin-sdk barrels instead of depending on the old embedded runtime layout.

* refactor: remove pi runtime internals

Rename remaining Pi-shaped agent surfaces to OpenClaw agent runtime names, delete obsolete Pi docs and package graph checks, and add the third-party notice for incorporated code.

* refactor: tighten agent session runtime

Make agent-core/runtime dependencies explicit, consolidate compaction and session transcript helpers, and move model/session helpers behind OpenClaw-owned contracts.

* refactor: remove static model and pi auth paths

Drop static model catalogs and Pi auth bridges, move model/provider facts to manifest-owned runtime contracts, and harden internal embedded-agent utilities.

* refactor: remove legacy provider compat paths

* docs: remove agent parity notes

* fix: skip provider wildcard metadata parsing

* refactor: share session extension sdk loading

* refactor: inline acpx proxy error formatter

* refactor: fold edit recovery into edit tool

* fix: accept extension batch separator

* test: align startup provider plugin expectations

* fix: restore provider-scoped release discovery

* test: align static asset packaging expectations

* fix: run static provider catalogs during scoped discovery

* fix: add provider entry catalogs for scoped live discovery

* fix: load lightweight provider catalog entries

* fix: refresh provider-scoped plugin metadata

* fix: keep provider catalog entries on release live path

* fix: keep static manifest models in release live checks

* fix: harden release model discovery

* fix: reduce OpenAI live cache probe reasoning

* fix: disable OpenAI cache probe reasoning

* ci: extend OpenAI gateway live timeout

* fix: extend live gateway model budget

* fix: stabilize release validation regressions

* fix: honor provider aliases in model rows

* fix: stabilize release validation lanes

* fix: stabilize release memory qa

* ci: stabilize release validation lanes

* ci: prefer ipv4 for live docker node calls

* fix: restore shared tool-call stream wrapper

* ci: remove legacy pi test shard alias

* fix: clean up embedded agent test drift

* fix: stabilize runtime alias status

* fix: clean up embedded agent ci drift

* fix: restore release ci invariants

* fix: clean up post-rebase runtime drift

* fix: restore release ci checks

* fix: restore release ci after rebase

* fix: remove stale pi runtime path

* test: align compaction runtime expectations

* test: update plugin prerelease expectations

* fix: handle claude live tool approvals

* fix: stabilize release validation gates

* fix: finish agent runtime import

* test: finish post-rebase agent runtime mocks

* fix: keep codex compaction native

* fix: stabilize codex app-server hook tests

* test: isolate codex diagnostic active run

* test: remove codex diagnostic completion race

# Conflicts:
#	extensions/codex/src/app-server/run-attempt.test.ts

* ci: fix full release manifest performance run id

* refactor: narrow llm plugin sdk boundary

* chore: drop generated google boundary stamps

* fix: repair rebase fallout

* fix: clean up rebased runtime references

* fix: decode codex jwt payloads as base64url

* fix: preserve shipped pi runtime alias

* fix: add scoped sdk virtual modules

* fix: decode llm codex oauth jwt as base64url

* fix: avoid stale vertex adc negative cache

* fix: harden tool arg decoding and codeql path

* fix: keep vertex adc negative checks live

* refactor: consolidate codex jwt and edit helpers

* fix: await codex oauth node runtime imports

* fix: preserve sdk tool and notice contracts

* fix: preserve shipped compat config boundaries

* fix: align codex oauth callback host

* fix: terminate agent-core loop streams on failure

* fix: keep codex oauth callback alive during fallback

* ci: include session tools in critical codeql scans

* fix: keep Cloudflare Anthropic provider auth header

* docs: redirect legacy pi runtime pages

* fix: honor bundled web provider compat discovery

* fix: protect session output spill files

* fix: keep legacy agent dir env blocked

* fix: contain auto-discovered skill symlinks

* fix: harden agent core sdk proxy surfaces

* fix: restore approval reaction sdk compat

* fix: keep live docker runs bounded

* fix: keep codex oauth redirect host aligned

* fix: resolve post-rebase agent runtime drift

* fix: redact anthropic oauth parse failures

* fix: preserve responses strict tool shaping

* fix: repair agent runtime rebase cleanup

* docs: redirect retired parity pages

* fix: bound auto-discovered resources to roots

* fix: repair post-rebase agent test drift

* fix: preserve bundled provider allowlist migration

* fix: preserve manifest-owned provider aliases

* fix: declare photon image dependency

* fix: keep provider headers out of proxy body

* fix: preserve shipped env aliases

* fix: refresh control ui i18n generated state

* fix: quote read fallback paths

* fix: preview edits through configured backend

* test: satisfy core test typecheck

* fix: preserve ZAI usage auth fallback

* test: repair codex diagnostic test

* fix: repair agent runtime rebase drift

* test: finish embedded runner import rename

* fix: repair agent runtime rebase integrations

* test: align compaction oauth fallback expectations

* fix: allow sdk-auth session models

* fix: update doctor tool schema import

* fix: preserve bedrock plugin region

* fix: stream harmony-like prose immediately

* ci: include session runtime in codeql shards

* fix: repair latest rebase integrations

* fix: honor explicit codex websocket transport

* fix: keep openai-compatible credentials provider-scoped

* fix: refresh sdk api baseline after rebase

* fix: route cli runtime aliases through openclaw harness

* test: rename stale harness mock expectation

* test: rename embedded agent overflow calls

* test: clean embedded auth test wording

* test: use openclaw stream types in deepinfra cache test

* fix: refresh sdk api baseline on latest main

* fix: honor bundled discovery compat allowlists

* fix: refresh sdk api baseline after latest rebase

* fix: remove stale rebase imports

* test: rename stale model catalog mock

* test: mock renamed doctor runtime modules

* fix: map canonical kimi env auth

* fix: use internal model registry in bench script

* fix: migrate deepinfra provider catalog entry

* fix: enforce builtin tool suppression

* fix: route compaction auth and proxy payloads safely

* refactor: prune unused llm registry leftovers

* test: update codex hooks session import

* test: fix model picker ci coverage

* test: align model picker auth mock types
2026-05-27 19:24:04 +01:00

760 lines
27 KiB
TypeScript

import fs from "node:fs/promises";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import {
copyStaticExtensionAssetsToRuntimeOverlay,
discoverStaticExtensionAssets,
} from "../../scripts/lib/static-extension-assets.mjs";
import {
copyStaticExtensionAssets,
listStaticExtensionAssetOutputs,
rewriteRootRuntimeImportsToStableAliases,
runRuntimePostBuild,
writeLegacyCliExitCompatChunks,
writeLegacyRootRuntimeCompatAliases,
writeStableRootRuntimeAliases,
} from "../../scripts/runtime-postbuild.mjs";
import { expectNoNodeFsScans } from "../../src/test-utils/fs-scan-assertions.js";
import { createScriptTestHarness } from "./test-helpers.js";
const { createTempDir } = createScriptTestHarness();
async function expectPathMissing(targetPath: string): Promise<void> {
let statError: unknown;
try {
await fs.stat(targetPath);
} catch (error) {
statError = error;
}
expect(statError).toBeInstanceOf(Error);
if (!(statError instanceof Error)) {
throw new Error("expected missing path error");
}
expect(Reflect.get(statError, "code")).toBe("ENOENT");
}
describe("runtime postbuild static assets", () => {
it("tracks plugin-owned static assets that release packaging must ship", () => {
expect(listStaticExtensionAssetOutputs()).toEqual([
"dist/extensions/acpx/mcp-command-line.mjs",
"dist/extensions/acpx/mcp-proxy.mjs",
"dist/extensions/diffs-language-pack/assets/viewer-runtime.js",
"dist/extensions/diffs/assets/viewer-runtime.js",
]);
});
it("discovers repo static asset metadata without scanning extension directories", () => {
const payload = expectNoNodeFsScans<{
outputs: string[];
sources: string[];
}>(`
const assets = await import("./scripts/lib/static-extension-assets.mjs");
return {
outputs: assets.listStaticExtensionAssetOutputs(),
sources: assets.listStaticExtensionAssetSources(),
};
`);
expect(payload.outputs).toEqual([
"dist/extensions/acpx/mcp-command-line.mjs",
"dist/extensions/acpx/mcp-proxy.mjs",
"dist/extensions/diffs-language-pack/assets/viewer-runtime.js",
"dist/extensions/diffs/assets/viewer-runtime.js",
]);
expect(payload.sources).toContain(
"extensions/diffs-language-pack/assets/viewer-runtime.js",
);
expect(payload.sources).toContain("extensions/diffs/assets/viewer-runtime.js");
});
it("discovers static assets from plugin package metadata", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const packageDir = path.join(rootDir, "extensions", "demo");
await fs.mkdir(packageDir, { recursive: true });
await fs.writeFile(
path.join(packageDir, "package.json"),
JSON.stringify({
name: "@openclaw/demo",
openclaw: {
build: {
staticAssets: [
{
source: "./assets/runtime.js",
output: "assets/runtime.js",
},
],
},
},
}),
"utf8",
);
expect(discoverStaticExtensionAssets({ rootDir })).toEqual([
{
pluginDir: "demo",
src: "extensions/demo/assets/runtime.js",
dest: "dist/extensions/demo/assets/runtime.js",
},
]);
});
it("copies declared static assets into dist", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const src = "extensions/acpx/src/runtime-internals/mcp-proxy.mjs";
const dest = "dist/extensions/acpx/mcp-proxy.mjs";
const sourcePath = path.join(rootDir, src);
const destPath = path.join(rootDir, dest);
await fs.mkdir(path.dirname(sourcePath), { recursive: true });
await fs.writeFile(sourcePath, "proxy-data\n", "utf8");
copyStaticExtensionAssets({
rootDir,
assets: [{ src, dest }],
});
expect(await fs.readFile(destPath, "utf8")).toBe("proxy-data\n");
});
it("stages copied static assets byte-for-byte during the same postbuild run", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const source = "extensions/diffs/assets/viewer-runtime.js";
const output = "assets/viewer-runtime.js";
const distAsset = "dist/extensions/diffs/assets/viewer-runtime.js";
const runtimeAsset = "dist-runtime/extensions/diffs/assets/viewer-runtime.js";
await fs.mkdir(path.join(rootDir, "src", "plugin-sdk"), { recursive: true });
await fs.writeFile(
path.join(rootDir, "src", "plugin-sdk", "root-alias.cjs"),
"module.exports = {};\n",
"utf8",
);
await fs.mkdir(path.join(rootDir, "extensions", "diffs", "assets"), { recursive: true });
await fs.writeFile(
path.join(rootDir, "extensions", "diffs", "package.json"),
JSON.stringify({
name: "@openclaw/diffs",
openclaw: {
extensions: ["./index.ts"],
build: {
staticAssets: [{ source: `./${output}`, output }],
},
},
}),
"utf8",
);
await fs.writeFile(
path.join(rootDir, "extensions", "diffs", "openclaw.plugin.json"),
'{"id":"diffs"}\n',
"utf8",
);
await fs.writeFile(path.join(rootDir, source), "export const viewer = true;\n", "utf8");
runRuntimePostBuild({
cwd: rootDir,
repoRoot: rootDir,
rootDir,
timings: false,
});
await expect(fs.readFile(path.join(rootDir, distAsset), "utf8")).resolves.toBe(
"export const viewer = true;\n",
);
await expect(fs.readFile(path.join(rootDir, runtimeAsset), "utf8")).resolves.toBe(
"export const viewer = true;\n",
);
});
it("preserves restored dist static assets when plugin sources are absent", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const output = "assets/viewer-runtime.js";
const distPluginDir = path.join(rootDir, "dist", "extensions", "diffs");
const runtimeAsset = path.join(rootDir, "dist-runtime", "extensions", "diffs", output);
await fs.mkdir(path.join(rootDir, "src", "plugin-sdk"), { recursive: true });
await fs.writeFile(
path.join(rootDir, "src", "plugin-sdk", "root-alias.cjs"),
"module.exports = {};\n",
"utf8",
);
await fs.mkdir(path.join(distPluginDir, "assets"), { recursive: true });
await fs.writeFile(path.join(distPluginDir, "index.js"), "export default {};\n", "utf8");
await fs.writeFile(
path.join(distPluginDir, "openclaw.plugin.json"),
'{"id":"diffs"}\n',
"utf8",
);
await fs.writeFile(
path.join(distPluginDir, "package.json"),
JSON.stringify({
name: "@openclaw/diffs",
openclaw: {
extensions: ["./index.js"],
build: {
staticAssets: [{ source: `./${output}`, output }],
},
},
}),
"utf8",
);
await fs.writeFile(path.join(distPluginDir, output), "console.log('viewer');\n", "utf8");
runRuntimePostBuild({
cwd: rootDir,
repoRoot: rootDir,
rootDir,
timings: false,
});
await expect(fs.readFile(runtimeAsset, "utf8")).resolves.toBe("console.log('viewer');\n");
});
it("skips runtime overlay asset copies when the runtime extension root is absent", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
await fs.mkdir(path.join(rootDir, "extensions", "demo", "assets"), { recursive: true });
await fs.writeFile(
path.join(rootDir, "extensions", "demo", "assets", "viewer.js"),
"viewer\n",
"utf8",
);
copyStaticExtensionAssetsToRuntimeOverlay({
rootDir,
assets: [
{
src: "extensions/demo/assets/viewer.js",
dest: "dist/extensions/demo/assets/viewer.js",
},
],
});
await expectPathMissing(path.join(rootDir, "dist-runtime", "extensions", "demo", "assets"));
});
it("ignores runtime overlay static assets outside dist extensions", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
await fs.mkdir(path.join(rootDir, "dist-runtime", "extensions"), { recursive: true });
await fs.mkdir(path.join(rootDir, "extensions", "demo", "assets"), { recursive: true });
await fs.writeFile(
path.join(rootDir, "extensions", "demo", "assets", "viewer.js"),
"viewer\n",
"utf8",
);
copyStaticExtensionAssetsToRuntimeOverlay({
rootDir,
assets: [
{
src: "extensions/demo/assets/viewer.js",
dest: "dist/other/demo/assets/viewer.js",
},
],
});
await expectPathMissing(path.join(rootDir, "dist-runtime", "other", "demo", "assets"));
});
it("warns when a runtime overlay static asset source is missing", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const warn = vi.fn();
await fs.mkdir(path.join(rootDir, "dist-runtime", "extensions"), { recursive: true });
copyStaticExtensionAssetsToRuntimeOverlay({
rootDir,
assets: [
{
src: "extensions/demo/assets/missing.js",
dest: "dist/extensions/demo/assets/missing.js",
},
],
warn,
});
expect(warn).toHaveBeenCalledWith(
"[runtime-postbuild] static asset not found, skipping: extensions/demo/assets/missing.js",
);
await expectPathMissing(
path.join(rootDir, "dist-runtime", "extensions", "demo", "assets", "missing.js"),
);
});
it("warns when a declared static asset is missing", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const warn = vi.fn();
copyStaticExtensionAssets({
rootDir,
assets: [{ src: "missing/file.mjs", dest: "dist/file.mjs" }],
warn,
});
expect(warn).toHaveBeenCalledWith(
"[runtime-postbuild] static asset not found, skipping: missing/file.mjs",
);
});
it("writes stable aliases for hashed root runtime modules", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "runtime-model-auth.runtime-XyZ987.js"),
"export const auth = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "runtime-tts.runtime-AbCd1234.js"),
"export const tts = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "library-Other123.js"),
"export const x = true;\n",
"utf8",
);
writeStableRootRuntimeAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "runtime-model-auth.runtime.js"), "utf8")).toBe(
'export * from "./runtime-model-auth.runtime-XyZ987.js";\n',
);
expect(await fs.readFile(path.join(distDir, "runtime-tts.runtime.js"), "utf8")).toBe(
'export * from "./runtime-tts.runtime-AbCd1234.js";\n',
);
await expectPathMissing(path.join(distDir, "library.js"));
});
it("does not write ambiguous stable aliases for colliding root runtime chunks", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "install.runtime-Aaa111.js"),
"export const pluginInstall = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "install.runtime-Bbb222.js"),
"export const daemonInstall = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "install.runtime.js"),
'export * from "./install.runtime-Stale.js";\n',
"utf8",
);
writeStableRootRuntimeAliases({ rootDir });
await expectPathMissing(path.join(distDir, "install.runtime.js"));
});
it("writes a stable plugin install runtime alias when install runtimes collide", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "install.runtime-Aaa111.js"),
[
"export const scanPackageInstallSource = true;",
"export const scanFileInstallSource = true;",
"export const scanInstalledPackageDependencyTree = true;",
"export const scanBundleInstallSource = true;",
"",
].join("\n"),
"utf8",
);
await fs.writeFile(
path.join(distDir, "install.runtime-Bbb222.js"),
"export const daemonInstall = true;\n",
"utf8",
);
writeStableRootRuntimeAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "install.runtime.js"), "utf8")).toBe(
'export * from "./install.runtime-Aaa111.js";\n',
);
});
it("keeps stable aliases when one colliding root runtime chunk re-exports the implementation", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "runtime-model-auth.runtime-Impl123.js"),
"export const auth = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "runtime-model-auth.runtime-Wrap456.js"),
'import { auth } from "./runtime-model-auth.runtime-Impl123.js";\nexport { auth };\n',
"utf8",
);
writeStableRootRuntimeAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "runtime-model-auth.runtime.js"), "utf8")).toBe(
'export * from "./runtime-model-auth.runtime-Wrap456.js";\n',
);
});
it("ignores legacy wrappers to the stable runtime alias when choosing the implementation", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "runtime-plugins.runtime-NewHash.js"),
"export const ready = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "runtime-plugins.runtime-OldHash.js"),
'export * from "./runtime-plugins.runtime.js";\n',
"utf8",
);
await fs.writeFile(
path.join(distDir, "dispatch-OldHash.js"),
['const lazy = () => import("./runtime-plugins.runtime-NewHash.js");', ""].join("\n"),
"utf8",
);
rewriteRootRuntimeImportsToStableAliases({ rootDir });
writeStableRootRuntimeAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "dispatch-OldHash.js"), "utf8")).toBe(
['const lazy = () => import("./runtime-plugins.runtime.js");', ""].join("\n"),
);
expect(await fs.readFile(path.join(distDir, "runtime-plugins.runtime.js"), "utf8")).toBe(
'export * from "./runtime-plugins.runtime-NewHash.js";\n',
);
});
it("rewrites root runtime imports to stable aliases", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "runtime-plugins.runtime-AbCd1234.js"),
"export const ready = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "dispatch-OldHash.js"),
[
'const lazy = () => import("./runtime-plugins.runtime-AbCd1234.js");',
'import "./missing.runtime-Nope.js";',
"",
].join("\n"),
"utf8",
);
rewriteRootRuntimeImportsToStableAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "dispatch-OldHash.js"), "utf8")).toBe(
[
'const lazy = () => import("./runtime-plugins.runtime.js");',
'import "./missing.runtime-Nope.js";',
"",
].join("\n"),
);
});
it("rewrites gateway shutdown imports to stable runtime aliases", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "server-close.runtime-AbCd1234.js"),
"export const close = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "server.impl-OldHash.js"),
[
'const closeModule = () => import("./server-close.runtime-AbCd1234.js");',
'const ordinaryChunk = () => import("./server-close-OldHash.js");',
"",
].join("\n"),
"utf8",
);
rewriteRootRuntimeImportsToStableAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "server.impl-OldHash.js"), "utf8")).toBe(
[
'const closeModule = () => import("./server-close.runtime.js");',
'const ordinaryChunk = () => import("./server-close-OldHash.js");',
"",
].join("\n"),
);
});
it("rewrites reply-dispatch imports to the stable provider dispatcher runtime alias", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "provider-dispatcher.runtime-NewHash.js"),
'export * from "./provider-dispatcher-ImplHash.js";\n',
"utf8",
);
await fs.writeFile(
path.join(distDir, "reply-dispatch-runtime-OldHash.js"),
['const dispatcher = () => import("./provider-dispatcher.runtime-NewHash.js");', ""].join(
"\n",
),
"utf8",
);
rewriteRootRuntimeImportsToStableAliases({ rootDir });
writeStableRootRuntimeAliases({ rootDir });
writeLegacyRootRuntimeCompatAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "reply-dispatch-runtime-OldHash.js"), "utf8")).toBe(
['const dispatcher = () => import("./provider-dispatcher.runtime.js");', ""].join("\n"),
);
expect(await fs.readFile(path.join(distDir, "provider-dispatcher.runtime.js"), "utf8")).toBe(
'export * from "./provider-dispatcher.runtime-NewHash.js";\n',
);
expect(await fs.readFile(path.join(distDir, "provider-dispatcher-6EQEtc-t.js"), "utf8")).toBe(
'export * from "./provider-dispatcher.runtime.js";\n',
);
});
it("keeps hashed imports when a stable runtime alias would collide", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "install.runtime-Aaa111.js"),
"export const pluginInstall = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "install.runtime-Bbb222.js"),
"export const daemonInstall = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "install-OldHash.js"),
[
'const pluginRuntime = () => import("./install.runtime-Aaa111.js");',
'const daemonRuntime = () => import("./install.runtime-Bbb222.js");',
"",
].join("\n"),
"utf8",
);
rewriteRootRuntimeImportsToStableAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "install-OldHash.js"), "utf8")).toBe(
[
'const pluginRuntime = () => import("./install.runtime-Aaa111.js");',
'const daemonRuntime = () => import("./install.runtime-Bbb222.js");',
"",
].join("\n"),
);
});
it("rewrites plugin install runtime imports to stable aliases when install runtimes collide", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "install.runtime-Aaa111.js"),
[
"export const scanPackageInstallSource = true;",
"export const scanFileInstallSource = true;",
"export const scanInstalledPackageDependencyTree = true;",
"export const scanBundleInstallSource = true;",
"",
].join("\n"),
"utf8",
);
await fs.writeFile(
path.join(distDir, "install.runtime-Bbb222.js"),
"export const daemonInstall = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "install-OldHash.js"),
[
'const pluginRuntime = () => import("./install.runtime-Aaa111.js");',
'const daemonRuntime = () => import("./install.runtime-Bbb222.js");',
"",
].join("\n"),
"utf8",
);
rewriteRootRuntimeImportsToStableAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "install-OldHash.js"), "utf8")).toBe(
[
'const pluginRuntime = () => import("./install.runtime.js");',
'const daemonRuntime = () => import("./install.runtime-Bbb222.js");',
"",
].join("\n"),
);
});
it("leaves stable alias files pointing at their hashed runtime chunks", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "runtime-plugins.runtime-AbCd1234.js"),
"export const ready = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "runtime-plugins.runtime.js"),
'export * from "./runtime-plugins.runtime-AbCd1234.js";\n',
"utf8",
);
rewriteRootRuntimeImportsToStableAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "runtime-plugins.runtime.js"), "utf8")).toBe(
'export * from "./runtime-plugins.runtime-AbCd1234.js";\n',
);
});
it("writes compatibility aliases for previous release runtime chunk names", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "runtime-plugins.runtime.js"),
'export * from "./runtime-plugins.runtime-NewHash.js";\n',
"utf8",
);
await fs.writeFile(
path.join(distDir, "provider-dispatcher.runtime.js"),
'export * from "./provider-dispatcher.runtime-NewHash.js";\n',
"utf8",
);
await fs.writeFile(
path.join(distDir, "install.runtime-NewPluginHash.js"),
[
"export const scanPackageInstallSource = true;",
"export const scanFileInstallSource = true;",
"export const scanInstalledPackageDependencyTree = true;",
"export const scanBundleInstallSource = true;",
"",
].join("\n"),
"utf8",
);
await fs.writeFile(
path.join(distDir, "install.runtime-OtherHash.js"),
"export const installFromValidatedNpmSpecArchive = true;\n",
"utf8",
);
writeLegacyRootRuntimeCompatAliases({ rootDir });
expect(
await fs.readFile(path.join(distDir, "runtime-plugins.runtime-fLHuT7Vs.js"), "utf8"),
).toBe('export * from "./runtime-plugins.runtime.js";\n');
expect(
await fs.readFile(path.join(distDir, "runtime-plugins.runtime-CNAfmQRG.js"), "utf8"),
).toBe('export * from "./runtime-plugins.runtime.js";\n');
expect(await fs.readFile(path.join(distDir, "provider-dispatcher-6EQEtc-t.js"), "utf8")).toBe(
'export * from "./provider-dispatcher.runtime.js";\n',
);
expect(await fs.readFile(path.join(distDir, "install.runtime-D7SL02B2.js"), "utf8")).toBe(
'export * from "./install.runtime-NewPluginHash.js";\n',
);
expect(await fs.readFile(path.join(distDir, "install.runtime-Deq6Beal.js"), "utf8")).toBe(
'export * from "./install.runtime-NewPluginHash.js";\n',
);
expect(await fs.readFile(path.join(distDir, "install.runtime-BRVACueI.js"), "utf8")).toBe(
'export * from "./install.runtime-NewPluginHash.js";\n',
);
expect(await fs.readFile(path.join(distDir, "install.runtime-DX8jy7tN.js"), "utf8")).toBe(
'export * from "./install.runtime-NewPluginHash.js";\n',
);
expect(await fs.readFile(path.join(distDir, "install.runtime-D6FSd9v2.js"), "utf8")).toBe(
'export * from "./install.runtime-NewPluginHash.js";\n',
);
expect(await fs.readFile(path.join(distDir, "install.runtime-DQ-ui3nL.js"), "utf8")).toBe(
'export * from "./install.runtime-NewPluginHash.js";\n',
);
expect(await fs.readFile(path.join(distDir, "install.runtime-Xom5hOHq.js"), "utf8")).toBe(
'export * from "./install.runtime-NewPluginHash.js";\n',
);
expect(await fs.readFile(path.join(distDir, "install.runtime-tnhNR9WW.js"), "utf8")).toBe(
'export * from "./install.runtime-NewPluginHash.js";\n',
);
expect(await fs.readFile(path.join(distDir, "install.runtime-CNHwKOIb.js"), "utf8")).toBe(
'export * from "./install.runtime-NewPluginHash.js";\n',
);
});
it("writes compatibility aliases for previous gateway shutdown chunk names", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(path.join(distDir, "plugins"), { recursive: true });
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "server-close.runtime.js"),
'export * from "./server-close.runtime-NewHash.js";\n',
"utf8",
);
await fs.writeFile(
path.join(distDir, "plugins", "hook-runner-global.js"),
"export const runGlobalHook = true;\n",
"utf8",
);
writeLegacyRootRuntimeCompatAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "server-close-DsVPJDIx.js"), "utf8")).toBe(
'export * from "./server-close.runtime.js";\n',
);
expect(await fs.readFile(path.join(distDir, "server-close-DvAvfgr8.js"), "utf8")).toBe(
'export * from "./server-close.runtime.js";\n',
);
expect(await fs.readFile(path.join(distDir, "hook-runner-global-B8rMIo8I.js"), "utf8")).toBe(
'export * from "./plugins/hook-runner-global.js";\n',
);
});
it("writes compatibility aliases for previous tool and ACP manager chunk names", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(path.join(distDir, "acp", "control-plane"), { recursive: true });
await fs.mkdir(path.join(distDir, "web-fetch"), { recursive: true });
await fs.writeFile(
path.join(distDir, "acp", "control-plane", "manager.js"),
"export const getAcpSessionManager = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "web-fetch", "runtime.js"),
"export const resolveWebFetchDefinition = true;\n",
"utf8",
);
writeLegacyRootRuntimeCompatAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "manager-DzRWrKSA.js"), "utf8")).toBe(
'export * from "./acp/control-plane/manager.js";\n',
);
expect(await fs.readFile(path.join(distDir, "runtime-CeGN4XUC.js"), "utf8")).toBe(
'export * from "./web-fetch/runtime.js";\n',
);
});
it("writes legacy CLI exit compatibility chunks", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
writeLegacyCliExitCompatChunks({ rootDir });
for (const chunk of ["memory-state-CcqRgDZU.js", "memory-state-DwGdReW4.js"]) {
await expect(fs.readFile(path.join(rootDir, "dist", chunk), "utf8")).resolves.toContain(
"function hasMemoryRuntime()",
);
}
});
});