mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:30:44 +00:00
fix: tighten read-only external channel discovery
This commit is contained in:
@@ -78,7 +78,7 @@ Docs: https://docs.openclaw.ai
|
||||
- fix(security): block MINIMAX_API_HOST workspace env injection and remove env-driven URL routing [AI-assisted]. (#67300) Thanks @pgondhi987.
|
||||
- Cron/delivery: treat explicit `delivery.mode: "none"` runs as not requested even if the runner reports `delivered: false`, so no-delivery cron jobs no longer persist false delivery failures or errors. (#69285) Thanks @matsuri1987.
|
||||
- Plugins/install: repair active and default-enabled bundled plugin runtime dependencies before import in packaged installs, so bundled Discord, WhatsApp, Slack, Telegram, and provider plugins work without putting their dependency trees in core.
|
||||
- CLI/channels: keep `status`, `health`, `channels list`, and `channels status` on read-only channel metadata when Telegram, Slack, or Discord are configured, avoiding full bundled plugin runtime imports on those cold paths. Fixes #69042.
|
||||
- CLI/channels: keep `status`, `health`, `channels list`, and `channels status` on read-only channel metadata when Telegram, Slack, Discord, or third-party channel plugins are configured, avoiding full bundled plugin runtime imports on those cold paths. Fixes #69042. (#69479) Thanks @gumadeiras.
|
||||
- BlueBubbles: raise the outbound `/api/v1/message/text` send timeout default from 10s to 30s, and add a configurable `channels.bluebubbles.sendTimeoutMs` (also per-account) so macOS 26 setups where Private API iMessage sends stall for 60+ seconds no longer silently lose messages at the 10s abort. Probes, chat lookups, and health checks keep the shorter 10s default. Fixes #67486. (#69193) Thanks @omarshahine.
|
||||
- Agents/bootstrap: budget truncation markers against per-file caps, preserve source content instead of silently wasting bootstrap bytes, and avoid marker-only output in tiny-budget truncation cases. (#69114) Thanks @BKF-Gitty.
|
||||
- Context engine/plugins: stop rejecting third-party context engines whose `info.id` differs from the registered plugin slot id. The strict-match contract added in 2026.4.14 broke `lossless-claw` and other plugins whose internal engine id does not equal the slot id they are registered under, producing repeated `info.id must match registered id` lane failures on every turn. Fixes #66601. (#66678) Thanks @GodsBoy.
|
||||
|
||||
@@ -510,7 +510,7 @@ Important examples:
|
||||
| Field | What it means |
|
||||
| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `openclaw.extensions` | Declares native plugin entrypoints. |
|
||||
| `openclaw.setupEntry` | Lightweight setup-only entrypoint used during onboarding and deferred channel startup. |
|
||||
| `openclaw.setupEntry` | Lightweight setup-only entrypoint used during onboarding, deferred channel startup, and read-only channel status/SecretRef discovery. |
|
||||
| `openclaw.channel` | Cheap channel catalog metadata like labels, docs paths, aliases, and selection copy. |
|
||||
| `openclaw.channel.configuredState` | Lightweight configured-state checker metadata that can answer "does env-only setup already exist?" without loading the full channel runtime. |
|
||||
| `openclaw.channel.persistedAuthState` | Lightweight persisted-auth checker metadata that can answer "is anything already signed in?" without loading the full channel runtime. |
|
||||
@@ -524,6 +524,12 @@ Important examples:
|
||||
registry loading. Invalid values are rejected; newer-but-valid values skip the
|
||||
plugin on older hosts.
|
||||
|
||||
Channel plugins should provide `openclaw.setupEntry` when status, channel list,
|
||||
or SecretRef scans need to identify configured accounts without loading the full
|
||||
runtime. The setup entry should expose channel metadata plus setup-safe config,
|
||||
status, and secrets adapters; keep network clients, gateway listeners, and
|
||||
transport runtimes in the main extension entrypoint.
|
||||
|
||||
`openclaw.install.allowInvalidConfigRecovery` is intentionally narrow. It does
|
||||
not make arbitrary broken configs installable. Today it only allows install
|
||||
flows to recover from specific stale bundled-plugin upgrade failures, such as a
|
||||
|
||||
@@ -139,6 +139,14 @@ If your channel supports env-driven setup or auth and generic startup/config
|
||||
flows should know those env names before runtime loads, declare them in the
|
||||
plugin manifest with `channelEnvVars`. Keep channel runtime `envVars` or local
|
||||
constants for operator-facing copy only.
|
||||
|
||||
If your channel can appear in `status`, `channels list`, `channels status`, or
|
||||
SecretRef scans before the plugin runtime starts, add `openclaw.setupEntry` in
|
||||
`package.json`. That entrypoint should be safe to import in read-only command
|
||||
paths and should return the channel metadata, setup-safe config adapter, status
|
||||
adapter, and channel secret target metadata needed for those summaries. Do not
|
||||
start clients, listeners, or transport runtimes from the setup entry.
|
||||
|
||||
`createOptionalChannelSetupWizard`, `DEFAULT_ACCOUNT_ID`,
|
||||
`createTopLevelChannelDmPolicy`, `setSetupChannelEnabled`, and
|
||||
`splitSetupEntries`
|
||||
|
||||
@@ -10,9 +10,18 @@ import {
|
||||
} from "../../plugins/loader.test-fixtures.js";
|
||||
import { listReadOnlyChannelPluginsForConfig } from "./read-only.js";
|
||||
|
||||
function writeExternalSetupChannelPlugin(options: { setupEntry?: boolean } = {}) {
|
||||
function writeExternalSetupChannelPlugin(
|
||||
options: {
|
||||
setupEntry?: boolean;
|
||||
pluginDir?: string;
|
||||
pluginId?: string;
|
||||
channelId?: string;
|
||||
} = {},
|
||||
) {
|
||||
useNoBundledPlugins();
|
||||
const pluginDir = makeTempDir();
|
||||
const pluginDir = options.pluginDir ?? makeTempDir();
|
||||
const pluginId = options.pluginId ?? "external-chat";
|
||||
const channelId = options.channelId ?? "external-chat";
|
||||
const fullMarker = path.join(pluginDir, "full-loaded.txt");
|
||||
const setupMarker = path.join(pluginDir, "setup-loaded.txt");
|
||||
const setupEntry = options.setupEntry !== false;
|
||||
@@ -21,7 +30,7 @@ function writeExternalSetupChannelPlugin(options: { setupEntry?: boolean } = {})
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "@example/openclaw-external-chat",
|
||||
name: `@example/openclaw-${pluginId}`,
|
||||
version: "1.0.0",
|
||||
openclaw: {
|
||||
extensions: ["./index.cjs"],
|
||||
@@ -37,9 +46,12 @@ function writeExternalSetupChannelPlugin(options: { setupEntry?: boolean } = {})
|
||||
path.join(pluginDir, "openclaw.plugin.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
id: "external-chat",
|
||||
id: pluginId,
|
||||
configSchema: EMPTY_PLUGIN_SCHEMA,
|
||||
channels: ["external-chat"],
|
||||
channels: [channelId],
|
||||
channelEnvVars: {
|
||||
[channelId]: ["EXTERNAL_CHAT_TOKEN"],
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
@@ -50,16 +62,16 @@ function writeExternalSetupChannelPlugin(options: { setupEntry?: boolean } = {})
|
||||
path.join(pluginDir, "index.cjs"),
|
||||
`require("node:fs").writeFileSync(${JSON.stringify(fullMarker)}, "loaded", "utf-8");
|
||||
module.exports = {
|
||||
id: "external-chat",
|
||||
id: ${JSON.stringify(pluginId)},
|
||||
register(api) {
|
||||
api.registerChannel({
|
||||
plugin: {
|
||||
id: "external-chat",
|
||||
id: ${JSON.stringify(channelId)},
|
||||
meta: {
|
||||
id: "external-chat",
|
||||
id: ${JSON.stringify(channelId)},
|
||||
label: "External Chat",
|
||||
selectionLabel: "External Chat",
|
||||
docsPath: "/channels/external-chat",
|
||||
docsPath: ${JSON.stringify(`/channels/${channelId}`)},
|
||||
blurb: "full entry",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
@@ -71,10 +83,10 @@ module.exports = {
|
||||
secrets: {
|
||||
secretTargetRegistryEntries: [
|
||||
{
|
||||
id: "channels.external-chat.token",
|
||||
id: ${JSON.stringify(`channels.${channelId}.token`)},
|
||||
targetType: "channel",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "channels.external-chat.token",
|
||||
pathPattern: ${JSON.stringify(`channels.${channelId}.token`)},
|
||||
secretShape: "secret_input",
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
@@ -95,12 +107,12 @@ module.exports = {
|
||||
`require("node:fs").writeFileSync(${JSON.stringify(setupMarker)}, "loaded", "utf-8");
|
||||
module.exports = {
|
||||
plugin: {
|
||||
id: "external-chat",
|
||||
id: ${JSON.stringify(channelId)},
|
||||
meta: {
|
||||
id: "external-chat",
|
||||
id: ${JSON.stringify(channelId)},
|
||||
label: "External Chat",
|
||||
selectionLabel: "External Chat",
|
||||
docsPath: "/channels/external-chat",
|
||||
docsPath: ${JSON.stringify(`/channels/${channelId}`)},
|
||||
blurb: "setup entry",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
@@ -112,10 +124,10 @@ module.exports = {
|
||||
secrets: {
|
||||
secretTargetRegistryEntries: [
|
||||
{
|
||||
id: "channels.external-chat.token",
|
||||
id: ${JSON.stringify(`channels.${channelId}.token`)},
|
||||
targetType: "channel",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "channels.external-chat.token",
|
||||
pathPattern: ${JSON.stringify(`channels.${channelId}.token`)},
|
||||
secretShape: "secret_input",
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
@@ -192,13 +204,72 @@ describe("listReadOnlyChannelPluginsForConfig", () => {
|
||||
);
|
||||
|
||||
const plugin = plugins.find((entry) => entry.id === "external-chat");
|
||||
expect(plugin?.meta.blurb).toBe("full entry");
|
||||
expect(plugin).toBeUndefined();
|
||||
expect(fs.existsSync(setupMarker)).toBe(false);
|
||||
expect(fs.existsSync(fullMarker)).toBe(false);
|
||||
});
|
||||
|
||||
it("uses external channel env vars as read-only configuration triggers", () => {
|
||||
const { pluginDir, fullMarker, setupMarker } = writeExternalSetupChannelPlugin({
|
||||
pluginId: "external-chat-plugin",
|
||||
channelId: "external-chat",
|
||||
});
|
||||
const plugins = listReadOnlyChannelPluginsForConfig(
|
||||
{
|
||||
plugins: {
|
||||
load: { paths: [pluginDir] },
|
||||
allow: ["external-chat-plugin"],
|
||||
},
|
||||
} as never,
|
||||
{
|
||||
env: { ...process.env, EXTERNAL_CHAT_TOKEN: "configured" },
|
||||
includePersistedAuthState: false,
|
||||
},
|
||||
);
|
||||
|
||||
const plugin = plugins.find((entry) => entry.id === "external-chat");
|
||||
expect(plugin?.meta.blurb).toBe("setup entry");
|
||||
expect(
|
||||
plugin?.secrets?.secretTargetRegistryEntries?.some(
|
||||
(entry) => entry.id === "channels.external-chat.token",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(fs.existsSync(setupMarker)).toBe(false);
|
||||
expect(fs.existsSync(fullMarker)).toBe(true);
|
||||
expect(fs.existsSync(setupMarker)).toBe(true);
|
||||
expect(fs.existsSync(fullMarker)).toBe(false);
|
||||
});
|
||||
|
||||
it("discovers trusted external channel plugins from the default agent workspace", () => {
|
||||
const workspaceDir = makeTempDir();
|
||||
const pluginDir = path.join(workspaceDir, ".openclaw", "extensions", "external-chat-plugin");
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
const { fullMarker, setupMarker } = writeExternalSetupChannelPlugin({
|
||||
pluginDir,
|
||||
pluginId: "external-chat-plugin",
|
||||
channelId: "external-chat",
|
||||
});
|
||||
const plugins = listReadOnlyChannelPluginsForConfig(
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: workspaceDir,
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
"external-chat": { token: "configured" },
|
||||
},
|
||||
plugins: {
|
||||
allow: ["external-chat-plugin"],
|
||||
},
|
||||
} as never,
|
||||
{
|
||||
env: { ...process.env },
|
||||
includePersistedAuthState: false,
|
||||
},
|
||||
);
|
||||
|
||||
const plugin = plugins.find((entry) => entry.id === "external-chat");
|
||||
expect(plugin?.meta.blurb).toBe("setup entry");
|
||||
expect(fs.existsSync(setupMarker)).toBe(true);
|
||||
expect(fs.existsSync(fullMarker)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { resolveDiscoverableScopedChannelPluginIds } from "../../plugins/channel-plugin-ids.js";
|
||||
import { loadOpenClawPlugins } from "../../plugins/loader.js";
|
||||
import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js";
|
||||
import {
|
||||
loadPluginManifestRegistry,
|
||||
type PluginManifestRecord,
|
||||
} from "../../plugins/manifest-registry.js";
|
||||
import { listPotentialConfiguredChannelIds } from "../config-presence.js";
|
||||
import { getBundledChannelSetupPlugin } from "./bundled.js";
|
||||
import { listChannelPlugins } from "./registry.js";
|
||||
@@ -44,10 +48,53 @@ function addChannelPlugins(
|
||||
}
|
||||
}
|
||||
|
||||
function hasNonEmptyEnvValue(env: NodeJS.ProcessEnv, key: string): boolean {
|
||||
const value = env[key];
|
||||
return typeof value === "string" && value.trim().length > 0;
|
||||
}
|
||||
|
||||
function resolveReadOnlyWorkspaceDir(
|
||||
cfg: OpenClawConfig,
|
||||
options: ReadOnlyChannelPluginOptions,
|
||||
): string | undefined {
|
||||
return options.workspaceDir ?? resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
|
||||
}
|
||||
|
||||
function listExternalChannelManifestRecords(params: {
|
||||
cfg: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
cache?: boolean;
|
||||
}): PluginManifestRecord[] {
|
||||
return loadPluginManifestRegistry({
|
||||
config: params.cfg,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
cache: params.cache,
|
||||
}).plugins.filter((plugin) => plugin.origin !== "bundled" && plugin.channels.length > 0);
|
||||
}
|
||||
|
||||
function listExternalEnvConfiguredChannelIds(params: {
|
||||
records: readonly PluginManifestRecord[];
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): string[] {
|
||||
const channelIds = new Set<string>();
|
||||
for (const record of params.records) {
|
||||
for (const channelId of record.channels) {
|
||||
const envVars = record.channelEnvVars?.[channelId] ?? [];
|
||||
if (envVars.some((envVar) => hasNonEmptyEnvValue(params.env, envVar))) {
|
||||
channelIds.add(channelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...channelIds].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function resolveExternalReadOnlyChannelPluginIds(params: {
|
||||
cfg: OpenClawConfig;
|
||||
activationSourceConfig?: OpenClawConfig;
|
||||
channelIds: readonly string[];
|
||||
records: readonly PluginManifestRecord[];
|
||||
workspaceDir?: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
cache?: boolean;
|
||||
@@ -69,16 +116,10 @@ function resolveExternalReadOnlyChannelPluginIds(params: {
|
||||
|
||||
const requestedChannelIds = new Set(params.channelIds);
|
||||
const candidatePluginIdSet = new Set(candidatePluginIds);
|
||||
return loadPluginManifestRegistry({
|
||||
config: params.cfg,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
cache: params.cache,
|
||||
})
|
||||
.plugins.filter(
|
||||
return params.records
|
||||
.filter(
|
||||
(plugin) =>
|
||||
candidatePluginIdSet.has(plugin.id) &&
|
||||
plugin.origin !== "bundled" &&
|
||||
plugin.channels.some((channelId) => requestedChannelIds.has(channelId)),
|
||||
)
|
||||
.map((plugin) => plugin.id)
|
||||
@@ -99,9 +140,24 @@ export function listReadOnlyChannelPluginsForConfig(
|
||||
): ChannelPlugin[] {
|
||||
const options = resolveReadOnlyChannelPluginOptions(envOrOptions);
|
||||
const env = options.env ?? process.env;
|
||||
const configuredChannelIds = listPotentialConfiguredChannelIds(cfg, env, {
|
||||
includePersistedAuthState: options.includePersistedAuthState,
|
||||
const workspaceDir = resolveReadOnlyWorkspaceDir(cfg, options);
|
||||
const externalManifestRecords = listExternalChannelManifestRecords({
|
||||
cfg,
|
||||
workspaceDir,
|
||||
env,
|
||||
cache: options.cache,
|
||||
});
|
||||
const configuredChannelIds = [
|
||||
...new Set([
|
||||
...listPotentialConfiguredChannelIds(cfg, env, {
|
||||
includePersistedAuthState: options.includePersistedAuthState,
|
||||
}),
|
||||
...listExternalEnvConfiguredChannelIds({
|
||||
records: externalManifestRecords,
|
||||
env,
|
||||
}),
|
||||
]),
|
||||
];
|
||||
const byId = new Map<string, ChannelPlugin>();
|
||||
|
||||
addChannelPlugins(byId, listChannelPlugins());
|
||||
@@ -120,7 +176,8 @@ export function listReadOnlyChannelPluginsForConfig(
|
||||
cfg,
|
||||
activationSourceConfig: options.activationSourceConfig ?? cfg,
|
||||
channelIds: missingConfiguredChannelIds,
|
||||
workspaceDir: options.workspaceDir,
|
||||
records: externalManifestRecords,
|
||||
workspaceDir,
|
||||
env,
|
||||
cache: options.cache,
|
||||
});
|
||||
@@ -129,11 +186,12 @@ export function listReadOnlyChannelPluginsForConfig(
|
||||
config: cfg,
|
||||
activationSourceConfig: options.activationSourceConfig ?? cfg,
|
||||
env,
|
||||
workspaceDir: options.workspaceDir,
|
||||
workspaceDir,
|
||||
cache: false,
|
||||
activate: false,
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
forceSetupOnlyChannelPlugins: true,
|
||||
requireSetupEntryForSetupOnlyChannelPlugins: true,
|
||||
onlyPluginIds: externalPluginIds,
|
||||
});
|
||||
addChannelPlugins(
|
||||
|
||||
@@ -135,6 +135,7 @@ export type PluginLoadOptions = {
|
||||
onlyPluginIds?: string[];
|
||||
includeSetupOnlyChannelPlugins?: boolean;
|
||||
forceSetupOnlyChannelPlugins?: boolean;
|
||||
requireSetupEntryForSetupOnlyChannelPlugins?: boolean;
|
||||
/**
|
||||
* Prefer `setupEntry` for configured channel plugins that explicitly opt in
|
||||
* via package metadata because their setup entry covers the pre-listen startup surface.
|
||||
@@ -507,6 +508,7 @@ function buildCacheKey(params: {
|
||||
onlyPluginIds?: string[];
|
||||
includeSetupOnlyChannelPlugins?: boolean;
|
||||
forceSetupOnlyChannelPlugins?: boolean;
|
||||
requireSetupEntryForSetupOnlyChannelPlugins?: boolean;
|
||||
preferSetupRuntimeForChannelPlugins?: boolean;
|
||||
loadModules?: boolean;
|
||||
runtimeSubagentMode?: "default" | "explicit" | "gateway-bindable";
|
||||
@@ -538,6 +540,10 @@ function buildCacheKey(params: {
|
||||
const setupOnlyKey = params.includeSetupOnlyChannelPlugins === true ? "setup-only" : "runtime";
|
||||
const setupOnlyModeKey =
|
||||
params.forceSetupOnlyChannelPlugins === true ? "force-setup" : "normal-setup";
|
||||
const setupOnlyRequirementKey =
|
||||
params.requireSetupEntryForSetupOnlyChannelPlugins === true
|
||||
? "require-setup-entry"
|
||||
: "allow-full-fallback";
|
||||
const startupChannelMode =
|
||||
params.preferSetupRuntimeForChannelPlugins === true ? "prefer-setup" : "full";
|
||||
const moduleLoadMode = params.loadModules === false ? "manifest-only" : "load-modules";
|
||||
@@ -548,7 +554,7 @@ function buildCacheKey(params: {
|
||||
installs,
|
||||
loadPaths,
|
||||
activationMetadataKey: params.activationMetadataKey ?? "",
|
||||
})}::${scopeKey}::${setupOnlyKey}::${setupOnlyModeKey}::${startupChannelMode}::${moduleLoadMode}::${runtimeSubagentMode}::${params.pluginSdkResolution ?? "auto"}::${gatewayMethodsKey}`;
|
||||
})}::${scopeKey}::${setupOnlyKey}::${setupOnlyModeKey}::${setupOnlyRequirementKey}::${startupChannelMode}::${moduleLoadMode}::${runtimeSubagentMode}::${params.pluginSdkResolution ?? "auto"}::${gatewayMethodsKey}`;
|
||||
}
|
||||
|
||||
function matchesScopedPluginRequest(params: {
|
||||
@@ -624,6 +630,7 @@ function hasExplicitCompatibilityInputs(options: PluginLoadOptions): boolean {
|
||||
options.coreGatewayHandlers !== undefined ||
|
||||
options.includeSetupOnlyChannelPlugins === true ||
|
||||
options.forceSetupOnlyChannelPlugins === true ||
|
||||
options.requireSetupEntryForSetupOnlyChannelPlugins === true ||
|
||||
options.preferSetupRuntimeForChannelPlugins === true ||
|
||||
options.loadModules === false
|
||||
);
|
||||
@@ -640,6 +647,8 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
|
||||
const onlyPluginIds = normalizePluginIdScope(options.onlyPluginIds);
|
||||
const includeSetupOnlyChannelPlugins = options.includeSetupOnlyChannelPlugins === true;
|
||||
const forceSetupOnlyChannelPlugins = options.forceSetupOnlyChannelPlugins === true;
|
||||
const requireSetupEntryForSetupOnlyChannelPlugins =
|
||||
options.requireSetupEntryForSetupOnlyChannelPlugins === true;
|
||||
const preferSetupRuntimeForChannelPlugins = options.preferSetupRuntimeForChannelPlugins === true;
|
||||
const runtimeSubagentMode = resolveRuntimeSubagentMode(options.runtimeOptions);
|
||||
const coreGatewayMethodNames = Object.keys(options.coreGatewayHandlers ?? {}).toSorted();
|
||||
@@ -655,6 +664,7 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
|
||||
onlyPluginIds,
|
||||
includeSetupOnlyChannelPlugins,
|
||||
forceSetupOnlyChannelPlugins,
|
||||
requireSetupEntryForSetupOnlyChannelPlugins,
|
||||
preferSetupRuntimeForChannelPlugins,
|
||||
loadModules: options.loadModules,
|
||||
runtimeSubagentMode,
|
||||
@@ -671,6 +681,7 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
|
||||
onlyPluginIds,
|
||||
includeSetupOnlyChannelPlugins,
|
||||
forceSetupOnlyChannelPlugins,
|
||||
requireSetupEntryForSetupOnlyChannelPlugins,
|
||||
preferSetupRuntimeForChannelPlugins,
|
||||
shouldActivate: options.activate !== false,
|
||||
shouldLoadModules: options.loadModules !== false,
|
||||
@@ -988,6 +999,17 @@ function shouldLoadChannelPluginInSetupRuntime(params: {
|
||||
);
|
||||
}
|
||||
|
||||
function channelPluginIdBelongsToManifest(params: {
|
||||
channelId: string | undefined;
|
||||
pluginId: string;
|
||||
manifestChannels: readonly string[];
|
||||
}): boolean {
|
||||
if (!params.channelId) {
|
||||
return true;
|
||||
}
|
||||
return params.channelId === params.pluginId || params.manifestChannels.includes(params.channelId);
|
||||
}
|
||||
|
||||
function createPluginRecord(params: {
|
||||
id: string;
|
||||
name?: string;
|
||||
@@ -1419,6 +1441,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
onlyPluginIds,
|
||||
includeSetupOnlyChannelPlugins,
|
||||
forceSetupOnlyChannelPlugins,
|
||||
requireSetupEntryForSetupOnlyChannelPlugins,
|
||||
preferSetupRuntimeForChannelPlugins,
|
||||
shouldActivate,
|
||||
shouldLoadModules,
|
||||
@@ -1749,29 +1772,34 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
}
|
||||
}
|
||||
|
||||
const canLoadScopedSetupOnlyChannelPlugin =
|
||||
const scopedSetupOnlyChannelPluginRequested =
|
||||
includeSetupOnlyChannelPlugins &&
|
||||
!validateOnly &&
|
||||
onlyPluginIdSet &&
|
||||
manifestRecord.channels.length > 0 &&
|
||||
(!enableState.enabled || forceSetupOnlyChannelPlugins);
|
||||
const canLoadScopedSetupOnlyChannelPlugin =
|
||||
scopedSetupOnlyChannelPluginRequested &&
|
||||
(!requireSetupEntryForSetupOnlyChannelPlugins || Boolean(manifestRecord.setupSource));
|
||||
const registrationMode = canLoadScopedSetupOnlyChannelPlugin
|
||||
? "setup-only"
|
||||
: enableState.enabled
|
||||
? shouldLoadModules &&
|
||||
!validateOnly &&
|
||||
shouldLoadChannelPluginInSetupRuntime({
|
||||
manifestChannels: manifestRecord.channels,
|
||||
setupSource: manifestRecord.setupSource,
|
||||
startupDeferConfiguredChannelFullLoadUntilAfterListen:
|
||||
manifestRecord.startupDeferConfiguredChannelFullLoadUntilAfterListen,
|
||||
cfg,
|
||||
env,
|
||||
preferSetupRuntimeForChannelPlugins,
|
||||
})
|
||||
? "setup-runtime"
|
||||
: "full"
|
||||
: null;
|
||||
: 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";
|
||||
@@ -1992,7 +2020,13 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
continue;
|
||||
}
|
||||
if (setupRegistration.plugin) {
|
||||
if (setupRegistration.plugin.id && setupRegistration.plugin.id !== record.id) {
|
||||
if (
|
||||
!channelPluginIdBelongsToManifest({
|
||||
channelId: setupRegistration.plugin.id,
|
||||
pluginId: record.id,
|
||||
manifestChannels: manifestRecord.channels,
|
||||
})
|
||||
) {
|
||||
pushPluginLoadError(
|
||||
`plugin id mismatch (config uses "${record.id}", setup export uses "${setupRegistration.plugin.id}")`,
|
||||
);
|
||||
@@ -2117,7 +2151,13 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
if (!mergedSetupPlugin) {
|
||||
continue;
|
||||
}
|
||||
if (mergedSetupPlugin.id && mergedSetupPlugin.id !== record.id) {
|
||||
if (
|
||||
!channelPluginIdBelongsToManifest({
|
||||
channelId: mergedSetupPlugin.id,
|
||||
pluginId: record.id,
|
||||
manifestChannels: manifestRecord.channels,
|
||||
})
|
||||
) {
|
||||
pushPluginLoadError(
|
||||
`plugin id mismatch (config uses "${record.id}", setup export uses "${mergedSetupPlugin.id}")`,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user