fix(auto-reply): expand standalone stop phrases

This commit is contained in:
Peter Steinberger
2026-02-24 04:02:18 +00:00
parent 588a188d6f
commit aea28e26fb
6 changed files with 92 additions and 14 deletions

View File

@@ -122,25 +122,52 @@ describe("abort detection", () => {
expect(result.triggerBodyNormalized).toBe("/stop");
});
it("isAbortTrigger matches bare word triggers (without slash)", () => {
expect(isAbortTrigger("stop")).toBe(true);
expect(isAbortTrigger("esc")).toBe(true);
expect(isAbortTrigger("abort")).toBe(true);
expect(isAbortTrigger("wait")).toBe(true);
expect(isAbortTrigger("exit")).toBe(true);
expect(isAbortTrigger("interrupt")).toBe(true);
it("isAbortTrigger matches standalone abort trigger phrases", () => {
const positives = [
"stop",
"esc",
"abort",
"wait",
"exit",
"interrupt",
"stop openclaw",
"openclaw stop",
"stop action",
"stop current action",
"stop run",
"stop current run",
"stop agent",
"stop the agent",
"stop don't do anything",
"stop dont do anything",
"stop do not do anything",
"stop doing anything",
"please stop",
"stop please",
"STOP OPENCLAW",
"stop openclaw!!!",
"stop dont do anything",
];
for (const candidate of positives) {
expect(isAbortTrigger(candidate)).toBe(true);
}
expect(isAbortTrigger("hello")).toBe(false);
// /stop is NOT matched by isAbortTrigger - it's handled separately
expect(isAbortTrigger("do not do that")).toBe(false);
// /stop is NOT matched by isAbortTrigger - it's handled separately.
expect(isAbortTrigger("/stop")).toBe(false);
});
it("isAbortRequestText aligns abort command semantics", () => {
expect(isAbortRequestText("/stop")).toBe(true);
expect(isAbortRequestText("/stop!!!")).toBe(true);
expect(isAbortRequestText("stop")).toBe(true);
expect(isAbortRequestText("stop action")).toBe(true);
expect(isAbortRequestText("stop openclaw!!!")).toBe(true);
expect(isAbortRequestText("/stop@openclaw_bot", { botUsername: "openclaw_bot" })).toBe(true);
expect(isAbortRequestText("/status")).toBe(false);
expect(isAbortRequestText("stop please")).toBe(false);
expect(isAbortRequestText("do not do that")).toBe(false);
expect(isAbortRequestText("/abort")).toBe(false);
});

View File

@@ -23,15 +23,47 @@ import type { FinalizedMsgContext, MsgContext } from "../templating.js";
import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
import { clearSessionQueues } from "./queue.js";
const ABORT_TRIGGERS = new Set(["stop", "esc", "abort", "wait", "exit", "interrupt"]);
const ABORT_TRIGGERS = new Set([
"stop",
"esc",
"abort",
"wait",
"exit",
"interrupt",
"stop openclaw",
"openclaw stop",
"stop action",
"stop current action",
"stop run",
"stop current run",
"stop agent",
"stop the agent",
"stop don't do anything",
"stop dont do anything",
"stop do not do anything",
"stop doing anything",
"please stop",
"stop please",
]);
const ABORT_MEMORY = new Map<string, boolean>();
const ABORT_MEMORY_MAX = 2000;
const TRAILING_ABORT_PUNCTUATION_RE = /[.!?,;:'")\]}]+$/u;
function normalizeAbortTriggerText(text: string): string {
return text
.trim()
.toLowerCase()
.replace(/[`]/g, "'")
.replace(/\s+/g, " ")
.replace(TRAILING_ABORT_PUNCTUATION_RE, "")
.trim();
}
export function isAbortTrigger(text?: string): boolean {
if (!text) {
return false;
}
const normalized = text.trim().toLowerCase();
const normalized = normalizeAbortTriggerText(text);
return ABORT_TRIGGERS.has(normalized);
}
@@ -43,7 +75,12 @@ export function isAbortRequestText(text?: string, options?: CommandNormalizeOpti
if (!normalized) {
return false;
}
return normalized.toLowerCase() === "/stop" || isAbortTrigger(normalized);
const normalizedLower = normalized.toLowerCase();
return (
normalizedLower === "/stop" ||
normalizeAbortTriggerText(normalizedLower) === "/stop" ||
isAbortTrigger(normalizedLower)
);
}
export function getAbortMemory(key: string): boolean | undefined {