import { resolveConfigWriteTargetFromPath } from "../../channels/plugins/config-writes.js" ;
import { normalizeChannelId } from "../../channels/registry.js" ;
import {
getConfigValueAtPath,
parseConfigPath,
setConfigValueAtPath,
unsetConfigValueAtPath,
} from "../../config/config-paths.js" ;
import {
readConfigFileSnapshot,
validateConfigObjectWithPlugins,
writeConfigFile,
} from "../../config/config.js" ;
import {
getConfigOverrides,
resetConfigOverrides,
setConfigOverride,
unsetConfigOverride,
} from "../../config/runtime-overrides.js" ;
import { normalizeOptionalString } from "../../shared/string-coerce.js" ;
import { isInternalMessageChannel } from "../../utils/message-channel.js" ;
import { resolveChannelAccountId } from "./channel-context.js" ;
import {
rejectNonOwnerCommand,
rejectUnauthorizedCommand,
requireCommandFlagEnabled,
requireGatewayClientScopeForInternalChannel,
} from "./command-gates.js" ;
import type { CommandHandler } from "./commands-types.js" ;
import { parseConfigCommand } from "./config-commands.js" ;
import { resolveConfigWriteDeniedText } from "./config-write-authorization.js" ;
import { parseDebugCommand } from "./debug-commands.js" ;
export const handleConfigCommand: CommandHandler = async (params, allowTextCommands) => {
if (!allowTextCommands) {
return null ;
}
const configCommand = parseConfigCommand(params.command.commandBodyNormalized);
if (!configCommand) {
return null ;
}
const unauthorized = rejectUnauthorizedCommand(params, "/config" );
if (unauthorized) {
return unauthorized;
}
const allowInternalReadOnlyShow =
configCommand.action === "show" && isInternalMessageChannel(params.command.channel);
const nonOwner = allowInternalReadOnlyShow ? null : rejectNonOwnerCommand(params, "/config" );
if (nonOwner) {
return nonOwner;
}
const disabled = requireCommandFlagEnabled(params.cfg, {
label: "/config" ,
configKey: "config" ,
});
if (disabled) {
return disabled;
}
if (configCommand.action === "error" ) {
return {
shouldContinue: false ,
reply: { text: `⚠️ ${configCommand.message}` },
};
}
let parsedWritePath: string[] | undefined;
if (configCommand.action === "set" || configCommand.action === "unset" ) {
const missingAdminScope = requireGatewayClientScopeForInternalChannel(params, {
label: "/config write" ,
allowedScopes: ["operator.admin" ],
missingText: "❌ /config set|unset requires operator.admin for gateway clients." ,
});
if (missingAdminScope) {
return missingAdminScope;
}
const parsedPath = parseConfigPath(configCommand.path);
if (!parsedPath.ok || !parsedPath.path) {
return {
shouldContinue: false ,
reply: { text: `⚠️ ${parsedPath.error ?? "Invalid path." }` },
};
}
parsedWritePath = parsedPath.path;
const channelId = params.command.channelId ?? normalizeChannelId(params.command.channel);
const deniedText = resolveConfigWriteDeniedText({
cfg: params.cfg,
channel: params.command.channel,
channelId,
accountId: resolveChannelAccountId({
cfg: params.cfg,
ctx: params.ctx,
command: params.command,
}),
gatewayClientScopes: params.ctx.GatewayClientScopes,
target: resolveConfigWriteTargetFromPath(parsedWritePath),
});
if (deniedText) {
return {
shouldContinue: false ,
reply: {
text: deniedText,
},
};
}
}
const snapshot = await readConfigFileSnapshot();
if (!snapshot.valid || !snapshot.parsed || typeof snapshot.parsed !== "object" ) {
return {
shouldContinue: false ,
reply: {
text: "⚠️ Config file is invalid; fix it before using /config." ,
},
};
}
const parsedBase = structuredClone(snapshot.parsed as Record<string, unknown>);
if (configCommand.action === "show" ) {
const pathRaw = normalizeOptionalString(configCommand.path);
if (pathRaw) {
const parsedPath = parseConfigPath(pathRaw);
if (!parsedPath.ok || !parsedPath.path) {
return {
shouldContinue: false ,
reply: { text: `⚠️ ${parsedPath.error ?? "Invalid path." }` },
};
}
const value = getConfigValueAtPath(parsedBase, parsedPath.path);
const rendered = JSON.stringify(value ?? null , null , 2 );
return {
shouldContinue: false ,
reply: {
text: `⚙️ Config ${pathRaw}:\n\`\`\`json\n${rendered}\n\`\`\``,
},
};
}
const json = JSON.stringify(parsedBase, null , 2 );
return {
shouldContinue: false ,
reply: { text: `⚙️ Config (raw):\n\`\`\`json\n${json}\n\`\`\`` },
};
}
if (configCommand.action === "unset" ) {
const removed = unsetConfigValueAtPath(parsedBase, parsedWritePath ?? []);
if (!removed) {
return {
shouldContinue: false ,
reply: { text: `⚙️ No config value found for ${configCommand.path}.` },
};
}
const validated = validateConfigObjectWithPlugins(parsedBase);
if (!validated.ok) {
const issue = validated.issues[0 ];
return {
shouldContinue: false ,
reply: {
text: `⚠️ Config invalid after unset (${issue.path}: ${issue.message}).`,
},
};
}
await writeConfigFile(validated.config);
return {
shouldContinue: false ,
reply: { text: `⚙️ Config updated: ${configCommand.path} removed.` },
};
}
if (configCommand.action === "set" ) {
setConfigValueAtPath(parsedBase, parsedWritePath ?? [], configCommand.value);
const validated = validateConfigObjectWithPlugins(parsedBase);
if (!validated.ok) {
const issue = validated.issues[0 ];
return {
shouldContinue: false ,
reply: {
text: `⚠️ Config invalid after set (${issue.path}: ${issue.message}).`,
},
};
}
await writeConfigFile(validated.config);
const valueLabel =
typeof configCommand.value === "string"
? `"${configCommand.value}" `
: JSON.stringify(configCommand.value);
return {
shouldContinue: false ,
reply: {
text: `⚙️ Config updated: ${configCommand.path}=${valueLabel ?? "null" }`,
},
};
}
return null ;
};
export const handleDebugCommand: CommandHandler = async (params, allowTextCommands) => {
if (!allowTextCommands) {
return null ;
}
const debugCommand = parseDebugCommand(params.command.commandBodyNormalized);
if (!debugCommand) {
return null ;
}
const unauthorized = rejectUnauthorizedCommand(params, "/debug" );
if (unauthorized) {
return unauthorized;
}
const nonOwner = rejectNonOwnerCommand(params, "/debug" );
if (nonOwner) {
return nonOwner;
}
const disabled = requireCommandFlagEnabled(params.cfg, {
label: "/debug" ,
configKey: "debug" ,
});
if (disabled) {
return disabled;
}
if (debugCommand.action === "error" ) {
return {
shouldContinue: false ,
reply: { text: `⚠️ ${debugCommand.message}` },
};
}
if (debugCommand.action === "show" ) {
const overrides = getConfigOverrides();
const hasOverrides = Object.keys(overrides).length > 0 ;
if (!hasOverrides) {
return {
shouldContinue: false ,
reply: { text: "⚙️ Debug overrides: (none)" },
};
}
const json = JSON.stringify(overrides, null , 2 );
return {
shouldContinue: false ,
reply: {
text: `⚙️ Debug overrides (memory-only):\n\`\`\`json\n${json}\n\`\`\``,
},
};
}
if (debugCommand.action === "reset" ) {
resetConfigOverrides();
return {
shouldContinue: false ,
reply: { text: "⚙️ Debug overrides cleared; using config on disk." },
};
}
if (debugCommand.action === "unset" ) {
const result = unsetConfigOverride(debugCommand.path);
if (!result.ok) {
return {
shouldContinue: false ,
reply: { text: `⚠️ ${result.error ?? "Invalid path." }` },
};
}
if (!result.removed) {
return {
shouldContinue: false ,
reply: {
text: `⚙️ No debug override found for ${debugCommand.path}.`,
},
};
}
return {
shouldContinue: false ,
reply: { text: `⚙️ Debug override removed for ${debugCommand.path}.` },
};
}
if (debugCommand.action === "set" ) {
const result = setConfigOverride(debugCommand.path, debugCommand.value);
if (!result.ok) {
return {
shouldContinue: false ,
reply: { text: `⚠️ ${result.error ?? "Invalid override." }` },
};
}
const valueLabel =
typeof debugCommand.value === "string"
? `"${debugCommand.value}" `
: JSON.stringify(debugCommand.value);
return {
shouldContinue: false ,
reply: {
text: `⚙️ Debug override set: ${debugCommand.path}=${valueLabel ?? "null" }`,
},
};
}
return null ;
};
Messung V0.5 in Prozent C=96 H=96 G=95
¤ Dauer der Verarbeitung: 0.4 Sekunden
¤
*© Formatika GbR, Deutschland