fix(reply): stop mention-wrapped status double replies

This commit is contained in:
Vincent Koc 2026-04-01 06:31:16 +09:00
parent ad06d5ab4d
commit ee8baf6766
2 changed files with 48 additions and 5 deletions

View File

@ -214,6 +214,44 @@ describe("handleInlineActions", () => {
expect(typing.cleanup).toHaveBeenCalled();
});
it("does not continue into the agent after a mention-wrapped inline status-only turn", async () => {
const typing = createTypingController();
const ctx = buildTestCtx({
Body: "<@123> /status",
CommandBody: "<@123> /status",
Provider: "discord",
Surface: "discord",
ChatType: "channel",
WasMentioned: true,
});
const result = await handleInlineActions(
createHandleInlineActionsInput({
ctx,
typing,
cleanedBody: "<@123>",
command: {
surface: "discord",
channel: "discord",
channelId: "discord",
isAuthorizedSender: true,
rawBodyNormalized: "<@123> /status",
commandBodyNormalized: "<@123> /status",
},
overrides: {
allowTextCommands: true,
inlineStatusRequested: true,
isGroup: true,
},
}),
);
expect(result).toEqual({ kind: "reply", reply: undefined });
expect(buildStatusReplyMock).toHaveBeenCalledTimes(1);
expect(handleCommandsMock).not.toHaveBeenCalled();
expect(typing.cleanup).toHaveBeenCalled();
});
it("skips stale queued messages that are at or before the /stop cutoff", async () => {
const typing = createTypingController();
const sessionEntry: SessionEntry = {

View File

@ -25,8 +25,9 @@ import type { buildStatusReply, handleCommands } from "./commands.runtime.js";
import type { InlineDirectives } from "./directive-handling.parse.js";
import { isDirectiveOnly } from "./directive-handling.parse.js";
import { extractExplicitGroupId } from "./group-id.js";
import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
import type { createModelSelectionState } from "./model-selection.js";
import { extractInlineSimpleCommand, stripInlineStatus } from "./reply-inline.js";
import { extractInlineSimpleCommand } from "./reply-inline.js";
import type { TypingController } from "./typing.js";
let builtinSlashCommands: Set<string> | null = null;
@ -458,10 +459,14 @@ export async function handleInlineActions(params: {
abortedLastRun,
};
}
const statusOnlyCommand =
command.commandBodyNormalized.trim().length > 0 &&
stripInlineStatus(command.commandBodyNormalized).cleaned.length === 0;
if (didSendInlineStatus && statusOnlyCommand) {
const remainingBodyAfterInlineStatus = (() => {
const stripped = stripStructuralPrefixes(cleanedBody);
if (!isGroup) {
return stripped.trim();
}
return stripMentions(stripped, ctx, cfg, agentId).trim();
})();
if (didSendInlineStatus && remainingBodyAfterInlineStatus.length === 0) {
typing.cleanup();
return { kind: "reply", reply: undefined };
}