feat(commands): unify chat commands (#275)

* Chat commands: registry, access groups, Carbon

* Chat commands: clear native commands on disable

* fix(commands): align command surface typing

* docs(changelog): note commands registry (PR #275)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Shadow
2026-01-06 14:17:56 -06:00
committed by GitHub
parent 1bf44bf30c
commit 9b22e1f6e9
40 changed files with 2357 additions and 1459 deletions

View File

@@ -32,6 +32,7 @@ const GROUP_LABELS: Record<string, string> = {
models: "Models",
routing: "Routing",
messages: "Messages",
commands: "Commands",
session: "Session",
cron: "Cron",
hooks: "Hooks",
@@ -58,6 +59,7 @@ const GROUP_ORDER: Record<string, number> = {
models: 50,
routing: 60,
messages: 70,
commands: 75,
session: 80,
cron: 90,
hooks: 100,
@@ -94,6 +96,9 @@ const FIELD_LABELS: Record<string, string> = {
"agent.model.fallbacks": "Model Fallbacks",
"agent.imageModel.primary": "Image Model",
"agent.imageModel.fallbacks": "Image Model Fallbacks",
"commands.native": "Native Commands",
"commands.text": "Text Commands",
"commands.useAccessGroups": "Use Access Groups",
"ui.seamColor": "Accent Color",
"browser.controlUrl": "Browser Control URL",
"session.agentToAgent.maxPingPongTurns": "Agent-to-Agent Ping-Pong Turns",
@@ -137,6 +142,11 @@ const FIELD_HELP: Record<string, string> = {
"Optional image model (provider/model) used when the primary model lacks image input.",
"agent.imageModel.fallbacks":
"Ordered fallback image models (provider/model).",
"commands.native":
"Register native commands with connectors that support it (Discord/Slack/Telegram).",
"commands.text": "Allow text command parsing (slash commands only).",
"commands.useAccessGroups":
"Enforce access-group allowlists/policies for commands.",
"session.agentToAgent.maxPingPongTurns":
"Max reply-back turns between requester and target (05).",
"messages.ackReaction":

View File

@@ -300,17 +300,6 @@ export type DiscordGuildEntry = {
channels?: Record<string, DiscordGuildChannelConfig>;
};
export type DiscordSlashCommandConfig = {
/** Enable handling for the configured slash command (default: false). */
enabled?: boolean;
/** Slash command name (default: "clawd"). */
name?: string;
/** Session key prefix for slash commands (default: "discord:slash"). */
sessionPrefix?: string;
/** Reply ephemerally (default: true). */
ephemeral?: boolean;
};
export type DiscordActionConfig = {
reactions?: boolean;
stickers?: boolean;
@@ -350,7 +339,6 @@ export type DiscordConfig = {
actions?: DiscordActionConfig;
/** Control reply threading when reply tags are present (off|first|all). */
replyToMode?: ReplyToMode;
slashCommand?: DiscordSlashCommandConfig;
dm?: DiscordDmConfig;
/** New per-guild config keyed by guild id or slug. */
guilds?: Record<string, DiscordGuildEntry>;
@@ -577,6 +565,15 @@ export type MessagesConfig = {
ackReactionScope?: "group-mentions" | "group-all" | "direct" | "all";
};
export type CommandsConfig = {
/** Enable native command registration when supported (default: false). */
native?: boolean;
/** Enable text command parsing (default: true). */
text?: boolean;
/** Enforce access-group allowlists/policies for commands (default: true). */
useAccessGroups?: boolean;
};
export type BridgeBindMode = "auto" | "lan" | "tailnet" | "loopback";
export type BridgeConfig = {
@@ -998,6 +995,7 @@ export type ClawdbotConfig = {
};
routing?: RoutingConfig;
messages?: MessagesConfig;
commands?: CommandsConfig;
session?: SessionConfig;
web?: WebConfig;
whatsapp?: WhatsAppConfig;

View File

@@ -165,6 +165,14 @@ const MessagesSchema = z
})
.optional();
const CommandsSchema = z
.object({
native: z.boolean().optional(),
text: z.boolean().optional(),
useAccessGroups: z.boolean().optional(),
})
.optional();
const HeartbeatSchema = z
.object({
every: z.string().optional(),
@@ -632,6 +640,7 @@ export const ClawdbotSchema = z.object({
.optional(),
routing: RoutingSchema,
messages: MessagesSchema,
commands: CommandsSchema,
session: SessionSchema,
cron: z
.object({
@@ -786,14 +795,6 @@ export const ClawdbotSchema = z.object({
token: z.string().optional(),
groupPolicy: GroupPolicySchema.optional().default("open"),
textChunkLimit: z.number().int().positive().optional(),
slashCommand: z
.object({
enabled: z.boolean().optional(),
name: z.string().optional(),
sessionPrefix: z.string().optional(),
ephemeral: z.boolean().optional(),
})
.optional(),
mediaMaxMb: z.number().positive().optional(),
historyLimit: z.number().int().min(0).optional(),
actions: z