mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-19 05:50:47 +00:00
perf(plugins): lazy-load channel setup entrypoints
This commit is contained in:
@@ -769,10 +769,11 @@ Security note: `openclaw plugins install` installs plugin dependencies with
|
||||
trees "pure JS/TS" and avoid packages that require `postinstall` builds.
|
||||
|
||||
Optional: `openclaw.setupEntry` can point at a lightweight setup-only module.
|
||||
When OpenClaw needs onboarding/setup surfaces for a disabled channel plugin, it
|
||||
loads `setupEntry` instead of the full plugin entry. This keeps startup and
|
||||
onboarding lighter when your main plugin entry also wires tools, hooks, or
|
||||
other runtime-only code.
|
||||
When OpenClaw needs onboarding/setup surfaces for a disabled channel plugin, or
|
||||
when a channel plugin is enabled but still unconfigured, it loads `setupEntry`
|
||||
instead of the full plugin entry. This keeps startup and onboarding lighter
|
||||
when your main plugin entry also wires tools, hooks, or other runtime-only
|
||||
code.
|
||||
|
||||
### Channel catalog metadata
|
||||
|
||||
@@ -1663,7 +1664,7 @@ Recommended packaging:
|
||||
Publishing contract:
|
||||
|
||||
- Plugin `package.json` must include `openclaw.extensions` with one or more entry files.
|
||||
- Optional: `openclaw.setupEntry` may point at a lightweight setup-only entry for disabled channel onboarding/setup.
|
||||
- Optional: `openclaw.setupEntry` may point at a lightweight setup-only entry for disabled or still-unconfigured channel onboarding/setup.
|
||||
- Entry files can be `.js` or `.ts` (jiti loads TS at runtime).
|
||||
- `openclaw plugins install <npm-spec>` uses `npm pack`, extracts into `~/.openclaw/extensions/<id>/`, and enables it in config.
|
||||
- Config key stability: scoped packages are normalized to the **unscoped** id for `plugins.entries.*`.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
}
|
||||
}
|
||||
|
||||
3
extensions/discord/setup-entry.ts
Normal file
3
extensions/discord/setup-entry.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { discordPlugin } from "./src/channel.js";
|
||||
|
||||
export default { plugin: discordPlugin };
|
||||
@@ -7,6 +7,7 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
}
|
||||
}
|
||||
|
||||
3
extensions/imessage/setup-entry.ts
Normal file
3
extensions/imessage/setup-entry.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { imessagePlugin } from "./src/channel.js";
|
||||
|
||||
export default { plugin: imessagePlugin };
|
||||
@@ -7,6 +7,7 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
}
|
||||
}
|
||||
|
||||
3
extensions/signal/setup-entry.ts
Normal file
3
extensions/signal/setup-entry.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { signalPlugin } from "./src/channel.js";
|
||||
|
||||
export default { plugin: signalPlugin };
|
||||
@@ -7,6 +7,7 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
}
|
||||
}
|
||||
|
||||
3
extensions/slack/setup-entry.ts
Normal file
3
extensions/slack/setup-entry.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { slackPlugin } from "./src/channel.js";
|
||||
|
||||
export default { plugin: slackPlugin };
|
||||
@@ -7,6 +7,7 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
}
|
||||
}
|
||||
|
||||
3
extensions/telegram/setup-entry.ts
Normal file
3
extensions/telegram/setup-entry.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { telegramPlugin } from "./src/channel.js";
|
||||
|
||||
export default { plugin: telegramPlugin };
|
||||
@@ -7,6 +7,7 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
}
|
||||
}
|
||||
|
||||
3
extensions/whatsapp/setup-entry.ts
Normal file
3
extensions/whatsapp/setup-entry.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { whatsappPlugin } from "./src/channel.js";
|
||||
|
||||
export default { plugin: whatsappPlugin };
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
getChannelSetupPlugin,
|
||||
listChannelSetupPlugins,
|
||||
} from "../channels/plugins/setup-registry.js";
|
||||
import { buildChannelOnboardingAdapterFromSetupWizard } from "../channels/plugins/setup-wizard.js";
|
||||
import type { ChannelMeta, ChannelPlugin } from "../channels/plugins/types.js";
|
||||
import {
|
||||
formatChannelPrimerLine,
|
||||
@@ -28,8 +27,8 @@ import {
|
||||
loadOnboardingPluginRegistrySnapshotForChannel,
|
||||
} from "./onboarding/plugin-install.js";
|
||||
import {
|
||||
getChannelOnboardingAdapter,
|
||||
listChannelOnboardingAdapters,
|
||||
loadBundledChannelOnboardingPlugin,
|
||||
resolveChannelOnboardingAdapterForPlugin,
|
||||
} from "./onboarding/registry.js";
|
||||
import type {
|
||||
ChannelOnboardingAdapter,
|
||||
@@ -121,7 +120,8 @@ async function collectChannelStatus(params: {
|
||||
cfg: OpenClawConfig;
|
||||
options?: SetupChannelsOptions;
|
||||
accountOverrides: Partial<Record<ChannelChoice, string>>;
|
||||
installedPlugins?: ReturnType<typeof listChannelSetupPlugins>;
|
||||
installedPlugins?: ChannelPlugin[];
|
||||
resolveAdapter?: (channel: ChannelChoice) => ChannelOnboardingAdapter | undefined;
|
||||
}): Promise<ChannelStatusSummary> {
|
||||
const installedPlugins = params.installedPlugins ?? listChannelSetupPlugins();
|
||||
const workspaceDir = resolveAgentWorkspaceDir(params.cfg, resolveDefaultAgentId(params.cfg));
|
||||
@@ -134,14 +134,24 @@ async function collectChannelStatus(params: {
|
||||
}).plugins.flatMap((plugin) => plugin.channels),
|
||||
);
|
||||
const catalogEntries = allCatalogEntries.filter((entry) => !installedChannelIds.has(entry.id));
|
||||
const resolveAdapter =
|
||||
params.resolveAdapter ??
|
||||
((channel: ChannelChoice) =>
|
||||
resolveChannelOnboardingAdapterForPlugin(
|
||||
installedPlugins.find((plugin) => plugin.id === channel),
|
||||
));
|
||||
const statusEntries = await Promise.all(
|
||||
listChannelOnboardingAdapters().map((adapter) =>
|
||||
adapter.getStatus({
|
||||
installedPlugins.flatMap((plugin) => {
|
||||
const adapter = resolveAdapter(plugin.id);
|
||||
if (!adapter) {
|
||||
return [];
|
||||
}
|
||||
return adapter.getStatus({
|
||||
cfg: params.cfg,
|
||||
options: params.options,
|
||||
accountOverrides: params.accountOverrides,
|
||||
}),
|
||||
),
|
||||
});
|
||||
}),
|
||||
);
|
||||
const statusByChannel = new Map(statusEntries.map((entry) => [entry.channel, entry]));
|
||||
const fallbackStatuses = listChatChannels()
|
||||
@@ -270,7 +280,7 @@ async function maybeConfigureDmPolicies(params: {
|
||||
resolveAdapter?: (channel: ChannelChoice) => ChannelOnboardingAdapter | undefined;
|
||||
}): Promise<OpenClawConfig> {
|
||||
const { selection, prompter, accountIdsByChannel } = params;
|
||||
const resolve = params.resolveAdapter ?? getChannelOnboardingAdapter;
|
||||
const resolve = params.resolveAdapter;
|
||||
const dmPolicies = selection
|
||||
.map((channel) => resolve(channel)?.dmPolicy)
|
||||
.filter(Boolean) as ChannelOnboardingDmPolicy[];
|
||||
@@ -362,10 +372,10 @@ export async function setupChannels(
|
||||
}
|
||||
return Array.from(merged.values());
|
||||
};
|
||||
const loadScopedChannelPlugin = (
|
||||
const loadScopedChannelPlugin = async (
|
||||
channel: ChannelChoice,
|
||||
pluginId?: string,
|
||||
): ChannelPlugin | undefined => {
|
||||
): Promise<ChannelPlugin | undefined> => {
|
||||
const existing = getVisibleChannelPlugin(channel);
|
||||
if (existing) {
|
||||
return existing;
|
||||
@@ -382,22 +392,20 @@ export async function setupChannels(
|
||||
snapshot.channelSetups.find((entry) => entry.plugin.id === channel)?.plugin;
|
||||
if (plugin) {
|
||||
rememberScopedPlugin(plugin);
|
||||
return plugin;
|
||||
}
|
||||
return plugin;
|
||||
const bundledPlugin = await loadBundledChannelOnboardingPlugin(channel);
|
||||
if (bundledPlugin) {
|
||||
rememberScopedPlugin(bundledPlugin);
|
||||
}
|
||||
return bundledPlugin;
|
||||
};
|
||||
const getVisibleOnboardingAdapter = (channel: ChannelChoice) => {
|
||||
const adapter = getChannelOnboardingAdapter(channel);
|
||||
if (adapter) {
|
||||
return adapter;
|
||||
}
|
||||
const scopedPlugin = scopedPluginsById.get(channel);
|
||||
if (!scopedPlugin?.setupWizard) {
|
||||
return undefined;
|
||||
if (scopedPlugin) {
|
||||
return resolveChannelOnboardingAdapterForPlugin(scopedPlugin);
|
||||
}
|
||||
return buildChannelOnboardingAdapterFromSetupWizard({
|
||||
plugin: scopedPlugin,
|
||||
wizard: scopedPlugin.setupWizard,
|
||||
});
|
||||
return resolveChannelOnboardingAdapterForPlugin(getChannelSetupPlugin(channel));
|
||||
};
|
||||
const preloadConfiguredExternalPlugins = () => {
|
||||
// Keep onboarding memory bounded by snapshot-loading only configured external plugins.
|
||||
@@ -412,7 +420,7 @@ export async function setupChannels(
|
||||
if (!explicitlyEnabled && !isChannelConfigured(next, channel)) {
|
||||
continue;
|
||||
}
|
||||
loadScopedChannelPlugin(channel, entry.pluginId);
|
||||
void loadScopedChannelPlugin(channel, entry.pluginId);
|
||||
}
|
||||
};
|
||||
if (options?.whatsappAccountId?.trim()) {
|
||||
@@ -426,6 +434,7 @@ export async function setupChannels(
|
||||
options,
|
||||
accountOverrides,
|
||||
installedPlugins: listVisibleInstalledPlugins(),
|
||||
resolveAdapter: getVisibleOnboardingAdapter,
|
||||
});
|
||||
if (!options?.skipStatusNote && statusLines.length > 0) {
|
||||
await prompter.note(statusLines.join("\n"), "Channel status");
|
||||
@@ -586,8 +595,8 @@ export async function setupChannels(
|
||||
);
|
||||
return false;
|
||||
}
|
||||
const plugin = await loadScopedChannelPlugin(channel);
|
||||
const adapter = getVisibleOnboardingAdapter(channel);
|
||||
const plugin = loadScopedChannelPlugin(channel);
|
||||
if (!plugin) {
|
||||
if (adapter) {
|
||||
await prompter.note(
|
||||
@@ -752,7 +761,7 @@ export async function setupChannels(
|
||||
if (!result.installed) {
|
||||
return;
|
||||
}
|
||||
loadScopedChannelPlugin(channel, result.pluginId ?? catalogEntry.pluginId);
|
||||
await loadScopedChannelPlugin(channel, result.pluginId ?? catalogEntry.pluginId);
|
||||
await refreshStatus(channel);
|
||||
} else {
|
||||
const enabled = await enableBundledPluginForSetup(channel);
|
||||
|
||||
@@ -1,54 +1,15 @@
|
||||
import { discordPlugin } from "../../../extensions/discord/src/channel.js";
|
||||
import { imessagePlugin } from "../../../extensions/imessage/src/channel.js";
|
||||
import { signalPlugin } from "../../../extensions/signal/src/channel.js";
|
||||
import { slackPlugin } from "../../../extensions/slack/src/channel.js";
|
||||
import { telegramPlugin } from "../../../extensions/telegram/src/channel.js";
|
||||
import { whatsappPlugin } from "../../../extensions/whatsapp/src/channel.js";
|
||||
import { listChannelSetupPlugins } from "../../channels/plugins/setup-registry.js";
|
||||
import { buildChannelOnboardingAdapterFromSetupWizard } from "../../channels/plugins/setup-wizard.js";
|
||||
import type { ChannelPlugin } from "../../channels/plugins/types.js";
|
||||
import type { ChannelChoice } from "../onboard-types.js";
|
||||
import type { ChannelOnboardingAdapter } from "./types.js";
|
||||
|
||||
const telegramOnboardingAdapter = buildChannelOnboardingAdapterFromSetupWizard({
|
||||
plugin: telegramPlugin,
|
||||
wizard: telegramPlugin.setupWizard!,
|
||||
});
|
||||
const discordOnboardingAdapter = buildChannelOnboardingAdapterFromSetupWizard({
|
||||
plugin: discordPlugin,
|
||||
wizard: discordPlugin.setupWizard!,
|
||||
});
|
||||
const slackOnboardingAdapter = buildChannelOnboardingAdapterFromSetupWizard({
|
||||
plugin: slackPlugin,
|
||||
wizard: slackPlugin.setupWizard!,
|
||||
});
|
||||
const signalOnboardingAdapter = buildChannelOnboardingAdapterFromSetupWizard({
|
||||
plugin: signalPlugin,
|
||||
wizard: signalPlugin.setupWizard!,
|
||||
});
|
||||
const imessageOnboardingAdapter = buildChannelOnboardingAdapterFromSetupWizard({
|
||||
plugin: imessagePlugin,
|
||||
wizard: imessagePlugin.setupWizard!,
|
||||
});
|
||||
const whatsappOnboardingAdapter = buildChannelOnboardingAdapterFromSetupWizard({
|
||||
plugin: whatsappPlugin,
|
||||
wizard: whatsappPlugin.setupWizard!,
|
||||
});
|
||||
|
||||
const BUILTIN_ONBOARDING_ADAPTERS: ChannelOnboardingAdapter[] = [
|
||||
telegramOnboardingAdapter,
|
||||
whatsappOnboardingAdapter,
|
||||
discordOnboardingAdapter,
|
||||
slackOnboardingAdapter,
|
||||
signalOnboardingAdapter,
|
||||
imessageOnboardingAdapter,
|
||||
];
|
||||
|
||||
const setupWizardAdapters = new WeakMap<object, ChannelOnboardingAdapter>();
|
||||
|
||||
function resolveChannelOnboardingAdapter(
|
||||
plugin: ReturnType<typeof listChannelSetupPlugins>[number],
|
||||
export function resolveChannelOnboardingAdapterForPlugin(
|
||||
plugin?: ChannelPlugin,
|
||||
): ChannelOnboardingAdapter | undefined {
|
||||
if (plugin.setupWizard) {
|
||||
if (plugin?.setupWizard) {
|
||||
const cached = setupWizardAdapters.get(plugin);
|
||||
if (cached) {
|
||||
return cached;
|
||||
@@ -64,11 +25,9 @@ function resolveChannelOnboardingAdapter(
|
||||
}
|
||||
|
||||
const CHANNEL_ONBOARDING_ADAPTERS = () => {
|
||||
const adapters = new Map<ChannelChoice, ChannelOnboardingAdapter>(
|
||||
BUILTIN_ONBOARDING_ADAPTERS.map((adapter) => [adapter.channel, adapter] as const),
|
||||
);
|
||||
const adapters = new Map<ChannelChoice, ChannelOnboardingAdapter>();
|
||||
for (const plugin of listChannelSetupPlugins()) {
|
||||
const adapter = resolveChannelOnboardingAdapter(plugin);
|
||||
const adapter = resolveChannelOnboardingAdapterForPlugin(plugin);
|
||||
if (!adapter) {
|
||||
continue;
|
||||
}
|
||||
@@ -87,6 +46,27 @@ export function listChannelOnboardingAdapters(): ChannelOnboardingAdapter[] {
|
||||
return Array.from(CHANNEL_ONBOARDING_ADAPTERS().values());
|
||||
}
|
||||
|
||||
export async function loadBundledChannelOnboardingPlugin(
|
||||
channel: ChannelChoice,
|
||||
): Promise<ChannelPlugin | undefined> {
|
||||
switch (channel) {
|
||||
case "discord":
|
||||
return (await import("../../../extensions/discord/setup-entry.js")).default.plugin;
|
||||
case "imessage":
|
||||
return (await import("../../../extensions/imessage/setup-entry.js")).default.plugin;
|
||||
case "signal":
|
||||
return (await import("../../../extensions/signal/setup-entry.js")).default.plugin;
|
||||
case "slack":
|
||||
return (await import("../../../extensions/slack/setup-entry.js")).default.plugin;
|
||||
case "telegram":
|
||||
return (await import("../../../extensions/telegram/setup-entry.js")).default.plugin;
|
||||
case "whatsapp":
|
||||
return (await import("../../../extensions/whatsapp/setup-entry.js")).default.plugin;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy aliases (pre-rename).
|
||||
export const getProviderOnboardingAdapter = getChannelOnboardingAdapter;
|
||||
export const listProviderOnboardingAdapters = listChannelOnboardingAdapters;
|
||||
|
||||
@@ -1885,6 +1885,107 @@ module.exports = {
|
||||
expect(setupRegistry.channels).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("uses package setupEntry for enabled but unconfigured channel loads", () => {
|
||||
useNoBundledPlugins();
|
||||
const pluginDir = makeTempDir();
|
||||
const fullMarker = path.join(pluginDir, "full-loaded.txt");
|
||||
const setupMarker = path.join(pluginDir, "setup-loaded.txt");
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "@openclaw/setup-runtime-test",
|
||||
openclaw: {
|
||||
extensions: ["./index.cjs"],
|
||||
setupEntry: "./setup-entry.cjs",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "openclaw.plugin.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
id: "setup-runtime-test",
|
||||
configSchema: EMPTY_PLUGIN_SCHEMA,
|
||||
channels: ["setup-runtime-test"],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "index.cjs"),
|
||||
`require("node:fs").writeFileSync(${JSON.stringify(fullMarker)}, "loaded", "utf-8");
|
||||
module.exports = {
|
||||
id: "setup-runtime-test",
|
||||
register(api) {
|
||||
api.registerChannel({
|
||||
plugin: {
|
||||
id: "setup-runtime-test",
|
||||
meta: {
|
||||
id: "setup-runtime-test",
|
||||
label: "Setup Runtime Test",
|
||||
selectionLabel: "Setup Runtime Test",
|
||||
docsPath: "/channels/setup-runtime-test",
|
||||
blurb: "full entry should not run while unconfigured",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: {
|
||||
listAccountIds: () => [],
|
||||
resolveAccount: () => ({ accountId: "default" }),
|
||||
},
|
||||
outbound: { deliveryMode: "direct" },
|
||||
},
|
||||
});
|
||||
},
|
||||
};`,
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "setup-entry.cjs"),
|
||||
`require("node:fs").writeFileSync(${JSON.stringify(setupMarker)}, "loaded", "utf-8");
|
||||
module.exports = {
|
||||
plugin: {
|
||||
id: "setup-runtime-test",
|
||||
meta: {
|
||||
id: "setup-runtime-test",
|
||||
label: "Setup Runtime Test",
|
||||
selectionLabel: "Setup Runtime Test",
|
||||
docsPath: "/channels/setup-runtime-test",
|
||||
blurb: "setup runtime",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: {
|
||||
listAccountIds: () => [],
|
||||
resolveAccount: () => ({ accountId: "default" }),
|
||||
},
|
||||
outbound: { deliveryMode: "direct" },
|
||||
},
|
||||
};`,
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [pluginDir] },
|
||||
allow: ["setup-runtime-test"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(fs.existsSync(setupMarker)).toBe(true);
|
||||
expect(fs.existsSync(fullMarker)).toBe(false);
|
||||
expect(registry.channelSetups).toHaveLength(1);
|
||||
expect(registry.channels).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("blocks before_prompt_build but preserves legacy model overrides when prompt injection is disabled", async () => {
|
||||
useNoBundledPlugins();
|
||||
const plugin = writePlugin({
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createJiti } from "jiti";
|
||||
import type { ChannelDock } from "../channels/dock.js";
|
||||
import type { ChannelPlugin } from "../channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { isChannelConfigured } from "../config/plugin-auto-enable.js";
|
||||
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
@@ -357,6 +358,20 @@ function resolveSetupChannelRegistration(moduleExport: unknown): {
|
||||
};
|
||||
}
|
||||
|
||||
function shouldLoadChannelPluginInSetupRuntime(params: {
|
||||
manifestChannels: string[];
|
||||
setupSource?: string;
|
||||
cfg: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): boolean {
|
||||
if (!params.setupSource || params.manifestChannels.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return !params.manifestChannels.some((channelId) =>
|
||||
isChannelConfigured(params.cfg, channelId, params.env),
|
||||
);
|
||||
}
|
||||
|
||||
function createPluginRecord(params: {
|
||||
id: string;
|
||||
name?: string;
|
||||
@@ -924,7 +939,15 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
};
|
||||
|
||||
const registrationMode = enableState.enabled
|
||||
? "full"
|
||||
? !validateOnly &&
|
||||
shouldLoadChannelPluginInSetupRuntime({
|
||||
manifestChannels: manifestRecord.channels,
|
||||
setupSource: manifestRecord.setupSource,
|
||||
cfg,
|
||||
env,
|
||||
})
|
||||
? "setup-runtime"
|
||||
: "full"
|
||||
: includeSetupOnlyChannelPlugins && !validateOnly && manifestRecord.channels.length > 0
|
||||
? "setup-only"
|
||||
: null;
|
||||
@@ -994,7 +1017,8 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
|
||||
const pluginRoot = safeRealpathOrResolve(candidate.rootDir);
|
||||
const loadSource =
|
||||
registrationMode === "setup-only" && manifestRecord.setupSource
|
||||
(registrationMode === "setup-only" || registrationMode === "setup-runtime") &&
|
||||
manifestRecord.setupSource
|
||||
? manifestRecord.setupSource
|
||||
: candidate.source;
|
||||
const opened = openBoundaryFileSync({
|
||||
@@ -1029,7 +1053,10 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
continue;
|
||||
}
|
||||
|
||||
if (registrationMode === "setup-only" && manifestRecord.setupSource) {
|
||||
if (
|
||||
(registrationMode === "setup-only" || registrationMode === "setup-runtime") &&
|
||||
manifestRecord.setupSource
|
||||
) {
|
||||
const setupRegistration = resolveSetupChannelRegistration(mod);
|
||||
if (setupRegistration.plugin) {
|
||||
if (setupRegistration.plugin.id && setupRegistration.plugin.id !== record.id) {
|
||||
|
||||
@@ -481,7 +481,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
return;
|
||||
}
|
||||
const existingRuntime = registry.channels.find((entry) => entry.plugin.id === id);
|
||||
if (mode === "full" && existingRuntime) {
|
||||
if (mode !== "setup-only" && existingRuntime) {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
|
||||
@@ -956,7 +956,7 @@ export type OpenClawPluginModule =
|
||||
| OpenClawPluginDefinition
|
||||
| ((api: OpenClawPluginApi) => void | Promise<void>);
|
||||
|
||||
export type PluginRegistrationMode = "full" | "setup-only";
|
||||
export type PluginRegistrationMode = "full" | "setup-only" | "setup-runtime";
|
||||
|
||||
export type OpenClawPluginApi = {
|
||||
id: string;
|
||||
|
||||
Reference in New Issue
Block a user