Compare commits

...

14 Commits

Author SHA1 Message Date
Vincent Koc
679d7c0c80 Telegram tests: route exact do not do that to control lane 2026-02-24 18:44:47 -05:00
Vincent Koc
e63996c376 Gateway tests: cover exact do not do that stop behavior 2026-02-24 18:44:38 -05:00
Vincent Koc
a36f27b0b1 Auto-reply tests: assert exact do not do that stop matching 2026-02-24 18:44:26 -05:00
Vincent Koc
0f564033d2 Auto-reply: add exact abort trigger for do not do that 2026-02-24 18:44:15 -05:00
Vincent Koc
bea6b3bd00 Changelog: add shared credit for abort shortcut update 2026-02-24 01:06:48 -05:00
Vincent Koc
ad74229a57 Changelog: note multilingual abort stop coverage 2026-02-24 01:05:28 -05:00
Vincent Koc
1c189c4d5f Telegram tests: route Russian and German stop forms to control lane 2026-02-24 01:02:50 -05:00
Vincent Koc
1b57e2c52f Gateway tests: include Russian and German stop forms 2026-02-24 01:02:41 -05:00
Vincent Koc
660e97dbe6 Auto-reply: add Russian and German abort triggers 2026-02-24 01:02:22 -05:00
Vincent Koc
172578fb4c Auto-reply tests: cover Russian and German stop words 2026-02-24 01:01:59 -05:00
Vincent Koc
56042559c8 Gateway tests: cover chat stop parsing variants 2026-02-24 00:58:05 -05:00
Vincent Koc
60c874bedd Gateway: route chat stop matching through abort parser 2026-02-24 00:58:05 -05:00
Vincent Koc
cab88c6636 Auto-reply: normalize multilingual abort triggers 2026-02-24 00:58:02 -05:00
Vincent Koc
ad3f49fd6c Auto-reply tests: cover multilingual abort triggers 2026-02-24 00:56:41 -05:00
6 changed files with 93 additions and 9 deletions

View File

