mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 08:40:21 +00:00
Gateway: gate deferred channel startup behind opt-in
This commit is contained in:
@@ -29,3 +29,27 @@ export function resolveConfiguredChannelPluginIds(params: {
|
||||
}
|
||||
return resolveChannelPluginIds(params).filter((pluginId) => configuredChannelIds.has(pluginId));
|
||||
}
|
||||
|
||||
export function resolveConfiguredDeferredChannelPluginIds(params: {
|
||||
config: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): string[] {
|
||||
const configuredChannelIds = new Set(
|
||||
listPotentialConfiguredChannelIds(params.config, params.env).map((id) => id.trim()),
|
||||
);
|
||||
if (configuredChannelIds.size === 0) {
|
||||
return [];
|
||||
}
|
||||
return loadPluginManifestRegistry({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
})
|
||||
.plugins.filter(
|
||||
(plugin) =>
|
||||
plugin.channels.some((channelId) => configuredChannelIds.has(channelId)) &&
|
||||
plugin.startupDeferConfiguredChannelFullLoadUntilAfterListen === true,
|
||||
)
|
||||
.map((plugin) => plugin.id);
|
||||
}
|
||||
|
||||
@@ -2043,6 +2043,9 @@ module.exports = {
|
||||
openclaw: {
|
||||
extensions: ["./index.cjs"],
|
||||
setupEntry: "./setup-entry.cjs",
|
||||
startup: {
|
||||
deferConfiguredChannelFullLoadUntilAfterListen: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
@@ -2137,6 +2140,113 @@ module.exports = {
|
||||
expect(registry.channels).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("does not prefer setupEntry for configured channel loads without startup opt-in", () => {
|
||||
useNoBundledPlugins();
|
||||
const pluginDir = makeTempDir();
|
||||
const fullMarker = path.join(makeTempDir(), "full-loaded.txt");
|
||||
const setupMarker = path.join(makeTempDir(), "setup-loaded.txt");
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "@openclaw/setup-runtime-not-preferred-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-not-preferred-test",
|
||||
configSchema: EMPTY_PLUGIN_SCHEMA,
|
||||
channels: ["setup-runtime-not-preferred-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-not-preferred-test",
|
||||
register(api) {
|
||||
api.registerChannel({
|
||||
plugin: {
|
||||
id: "setup-runtime-not-preferred-test",
|
||||
meta: {
|
||||
id: "setup-runtime-not-preferred-test",
|
||||
label: "Setup Runtime Not Preferred Test",
|
||||
selectionLabel: "Setup Runtime Not Preferred Test",
|
||||
docsPath: "/channels/setup-runtime-not-preferred-test",
|
||||
blurb: "full entry should still load without explicit startup opt-in",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
resolveAccount: () => ({ accountId: "default", token: "configured" }),
|
||||
},
|
||||
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-not-preferred-test",
|
||||
meta: {
|
||||
id: "setup-runtime-not-preferred-test",
|
||||
label: "Setup Runtime Not Preferred Test",
|
||||
selectionLabel: "Setup Runtime Not Preferred Test",
|
||||
docsPath: "/channels/setup-runtime-not-preferred-test",
|
||||
blurb: "setup runtime not preferred",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
resolveAccount: () => ({ accountId: "default", token: "configured" }),
|
||||
},
|
||||
outbound: { deliveryMode: "direct" },
|
||||
},
|
||||
};`,
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
preferSetupRuntimeForChannelPlugins: true,
|
||||
config: {
|
||||
channels: {
|
||||
"setup-runtime-not-preferred-test": {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
load: { paths: [pluginDir] },
|
||||
allow: ["setup-runtime-not-preferred-test"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(fs.existsSync(fullMarker)).toBe(true);
|
||||
expect(fs.existsSync(setupMarker)).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({
|
||||
|
||||
@@ -54,6 +54,10 @@ export type PluginLoadOptions = {
|
||||
mode?: "full" | "validate";
|
||||
onlyPluginIds?: string[];
|
||||
includeSetupOnlyChannelPlugins?: boolean;
|
||||
/**
|
||||
* Prefer `setupEntry` for configured channel plugins that explicitly opt in
|
||||
* via package metadata because their setup entry covers the pre-listen startup surface.
|
||||
*/
|
||||
preferSetupRuntimeForChannelPlugins?: boolean;
|
||||
activate?: boolean;
|
||||
};
|
||||
@@ -449,6 +453,7 @@ function resolveSetupChannelRegistration(moduleExport: unknown): {
|
||||
function shouldLoadChannelPluginInSetupRuntime(params: {
|
||||
manifestChannels: string[];
|
||||
setupSource?: string;
|
||||
startupDeferConfiguredChannelFullLoadUntilAfterListen?: boolean;
|
||||
cfg: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
preferSetupRuntimeForChannelPlugins?: boolean;
|
||||
@@ -456,7 +461,10 @@ function shouldLoadChannelPluginInSetupRuntime(params: {
|
||||
if (!params.setupSource || params.manifestChannels.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (params.preferSetupRuntimeForChannelPlugins) {
|
||||
if (
|
||||
params.preferSetupRuntimeForChannelPlugins &&
|
||||
params.startupDeferConfiguredChannelFullLoadUntilAfterListen === true
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return !params.manifestChannels.some((channelId) =>
|
||||
@@ -1076,6 +1084,8 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
shouldLoadChannelPluginInSetupRuntime({
|
||||
manifestChannels: manifestRecord.channels,
|
||||
setupSource: manifestRecord.setupSource,
|
||||
startupDeferConfiguredChannelFullLoadUntilAfterListen:
|
||||
manifestRecord.startupDeferConfiguredChannelFullLoadUntilAfterListen,
|
||||
cfg,
|
||||
env,
|
||||
preferSetupRuntimeForChannelPlugins,
|
||||
|
||||
@@ -51,6 +51,7 @@ export type PluginManifestRecord = {
|
||||
rootDir: string;
|
||||
source: string;
|
||||
setupSource?: string;
|
||||
startupDeferConfiguredChannelFullLoadUntilAfterListen?: boolean;
|
||||
manifestPath: string;
|
||||
schemaCacheKey?: string;
|
||||
configSchema?: Record<string, unknown>;
|
||||
@@ -168,6 +169,9 @@ function buildRecord(params: {
|
||||
rootDir: params.candidate.rootDir,
|
||||
source: params.candidate.source,
|
||||
setupSource: params.candidate.setupSource,
|
||||
startupDeferConfiguredChannelFullLoadUntilAfterListen:
|
||||
params.candidate.packageManifest?.startup?.deferConfiguredChannelFullLoadUntilAfterListen ===
|
||||
true,
|
||||
manifestPath: params.manifestPath,
|
||||
schemaCacheKey: params.schemaCacheKey,
|
||||
configSchema: params.configSchema,
|
||||
|
||||
@@ -242,11 +242,20 @@ export type PluginPackageInstall = {
|
||||
defaultChoice?: "npm" | "local";
|
||||
};
|
||||
|
||||
export type OpenClawPackageStartup = {
|
||||
/**
|
||||
* Opt-in for channel plugins whose `setupEntry` fully covers the gateway
|
||||
* startup surface needed before the server starts listening.
|
||||
*/
|
||||
deferConfiguredChannelFullLoadUntilAfterListen?: boolean;
|
||||
};
|
||||
|
||||
export type OpenClawPackageManifest = {
|
||||
extensions?: string[];
|
||||
setupEntry?: string;
|
||||
channel?: PluginPackageChannel;
|
||||
install?: PluginPackageInstall;
|
||||
startup?: OpenClawPackageStartup;
|
||||
};
|
||||
|
||||
export const DEFAULT_PLUGIN_ENTRY_CANDIDATES = [
|
||||
|
||||
Reference in New Issue
Block a user