refactor: require explicit read-only plugin options

This commit is contained in:
Gustavo Madeira Santana
2026-04-21 11:44:14 -04:00
parent 06c23e36ef
commit a849ea5ef5
2 changed files with 11 additions and 73 deletions

View File

@@ -366,7 +366,7 @@ describe("listReadOnlyChannelPluginsForConfig", () => {
expect(fs.existsSync(fullMarker)).toBe(false);
});
it("treats process env maps with option-like keys as env maps", () => {
it("accepts option-like env keys through the explicit env option", () => {
const { pluginDir, fullMarker, setupMarker } = writeExternalSetupChannelPlugin({
pluginId: "external-chat-plugin",
channelId: "external-chat",
@@ -379,12 +379,14 @@ describe("listReadOnlyChannelPluginsForConfig", () => {
},
} as never,
{
...process.env,
cache: "true",
env: "prod",
EXTERNAL_CHAT_TOKEN: "configured",
workspaceDir: "workspace-env-value",
} as NodeJS.ProcessEnv,
env: {
...process.env,
cache: "true",
env: "prod",
EXTERNAL_CHAT_TOKEN: "configured",
workspaceDir: "workspace-env-value",
},
},
);
const plugin = plugins.find((entry) => entry.id === "external-chat");

View File

@@ -27,61 +27,6 @@ type ReadOnlyChannelPluginResolution = {
missingConfiguredChannelIds: string[];
};
const READ_ONLY_CHANNEL_PLUGIN_OPTION_KEYS = new Set([
"env",
"workspaceDir",
"activationSourceConfig",
"includePersistedAuthState",
"cache",
]);
function hasOwnRecordKey(record: Record<string, unknown>, key: string): boolean {
return Object.prototype.hasOwnProperty.call(record, key);
}
function isRecordLike(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function isReadOnlyChannelPluginOptions(
value: NodeJS.ProcessEnv | ReadOnlyChannelPluginOptions,
): value is ReadOnlyChannelPluginOptions {
const record = value as Record<string, unknown>;
if (hasOwnRecordKey(record, "env")) {
return record.env === undefined || isRecordLike(record.env);
}
if (hasOwnRecordKey(record, "activationSourceConfig")) {
return (
record.activationSourceConfig === undefined || isRecordLike(record.activationSourceConfig)
);
}
if (hasOwnRecordKey(record, "includePersistedAuthState")) {
return (
record.includePersistedAuthState === undefined ||
typeof record.includePersistedAuthState === "boolean"
);
}
if (hasOwnRecordKey(record, "cache")) {
return record.cache === undefined || typeof record.cache === "boolean";
}
if (hasOwnRecordKey(record, "workspaceDir")) {
return Object.keys(record).every((key) => READ_ONLY_CHANNEL_PLUGIN_OPTION_KEYS.has(key));
}
return false;
}
function resolveReadOnlyChannelPluginOptions(
envOrOptions?: NodeJS.ProcessEnv | ReadOnlyChannelPluginOptions,
): ReadOnlyChannelPluginOptions {
if (!envOrOptions) {
return {};
}
if (isReadOnlyChannelPluginOptions(envOrOptions)) {
return envOrOptions;
}
return { env: envOrOptions };
}
function addChannelPlugins(
byId: Map<string, ChannelPlugin>,
plugins: Iterable<ChannelPlugin | undefined>,
@@ -395,26 +340,17 @@ function resolveExternalReadOnlyChannelPluginIds(params: {
.toSorted((left, right) => left.localeCompare(right));
}
export function listReadOnlyChannelPluginsForConfig(
cfg: OpenClawConfig,
env?: NodeJS.ProcessEnv,
): ChannelPlugin[];
export function listReadOnlyChannelPluginsForConfig(
cfg: OpenClawConfig,
options?: ReadOnlyChannelPluginOptions,
): ChannelPlugin[];
export function listReadOnlyChannelPluginsForConfig(
cfg: OpenClawConfig,
envOrOptions?: NodeJS.ProcessEnv | ReadOnlyChannelPluginOptions,
): ChannelPlugin[] {
return resolveReadOnlyChannelPluginsForConfig(cfg, envOrOptions).plugins;
return resolveReadOnlyChannelPluginsForConfig(cfg, options).plugins;
}
export function resolveReadOnlyChannelPluginsForConfig(
cfg: OpenClawConfig,
envOrOptions?: NodeJS.ProcessEnv | ReadOnlyChannelPluginOptions,
options: ReadOnlyChannelPluginOptions = {},
): ReadOnlyChannelPluginResolution {
const options = resolveReadOnlyChannelPluginOptions(envOrOptions);
const env = options.env ?? process.env;
const workspaceDir = resolveReadOnlyWorkspaceDir(cfg, options);
const externalManifestRecords = listExternalChannelManifestRecords({