mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 21:46:48 +00:00
* 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
760 lines
27 KiB
TypeScript
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()",
|
|
);
|
|
}
|
|
});
|
|
});
|