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

@@ -27,6 +27,7 @@ import { normalizeE164 } from "../../utils.js";
import { resolveHeartbeatSeconds } from "../../web/reconnect.js";
import { getWebAuthAgeMs, webAuthExists } from "../../web/session.js";
import { resolveCommandAuthorization } from "../command-auth.js";
import { shouldHandleTextCommands } from "../commands-registry.js";
import {
normalizeGroupActivation,
parseActivationCommand,
@@ -47,6 +48,7 @@ import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
import { incrementCompactionCount } from "./session-updates.js";
export type CommandContext = {
surface: string;
provider: string;
isWhatsAppProvider: boolean;
ownerList: string[];
@@ -123,7 +125,8 @@ export function buildCommandContext(params: {
cfg,
commandAuthorized: params.commandAuthorized,
});
const provider = (ctx.Provider ?? "").trim().toLowerCase();
const surface = (ctx.Surface ?? ctx.Provider ?? "").trim().toLowerCase();
const provider = (ctx.Provider ?? surface).trim().toLowerCase();
const abortKey =
sessionKey ?? (auth.from || undefined) ?? (auth.to || undefined);
const rawBodyNormalized = triggerBodyNormalized;
@@ -132,6 +135,7 @@ export function buildCommandContext(params: {
: rawBodyNormalized;
return {
surface,
provider,
isWhatsAppProvider: auth.isWhatsAppProvider,
ownerList: auth.ownerList,
@@ -207,8 +211,13 @@ export async function handleCommands(params: {
const sendPolicyCommand = parseSendPolicyCommand(
command.commandBodyNormalized,
);
const allowTextCommands = shouldHandleTextCommands({
cfg,
surface: command.surface,
commandSource: ctx.CommandSource,
});
if (activationCommand.hasCommand) {
if (allowTextCommands && activationCommand.hasCommand) {
if (!isGroup) {
return {
shouldContinue: false,
@@ -255,7 +264,7 @@ export async function handleCommands(params: {
};
}
if (sendPolicyCommand.hasCommand) {
if (allowTextCommands && sendPolicyCommand.hasCommand) {
if (!command.isAuthorizedSender) {
logVerbose(
`Ignoring /send from unauthorized sender: ${command.senderE164 || "<unknown>"}`,
@@ -292,10 +301,7 @@ export async function handleCommands(params: {
};
}
if (
command.commandBodyNormalized === "/restart" ||
command.commandBodyNormalized.startsWith("/restart ")
) {
if (allowTextCommands && command.commandBodyNormalized === "/restart") {
if (!command.isAuthorizedSender) {
logVerbose(
`Ignoring /restart from unauthorized sender: ${command.senderE164 || "<unknown>"}`,
@@ -311,10 +317,8 @@ export async function handleCommands(params: {
};
}
const helpRequested =
command.commandBodyNormalized === "/help" ||
/(?:^|\s)\/help(?=$|\s|:)\b/i.test(command.commandBodyNormalized);
if (helpRequested) {
const helpRequested = command.commandBodyNormalized === "/help";
if (allowTextCommands && helpRequested) {
if (!command.isAuthorizedSender) {
logVerbose(
`Ignoring /help from unauthorized sender: ${command.senderE164 || "<unknown>"}`,
@@ -326,9 +330,8 @@ export async function handleCommands(params: {
const statusRequested =
directives.hasStatusDirective ||
command.commandBodyNormalized === "/status" ||
command.commandBodyNormalized.startsWith("/status ");
if (statusRequested) {
command.commandBodyNormalized === "/status";
if (allowTextCommands && statusRequested) {
if (!command.isAuthorizedSender) {
logVerbose(
`Ignoring /status from unauthorized sender: ${command.senderE164 || "<unknown>"}`,
@@ -451,7 +454,7 @@ export async function handleCommands(params: {
}
const abortRequested = isAbortTrigger(command.rawBodyNormalized);
if (abortRequested) {
if (allowTextCommands && abortRequested) {
if (sessionEntry && sessionStore && sessionKey) {
sessionEntry.abortedLastRun = true;
sessionEntry.updatedAt = Date.now();