fix(context-engine): bundle legacy runtime registration

This commit is contained in:
Peter Steinberger
2026-04-11 17:50:43 +01:00
parent 9aa9c3ff62
commit e26edee39e
3 changed files with 92 additions and 28 deletions

View File

@@ -41,6 +41,8 @@ type InstalledBundledExtensionManifestRecord = {
};
const MAX_BUNDLED_EXTENSION_MANIFEST_BYTES = 1024 * 1024;
const LEGACY_CONTEXT_ENGINE_UNRESOLVED_RUNTIME_MARKER =
"Failed to load legacy context engine runtime.";
const NPM_UPDATE_COMPAT_EXTENSION_DIRS = new Set(
[...NPM_UPDATE_COMPAT_SIDECAR_PATHS].map((relativePath) => {
const pathParts = relativePath.split("/");
@@ -101,6 +103,7 @@ export function collectInstalledPackageErrors(params: {
}
}
errors.push(...collectInstalledContextEngineRuntimeErrors(params.packageRoot));
errors.push(...collectInstalledMirroredRootDependencyManifestErrors(params.packageRoot));
return errors;
@@ -112,6 +115,48 @@ export function normalizeInstalledBinaryVersion(output: string): string {
return versionMatch?.[0] ?? trimmed;
}
function listDistJavaScriptFiles(packageRoot: string): string[] {
const distDir = join(packageRoot, "dist");
if (!existsSync(distDir)) {
return [];
}
const pending = [distDir];
const files: string[] = [];
while (pending.length > 0) {
const currentDir = pending.pop();
if (!currentDir) {
continue;
}
for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
const entryPath = join(currentDir, entry.name);
if (entry.isDirectory()) {
pending.push(entryPath);
continue;
}
if (entry.isFile() && entry.name.endsWith(".js")) {
files.push(entryPath);
}
}
}
return files;
}
export function collectInstalledContextEngineRuntimeErrors(packageRoot: string): string[] {
const errors: string[] = [];
for (const filePath of listDistJavaScriptFiles(packageRoot)) {
const contents = readFileSync(filePath, "utf8");
if (contents.includes(LEGACY_CONTEXT_ENGINE_UNRESOLVED_RUNTIME_MARKER)) {
errors.push(
"installed package includes unresolved legacy context engine runtime loader; rebuild with a bundler-traceable LegacyContextEngine import.",
);
break;
}
}
return errors;
}
export function resolveInstalledBinaryPath(prefixDir: string, platform = process.platform): string {
return platform === "win32"
? join(prefixDir, "openclaw.cmd")

View File

@@ -1,32 +1,8 @@
import { LegacyContextEngine } from "./legacy.js";
import { registerContextEngineForOwner } from "./registry.js";
import type { ContextEngine } from "./types.js";
type LegacyContextEngineModule = {
LegacyContextEngine: new () => ContextEngine;
};
async function loadLegacyContextEngineModule(): Promise<LegacyContextEngineModule> {
try {
return (await import("./legacy.js")) as LegacyContextEngineModule;
} catch {
try {
return (await import("./legacy.ts")) as LegacyContextEngineModule;
} catch {
throw new Error("Failed to load legacy context engine runtime.");
}
}
}
export function registerLegacyContextEngine(): void {
registerContextEngineForOwner(
"legacy",
async () => {
const { LegacyContextEngine } = await loadLegacyContextEngineModule();
return new LegacyContextEngine();
},
"core",
{
allowSameOwnerRefresh: true,
},
);
registerContextEngineForOwner("legacy", async () => new LegacyContextEngine(), "core", {
allowSameOwnerRefresh: true,
});
}

View File

@@ -5,6 +5,7 @@ import { describe, expect, it } from "vitest";
import {
buildPublishedInstallCommandArgs,
buildPublishedInstallScenarios,
collectInstalledContextEngineRuntimeErrors,
collectInstalledMirroredRootDependencyManifestErrors,
collectInstalledPackageErrors,
normalizeInstalledBinaryVersion,
@@ -79,6 +80,48 @@ describe("collectInstalledPackageErrors", () => {
});
});
describe("collectInstalledContextEngineRuntimeErrors", () => {
function makeInstalledPackageRoot(): string {
return mkdtempSync(join(tmpdir(), "openclaw-postpublish-context-engine-"));
}
it("rejects packaged bundles with unresolved legacy context engine runtime loaders", () => {
const packageRoot = makeInstalledPackageRoot();
try {
mkdirSync(join(packageRoot, "dist"), { recursive: true });
writeFileSync(
join(packageRoot, "dist", "runtime-plugins-BUG.js"),
'throw new Error("Failed to load legacy context engine runtime.");\n',
"utf8",
);
expect(collectInstalledContextEngineRuntimeErrors(packageRoot)).toEqual([
"installed package includes unresolved legacy context engine runtime loader; rebuild with a bundler-traceable LegacyContextEngine import.",
]);
} finally {
rmSync(packageRoot, { recursive: true, force: true });
}
});
it("accepts packaged bundles that inline the legacy context engine registration", () => {
const packageRoot = makeInstalledPackageRoot();
try {
mkdirSync(join(packageRoot, "dist"), { recursive: true });
writeFileSync(
join(packageRoot, "dist", "runtime-plugins-OK.js"),
"registerContextEngineForOwner('legacy', async () => new LegacyContextEngine());\n",
"utf8",
);
expect(collectInstalledContextEngineRuntimeErrors(packageRoot)).toEqual([]);
} finally {
rmSync(packageRoot, { recursive: true, force: true });
}
});
});
describe("normalizeInstalledBinaryVersion", () => {
it("accepts decorated CLI version output", () => {
expect(normalizeInstalledBinaryVersion("OpenClaw 2026.4.8 (9ece252)")).toBe("2026.4.8");