mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:40:43 +00:00
fix(channels): refresh plugin registry after on-demand installs
This commit is contained in:
@@ -29,6 +29,7 @@ type ResolveInstallableChannelPluginResult = {
|
||||
plugin?: ChannelPlugin;
|
||||
catalogEntry?: ChannelPluginCatalogEntry;
|
||||
configChanged: boolean;
|
||||
pluginInstalled: boolean;
|
||||
};
|
||||
|
||||
function resolveWorkspaceDir(cfg: OpenClawConfig) {
|
||||
@@ -197,6 +198,7 @@ export async function resolveInstallableChannelPlugin(params: {
|
||||
cfg: nextCfg,
|
||||
catalogEntry,
|
||||
configChanged: false,
|
||||
pluginInstalled: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -208,6 +210,7 @@ export async function resolveInstallableChannelPlugin(params: {
|
||||
plugin: existing,
|
||||
catalogEntry,
|
||||
configChanged: false,
|
||||
pluginInstalled: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -227,6 +230,7 @@ export async function resolveInstallableChannelPlugin(params: {
|
||||
plugin: scoped,
|
||||
catalogEntry,
|
||||
configChanged: false,
|
||||
pluginInstalled: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -258,6 +262,7 @@ export async function resolveInstallableChannelPlugin(params: {
|
||||
? { ...catalogEntry, pluginId: installedPluginId }
|
||||
: catalogEntry,
|
||||
configChanged: nextCfg !== params.cfg,
|
||||
pluginInstalled: installResult.installed,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -268,5 +273,6 @@ export async function resolveInstallableChannelPlugin(params: {
|
||||
plugin: existing,
|
||||
catalogEntry,
|
||||
configChanged: false,
|
||||
pluginInstalled: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ const pluginInstallMocks = vi.hoisted(() => ({
|
||||
loadChannelSetupPluginRegistrySnapshotForChannel: vi.fn(),
|
||||
}));
|
||||
|
||||
const registryRefreshMocks = vi.hoisted(() => ({
|
||||
refreshPluginRegistryAfterConfigMutation: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("../channels/plugins/catalog.js", () => ({
|
||||
listChannelPluginCatalogEntries: catalogMocks.listChannelPluginCatalogEntries,
|
||||
}));
|
||||
@@ -50,6 +54,8 @@ vi.mock("../channels/plugins/bundled.js", async () => {
|
||||
|
||||
vi.mock("./channel-setup/plugin-install.js", () => pluginInstallMocks);
|
||||
|
||||
vi.mock("../cli/plugins-registry-refresh.js", () => registryRefreshMocks);
|
||||
|
||||
const runtime = createTestRuntime();
|
||||
|
||||
function listConfiguredAccountIds(
|
||||
@@ -268,6 +274,7 @@ describe("channelsAddCommand", () => {
|
||||
vi.mocked(loadChannelSetupPluginRegistrySnapshotForChannel).mockReturnValue(
|
||||
createTestRegistry(),
|
||||
);
|
||||
registryRefreshMocks.refreshPluginRegistryAfterConfigMutation.mockClear();
|
||||
setMinimalChannelsAddRegistryForTests();
|
||||
});
|
||||
|
||||
@@ -481,6 +488,16 @@ describe("channelsAddCommand", () => {
|
||||
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ installRuntimeDeps: false }),
|
||||
);
|
||||
expect(registryRefreshMocks.refreshPluginRegistryAfterConfigMutation).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: expect.objectContaining({
|
||||
channels: expect.objectContaining({
|
||||
"external-chat": expect.objectContaining({ enabled: true }),
|
||||
}),
|
||||
}),
|
||||
reason: "source-changed",
|
||||
}),
|
||||
);
|
||||
expectExternalChatEnabledConfigWrite();
|
||||
expect(runtime.error).not.toHaveBeenCalled();
|
||||
expect(runtime.exit).not.toHaveBeenCalled();
|
||||
|
||||
@@ -19,6 +19,10 @@ const catalogMocks = vi.hoisted(() => ({
|
||||
listChannelPluginCatalogEntries: vi.fn((): ChannelPluginCatalogEntry[] => []),
|
||||
}));
|
||||
|
||||
const registryRefreshMocks = vi.hoisted(() => ({
|
||||
refreshPluginRegistryAfterConfigMutation: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("../channels/plugins/catalog.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../channels/plugins/catalog.js")>(
|
||||
"../channels/plugins/catalog.js",
|
||||
@@ -48,6 +52,8 @@ vi.mock("./channel-setup/plugin-install.js", async () => {
|
||||
return createMockChannelSetupPluginInstallModule(actual);
|
||||
});
|
||||
|
||||
vi.mock("../cli/plugins-registry-refresh.js", () => registryRefreshMocks);
|
||||
|
||||
const runtime = createTestRuntime();
|
||||
|
||||
describe("channelsRemoveCommand", () => {
|
||||
@@ -79,6 +85,7 @@ describe("channelsRemoveCommand", () => {
|
||||
vi.mocked(loadChannelSetupPluginRegistrySnapshotForChannel).mockReturnValue(
|
||||
createTestRegistry(),
|
||||
);
|
||||
registryRefreshMocks.refreshPluginRegistryAfterConfigMutation.mockClear();
|
||||
setActivePluginRegistry(createTestRegistry());
|
||||
});
|
||||
|
||||
@@ -123,6 +130,11 @@ describe("channelsRemoveCommand", () => {
|
||||
expect.objectContaining({ entry: catalogEntry }),
|
||||
);
|
||||
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledTimes(2);
|
||||
expect(registryRefreshMocks.refreshPluginRegistryAfterConfigMutation).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: "source-changed",
|
||||
}),
|
||||
);
|
||||
expect(configMocks.writeConfigFile).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({
|
||||
channels: expect.objectContaining({
|
||||
|
||||
@@ -8,6 +8,7 @@ const mocks = vi.hoisted(() => ({
|
||||
readConfigFileSnapshot: vi.fn(),
|
||||
applyPluginAutoEnable: vi.fn(),
|
||||
replaceConfigFile: vi.fn(),
|
||||
refreshPluginRegistryAfterConfigMutation: vi.fn(async () => undefined),
|
||||
resolveMessageChannelSelection: vi.fn(),
|
||||
resolveInstallableChannelPlugin: vi.fn(),
|
||||
getChannelPlugin: vi.fn(),
|
||||
@@ -27,6 +28,10 @@ vi.mock("../config/config.js", () => ({
|
||||
replaceConfigFile: mocks.replaceConfigFile,
|
||||
}));
|
||||
|
||||
vi.mock("../cli/plugins-registry-refresh.js", () => ({
|
||||
refreshPluginRegistryAfterConfigMutation: mocks.refreshPluginRegistryAfterConfigMutation,
|
||||
}));
|
||||
|
||||
vi.mock("../config/plugin-auto-enable.js", () => ({
|
||||
applyPluginAutoEnable: mocks.applyPluginAutoEnable,
|
||||
}));
|
||||
@@ -54,6 +59,7 @@ describe("channelsResolveCommand", () => {
|
||||
vi.clearAllMocks();
|
||||
mocks.loadConfig.mockReturnValue({ channels: {} });
|
||||
mocks.readConfigFileSnapshot.mockResolvedValue({ hash: "config-1" });
|
||||
mocks.refreshPluginRegistryAfterConfigMutation.mockResolvedValue(undefined);
|
||||
mocks.applyPluginAutoEnable.mockImplementation(({ config }) => ({ config, changes: [] }));
|
||||
mocks.replaceConfigFile.mockResolvedValue(undefined);
|
||||
mocks.resolveCommandSecretRefsViaGateway.mockResolvedValue({
|
||||
@@ -88,6 +94,7 @@ describe("channelsResolveCommand", () => {
|
||||
cfg: installedCfg,
|
||||
channelId: "whatsapp",
|
||||
configChanged: true,
|
||||
pluginInstalled: true,
|
||||
plugin: {
|
||||
id: "whatsapp",
|
||||
resolver: { resolveTargets },
|
||||
@@ -112,6 +119,12 @@ describe("channelsResolveCommand", () => {
|
||||
nextConfig: installedCfg,
|
||||
baseHash: "config-1",
|
||||
});
|
||||
expect(mocks.refreshPluginRegistryAfterConfigMutation).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: installedCfg,
|
||||
reason: "source-changed",
|
||||
}),
|
||||
);
|
||||
expect(resolveTargets).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
cfg: installedCfg,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { moveSingleAccountChannelSectionToDefaultAccount } from "../../channels/
|
||||
import type { ChannelSetupPlugin } from "../../channels/plugins/setup-wizard-types.js";
|
||||
import type { ChannelPlugin } from "../../channels/plugins/types.plugin.js";
|
||||
import type { ChannelId, ChannelSetupInput } from "../../channels/plugins/types.public.js";
|
||||
import { refreshPluginRegistryAfterConfigMutation } from "../../cli/plugins-registry-refresh.js";
|
||||
import { replaceConfigFile, type OpenClawConfig } from "../../config/config.js";
|
||||
import {
|
||||
PLUGIN_INSTALLS_CONFIG_PATH,
|
||||
@@ -116,6 +117,7 @@ export async function channelsAddCommand(
|
||||
const cfg = (configSnapshot.sourceConfig ?? configSnapshot.config) as OpenClawConfig;
|
||||
const baseHash = configSnapshot.hash;
|
||||
let nextConfig = cfg;
|
||||
let pluginRegistrySourceChanged = false;
|
||||
|
||||
const useWizard = shouldUseWizard(params);
|
||||
if (useWizard) {
|
||||
@@ -258,6 +260,13 @@ export async function channelsAddCommand(
|
||||
? { writeOptions: { unsetPaths: [Array.from(PLUGIN_INSTALLS_CONFIG_PATH)] } }
|
||||
: {}),
|
||||
});
|
||||
if (shouldMovePluginInstalls) {
|
||||
await refreshPluginRegistryAfterConfigMutation({
|
||||
config: writtenConfig,
|
||||
reason: "source-changed",
|
||||
logger: { warn: (message) => runtime.log(message) },
|
||||
});
|
||||
}
|
||||
await onboardChannels.runCollectedChannelOnboardingPostWriteHooks({
|
||||
hooks: postWriteHooks.drain(),
|
||||
cfg: writtenConfig,
|
||||
@@ -320,6 +329,7 @@ export async function channelsAddCommand(
|
||||
if (!result.installed) {
|
||||
return;
|
||||
}
|
||||
pluginRegistrySourceChanged = true;
|
||||
catalogEntry = {
|
||||
...catalogEntry,
|
||||
...(result.pluginId ? { pluginId: result.pluginId } : {}),
|
||||
@@ -401,6 +411,13 @@ export async function channelsAddCommand(
|
||||
? { writeOptions: { unsetPaths: [Array.from(PLUGIN_INSTALLS_CONFIG_PATH)] } }
|
||||
: {}),
|
||||
});
|
||||
if (shouldMovePluginInstalls || pluginRegistrySourceChanged) {
|
||||
await refreshPluginRegistryAfterConfigMutation({
|
||||
config: writtenConfig,
|
||||
reason: "source-changed",
|
||||
logger: { warn: (message) => runtime.log(message) },
|
||||
});
|
||||
}
|
||||
runtime.log(`Added ${plugin.meta.label ?? channelLabel(channel)} account "${accountId}".`);
|
||||
const afterAccountConfigWritten = plugin.setup?.afterAccountConfigWritten;
|
||||
if (afterAccountConfigWritten) {
|
||||
|
||||
@@ -12,6 +12,7 @@ const resolveDefaultAccountId = () => DEFAULT_ACCOUNT_ID;
|
||||
const mocks = vi.hoisted(() => ({
|
||||
readConfigFileSnapshot: vi.fn(),
|
||||
replaceConfigFile: vi.fn(),
|
||||
refreshPluginRegistryAfterConfigMutation: vi.fn(async () => undefined),
|
||||
resolveInstallableChannelPlugin: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -37,6 +38,10 @@ vi.mock("../../config/config.js", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../cli/plugins-registry-refresh.js", () => ({
|
||||
refreshPluginRegistryAfterConfigMutation: mocks.refreshPluginRegistryAfterConfigMutation,
|
||||
}));
|
||||
|
||||
vi.mock("../channel-setup/channel-plugin-resolution.js", () => ({
|
||||
resolveInstallableChannelPlugin: mocks.resolveInstallableChannelPlugin,
|
||||
}));
|
||||
@@ -203,6 +208,7 @@ describe("channelsCapabilitiesCommand", () => {
|
||||
channelId: "whatsapp",
|
||||
plugin,
|
||||
configChanged: true,
|
||||
pluginInstalled: true,
|
||||
});
|
||||
vi.mocked(listChannelPlugins).mockReturnValue([]);
|
||||
vi.mocked(getChannelPlugin).mockReturnValue(undefined);
|
||||
@@ -221,6 +227,11 @@ describe("channelsCapabilitiesCommand", () => {
|
||||
}),
|
||||
baseHash: "config-1",
|
||||
});
|
||||
expect(mocks.refreshPluginRegistryAfterConfigMutation).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: "source-changed",
|
||||
}),
|
||||
);
|
||||
expect(logs.join("\n")).toContain("Probe: linked");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
ChannelCapabilitiesDisplayLine,
|
||||
ChannelPlugin,
|
||||
} from "../../channels/plugins/types.public.js";
|
||||
import { refreshPluginRegistryAfterConfigMutation } from "../../cli/plugins-registry-refresh.js";
|
||||
import {
|
||||
readConfigFileSnapshot,
|
||||
replaceConfigFile,
|
||||
@@ -269,6 +270,13 @@ export async function channelsCapabilitiesCommand(
|
||||
? { writeOptions: { unsetPaths: [Array.from(PLUGIN_INSTALLS_CONFIG_PATH)] } }
|
||||
: {}),
|
||||
});
|
||||
if (shouldMovePluginInstalls || resolved.pluginInstalled) {
|
||||
await refreshPluginRegistryAfterConfigMutation({
|
||||
config: cfg,
|
||||
reason: "source-changed",
|
||||
logger: { warn: (message) => runtime.log(message) },
|
||||
});
|
||||
}
|
||||
}
|
||||
return resolved.plugin ? [resolved.plugin] : null;
|
||||
})();
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
listChannelPlugins,
|
||||
normalizeChannelId,
|
||||
} from "../../channels/plugins/index.js";
|
||||
import { refreshPluginRegistryAfterConfigMutation } from "../../cli/plugins-registry-refresh.js";
|
||||
import { replaceConfigFile, type OpenClawConfig } from "../../config/config.js";
|
||||
import {
|
||||
PLUGIN_INSTALLS_CONFIG_PATH,
|
||||
@@ -190,6 +191,13 @@ export async function channelsRemoveCommand(
|
||||
? { writeOptions: { unsetPaths: [Array.from(PLUGIN_INSTALLS_CONFIG_PATH)] } }
|
||||
: {}),
|
||||
});
|
||||
if (shouldMovePluginInstalls || resolvedPluginState?.pluginInstalled) {
|
||||
await refreshPluginRegistryAfterConfigMutation({
|
||||
config: next,
|
||||
reason: "source-changed",
|
||||
logger: { warn: (message) => runtime.log(message) },
|
||||
});
|
||||
}
|
||||
if (useWizard && prompter) {
|
||||
await prompter.outro(
|
||||
deleteConfig
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
} from "../../channels/plugins/types.adapters.js";
|
||||
import { resolveCommandConfigWithSecrets } from "../../cli/command-config-resolution.js";
|
||||
import { getChannelsCommandSecretTargetIds } from "../../cli/command-secret-targets.js";
|
||||
import { refreshPluginRegistryAfterConfigMutation } from "../../cli/plugins-registry-refresh.js";
|
||||
import { loadConfig, readConfigFileSnapshot, replaceConfigFile } from "../../config/config.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { resolveMessageChannelSelection } from "../../infra/outbound/channel-selection.js";
|
||||
@@ -158,6 +159,13 @@ export async function channelsResolveCommand(opts: ChannelsResolveOptions, runti
|
||||
? { writeOptions: { unsetPaths: [Array.from(PLUGIN_INSTALLS_CONFIG_PATH)] } }
|
||||
: {}),
|
||||
});
|
||||
if (shouldMovePluginInstalls || resolvedExplicit.pluginInstalled) {
|
||||
await refreshPluginRegistryAfterConfigMutation({
|
||||
config: cfg,
|
||||
reason: "source-changed",
|
||||
logger: { warn: (message) => runtime.log(message) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const selection = explicitChannel
|
||||
|
||||
Reference in New Issue
Block a user