fix: require slash for control commands
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
- Groups: `whatsapp.groups`, `telegram.groups`, and `imessage.groups` now act as allowlists when set. Add `"*"` to keep allow-all behavior.
|
||||
|
||||
### Fixes
|
||||
- Auto-reply: require slash for control commands to avoid false triggers in normal text.
|
||||
- Auto-reply: treat steer during compaction as a follow-up, queued until compaction completes.
|
||||
- Auth: lock auth profile refreshes to avoid multi-instance OAuth logouts; keep credentials on refresh failure.
|
||||
- Onboarding: prompt immediately for OpenAI Codex redirect URL on remote/headless logins.
|
||||
|
||||
35
src/auto-reply/command-detection.test.ts
Normal file
35
src/auto-reply/command-detection.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { hasControlCommand } from "./command-detection.js";
|
||||
import { parseActivationCommand } from "./group-activation.js";
|
||||
import { parseSendPolicyCommand } from "./send-policy.js";
|
||||
|
||||
describe("control command parsing", () => {
|
||||
it("requires slash for send policy", () => {
|
||||
expect(parseSendPolicyCommand("/send on")).toEqual({
|
||||
hasCommand: true,
|
||||
mode: "allow",
|
||||
});
|
||||
expect(parseSendPolicyCommand("/send")).toEqual({ hasCommand: true });
|
||||
expect(parseSendPolicyCommand("send on")).toEqual({ hasCommand: false });
|
||||
expect(parseSendPolicyCommand("send")).toEqual({ hasCommand: false });
|
||||
});
|
||||
|
||||
it("requires slash for activation", () => {
|
||||
expect(parseActivationCommand("/activation mention")).toEqual({
|
||||
hasCommand: true,
|
||||
mode: "mention",
|
||||
});
|
||||
expect(parseActivationCommand("activation mention")).toEqual({
|
||||
hasCommand: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("treats bare commands as non-control", () => {
|
||||
expect(hasControlCommand("/send")).toBe(true);
|
||||
expect(hasControlCommand("send")).toBe(false);
|
||||
expect(hasControlCommand("/help")).toBe(true);
|
||||
expect(hasControlCommand("help")).toBe(false);
|
||||
expect(hasControlCommand("/status")).toBe(true);
|
||||
expect(hasControlCommand("status")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -2,21 +2,13 @@ const CONTROL_COMMAND_RE =
|
||||
/(?:^|\s)\/(?:status|help|thinking|think|t|verbose|v|elevated|elev|model|queue|activation|send|restart|reset|new|compact)(?=$|\s|:)\b/i;
|
||||
|
||||
const CONTROL_COMMAND_EXACT = new Set([
|
||||
"help",
|
||||
"/help",
|
||||
"status",
|
||||
"/status",
|
||||
"restart",
|
||||
"/restart",
|
||||
"activation",
|
||||
"/activation",
|
||||
"send",
|
||||
"/send",
|
||||
"reset",
|
||||
"/reset",
|
||||
"new",
|
||||
"/new",
|
||||
"compact",
|
||||
"/compact",
|
||||
]);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export function parseActivationCommand(raw?: string): {
|
||||
if (!raw) return { hasCommand: false };
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return { hasCommand: false };
|
||||
const match = trimmed.match(/^\/?activation\b(?:\s+([a-zA-Z]+))?/i);
|
||||
const match = trimmed.match(/^\/activation\b(?:\s+([a-zA-Z]+))?/i);
|
||||
if (!match) return { hasCommand: false };
|
||||
const mode = normalizeGroupActivation(match[1]);
|
||||
return { hasCommand: true, mode };
|
||||
|
||||
@@ -102,11 +102,7 @@ function extractCompactInstructions(params: {
|
||||
const trimmed = stripped.trim();
|
||||
if (!trimmed) return undefined;
|
||||
const lowered = trimmed.toLowerCase();
|
||||
const prefix = lowered.startsWith("/compact")
|
||||
? "/compact"
|
||||
: lowered.startsWith("compact")
|
||||
? "compact"
|
||||
: null;
|
||||
const prefix = lowered.startsWith("/compact") ? "/compact" : null;
|
||||
if (!prefix) return undefined;
|
||||
let rest = trimmed.slice(prefix.length).trimStart();
|
||||
if (rest.startsWith(":")) rest = rest.slice(1).trimStart();
|
||||
@@ -197,9 +193,7 @@ export async function handleCommands(params: {
|
||||
|
||||
const resetRequested =
|
||||
command.commandBodyNormalized === "/reset" ||
|
||||
command.commandBodyNormalized === "reset" ||
|
||||
command.commandBodyNormalized === "/new" ||
|
||||
command.commandBodyNormalized === "new";
|
||||
command.commandBodyNormalized === "/new";
|
||||
if (resetRequested && !command.isAuthorizedSender) {
|
||||
logVerbose(
|
||||
`Ignoring /reset from unauthorized sender: ${command.senderE164 || "<unknown>"}`,
|
||||
@@ -300,7 +294,6 @@ export async function handleCommands(params: {
|
||||
|
||||
if (
|
||||
command.commandBodyNormalized === "/restart" ||
|
||||
command.commandBodyNormalized === "restart" ||
|
||||
command.commandBodyNormalized.startsWith("/restart ")
|
||||
) {
|
||||
if (!command.isAuthorizedSender) {
|
||||
@@ -320,7 +313,6 @@ export async function handleCommands(params: {
|
||||
|
||||
const helpRequested =
|
||||
command.commandBodyNormalized === "/help" ||
|
||||
command.commandBodyNormalized === "help" ||
|
||||
/(?:^|\s)\/help(?=$|\s|:)\b/i.test(command.commandBodyNormalized);
|
||||
if (helpRequested) {
|
||||
if (!command.isAuthorizedSender) {
|
||||
@@ -335,7 +327,6 @@ export async function handleCommands(params: {
|
||||
const statusRequested =
|
||||
directives.hasStatusDirective ||
|
||||
command.commandBodyNormalized === "/status" ||
|
||||
command.commandBodyNormalized === "status" ||
|
||||
command.commandBodyNormalized.startsWith("/status ");
|
||||
if (statusRequested) {
|
||||
if (!command.isAuthorizedSender) {
|
||||
@@ -383,9 +374,7 @@ export async function handleCommands(params: {
|
||||
|
||||
const compactRequested =
|
||||
command.commandBodyNormalized === "/compact" ||
|
||||
command.commandBodyNormalized === "compact" ||
|
||||
command.commandBodyNormalized.startsWith("/compact ") ||
|
||||
command.commandBodyNormalized.startsWith("compact ");
|
||||
command.commandBodyNormalized.startsWith("/compact ");
|
||||
if (compactRequested) {
|
||||
if (!command.isAuthorizedSender) {
|
||||
logVerbose(
|
||||
|
||||
@@ -17,7 +17,7 @@ export function parseSendPolicyCommand(raw?: string): {
|
||||
if (!raw) return { hasCommand: false };
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return { hasCommand: false };
|
||||
const match = trimmed.match(/^\/?send\b(?:\s+([a-zA-Z]+))?/i);
|
||||
const match = trimmed.match(/^\/send\b(?:\s+([a-zA-Z]+))?/i);
|
||||
if (!match) return { hasCommand: false };
|
||||
const token = match[1]?.trim().toLowerCase();
|
||||
if (!token) return { hasCommand: true };
|
||||
|
||||
Reference in New Issue
Block a user