Files
openclaw/src/channels/plugins/read-only-command-defaults.ts
T

112 lines
4.0 KiB
TypeScript

import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { isBlockedObjectKey } from "../../infra/prototype-keys.js";
import { getCurrentPluginMetadataSnapshot } from "../../plugins/current-plugin-metadata-snapshot.js";
import { isInstalledPluginEnabled } from "../../plugins/installed-plugin-index.js";
import type { PluginManifestRecord } from "../../plugins/manifest-registry.js";
import { loadPluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import type { ChannelPlugin } from "./types.plugin.js";
const SAFE_MANIFEST_CHANNEL_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
export type ChannelCommandDefaults = Pick<
NonNullable<ChannelPlugin["commands"]>,
"nativeCommandsAutoEnabled" | "nativeSkillsAutoEnabled"
>;
type ManifestChannelConfigRecord = NonNullable<PluginManifestRecord["channelConfigs"]>[string];
export function isSafeManifestChannelId(channelId: string): boolean {
return SAFE_MANIFEST_CHANNEL_ID_PATTERN.test(channelId) && !isBlockedObjectKey(channelId);
}
export function readOwnRecordValue(record: Record<string, unknown>, key: string): unknown {
if (isBlockedObjectKey(key) || !Object.prototype.hasOwnProperty.call(record, key)) {
return undefined;
}
return record[key];
}
export function normalizeChannelCommandDefaults(
value: ChannelCommandDefaults | undefined,
): ChannelCommandDefaults | undefined {
if (!value) {
return undefined;
}
const nativeCommandsAutoEnabled =
typeof value.nativeCommandsAutoEnabled === "boolean"
? value.nativeCommandsAutoEnabled
: undefined;
const nativeSkillsAutoEnabled =
typeof value.nativeSkillsAutoEnabled === "boolean" ? value.nativeSkillsAutoEnabled : undefined;
if (nativeCommandsAutoEnabled === undefined && nativeSkillsAutoEnabled === undefined) {
return undefined;
}
const defaults: ChannelCommandDefaults = {};
if (nativeCommandsAutoEnabled !== undefined) {
defaults.nativeCommandsAutoEnabled = nativeCommandsAutoEnabled;
}
if (nativeSkillsAutoEnabled !== undefined) {
defaults.nativeSkillsAutoEnabled = nativeSkillsAutoEnabled;
}
return defaults;
}
export function resolveReadOnlyChannelCommandDefaults(
channelId: string,
options: {
env?: NodeJS.ProcessEnv;
stateDir?: string;
workspaceDir?: string;
config: OpenClawConfig;
},
): ChannelCommandDefaults | undefined {
const normalizedChannelId = normalizeOptionalString(channelId) ?? "";
if (!normalizedChannelId || !isSafeManifestChannelId(normalizedChannelId)) {
return undefined;
}
const env = options.env ?? process.env;
const snapshot =
options.stateDir === undefined
? getCurrentPluginMetadataSnapshot({
config: options.config,
env,
workspaceDir: options.workspaceDir,
})
: undefined;
const resolvedSnapshot =
snapshot ??
loadPluginMetadataSnapshot({
config: options.config,
stateDir: options.stateDir,
workspaceDir: options.workspaceDir,
env,
});
for (const record of resolvedSnapshot.plugins) {
if (!record.channels.includes(normalizedChannelId)) {
continue;
}
if (!isInstalledPluginEnabled(resolvedSnapshot.index, record.id, options.config)) {
continue;
}
const channelConfigValue = record.channelConfigs
? readOwnRecordValue(record.channelConfigs as Record<string, unknown>, normalizedChannelId)
: undefined;
const channelConfig =
channelConfigValue &&
typeof channelConfigValue === "object" &&
!Array.isArray(channelConfigValue)
? (channelConfigValue as ManifestChannelConfigRecord)
: undefined;
const catalogCommands =
record.channelCatalogMeta?.id === normalizedChannelId
? record.channelCatalogMeta.commands
: undefined;
const commands = normalizeChannelCommandDefaults(channelConfig?.commands ?? catalogCommands);
if (commands) {
return commands;
}
}
return undefined;
}