From d228463120a5057f7ccf9a5c31af62fe4a922088 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 25 Apr 2026 16:25:24 -0700 Subject: [PATCH] fix(onboarding): refresh plugin registry after plugin installs --- src/commands/channel-setup/plugin-install.ts | 1 + .../onboarding-plugin-install.test.ts | 12 ++++++ src/commands/onboarding-plugin-install.ts | 37 +++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/src/commands/channel-setup/plugin-install.ts b/src/commands/channel-setup/plugin-install.ts index 293441a4fee..ed78318b5be 100644 --- a/src/commands/channel-setup/plugin-install.ts +++ b/src/commands/channel-setup/plugin-install.ts @@ -46,6 +46,7 @@ export async function ensureChannelSetupPluginInstalled(params: { cfg: params.cfg, entry: toOnboardingPluginInstallEntry(params.entry), prompter: params.prompter, + refreshRegistry: false, runtime: params.runtime, workspaceDir: params.workspaceDir, }); diff --git a/src/commands/onboarding-plugin-install.test.ts b/src/commands/onboarding-plugin-install.test.ts index ddf94a466e4..87bf700f203 100644 --- a/src/commands/onboarding-plugin-install.test.ts +++ b/src/commands/onboarding-plugin-install.test.ts @@ -10,6 +10,11 @@ vi.mock("../cli/plugin-install-plan.js", () => ({ resolveBundledInstallPlanForCatalogEntry, })); +const refreshPluginRegistryAfterConfigMutation = vi.hoisted(() => vi.fn(async () => undefined)); +vi.mock("../cli/plugins-registry-refresh.js", () => ({ + refreshPluginRegistryAfterConfigMutation, +})); + const resolveBundledPluginSources = vi.hoisted(() => vi.fn(() => new Map())); const findBundledPluginSourceInMap = vi.hoisted(() => vi.fn(() => null)); vi.mock("../plugins/bundled-sources.js", () => ({ @@ -61,6 +66,7 @@ describe("ensureOnboardingPluginInstalled", () => { beforeEach(() => { vi.clearAllMocks(); withTimeout.mockImplementation(async (promise: Promise) => await promise); + refreshPluginRegistryAfterConfigMutation.mockResolvedValue(undefined); }); it("passes npm specs and optional expected integrity to npm installs with progress", async () => { @@ -135,6 +141,12 @@ describe("ensureOnboardingPluginInstalled", () => { expect(result.installed).toBe(true); expect(result.status).toBe("installed"); expect(result.cfg.plugins?.installs).toBeUndefined(); + expect(refreshPluginRegistryAfterConfigMutation).toHaveBeenCalledWith( + expect.objectContaining({ + config: result.cfg, + reason: "source-changed", + }), + ); }); it("returns a timed out status and notes the retry path when npm install hangs", async () => { diff --git a/src/commands/onboarding-plugin-install.ts b/src/commands/onboarding-plugin-install.ts index c7f8acade66..b9cbab9ad77 100644 --- a/src/commands/onboarding-plugin-install.ts +++ b/src/commands/onboarding-plugin-install.ts @@ -1,6 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import { resolveBundledInstallPlanForCatalogEntry } from "../cli/plugin-install-plan.js"; +import { refreshPluginRegistryAfterConfigMutation } from "../cli/plugins-registry-refresh.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { parseRegistryNpmSpec } from "../infra/npm-registry-spec.js"; import { @@ -149,6 +150,23 @@ async function persistOnboardingPluginInstallRecord(params: { await writePersistedPluginInstallLedger(recordPluginInstallInRecords(records, params.install)); } +async function refreshRegistryAfterOnboardingPluginInstall(params: { + cfg: OpenClawConfig; + refreshRegistry?: boolean; + runtime: RuntimeEnv; + workspaceDir?: string; +}) { + if (params.refreshRegistry === false) { + return; + } + await refreshPluginRegistryAfterConfigMutation({ + config: params.cfg, + reason: "source-changed", + ...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}), + logger: { warn: (message) => params.runtime.log(message) }, + }); +} + async function recordLocalPluginInstall(params: { cfg: OpenClawConfig; entry: OnboardingPluginInstallEntry; @@ -438,6 +456,7 @@ export async function ensureOnboardingPluginInstalled(params: { cfg: OpenClawConfig; entry: OnboardingPluginInstallEntry; prompter: WizardPrompter; + refreshRegistry?: boolean; runtime: RuntimeEnv; workspaceDir?: string; }): Promise { @@ -494,6 +513,12 @@ export async function ensureOnboardingPluginInstalled(params: { } next = addPluginLoadPath(enableResult.config, localPath); next = await recordLocalPluginInstall({ cfg: next, entry, localPath, npmSpec, workspaceDir }); + await refreshRegistryAfterOnboardingPluginInstall({ + cfg: next, + refreshRegistry: params.refreshRegistry, + runtime, + workspaceDir, + }); return { cfg: next, installed: true, @@ -576,6 +601,12 @@ export async function ensureOnboardingPluginInstalled(params: { install, }); next = withoutPluginInstallRecords(recordPluginInstall(next, install)); + await refreshRegistryAfterOnboardingPluginInstall({ + cfg: next, + refreshRegistry: params.refreshRegistry, + runtime, + workspaceDir, + }); return { cfg: next, installed: true, @@ -615,6 +646,12 @@ export async function ensureOnboardingPluginInstalled(params: { } next = addPluginLoadPath(enableResult.config, localPath); next = await recordLocalPluginInstall({ cfg: next, entry, localPath, npmSpec, workspaceDir }); + await refreshRegistryAfterOnboardingPluginInstall({ + cfg: next, + refreshRegistry: params.refreshRegistry, + runtime, + workspaceDir, + }); return { cfg: next, installed: true,