fix(telegram): prevent non-abort slash commands from racing chat replies (#17899)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 5c2f6f2c96
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
This commit is contained in:
Ayaan Zaidi
2026-02-16 16:21:10 +05:30
committed by GitHub
parent bc67af6ad8
commit b2aa6e094d
5 changed files with 44 additions and 9 deletions

View File

@@ -6,6 +6,7 @@ import type { OpenClawConfig } from "../../config/config.js";
import {
getAbortMemory,
getAbortMemorySizeForTest,
isAbortRequestText,
isAbortTrigger,
resetAbortMemoryForTest,
setAbortMemory,
@@ -75,6 +76,17 @@ describe("abort detection", () => {
expect(isAbortTrigger("/stop")).toBe(false);
});
it("isAbortRequestText aligns abort command semantics", () => {
expect(isAbortRequestText("/stop")).toBe(true);
expect(isAbortRequestText("stop")).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("/abort")).toBe(false);
expect(isAbortRequestText("/abort now")).toBe(false);
});
it("removes abort memory entry when flag is reset", () => {
setAbortMemory("session-1", true);
expect(getAbortMemory("session-1")).toBe(true);

View File

@@ -19,7 +19,7 @@ import {
import { logVerbose } from "../../globals.js";
import { parseAgentSessionKey } from "../../routing/session-key.js";
import { resolveCommandAuthorization } from "../command-auth.js";
import { normalizeCommandBody } from "../commands-registry.js";
import { normalizeCommandBody, type CommandNormalizeOptions } from "../commands-registry.js";
import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
import { clearSessionQueues } from "./queue.js";
@@ -35,6 +35,17 @@ export function isAbortTrigger(text?: string): boolean {
return ABORT_TRIGGERS.has(normalized);
}
export function isAbortRequestText(text?: string, options?: CommandNormalizeOptions): boolean {
if (!text) {
return false;
}
const normalized = normalizeCommandBody(text, options).trim();
if (!normalized) {
return false;
}
return normalized.toLowerCase() === "/stop" || isAbortTrigger(normalized);
}
export function getAbortMemory(key: string): boolean | undefined {
const normalized = key.trim();
if (!normalized) {
@@ -202,8 +213,7 @@ export async function tryFastAbortFromMessage(params: {
const raw = stripStructuralPrefixes(ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "");
const isGroup = ctx.ChatType?.trim().toLowerCase() === "group";
const stripped = isGroup ? stripMentions(raw, ctx, cfg, agentId) : raw;
const normalized = normalizeCommandBody(stripped);
const abortRequested = normalized === "/stop" || isAbortTrigger(stripped);
const abortRequested = isAbortRequestText(stripped);
if (!abortRequested) {
return { handled: false, aborted: false };
}