Skip to content

Commit 53cceff

Browse files
committed
fix(auto-reply): fast path text control commands
1 parent b34c188 commit 53cceff

5 files changed

Lines changed: 503 additions & 2 deletions

File tree

extensions/feishu/src/bot.broadcast.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ describe("broadcast dispatch", () => {
111111
mockWithReplyDispatcher as unknown as PluginRuntime["channel"]["reply"]["withReplyDispatcher"],
112112
},
113113
commands: {
114+
isControlCommandMessage: vi.fn(() => false),
114115
shouldComputeCommandAuthorized: mockShouldComputeCommandAuthorized,
115116
resolveCommandAuthorizedFromAuthorizers: vi.fn(() => false),
116117
},

extensions/feishu/src/bot.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ function createFeishuBotRuntime(overrides: DeepPartial<PluginRuntime> = {}): Plu
192192
withReplyDispatcher: withReplyDispatcherMock as never,
193193
},
194194
commands: {
195+
isControlCommandMessage: vi.fn(() => false),
195196
shouldComputeCommandAuthorized: vi.fn(() => false),
196197
resolveCommandAuthorizedFromAuthorizers: vi.fn(() => false),
197198
},
@@ -1082,6 +1083,9 @@ describe("handleFeishuMessage command authorization", () => {
10821083
withReplyDispatcher: mockWithReplyDispatcher as never,
10831084
},
10841085
commands: {
1086+
isControlCommandMessage: vi.fn(
1087+
(body?: string) => typeof body === "string" && body.trim().startsWith("/"),
1088+
),
10851089
shouldComputeCommandAuthorized: mockShouldComputeCommandAuthorized,
10861090
resolveCommandAuthorizedFromAuthorizers: mockResolveCommandAuthorizedFromAuthorizers,
10871091
},
@@ -1149,6 +1153,94 @@ describe("handleFeishuMessage command authorization", () => {
11491153
});
11501154
});
11511155

1156+
it("marks authorized Feishu text slash commands as text command turns", async () => {
1157+
mockShouldComputeCommandAuthorized.mockReturnValue(true);
1158+
1159+
const cfg: ClawdbotConfig = {
1160+
channels: {
1161+
feishu: {
1162+
dmPolicy: "open",
1163+
allowFrom: ["ou-admin"],
1164+
},
1165+
},
1166+
} as ClawdbotConfig;
1167+
1168+
const event: FeishuMessageEvent = {
1169+
sender: {
1170+
sender_id: {
1171+
open_id: "ou-admin",
1172+
},
1173+
},
1174+
message: {
1175+
message_id: "msg-text-command-turn",
1176+
chat_id: "oc-dm",
1177+
chat_type: "p2p",
1178+
message_type: "text",
1179+
content: JSON.stringify({ text: "/status" }),
1180+
},
1181+
};
1182+
1183+
await dispatchMessage({ cfg, event });
1184+
1185+
const context = mockCallArg<{
1186+
CommandAuthorized?: boolean;
1187+
CommandBody?: string;
1188+
CommandTurn?: unknown;
1189+
}>(mockFinalizeInboundContext, 0, 0);
1190+
expect(context.CommandBody).toBe("/status");
1191+
expect(context.CommandAuthorized).toBe(true);
1192+
expect(context.CommandTurn).toEqual({
1193+
kind: "text-slash",
1194+
source: "text",
1195+
authorized: true,
1196+
body: "/status",
1197+
});
1198+
});
1199+
1200+
it("does not mark inline Feishu command tokens as text command turns", async () => {
1201+
mockShouldComputeCommandAuthorized.mockReturnValue(true);
1202+
1203+
const cfg: ClawdbotConfig = {
1204+
channels: {
1205+
feishu: {
1206+
dmPolicy: "open",
1207+
allowFrom: ["ou-admin"],
1208+
},
1209+
},
1210+
} as ClawdbotConfig;
1211+
1212+
const event: FeishuMessageEvent = {
1213+
sender: {
1214+
sender_id: {
1215+
open_id: "ou-admin",
1216+
},
1217+
},
1218+
message: {
1219+
message_id: "msg-inline-command-token",
1220+
chat_id: "oc-dm",
1221+
chat_type: "p2p",
1222+
message_type: "text",
1223+
content: JSON.stringify({ text: "please check /status" }),
1224+
},
1225+
};
1226+
1227+
await dispatchMessage({ cfg, event });
1228+
1229+
const context = mockCallArg<{
1230+
CommandAuthorized?: boolean;
1231+
CommandBody?: string;
1232+
CommandTurn?: unknown;
1233+
}>(mockFinalizeInboundContext, 0, 0);
1234+
expect(context.CommandBody).toBe("please check /status");
1235+
expect(context.CommandAuthorized).toBe(true);
1236+
expect(context.CommandTurn).toEqual({
1237+
kind: "normal",
1238+
source: "message",
1239+
authorized: false,
1240+
body: "please check /status",
1241+
});
1242+
});
1243+
11521244
it("does not enqueue inbound preview text as system events", async () => {
11531245
mockShouldComputeCommandAuthorized.mockReturnValue(false);
11541246

extensions/feishu/src/bot.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
buildChannelInboundEventContext,
44
formatInboundMediaUnavailableText,
55
toInboundMediaFacts,
6+
type CommandTurnContext,
67
} from "openclaw/plugin-sdk/channel-inbound";
78
import { resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/channel-outbound";
89
import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
@@ -1165,6 +1166,23 @@ export async function handleFeishuMessage(params: {
11651166
})
11661167
).commandAccess.authorized
11671168
: undefined;
1169+
const isTextSlashCommandTurn = core.channel.commands.isControlCommandMessage(
1170+
effectiveCommandProbeBody,
1171+
effectiveCfg,
1172+
);
1173+
const commandTurn: CommandTurnContext = isTextSlashCommandTurn
1174+
? {
1175+
kind: "text-slash",
1176+
source: "text",
1177+
authorized: Boolean(commandAuthorized),
1178+
body: effectiveCommandProbeBody,
1179+
}
1180+
: {
1181+
kind: "normal",
1182+
source: "message",
1183+
authorized: false,
1184+
body: agentFacingContent,
1185+
};
11681186

11691187
const isTopicSessionForThread =
11701188
isGroup &&
@@ -1463,6 +1481,7 @@ export async function handleFeishuMessage(params: {
14631481
authorized: commandAuthorized,
14641482
},
14651483
},
1484+
commandTurn,
14661485
extra: {
14671486
RootMessageId: ctx.rootId,
14681487
Transcript: audioTranscript,

0 commit comments

Comments
 (0)