fix(update): adopt post-core plugin payloads

This commit is contained in:
Peter Steinberger
2026-05-20 13:20:59 +01:00
parent e8d8c5dd6f
commit 29faac2f9c
3 changed files with 82 additions and 7 deletions
+5
View File
@@ -2886,6 +2886,11 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
return;
}
const postCoreHostVersion = await readPackageVersion(root);
if (postCoreHostVersion) {
process.env.OPENCLAW_COMPATIBILITY_HOST_VERSION = postCoreHostVersion;
}
let postCoreConfigSnapshot = await readConfigFileSnapshot({ skipPluginValidation: true });
const preUpdateSourceConfig = await readPostCorePreUpdateSourceConfig({
sourceConfigPath: process.env[POST_CORE_UPDATE_SOURCE_CONFIG_PATH_ENV],
@@ -1143,6 +1143,10 @@ describe("repairMissingConfiguredPluginInstalls", () => {
const npmRoot = makeTempDir();
const packageDir = path.join(npmRoot, "node_modules", "@openclaw", "matrix");
fs.mkdirSync(packageDir, { recursive: true });
fs.writeFileSync(
path.join(packageDir, "package.json"),
JSON.stringify({ name: "@openclaw/matrix", version: "1.2.3" }),
);
mocks.resolveDefaultPluginNpmDir.mockReturnValue(npmRoot);
mocks.listChannelPluginCatalogEntries.mockReturnValue([
{
@@ -1192,13 +1196,17 @@ describe("repairMissingConfiguredPluginInstalls", () => {
});
expect(mocks.installPluginFromClawHub).not.toHaveBeenCalled();
expectRecordFields(mockCallArg(mocks.installPluginFromNpmSpec), {
spec: expectedNpmInstallSpec("@openclaw/matrix"),
npmDir: npmRoot,
mode: "update",
});
expect(mocks.installPluginFromNpmSpec).not.toHaveBeenCalled();
expect(result.warnings).toEqual([]);
expect(result.records.matrix?.installPath).toBe(packageDir);
expectRecordFields(result.records.matrix, {
source: "npm",
spec: "@openclaw/matrix",
installPath: packageDir,
version: "1.2.3",
resolvedName: "@openclaw/matrix",
resolvedVersion: "1.2.3",
resolvedSpec: "@openclaw/matrix@1.2.3",
});
});
it("repairs missing external payload during post-core convergence even with OPENCLAW_UPDATE_IN_PROGRESS=1", async () => {
@@ -1,5 +1,5 @@
import { existsSync } from "node:fs";
import { rm } from "node:fs/promises";
import { readFile, rm } from "node:fs/promises";
import path from "node:path";
import {
listExplicitlyDisabledChannelIdsForConfig,
@@ -54,6 +54,7 @@ import {
} from "./configured-runtime-plugin-installs.js";
import { asObjectRecord } from "./object.js";
import {
isPostCoreConvergencePass,
isLegacyPackageUpdateDoctorPass,
shouldDeferConfiguredPluginInstallRepair,
} from "./update-phase.js";
@@ -786,6 +787,21 @@ async function installCandidate(params: {
const existingNpmPackagePath = npmInstallSpec
? resolveExistingCandidateNpmPackagePath({ candidate, npmDir })
: null;
if (
existingNpmPackagePath &&
npmInstallSpec &&
clawhubInstallSpec &&
params.mode !== "update" &&
isPostCoreConvergencePass(params.env)
) {
return await adoptExistingNpmPackage({
candidate,
records: params.records,
npmInstallSpec,
npmRecordSpec: npmSpecs?.recordSpec ?? npmInstallSpec,
packagePath: existingNpmPackagePath,
});
}
const shouldTryClawHub =
clawhubInstallSpec &&
!existingNpmPackagePath &&
@@ -930,6 +946,52 @@ function resolveExistingCandidateClawHubPackagePath(params: {
}
}
async function readNpmPackageVersion(packagePath: string): Promise<string | undefined> {
try {
const parsed = JSON.parse(await readFile(path.join(packagePath, "package.json"), "utf-8")) as {
version?: unknown;
};
return typeof parsed.version === "string" && parsed.version.trim()
? parsed.version.trim()
: undefined;
} catch {
return undefined;
}
}
async function adoptExistingNpmPackage(params: {
candidate: DownloadableInstallCandidate;
records: Record<string, PluginInstallRecord>;
npmInstallSpec: string;
npmRecordSpec: string;
packagePath: string;
}): Promise<{
records: Record<string, PluginInstallRecord>;
changes: string[];
warnings: string[];
}> {
const version = await readNpmPackageVersion(params.packagePath);
const npmName = parseRegistryNpmSpec(params.npmInstallSpec)?.name;
return {
records: {
...params.records,
[params.candidate.pluginId]: {
source: "npm",
spec: params.npmRecordSpec,
installPath: params.packagePath,
installedAt: new Date().toISOString(),
...(version ? { version, resolvedVersion: version } : {}),
...(npmName ? { resolvedName: npmName } : {}),
...(npmName && version ? { resolvedSpec: `${npmName}@${version}` } : {}),
},
},
changes: [
`Repaired missing configured plugin "${params.candidate.pluginId}" from existing npm payload ${params.npmInstallSpec}.`,
],
warnings: [],
};
}
export type RepairMissingPluginInstallsResult = {
changes: string[];
warnings: string[];