mirror of https://github.com/openclaw/openclaw.git
fix(reply): avoid double status replies
This commit is contained in:
parent
968bc3d5b0
commit
e1d2b299f6
|
|
@ -3,12 +3,14 @@ import type { SkillCommandSpec } from "../../agents/skills.js";
|
|||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import type { TemplateContext } from "../templating.js";
|
||||
import { clearInlineDirectives } from "./get-reply-directives-utils.js";
|
||||
import { stripInlineStatus } from "./reply-inline.js";
|
||||
import { buildTestCtx } from "./test-ctx.js";
|
||||
import type { TypingController } from "./typing.js";
|
||||
|
||||
const handleCommandsMock = vi.fn();
|
||||
const getChannelPluginMock = vi.fn();
|
||||
const createOpenClawToolsMock = vi.fn();
|
||||
const buildStatusReplyMock = vi.fn();
|
||||
|
||||
let handleInlineActions: typeof import("./get-reply-inline-actions.js").handleInlineActions;
|
||||
type HandleInlineActionsInput = Parameters<
|
||||
|
|
@ -19,7 +21,7 @@ async function loadFreshInlineActionsModuleForTest() {
|
|||
vi.resetModules();
|
||||
vi.doMock("./commands.runtime.js", () => ({
|
||||
handleCommands: (...args: unknown[]) => handleCommandsMock(...args),
|
||||
buildStatusReply: vi.fn(),
|
||||
buildStatusReply: (...args: unknown[]) => buildStatusReplyMock(...args),
|
||||
}));
|
||||
vi.doMock("../../agents/openclaw-tools.runtime.js", () => ({
|
||||
createOpenClawTools: (...args: unknown[]) => createOpenClawToolsMock(...args),
|
||||
|
|
@ -120,6 +122,8 @@ describe("handleInlineActions", () => {
|
|||
handleCommandsMock.mockResolvedValue({ shouldContinue: true, reply: undefined });
|
||||
getChannelPluginMock.mockReset();
|
||||
createOpenClawToolsMock.mockReset();
|
||||
buildStatusReplyMock.mockReset();
|
||||
buildStatusReplyMock.mockResolvedValue({ text: "status" });
|
||||
createOpenClawToolsMock.mockReturnValue([]);
|
||||
getChannelPluginMock.mockImplementation((channelId?: string) =>
|
||||
channelId === "whatsapp" ? { commands: { skipWhenConfigEmpty: true } } : undefined,
|
||||
|
|
@ -180,6 +184,36 @@ describe("handleInlineActions", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("does not run command handlers after replying to an inline status-only turn", async () => {
|
||||
const typing = createTypingController();
|
||||
const ctx = buildTestCtx({
|
||||
Body: "/status",
|
||||
CommandBody: "/status",
|
||||
});
|
||||
|
||||
const result = await handleInlineActions(
|
||||
createHandleInlineActionsInput({
|
||||
ctx,
|
||||
typing,
|
||||
cleanedBody: stripInlineStatus("/status").cleaned,
|
||||
command: {
|
||||
isAuthorizedSender: true,
|
||||
rawBodyNormalized: "/status",
|
||||
commandBodyNormalized: "/status",
|
||||
},
|
||||
overrides: {
|
||||
allowTextCommands: true,
|
||||
inlineStatusRequested: 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 = {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import type { InlineDirectives } from "./directive-handling.parse.js";
|
|||
import { isDirectiveOnly } from "./directive-handling.parse.js";
|
||||
import { extractExplicitGroupId } from "./group-id.js";
|
||||
import type { createModelSelectionState } from "./model-selection.js";
|
||||
import { extractInlineSimpleCommand } from "./reply-inline.js";
|
||||
import { extractInlineSimpleCommand, stripInlineStatus } from "./reply-inline.js";
|
||||
import type { TypingController } from "./typing.js";
|
||||
|
||||
let builtinSlashCommands: Set<string> | null = null;
|
||||
|
|
@ -337,6 +337,7 @@ export async function handleInlineActions(params: {
|
|||
agentId,
|
||||
isGroup,
|
||||
}) && inlineStatusRequested;
|
||||
let didSendInlineStatus = false;
|
||||
if (handleInlineStatus) {
|
||||
const { buildStatusReply } = await import("./commands.runtime.js");
|
||||
const inlineStatusReply = await buildStatusReply({
|
||||
|
|
@ -359,6 +360,7 @@ export async function handleInlineActions(params: {
|
|||
mediaDecisions: ctx.MediaUnderstandingDecisions,
|
||||
});
|
||||
await sendInlineReply(inlineStatusReply);
|
||||
didSendInlineStatus = true;
|
||||
directives = { ...directives, hasStatusDirective: false };
|
||||
}
|
||||
|
||||
|
|
@ -456,6 +458,13 @@ export async function handleInlineActions(params: {
|
|||
abortedLastRun,
|
||||
};
|
||||
}
|
||||
const statusOnlyCommand =
|
||||
command.commandBodyNormalized.trim().length > 0 &&
|
||||
stripInlineStatus(command.commandBodyNormalized).cleaned.length === 0;
|
||||
if (didSendInlineStatus && statusOnlyCommand) {
|
||||
typing.cleanup();
|
||||
return { kind: "reply", reply: undefined };
|
||||
}
|
||||
|
||||
const commandResult = await runCommands(command);
|
||||
if (!commandResult.shouldContinue) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue