From 29faac2f9c1f21d13454965c7fc4dcd73a5fd130 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 20 May 2026 13:20:59 +0100 Subject: [PATCH] fix(update): adopt post-core plugin payloads --- src/cli/update-cli/update-command.ts | 5 ++ .../missing-configured-plugin-install.test.ts | 20 ++++-- .../missing-configured-plugin-install.ts | 64 ++++++++++++++++++- 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/cli/update-cli/update-command.ts b/src/cli/update-cli/update-command.ts index 3cff597d5e56..f2505564bbba 100644 --- a/src/cli/update-cli/update-command.ts +++ b/src/cli/update-cli/update-command.ts @@ -2886,6 +2886,11 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { 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], diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.test.ts b/src/commands/doctor/shared/missing-configured-plugin-install.test.ts index 392121593e74..7cbe2c570564 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.test.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.test.ts @@ -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 () => { diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.ts b/src/commands/doctor/shared/missing-configured-plugin-install.ts index 8861b3fba44d..36c4e4fad584 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.ts @@ -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 { + 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; + npmInstallSpec: string; + npmRecordSpec: string; + packagePath: string; +}): Promise<{ + records: Record; + 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[];