@@ -13,7 +13,7 @@ Docs: https://docs.openclaw.ai
- Subagents/Sessions: add `agents.defaults.subagents.runTimeoutSeconds` so `sessions_spawn` can inherit a configurable default timeout when the tool call omits `runTimeoutSeconds` (unset remains `0`, meaning no timeout). (#24594) Thanks @mitchmcalister.
- Config/Kilo Gateway: Kilo provider flow now surfaces an updated list of models. (#24921) thanks @gumadeiras.
- Auto-reply/Abort shortcuts: expand standalone stop phrases (`stop openclaw`, `stop action`, `stop run`, `stop agent`, `please stop`, and related variants) and accept trailing punctuation (for example `STOP OPENCLAW!!!`) so emergency stop messages are caught more reliably.
- Auto-reply/Abort shortcuts: expand standalone stop phrases (`stop openclaw`, `stop action`, `stop run`, `stop agent`, `please stop`, and related variants), accept trailing punctuation (for example `STOP OPENCLAW!!!`), and add multilingual stop keywords (including ES/FR/ZH/HI/AR/JP/DE/PT/RU forms) so emergency stop messages are caught more reliably. Thanks @steipete and @vincentkoc.
### Fixes

View File

@@ -142,18 +142,38 @@ describe("abort detection", () => {
"stop dont do anything",
"stop do not do anything",
"stop doing anything",
"do not do that",
"please stop",
"stop please",
"STOP OPENCLAW",
"stop openclaw!!!",
"stop dont do anything",
"detente",
"detén",
"arrête",
"停止",
"やめて",
"止めて",
"रुको",
"توقف",
"стоп",
"остановись",
"останови",
"остановить",
"прекрати",
"halt",
"anhalten",
"aufhören",
"hoer auf",
"stopp",
"pare",
];
for (const candidate of positives) {
expect(isAbortTrigger(candidate)).toBe(true);
}
expect(isAbortTrigger("hello")).toBe(false);
expect(isAbortTrigger("do not do that")).toBe(false);
expect(isAbortTrigger("please do not do that")).toBe(false);
// /stop is NOT matched by isAbortTrigger - it's handled separately.
expect(isAbortTrigger("/stop")).toBe(false);
});
@@ -164,10 +184,17 @@ describe("abort detection", () => {
expect(isAbortRequestText("stop")).toBe(true);
expect(isAbortRequestText("stop action")).toBe(true);
expect(isAbortRequestText("stop openclaw!!!")).toBe(true);
expect(isAbortRequestText("やめて")).toBe(true);
expect(isAbortRequestText("остановись")).toBe(true);
expect(isAbortRequestText("halt")).toBe(true);
expect(isAbortRequestText("stopp")).toBe(true);
expect(isAbortRequestText("pare")).toBe(true);
expect(isAbortRequestText(" توقف ")).toBe(true);
expect(isAbortRequestText("/stop@openclaw_bot", { botUsername: "openclaw_bot" })).toBe(true);
expect(isAbortRequestText("/status")).toBe(false);
expect(isAbortRequestText("do not do that")).toBe(false);
expect(isAbortRequestText("do not do that")).toBe(true);
expect(isAbortRequestText("please do not do that")).toBe(false);
expect(isAbortRequestText("/abort")).toBe(false);
});

View File

@@ -30,6 +30,27 @@ const ABORT_TRIGGERS = new Set([
"wait",
"exit",
"interrupt",
"detente",
"deten",
"detén",
"arrete",
"arrête",
"停止",
"やめて",
"止めて",
"रुको",
"توقف",
"стоп",
"остановись",
"останови",
"остановить",
"прекрати",
"halt",
"anhalten",
"aufhören",
"hoer auf",
"stopp",
"pare",
"stop openclaw",
"openclaw stop",
"stop action",
@@ -42,6 +63,7 @@ const ABORT_TRIGGERS = new Set([
"stop dont do anything",
"stop do not do anything",
"stop doing anything",
"do not do that",
"please stop",
"stop please",
]);

View File

@@ -1,6 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import {
abortChatRunById,
isChatStopCommandText,
type ChatAbortOps,
type ChatAbortControllerEntry,
} from "./chat-abort.js";
@@ -42,6 +43,24 @@ function createOps(params: {
};
}
describe("isChatStopCommandText", () => {
it("matches slash and standalone multilingual stop forms", () => {
expect(isChatStopCommandText(" /STOP!!! ")).toBe(true);
expect(isChatStopCommandText("stop please")).toBe(true);
expect(isChatStopCommandText("do not do that")).toBe(true);
expect(isChatStopCommandText("停止")).toBe(true);
expect(isChatStopCommandText("やめて")).toBe(true);
expect(isChatStopCommandText("توقف")).toBe(true);
expect(isChatStopCommandText("остановись")).toBe(true);
expect(isChatStopCommandText("halt")).toBe(true);
expect(isChatStopCommandText("stopp")).toBe(true);
expect(isChatStopCommandText("pare")).toBe(true);
expect(isChatStopCommandText("/status")).toBe(false);
expect(isChatStopCommandText("please do not do that")).toBe(false);
expect(isChatStopCommandText("keep going")).toBe(false);
});
});
describe("abortChatRunById", () => {
it("broadcasts aborted payload with partial message when buffered text exists", () => {
const runId = "run-1";

View File

@@ -1,4 +1,4 @@
import { isAbortTrigger } from "../auto-reply/reply/abort.js";
import { isAbortRequestText } from "../auto-reply/reply/abort.js";
export type ChatAbortControllerEntry = {
controller: AbortController;
@@ -9,11 +9,7 @@ export type ChatAbortControllerEntry = {
};
export function isChatStopCommandText(text: string): boolean {
const trimmed = text.trim();
if (!trimmed) {
return false;
}
return trimmed.toLowerCase() === "/stop" || isAbortTrigger(trimmed);
return isAbortRequestText(text);
}
export function resolveChatRunExpiresAtMs(params: {

View File

@@ -184,6 +184,21 @@ describe("createTelegramBot", () => {
message: mockMessage({ chat: mockChat({ id: 123 }), text: "stop please" }),
}),
).toBe("telegram:123:control");
expect(
getTelegramSequentialKey({
message: mockMessage({ chat: mockChat({ id: 123 }), text: "do not do that" }),
}),
).toBe("telegram:123:control");
expect(
getTelegramSequentialKey({
message: mockMessage({ chat: mockChat({ id: 123 }), text: "остановись" }),
}),
).toBe("telegram:123:control");
expect(
getTelegramSequentialKey({
message: mockMessage({ chat: mockChat({ id: 123 }), text: "halt" }),
}),
).toBe("telegram:123:control");
expect(
getTelegramSequentialKey({
message: mockMessage({ chat: mockChat({ id: 123 }), text: "/abort" }),
@@ -194,6 +209,11 @@ describe("createTelegramBot", () => {
message: mockMessage({ chat: mockChat({ id: 123 }), text: "/abort now" }),
}),
).toBe("telegram:123");
expect(
getTelegramSequentialKey({
message: mockMessage({ chat: mockChat({ id: 123 }), text: "please do not do that" }),
}),
).toBe("telegram:123");
});
it("routes callback_query payloads as messages and answers callbacks", async () => {
createTelegramBot({ token: "tok" });