fix: unify control command handling

This commit is contained in:
Peter Steinberger
2026-01-05 01:31:36 +01:00
parent 54ad1ead80
commit 852f947b44
14 changed files with 273 additions and 375 deletions

View File

@@ -21,12 +21,13 @@ import type { ThinkLevel, VerboseLevel } from "../thinking.js";
import type { ReplyPayload } from "../types.js";
import { isAbortTrigger, setAbortMemory } from "./abort.js";
import { stripMentions } from "./mentions.js";
import type { InlineDirectives } from "./directive-handling.js";
export type CommandContext = {
surface: string;
isWhatsAppSurface: boolean;
ownerList: string[];
isOwnerSender: boolean;
isAuthorizedSender: boolean;
senderE164?: string;
abortKey?: string;
rawBodyNormalized: string;
@@ -41,8 +42,16 @@ export function buildCommandContext(params: {
sessionKey?: string;
isGroup: boolean;
triggerBodyNormalized: string;
commandAuthorized: boolean;
}): CommandContext {
const { ctx, cfg, sessionKey, isGroup, triggerBodyNormalized } = params;
const {
ctx,
cfg,
sessionKey,
isGroup,
triggerBodyNormalized,
commandAuthorized,
} = params;
const surface = (ctx.Surface ?? "").trim().toLowerCase();
const isWhatsAppSurface =
surface === "whatsapp" ||
@@ -80,14 +89,13 @@ export function buildCommandContext(params: {
const ownerList = ownerCandidates
.map((entry) => normalizeE164(entry))
.filter((entry): entry is string => Boolean(entry));
const isOwnerSender =
Boolean(senderE164) && ownerList.includes(senderE164 ?? "");
const isAuthorizedSender = commandAuthorized;
return {
surface,
isWhatsAppSurface,
ownerList,
isOwnerSender,
isAuthorizedSender,
senderE164: senderE164 || undefined,
abortKey,
rawBodyNormalized,
@@ -101,6 +109,7 @@ export async function handleCommands(params: {
ctx: MsgContext;
cfg: ClawdbotConfig;
command: CommandContext;
directives: InlineDirectives;
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
sessionKey?: string;
@@ -122,6 +131,7 @@ export async function handleCommands(params: {
const {
cfg,
command,
directives,
sessionEntry,
sessionStore,
sessionKey,
@@ -151,9 +161,9 @@ export async function handleCommands(params: {
reply: { text: "⚙️ Group activation only applies to group chats." },
};
}
if (!command.isOwnerSender) {
if (!command.isAuthorizedSender) {
logVerbose(
`Ignoring /activation from non-owner in group: ${command.senderE164 || "<unknown>"}`,
`Ignoring /activation from unauthorized sender in group: ${command.senderE164 || "<unknown>"}`,
);
return { shouldContinue: false };
}
@@ -179,9 +189,9 @@ export async function handleCommands(params: {
}
if (sendPolicyCommand.hasCommand) {
if (!command.isOwnerSender) {
if (!command.isAuthorizedSender) {
logVerbose(
`Ignoring /send from non-owner: ${command.senderE164 || "<unknown>"}`,
`Ignoring /send from unauthorized sender: ${command.senderE164 || "<unknown>"}`,
);
return { shouldContinue: false };
}
@@ -220,9 +230,9 @@ export async function handleCommands(params: {
command.commandBodyNormalized === "restart" ||
command.commandBodyNormalized.startsWith("/restart ")
) {
if (isGroup && !command.isOwnerSender) {
if (!command.isAuthorizedSender) {
logVerbose(
`Ignoring /restart from non-owner in group: ${command.senderE164 || "<unknown>"}`,
`Ignoring /restart from unauthorized sender: ${command.senderE164 || "<unknown>"}`,
);
return { shouldContinue: false };
}
@@ -235,14 +245,15 @@ export async function handleCommands(params: {
};
}
if (
const statusRequested =
directives.hasStatusDirective ||
command.commandBodyNormalized === "/status" ||
command.commandBodyNormalized === "status" ||
command.commandBodyNormalized.startsWith("/status ")
) {
if (isGroup && !command.isOwnerSender) {
command.commandBodyNormalized.startsWith("/status ");
if (statusRequested) {
if (!command.isAuthorizedSender) {
logVerbose(
`Ignoring /status from non-owner in group: ${command.senderE164 || "<unknown>"}`,
`Ignoring /status from unauthorized sender: ${command.senderE164 || "<unknown>"}`,
);
return { shouldContinue: false };
}

View File

@@ -20,6 +20,7 @@ import type { ReplyPayload } from "../types.js";
import {
type ElevatedLevel,
extractElevatedDirective,
extractStatusDirective,
extractThinkDirective,
extractVerboseDirective,
type ThinkLevel,
@@ -49,6 +50,7 @@ export type InlineDirectives = {
hasElevatedDirective: boolean;
elevatedLevel?: ElevatedLevel;
rawElevatedLevel?: string;
hasStatusDirective: boolean;
hasModelDirective: boolean;
rawModelDirective?: string;
hasQueueDirective: boolean;
@@ -83,11 +85,15 @@ export function parseInlineDirectives(body: string): InlineDirectives {
rawLevel: rawElevatedLevel,
hasDirective: hasElevatedDirective,
} = extractElevatedDirective(verboseCleaned);
const {
cleaned: statusCleaned,
hasDirective: hasStatusDirective,
} = extractStatusDirective(elevatedCleaned);
const {
cleaned: modelCleaned,
rawModel,
hasDirective: hasModelDirective,
} = extractModelDirective(elevatedCleaned);
} = extractModelDirective(statusCleaned);
const {
cleaned: queueCleaned,
queueMode,
@@ -114,6 +120,7 @@ export function parseInlineDirectives(body: string): InlineDirectives {
hasElevatedDirective,
elevatedLevel,
rawElevatedLevel,
hasStatusDirective,
hasModelDirective,
rawModelDirective: rawModel,
hasQueueDirective,

View File

@@ -74,4 +74,19 @@ export function extractElevatedDirective(body?: string): {
};
}
export function extractStatusDirective(body?: string): {
cleaned: string;
hasDirective: boolean;
} {
if (!body) return { cleaned: "", hasDirective: false };
const match = body.match(/(?:^|\s)\/status(?=$|\s|:)\b/i);
const cleaned = match
? body.replace(match[0], "").replace(/\s+/g, " ").trim()
: body.trim();
return {
cleaned,
hasDirective: !!match,
};
}
export type { ElevatedLevel, ThinkLevel, VerboseLevel };