mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
fix: keep channel command defaults read-only
This commit is contained in:
86
src/channels/plugins/read-only-command-defaults.ts
Normal file
86
src/channels/plugins/read-only-command-defaults.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { isBlockedObjectKey } from "../../infra/prototype-keys.js";
|
||||
import type { PluginManifestRecord } from "../../plugins/manifest-registry.js";
|
||||
import { loadPluginManifestRegistryForPluginRegistry } from "../../plugins/plugin-registry.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;
|
||||
return nativeCommandsAutoEnabled !== undefined || nativeSkillsAutoEnabled !== undefined
|
||||
? {
|
||||
...(nativeCommandsAutoEnabled !== undefined ? { nativeCommandsAutoEnabled } : {}),
|
||||
...(nativeSkillsAutoEnabled !== undefined ? { nativeSkillsAutoEnabled } : {}),
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function resolveReadOnlyChannelCommandDefaults(
|
||||
channelId: string,
|
||||
options: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
stateDir?: string;
|
||||
workspaceDir?: string;
|
||||
} = {},
|
||||
): ChannelCommandDefaults | undefined {
|
||||
const normalizedChannelId = normalizeOptionalString(channelId) ?? "";
|
||||
if (!normalizedChannelId || !isSafeManifestChannelId(normalizedChannelId)) {
|
||||
return undefined;
|
||||
}
|
||||
const registry = loadPluginManifestRegistryForPluginRegistry({
|
||||
stateDir: options.stateDir,
|
||||
workspaceDir: options.workspaceDir,
|
||||
env: options.env ?? process.env,
|
||||
includeDisabled: true,
|
||||
});
|
||||
for (const record of registry.plugins) {
|
||||
if (!record.channels.includes(normalizedChannelId)) {
|
||||
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 commands = normalizeChannelCommandDefaults(
|
||||
channelConfig?.commands ?? record.channelCatalogMeta?.commands,
|
||||
);
|
||||
if (commands) {
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -15,13 +15,17 @@ import type { loadOpenClawPlugins as loadOpenClawPluginsType } from "../../plugi
|
||||
import type { PluginManifestRecord } from "../../plugins/manifest-registry.js";
|
||||
import { loadPluginManifestRegistryForPluginRegistry } from "../../plugins/plugin-registry.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { sanitizeForLog } from "../../terminal/ansi.js";
|
||||
import { getBundledChannelSetupPlugin } from "./bundled.js";
|
||||
import {
|
||||
isSafeManifestChannelId,
|
||||
normalizeChannelCommandDefaults,
|
||||
readOwnRecordValue,
|
||||
resolveReadOnlyChannelCommandDefaults,
|
||||
} from "./read-only-command-defaults.js";
|
||||
import { listChannelPlugins } from "./registry.js";
|
||||
import type { ChannelPlugin } from "./types.plugin.js";
|
||||
|
||||
const SAFE_MANIFEST_CHANNEL_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
||||
const LOADER_MODULE_CANDIDATES = [
|
||||
new URL("../../plugins/loader.js", import.meta.url),
|
||||
new URL("../../plugins/loader.ts", import.meta.url),
|
||||
@@ -73,10 +77,6 @@ type ReadOnlyChannelPluginResolution = {
|
||||
missingConfiguredChannelIds: string[];
|
||||
};
|
||||
type ManifestChannelConfigRecord = NonNullable<PluginManifestRecord["channelConfigs"]>[string];
|
||||
type ChannelCommandDefaults = Pick<
|
||||
NonNullable<ChannelPlugin["commands"]>,
|
||||
"nativeCommandsAutoEnabled" | "nativeSkillsAutoEnabled"
|
||||
>;
|
||||
|
||||
function addChannelPlugins(
|
||||
byId: Map<string, ChannelPlugin>,
|
||||
@@ -115,41 +115,10 @@ function rebindChannelScopedString(
|
||||
return value;
|
||||
}
|
||||
|
||||
function isSafeManifestChannelId(channelId: string): boolean {
|
||||
return SAFE_MANIFEST_CHANNEL_ID_PATTERN.test(channelId) && !isBlockedObjectKey(channelId);
|
||||
}
|
||||
|
||||
function readOwnRecordValue(record: Record<string, unknown>, key: string): unknown {
|
||||
if (isBlockedObjectKey(key) || !Object.prototype.hasOwnProperty.call(record, key)) {
|
||||
return undefined;
|
||||
}
|
||||
return record[key];
|
||||
}
|
||||
|
||||
function normalizeManifestText(value: string | undefined, fallback: string): string {
|
||||
return sanitizeForLog(value?.trim() || fallback).trim();
|
||||
}
|
||||
|
||||
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;
|
||||
return nativeCommandsAutoEnabled !== undefined || nativeSkillsAutoEnabled !== undefined
|
||||
? {
|
||||
...(nativeCommandsAutoEnabled !== undefined ? { nativeCommandsAutoEnabled } : {}),
|
||||
...(nativeSkillsAutoEnabled !== undefined ? { nativeSkillsAutoEnabled } : {}),
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function rebindChannelConfig(
|
||||
cfg: OpenClawConfig,
|
||||
sourceChannelId: string,
|
||||
@@ -347,46 +316,7 @@ function canUseManifestChannelPlugin(record: PluginManifestRecord, channelId: st
|
||||
return record.channelCatalogMeta?.id === channelId;
|
||||
}
|
||||
|
||||
export function resolveReadOnlyChannelCommandDefaults(
|
||||
channelId: string,
|
||||
options: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
stateDir?: string;
|
||||
workspaceDir?: string;
|
||||
} = {},
|
||||
): ChannelCommandDefaults | undefined {
|
||||
const normalizedChannelId = normalizeOptionalString(channelId) ?? "";
|
||||
if (!normalizedChannelId || !isSafeManifestChannelId(normalizedChannelId)) {
|
||||
return undefined;
|
||||
}
|
||||
const registry = loadPluginManifestRegistryForPluginRegistry({
|
||||
stateDir: options.stateDir,
|
||||
workspaceDir: options.workspaceDir,
|
||||
env: options.env ?? process.env,
|
||||
includeDisabled: true,
|
||||
});
|
||||
for (const record of registry.plugins) {
|
||||
if (!record.channels.includes(normalizedChannelId)) {
|
||||
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 commands = normalizeChannelCommandDefaults(
|
||||
channelConfig?.commands ?? record.channelCatalogMeta?.commands,
|
||||
);
|
||||
if (commands) {
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
export { resolveReadOnlyChannelCommandDefaults };
|
||||
|
||||
function rebindChannelPluginConfig(
|
||||
config: ChannelPlugin["config"],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getLoadedChannelPlugin, normalizeChannelId } from "../channels/plugins/index.js";
|
||||
import { resolveReadOnlyChannelCommandDefaults } from "../channels/plugins/read-only.js";
|
||||
import { resolveReadOnlyChannelCommandDefaults } from "../channels/plugins/read-only-command-defaults.js";
|
||||
import type { ChannelId } from "../channels/plugins/types.public.js";
|
||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import type { NativeCommandsSetting } from "./types.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getLoadedChannelPlugin } from "../channels/plugins/index.js";
|
||||
import { resolveReadOnlyChannelCommandDefaults } from "../channels/plugins/read-only.js";
|
||||
import { resolveReadOnlyChannelCommandDefaults } from "../channels/plugins/read-only-command-defaults.js";
|
||||
import { resolveGlobalSingleton } from "../shared/global-singleton.js";
|
||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import type { OpenClawPluginCommandDefinition } from "./types.js";
|
||||
|
||||
Reference in New Issue
Block a user