mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
fix: defer bundled plugin runtime deps until enabled
This commit is contained in:
@@ -549,7 +549,7 @@ describe("bundled channel entry shape guards", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("loads bundled setup entries from external staged runtime deps", async () => {
|
||||
it("does not load bundled setup entries through external staged runtime deps during discovery", async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-setup-runtime-deps-"));
|
||||
const stageRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-stage-"));
|
||||
const previousBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
||||
@@ -620,8 +620,8 @@ describe("bundled channel entry shape guards", () => {
|
||||
"./bundled.js?scope=bundled-setup-runtime-deps",
|
||||
);
|
||||
|
||||
expect(bundled.getBundledChannelSetupPlugin("alpha")?.meta.label).toBe("staged-alpha");
|
||||
expect(testGlobal.__bundledSetupRuntimeDepMarker).toBe("staged-alpha");
|
||||
expect(bundled.getBundledChannelSetupPlugin("alpha")).toBeUndefined();
|
||||
expect(testGlobal.__bundledSetupRuntimeDepMarker).toBeUndefined();
|
||||
} finally {
|
||||
restoreBundledPluginsDir(previousBundledPluginsDir);
|
||||
if (previousPluginStageDir === undefined) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import type {
|
||||
BundledChannelLegacySessionSurface,
|
||||
BundledChannelLegacyStateMigrationDetector,
|
||||
BundledEntryModuleLoadOptions,
|
||||
} from "../../plugin-sdk/channel-entry-contract.js";
|
||||
import {
|
||||
listBundledChannelPluginMetadata,
|
||||
@@ -39,8 +40,10 @@ type BundledChannelEntryRuntimeContract = {
|
||||
|
||||
type BundledChannelSetupEntryRuntimeContract = {
|
||||
kind: "bundled-channel-setup-entry";
|
||||
loadSetupPlugin: () => ChannelPlugin;
|
||||
loadSetupSecrets?: () => ChannelPlugin["secrets"] | undefined;
|
||||
loadSetupPlugin: (options?: BundledEntryModuleLoadOptions) => ChannelPlugin;
|
||||
loadSetupSecrets?: (
|
||||
options?: BundledEntryModuleLoadOptions,
|
||||
) => ChannelPlugin["secrets"] | undefined;
|
||||
loadLegacyStateMigrationDetector?: () => BundledChannelLegacyStateMigrationDetector;
|
||||
loadLegacySessionSurface?: () => BundledChannelLegacySessionSurface;
|
||||
features?: {
|
||||
@@ -179,6 +182,7 @@ function loadGeneratedBundledChannelModule(params: {
|
||||
rootScope: BundledChannelRootScope;
|
||||
metadata: BundledChannelPluginMetadata;
|
||||
entry: BundledChannelPluginMetadata["source"] | BundledChannelPluginMetadata["setupSource"];
|
||||
installRuntimeDeps?: boolean;
|
||||
}): unknown {
|
||||
let modulePath = resolveGeneratedBundledChannelModulePath(params);
|
||||
if (!modulePath) {
|
||||
@@ -191,7 +195,7 @@ function loadGeneratedBundledChannelModule(params: {
|
||||
metadata: params.metadata,
|
||||
modulePath,
|
||||
});
|
||||
if (isBuiltBundledPluginRuntimeRoot(boundaryRoot)) {
|
||||
if (params.installRuntimeDeps !== false && isBuiltBundledPluginRuntimeRoot(boundaryRoot)) {
|
||||
const prepared = prepareBundledPluginRuntimeRoot({
|
||||
pluginId: params.metadata.manifest.id,
|
||||
pluginRoot: boundaryRoot,
|
||||
@@ -225,6 +229,7 @@ function loadGeneratedBundledChannelEntry(params: {
|
||||
rootScope: params.rootScope,
|
||||
metadata: params.metadata,
|
||||
entry: params.metadata.source,
|
||||
installRuntimeDeps: true,
|
||||
}),
|
||||
);
|
||||
if (!entry) {
|
||||
@@ -257,6 +262,7 @@ function loadGeneratedBundledChannelSetupEntry(params: {
|
||||
rootScope: params.rootScope,
|
||||
metadata: params.metadata,
|
||||
entry: params.metadata.setupSource,
|
||||
installRuntimeDeps: false,
|
||||
}),
|
||||
);
|
||||
if (!setupEntry) {
|
||||
@@ -563,7 +569,7 @@ function getBundledChannelSetupPluginForRoot(
|
||||
}
|
||||
cacheContext.setupPluginLoadInProgressIds.add(id);
|
||||
try {
|
||||
const plugin = entry.loadSetupPlugin();
|
||||
const plugin = entry.loadSetupPlugin({ installRuntimeDeps: false });
|
||||
cacheContext.lazySetupPluginsById.set(id, plugin);
|
||||
return plugin;
|
||||
} catch (error) {
|
||||
|
||||
@@ -107,14 +107,20 @@ export type BundledChannelEntryContract<TPlugin = ChannelPlugin> = {
|
||||
|
||||
export type BundledChannelSetupEntryContract<TPlugin = ChannelPlugin> = {
|
||||
kind: "bundled-channel-setup-entry";
|
||||
loadSetupPlugin: () => TPlugin;
|
||||
loadSetupSecrets?: () => ChannelPlugin["secrets"] | undefined;
|
||||
loadSetupPlugin: (options?: BundledEntryModuleLoadOptions) => TPlugin;
|
||||
loadSetupSecrets?: (
|
||||
options?: BundledEntryModuleLoadOptions,
|
||||
) => ChannelPlugin["secrets"] | undefined;
|
||||
loadLegacyStateMigrationDetector?: () => BundledChannelLegacyStateMigrationDetector;
|
||||
loadLegacySessionSurface?: () => BundledChannelLegacySessionSurface;
|
||||
setChannelRuntime?: (runtime: PluginRuntime) => void;
|
||||
features?: BundledChannelSetupEntryFeatures;
|
||||
};
|
||||
|
||||
export type BundledEntryModuleLoadOptions = {
|
||||
installRuntimeDeps?: boolean;
|
||||
};
|
||||
|
||||
const nodeRequire = createRequire(import.meta.url);
|
||||
const jitiLoaders: PluginJitiLoaderCache = new Map();
|
||||
const loadedModuleExports = new Map<string, unknown>();
|
||||
@@ -330,10 +336,14 @@ function canTryNodeRequireBuiltModule(modulePath: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function loadBundledEntryModuleSync(importMetaUrl: string, specifier: string): unknown {
|
||||
function loadBundledEntryModuleSync(
|
||||
importMetaUrl: string,
|
||||
specifier: string,
|
||||
options: BundledEntryModuleLoadOptions = {},
|
||||
): unknown {
|
||||
let modulePath = resolveBundledEntryModulePath(importMetaUrl, specifier);
|
||||
const boundaryRoot = resolveEntryBoundaryRoot(importMetaUrl);
|
||||
if (isBuiltBundledPluginRuntimeRoot(boundaryRoot)) {
|
||||
if (options.installRuntimeDeps !== false && isBuiltBundledPluginRuntimeRoot(boundaryRoot)) {
|
||||
const prepared = prepareBundledPluginRuntimeRoot({
|
||||
pluginId: path.basename(boundaryRoot),
|
||||
pluginRoot: boundaryRoot,
|
||||
@@ -396,8 +406,9 @@ function loadBundledEntryModuleSync(importMetaUrl: string, specifier: string): u
|
||||
export function loadBundledEntryExportSync<T>(
|
||||
importMetaUrl: string,
|
||||
reference: BundledEntryModuleRef,
|
||||
options?: BundledEntryModuleLoadOptions,
|
||||
): T {
|
||||
const loaded = loadBundledEntryModuleSync(importMetaUrl, reference.specifier);
|
||||
const loaded = loadBundledEntryModuleSync(importMetaUrl, reference.specifier, options);
|
||||
const resolved =
|
||||
loaded && typeof loaded === "object" && "default" in (loaded as Record<string, unknown>)
|
||||
? (loaded as { default: unknown }).default
|
||||
@@ -523,13 +534,15 @@ export function defineBundledChannelSetupEntry<TPlugin = ChannelPlugin>({
|
||||
: undefined;
|
||||
return {
|
||||
kind: "bundled-channel-setup-entry",
|
||||
loadSetupPlugin: () => loadBundledEntryExportSync<TPlugin>(importMetaUrl, plugin),
|
||||
loadSetupPlugin: (options) =>
|
||||
loadBundledEntryExportSync<TPlugin>(importMetaUrl, plugin, options),
|
||||
...(secrets
|
||||
? {
|
||||
loadSetupSecrets: () =>
|
||||
loadSetupSecrets: (options) =>
|
||||
loadBundledEntryExportSync<ChannelPlugin["secrets"] | undefined>(
|
||||
importMetaUrl,
|
||||
secrets,
|
||||
options,
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
|
||||
@@ -1051,6 +1051,193 @@ module.exports = {
|
||||
expect(registry.plugins.find((entry) => entry.id === "discord")?.status).toBe("disabled");
|
||||
});
|
||||
|
||||
it("does not repair disabled selected setup-only channel runtime deps", () => {
|
||||
const bundledDir = makeTempDir();
|
||||
const plugin = writePlugin({
|
||||
id: "feishu",
|
||||
dir: path.join(bundledDir, "feishu"),
|
||||
filename: "index.cjs",
|
||||
body: `module.exports = { id: "feishu", register() {} };`,
|
||||
});
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledDir;
|
||||
fs.writeFileSync(
|
||||
path.join(plugin.dir, "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "@openclaw/feishu",
|
||||
version: "1.0.0",
|
||||
dependencies: {
|
||||
"feishu-runtime": "1.0.0",
|
||||
},
|
||||
openclaw: {
|
||||
extensions: ["./index.cjs"],
|
||||
setupEntry: "./setup-entry.cjs",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(plugin.dir, "openclaw.plugin.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
id: "feishu",
|
||||
configSchema: EMPTY_PLUGIN_SCHEMA,
|
||||
channels: ["feishu"],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(plugin.dir, "setup-entry.cjs"),
|
||||
`
|
||||
module.exports = {
|
||||
plugin: {
|
||||
id: "feishu",
|
||||
meta: {
|
||||
id: "feishu",
|
||||
label: "Feishu",
|
||||
selectionLabel: "Feishu",
|
||||
docsPath: "/channels/feishu",
|
||||
blurb: "setup only",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: {
|
||||
listAccountIds: () => [],
|
||||
resolveAccount: () => ({ accountId: "default" }),
|
||||
},
|
||||
},
|
||||
};
|
||||
`,
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
config: {
|
||||
plugins: {
|
||||
enabled: true,
|
||||
entries: {
|
||||
feishu: { enabled: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
onlyPluginIds: ["feishu"],
|
||||
bundledRuntimeDepsInstaller: () => {
|
||||
throw new Error("disabled setup-only deps should not install");
|
||||
},
|
||||
});
|
||||
|
||||
expect(registry.channelSetups[0]?.plugin.meta.label).toBe("Feishu");
|
||||
expect(registry.plugins.find((entry) => entry.id === "feishu")?.status).toBe("disabled");
|
||||
});
|
||||
|
||||
it("repairs enabled selected setup-only channel runtime deps before loading setup entry", () => {
|
||||
const bundledDir = makeTempDir();
|
||||
const plugin = writePlugin({
|
||||
id: "feishu",
|
||||
dir: path.join(bundledDir, "feishu"),
|
||||
filename: "index.cjs",
|
||||
body: `module.exports = { id: "feishu", register() {} };`,
|
||||
});
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledDir;
|
||||
fs.writeFileSync(
|
||||
path.join(plugin.dir, "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "@openclaw/feishu",
|
||||
version: "1.0.0",
|
||||
dependencies: {
|
||||
"feishu-runtime": "1.0.0",
|
||||
},
|
||||
openclaw: {
|
||||
extensions: ["./index.cjs"],
|
||||
setupEntry: "./setup-entry.cjs",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(plugin.dir, "openclaw.plugin.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
id: "feishu",
|
||||
configSchema: EMPTY_PLUGIN_SCHEMA,
|
||||
channels: ["feishu"],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(plugin.dir, "setup-entry.cjs"),
|
||||
`
|
||||
const runtime = require("feishu-runtime");
|
||||
module.exports = {
|
||||
plugin: {
|
||||
id: "feishu",
|
||||
meta: {
|
||||
id: "feishu",
|
||||
label: runtime.label,
|
||||
selectionLabel: runtime.label,
|
||||
docsPath: "/channels/feishu",
|
||||
blurb: "setup only",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: {
|
||||
listAccountIds: () => [],
|
||||
resolveAccount: () => ({ accountId: "default" }),
|
||||
},
|
||||
},
|
||||
};
|
||||
`,
|
||||
"utf-8",
|
||||
);
|
||||
const installedSpecs: string[] = [];
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
config: {
|
||||
plugins: {
|
||||
enabled: true,
|
||||
entries: {
|
||||
feishu: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
onlyPluginIds: ["feishu"],
|
||||
bundledRuntimeDepsInstaller: ({ installRoot, missingSpecs }) => {
|
||||
installedSpecs.push(...missingSpecs);
|
||||
const depRoot = path.join(installRoot, "node_modules", "feishu-runtime");
|
||||
fs.mkdirSync(depRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(depRoot, "package.json"),
|
||||
JSON.stringify({ name: "feishu-runtime", version: "1.0.0", main: "index.cjs" }),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(depRoot, "index.cjs"),
|
||||
"module.exports = { label: 'Feishu Runtime Ready' };\n",
|
||||
"utf-8",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
expect(installedSpecs).toEqual(["feishu-runtime@1.0.0"]);
|
||||
expect(registry.channelSetups[0]?.plugin.meta.label).toBe("Feishu Runtime Ready");
|
||||
expect(registry.plugins.find((entry) => entry.id === "feishu")?.status).toBe("loaded");
|
||||
});
|
||||
|
||||
it("repairs default-enabled bundled plugin runtime deps", () => {
|
||||
const bundledDir = makeTempDir();
|
||||
const plugin = writePlugin({
|
||||
|
||||
@@ -1232,7 +1232,10 @@ function loadBundledRuntimeChannelPlugin(params: {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSetupChannelRegistration(moduleExport: unknown): {
|
||||
function resolveSetupChannelRegistration(
|
||||
moduleExport: unknown,
|
||||
params: { installRuntimeDeps?: boolean } = {},
|
||||
): {
|
||||
plugin?: ChannelPlugin;
|
||||
setChannelRuntime?: (runtime: PluginRuntime) => void;
|
||||
usesBundledSetupContract?: boolean;
|
||||
@@ -1253,10 +1256,14 @@ function resolveSetupChannelRegistration(moduleExport: unknown): {
|
||||
typeof setupEntryRecord.loadSetupPlugin === "function"
|
||||
) {
|
||||
try {
|
||||
const loadedPlugin = setupEntryRecord.loadSetupPlugin();
|
||||
const setupLoadOptions =
|
||||
params.installRuntimeDeps === false ? { installRuntimeDeps: false } : undefined;
|
||||
const loadedPlugin = setupEntryRecord.loadSetupPlugin(setupLoadOptions);
|
||||
const loadedSecrets =
|
||||
typeof setupEntryRecord.loadSetupSecrets === "function"
|
||||
? (setupEntryRecord.loadSetupSecrets() as ChannelPlugin["secrets"] | undefined)
|
||||
? (setupEntryRecord.loadSetupSecrets(setupLoadOptions) as
|
||||
| ChannelPlugin["secrets"]
|
||||
| undefined)
|
||||
: undefined;
|
||||
if (loadedPlugin && typeof loadedPlugin === "object") {
|
||||
const mergedSecrets = mergeChannelPluginSection(
|
||||
@@ -2062,6 +2069,49 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
let runtimeCandidateSource = candidate.source;
|
||||
let runtimeSetupSource = manifestRecord.setupSource;
|
||||
|
||||
const scopedSetupOnlyChannelPluginRequested =
|
||||
includeSetupOnlyChannelPlugins &&
|
||||
!validateOnly &&
|
||||
onlyPluginIdSet &&
|
||||
manifestRecord.channels.length > 0 &&
|
||||
(!enableState.enabled || forceSetupOnlyChannelPlugins);
|
||||
const canLoadScopedSetupOnlyChannelPlugin =
|
||||
scopedSetupOnlyChannelPluginRequested &&
|
||||
(!requireSetupEntryForSetupOnlyChannelPlugins || Boolean(manifestRecord.setupSource));
|
||||
const registrationMode = canLoadScopedSetupOnlyChannelPlugin
|
||||
? "setup-only"
|
||||
: scopedSetupOnlyChannelPluginRequested && requireSetupEntryForSetupOnlyChannelPlugins
|
||||
? null
|
||||
: enableState.enabled
|
||||
? shouldLoadModules &&
|
||||
!validateOnly &&
|
||||
shouldLoadChannelPluginInSetupRuntime({
|
||||
manifestChannels: manifestRecord.channels,
|
||||
setupSource: manifestRecord.setupSource,
|
||||
startupDeferConfiguredChannelFullLoadUntilAfterListen:
|
||||
manifestRecord.startupDeferConfiguredChannelFullLoadUntilAfterListen,
|
||||
cfg,
|
||||
env,
|
||||
preferSetupRuntimeForChannelPlugins,
|
||||
})
|
||||
? "setup-runtime"
|
||||
: "full"
|
||||
: null;
|
||||
|
||||
if (!registrationMode) {
|
||||
record.status = "disabled";
|
||||
record.error = enableState.reason;
|
||||
markPluginActivationDisabled(record, enableState.reason);
|
||||
registry.plugins.push(record);
|
||||
seenIds.set(pluginId, candidate.origin);
|
||||
continue;
|
||||
}
|
||||
if (!enableState.enabled) {
|
||||
record.status = "disabled";
|
||||
record.error = enableState.reason;
|
||||
markPluginActivationDisabled(record, enableState.reason);
|
||||
}
|
||||
|
||||
if (shouldLoadModules && candidate.origin === "bundled" && enableState.enabled) {
|
||||
try {
|
||||
const installRoot = resolveBundledRuntimeDependencyInstallRoot(pluginRoot, { env });
|
||||
@@ -2112,49 +2162,6 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
}
|
||||
}
|
||||
|
||||
const scopedSetupOnlyChannelPluginRequested =
|
||||
includeSetupOnlyChannelPlugins &&
|
||||
!validateOnly &&
|
||||
onlyPluginIdSet &&
|
||||
manifestRecord.channels.length > 0 &&
|
||||
(!enableState.enabled || forceSetupOnlyChannelPlugins);
|
||||
const canLoadScopedSetupOnlyChannelPlugin =
|
||||
scopedSetupOnlyChannelPluginRequested &&
|
||||
(!requireSetupEntryForSetupOnlyChannelPlugins || Boolean(manifestRecord.setupSource));
|
||||
const registrationMode = canLoadScopedSetupOnlyChannelPlugin
|
||||
? "setup-only"
|
||||
: scopedSetupOnlyChannelPluginRequested && requireSetupEntryForSetupOnlyChannelPlugins
|
||||
? null
|
||||
: enableState.enabled
|
||||
? shouldLoadModules &&
|
||||
!validateOnly &&
|
||||
shouldLoadChannelPluginInSetupRuntime({
|
||||
manifestChannels: manifestRecord.channels,
|
||||
setupSource: manifestRecord.setupSource,
|
||||
startupDeferConfiguredChannelFullLoadUntilAfterListen:
|
||||
manifestRecord.startupDeferConfiguredChannelFullLoadUntilAfterListen,
|
||||
cfg,
|
||||
env,
|
||||
preferSetupRuntimeForChannelPlugins,
|
||||
})
|
||||
? "setup-runtime"
|
||||
: "full"
|
||||
: null;
|
||||
|
||||
if (!registrationMode) {
|
||||
record.status = "disabled";
|
||||
record.error = enableState.reason;
|
||||
markPluginActivationDisabled(record, enableState.reason);
|
||||
registry.plugins.push(record);
|
||||
seenIds.set(pluginId, candidate.origin);
|
||||
continue;
|
||||
}
|
||||
if (!enableState.enabled) {
|
||||
record.status = "disabled";
|
||||
record.error = enableState.reason;
|
||||
markPluginActivationDisabled(record, enableState.reason);
|
||||
}
|
||||
|
||||
if (record.format === "bundle") {
|
||||
const unsupportedCapabilities = (record.bundleCapabilities ?? []).filter(
|
||||
(capability) =>
|
||||
@@ -2345,7 +2352,9 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
(registrationMode === "setup-only" || registrationMode === "setup-runtime") &&
|
||||
manifestRecord.setupSource
|
||||
) {
|
||||
const setupRegistration = resolveSetupChannelRegistration(mod);
|
||||
const setupRegistration = resolveSetupChannelRegistration(mod, {
|
||||
installRuntimeDeps: enableState.enabled,
|
||||
});
|
||||
if (setupRegistration.loadError) {
|
||||
recordPluginError({
|
||||
logger,
|
||||
|
||||
@@ -520,6 +520,38 @@ describe("runSetupWizard", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("defers channel setup plugin loads during QuickStart until a channel is selected", async () => {
|
||||
const prompter = buildWizardPrompter({});
|
||||
const runtime = createRuntime();
|
||||
|
||||
await runSetupWizard(
|
||||
{
|
||||
acceptRisk: true,
|
||||
flow: "quickstart",
|
||||
authChoice: "skip",
|
||||
installDaemon: false,
|
||||
skipProviders: true,
|
||||
skipChannels: false,
|
||||
skipSkills: true,
|
||||
skipSearch: true,
|
||||
skipHealth: true,
|
||||
skipUi: true,
|
||||
},
|
||||
runtime,
|
||||
prompter,
|
||||
);
|
||||
|
||||
expect(setupChannels).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
deferStatusUntilSelection: true,
|
||||
quickstartDefaults: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("prompts for a model during explicit interactive Ollama setup", async () => {
|
||||
promptDefaultModel.mockClear();
|
||||
resolveProviderPluginChoice.mockReturnValue({
|
||||
|
||||
@@ -618,6 +618,7 @@ export async function runSetupWizard(
|
||||
: [];
|
||||
nextConfig = await setupChannels(nextConfig, runtime, prompter, {
|
||||
allowSignalInstall: true,
|
||||
deferStatusUntilSelection: flow === "quickstart",
|
||||
forceAllowFromChannels: quickstartAllowFromChannels,
|
||||
skipDmPolicyPrompt: flow === "quickstart",
|
||||
skipConfirm: flow === "quickstart",
|
||||
|
||||
Reference in New Issue
Block a user