Files
openclaw/src/auto-reply/command-turn-context.test.ts
Alex Knight 2899560a6b fix(reply): derive explicit control command turns
Derive explicit source-reply command turns from authorized control-command bodies when legacy command source metadata is missing.

Preserve native/text structured command semantics, keep unauthorized native commands and structured normal command bodies on plugin-owned fallback paths, and pass bot username normalization through the derived detection.

Co-authored-by: Alex Knight <aknight@atlassian.com>
2026-05-27 05:57:04 +01:00

211 lines
5.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import {
createCommandTurnContext,
isAuthorizedTextSlashCommandTurn,
isExplicitCommandTurn,
isNativeCommandTurn,
resolveCommandTurnContext,
resolveCommandTurnTargetSessionKey,
} from "./command-turn-context.js";
import { isExplicitCommandTurnContext } from "./command-turn-detection.js";
const emptyConfig = {} as const satisfies OpenClawConfig;
describe("resolveCommandTurnContext", () => {
it("derives native command turns from legacy context fields", () => {
expect(
resolveCommandTurnContext({
CommandSource: "native",
CommandAuthorized: true,
CommandBody: "/status now",
}),
).toEqual({
kind: "native",
source: "native",
authorized: true,
commandName: "status",
body: "/status now",
});
});
it("derives text slash command turns from legacy context fields", () => {
expect(
resolveCommandTurnContext({
CommandSource: "text",
CommandAuthorized: true,
CommandBody: "/model gpt-5.5",
}),
).toMatchObject({
kind: "text-slash",
source: "text",
authorized: true,
commandName: "model",
});
});
it("keeps normal message turns non-explicit even when command auth is true elsewhere", () => {
const commandTurn = resolveCommandTurnContext({
CommandAuthorized: true,
CommandBody: "hello",
});
expect(commandTurn).toMatchObject({
kind: "normal",
source: "message",
authorized: false,
});
expect(isExplicitCommandTurn(commandTurn)).toBe(false);
});
it("treats authorized control command bodies as explicit without legacy source tags", () => {
expect(
isExplicitCommandTurnContext(
{
CommandAuthorized: true,
CommandBody: "/reset",
},
emptyConfig,
),
).toBe(true);
expect(
isExplicitCommandTurnContext(
{
CommandAuthorized: true,
CommandBody: "hey can you /status please",
},
emptyConfig,
),
).toBe(false);
});
it("keeps structured normal command turns non-explicit", () => {
expect(
isExplicitCommandTurnContext(
{
CommandTurn: {
kind: "normal",
source: "message",
authorized: false,
body: "/think high through this",
},
CommandAuthorized: true,
Body: "through this",
RawBody: "through this",
CommandBody: "/think high through this",
},
emptyConfig,
),
).toBe(false);
});
it("uses cleaned command bodies for command-shaped structured normal turns", () => {
expect(
isExplicitCommandTurnContext(
{
CommandTurn: {
kind: "normal",
source: "message",
authorized: false,
body: "/reset",
},
CommandAuthorized: true,
Body: "/reset@openclaw",
RawBody: "/reset@openclaw",
CommandBody: "/reset",
},
emptyConfig,
),
).toBe(true);
});
it("normalizes bot-mentioned command bodies for structured normal turns", () => {
expect(
isExplicitCommandTurnContext(
{
CommandTurn: {
kind: "normal",
source: "message",
authorized: false,
body: "/reset@openclaw",
},
CommandAuthorized: true,
Body: "/reset@openclaw",
RawBody: "/reset@openclaw",
CommandBody: "/reset@openclaw",
BotUsername: "openclaw",
},
emptyConfig,
),
).toBe(true);
});
it("lets structured command turns override legacy command fields", () => {
expect(
resolveCommandTurnContext({
CommandTurn: {
kind: "text-slash",
source: "text",
authorized: false,
commandName: "status",
body: "/status",
},
CommandSource: "native",
CommandAuthorized: true,
}),
).toEqual({
kind: "text-slash",
source: "text",
authorized: false,
commandName: "status",
body: "/status",
});
});
it("rejects inconsistent structured command turn pairs", () => {
expect(
resolveCommandTurnContext({
CommandTurn: {
kind: "native",
source: "message",
authorized: true,
},
CommandSource: "text",
CommandAuthorized: true,
CommandBody: "/status",
}),
).toMatchObject({
kind: "text-slash",
source: "text",
authorized: true,
});
});
it("exposes native/text helper predicates and target session resolution", () => {
const nativeTurn = createCommandTurnContext("native", {
authorized: true,
body: "/stop",
});
const textTurn = createCommandTurnContext("text", {
authorized: true,
body: "/status",
});
expect(isNativeCommandTurn(nativeTurn)).toBe(true);
expect(isAuthorizedTextSlashCommandTurn(textTurn)).toBe(true);
expect(
resolveCommandTurnTargetSessionKey({
CommandTurn: nativeTurn,
CommandTargetSessionKey: " target-session ",
}),
).toBe("target-session");
expect(
resolveCommandTurnTargetSessionKey({
CommandSource: "native",
CommandAuthorized: true,
CommandTargetSessionKey: " legacy-target ",
}),
).toBe("legacy-target");
expect(isExplicitCommandTurn(undefined)).toBe(false);
});
});