fix(channels): defer setup runtime deps until login

This commit is contained in:
Peter Steinberger
2026-04-24 06:40:17 +01:00
parent 6c509d8d4b
commit 73288c20bd
5 changed files with 37 additions and 11 deletions

View File

@@ -71,6 +71,7 @@ function loadChannelSetupPluginRegistry(params: {
workspaceDir?: string;
onlyPluginIds?: string[];
activate?: boolean;
installRuntimeDeps?: boolean;
}): PluginRegistry {
clearPluginDiscoveryCache();
const autoEnabled = applyPluginAutoEnable({ config: params.cfg, env: process.env });
@@ -88,7 +89,9 @@ function loadChannelSetupPluginRegistry(params: {
logger: createPluginLoaderLogger(log),
onlyPluginIds: params.onlyPluginIds,
includeSetupOnlyChannelPlugins: true,
forceSetupOnlyChannelPlugins: params.installRuntimeDeps === false,
activate: params.activate,
installBundledRuntimeDeps: params.installRuntimeDeps !== false,
});
}
@@ -156,6 +159,7 @@ export function loadChannelSetupPluginRegistrySnapshotForChannel(params: {
channel: string;
pluginId?: string;
workspaceDir?: string;
installRuntimeDeps?: boolean;
}): PluginRegistry {
const scopedPluginId = resolveScopedChannelPluginId({
cfg: params.cfg,

View File

@@ -327,6 +327,9 @@ describe("channelsAddCommand", () => {
expect.objectContaining({ entry: catalogEntry }),
);
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledTimes(1);
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledWith(
expect.objectContaining({ installRuntimeDeps: false }),
);
expectExternalChatEnabledConfigWrite();
expect(runtime.error).not.toHaveBeenCalled();
expect(runtime.exit).not.toHaveBeenCalled();
@@ -348,6 +351,9 @@ describe("channelsAddCommand", () => {
expect(ensureChannelSetupPluginInstalled).not.toHaveBeenCalled();
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledTimes(1);
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledWith(
expect.objectContaining({ installRuntimeDeps: false }),
);
expectExternalChatEnabledConfigWrite();
});

View File

@@ -1,6 +1,6 @@
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
import { parseOptionalDelimitedEntries } from "../../channels/plugins/helpers.js";
import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/index.js";
import { getLoadedChannelPlugin, normalizeChannelId } from "../../channels/plugins/index.js";
import { moveSingleAccountChannelSectionToDefaultAccount } from "../../channels/plugins/setup-helpers.js";
import type { ChannelSetupPlugin } from "../../channels/plugins/setup-wizard-types.js";
import type { ChannelPlugin } from "../../channels/plugins/types.plugin.js";
@@ -141,7 +141,7 @@ export async function channelsAddCommand(
if (wantsNames) {
for (const channel of selection) {
const accountId = accountIds[channel] ?? DEFAULT_ACCOUNT_ID;
const plugin = resolvedPlugins.get(channel) ?? getChannelPlugin(channel);
const plugin = resolvedPlugins.get(channel) ?? getLoadedChannelPlugin(channel);
const account = plugin?.config.resolveAccount(nextConfig, accountId) as
| { name?: string }
| undefined;
@@ -248,7 +248,7 @@ export async function channelsAddCommand(
channelId: ChannelId,
pluginId?: string,
): Promise<ChannelPlugin | undefined> => {
const existing = getChannelPlugin(channelId);
const existing = getLoadedChannelPlugin(channelId);
if (existing) {
return existing;
}
@@ -260,10 +260,11 @@ export async function channelsAddCommand(
channel: channelId,
...(pluginId ? { pluginId } : {}),
workspaceDir: resolveWorkspaceDir(),
installRuntimeDeps: false,
});
return (
snapshot.channels.find((entry) => entry.plugin.id === channelId)?.plugin ??
snapshot.channelSetups.find((entry) => entry.plugin.id === channelId)?.plugin
snapshot.channelSetups.find((entry) => entry.plugin.id === channelId)?.plugin ??
snapshot.channels.find((entry) => entry.plugin.id === channelId)?.plugin
);
};
@@ -359,7 +360,7 @@ export async function channelsAddCommand(
nextConfig,
...(baseHash !== undefined ? { baseHash } : {}),
});
runtime.log(`Added ${channelLabel(channel)} account "${accountId}".`);
runtime.log(`Added ${plugin.meta.label ?? channelLabel(channel)} account "${accountId}".`);
const afterAccountConfigWritten = plugin.setup?.afterAccountConfigWritten;
if (afterAccountConfigWritten) {
const { runCollectedChannelOnboardingPostWriteHooks } = await loadOnboardChannels();

View File

@@ -174,10 +174,11 @@ export async function setupChannels(
channel,
...(pluginId ? { pluginId } : {}),
workspaceDir: resolveWorkspaceDir(),
installRuntimeDeps: false,
});
const plugin =
snapshot.channels.find((entry) => entry.plugin.id === channel)?.plugin ??
snapshot.channelSetups.find((entry) => entry.plugin.id === channel)?.plugin;
snapshot.channelSetups.find((entry) => entry.plugin.id === channel)?.plugin ??
snapshot.channels.find((entry) => entry.plugin.id === channel)?.plugin;
if (plugin) {
rememberScopedPlugin(plugin);
return plugin;

View File

@@ -145,6 +145,7 @@ export type PluginLoadOptions = {
preferSetupRuntimeForChannelPlugins?: boolean;
activate?: boolean;
loadModules?: boolean;
installBundledRuntimeDeps?: boolean;
throwOnLoadError?: boolean;
bundledRuntimeDepsInstaller?: (params: BundledRuntimeDepsInstallParams) => void;
};
@@ -780,6 +781,7 @@ function buildCacheKey(params: {
requireSetupEntryForSetupOnlyChannelPlugins?: boolean;
preferSetupRuntimeForChannelPlugins?: boolean;
loadModules?: boolean;
installBundledRuntimeDeps?: boolean;
runtimeSubagentMode?: "default" | "explicit" | "gateway-bindable";
pluginSdkResolution?: PluginSdkResolutionPreference;
coreGatewayMethodNames?: string[];
@@ -816,6 +818,8 @@ function buildCacheKey(params: {
const startupChannelMode =
params.preferSetupRuntimeForChannelPlugins === true ? "prefer-setup" : "full";
const moduleLoadMode = params.loadModules === false ? "manifest-only" : "load-modules";
const bundledRuntimeDepsMode =
params.installBundledRuntimeDeps === false ? "skip-runtime-deps" : "install-runtime-deps";
const runtimeSubagentMode = params.runtimeSubagentMode ?? "default";
const gatewayMethodsKey = JSON.stringify(params.coreGatewayMethodNames ?? []);
return `${roots.workspace ?? ""}::${roots.global ?? ""}::${roots.stock ?? ""}::${JSON.stringify({
@@ -823,7 +827,7 @@ function buildCacheKey(params: {
installs,
loadPaths,
activationMetadataKey: params.activationMetadataKey ?? "",
})}::${scopeKey}::${setupOnlyKey}::${setupOnlyModeKey}::${setupOnlyRequirementKey}::${startupChannelMode}::${moduleLoadMode}::${runtimeSubagentMode}::${params.pluginSdkResolution ?? "auto"}::${gatewayMethodsKey}`;
})}::${scopeKey}::${setupOnlyKey}::${setupOnlyModeKey}::${setupOnlyRequirementKey}::${startupChannelMode}::${moduleLoadMode}::${bundledRuntimeDepsMode}::${runtimeSubagentMode}::${params.pluginSdkResolution ?? "auto"}::${gatewayMethodsKey}`;
}
function matchesScopedPluginRequest(params: {
@@ -901,6 +905,7 @@ function hasExplicitCompatibilityInputs(options: PluginLoadOptions): boolean {
options.forceSetupOnlyChannelPlugins === true ||
options.requireSetupEntryForSetupOnlyChannelPlugins === true ||
options.preferSetupRuntimeForChannelPlugins === true ||
options.installBundledRuntimeDeps === false ||
options.loadModules === false
);
}
@@ -926,6 +931,7 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
const requireSetupEntryForSetupOnlyChannelPlugins =
options.requireSetupEntryForSetupOnlyChannelPlugins === true;
const preferSetupRuntimeForChannelPlugins = options.preferSetupRuntimeForChannelPlugins === true;
const shouldInstallBundledRuntimeDeps = options.installBundledRuntimeDeps !== false;
const runtimeSubagentMode = resolveRuntimeSubagentMode(options.runtimeOptions);
const coreGatewayMethodNames = Object.keys(options.coreGatewayHandlers ?? {}).toSorted();
const cacheKey = buildCacheKey({
@@ -943,6 +949,7 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
requireSetupEntryForSetupOnlyChannelPlugins,
preferSetupRuntimeForChannelPlugins,
loadModules: options.loadModules,
installBundledRuntimeDeps: options.installBundledRuntimeDeps,
runtimeSubagentMode,
pluginSdkResolution: options.pluginSdkResolution,
coreGatewayMethodNames,
@@ -961,6 +968,7 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
preferSetupRuntimeForChannelPlugins,
shouldActivate: options.activate !== false,
shouldLoadModules: options.loadModules !== false,
shouldInstallBundledRuntimeDeps,
runtimeSubagentMode,
cacheKey,
};
@@ -1839,6 +1847,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
preferSetupRuntimeForChannelPlugins,
shouldActivate,
shouldLoadModules,
shouldInstallBundledRuntimeDeps,
cacheKey,
runtimeSubagentMode,
} = resolvePluginLoadCacheContext(options);
@@ -2183,7 +2192,12 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
markPluginActivationDisabled(record, enableState.reason);
}
if (shouldLoadModules && candidate.origin === "bundled" && enableState.enabled) {
if (
shouldLoadModules &&
shouldInstallBundledRuntimeDeps &&
candidate.origin === "bundled" &&
enableState.enabled
) {
try {
const installRoot = resolveBundledRuntimeDependencyInstallRoot(pluginRoot, { env });
const retainSpecs = bundledRuntimeDepsRetainSpecsByInstallRoot.get(installRoot) ?? [];
@@ -2426,7 +2440,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
manifestRecord.setupSource
) {
const setupRegistration = resolveSetupChannelRegistration(mod, {
installRuntimeDeps: enableState.enabled,
installRuntimeDeps: shouldInstallBundledRuntimeDeps && enableState.enabled,
});
if (setupRegistration.loadError) {
recordPluginError({