fix(slack): accept bare approve fallback

This commit is contained in:
Vincent Koc 2026-04-01 06:32:40 +09:00
parent ee8baf6766
commit 94d72efedc
4 changed files with 31 additions and 4 deletions

View File

@ -10,7 +10,7 @@ import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-
import { requireGatewayClientScopeForInternalChannel } from "./command-gates.js";
import type { CommandHandler } from "./commands-types.js";
const COMMAND_REGEX = /^\/approve(?:\s|$)/i;
const COMMAND_REGEX = /^\/?approve(?:\s|$)/i;
const FOREIGN_COMMAND_MENTION_REGEX = /^\/approve@([^\s]+)(?:\s|$)/i;
const DECISION_ALIASES: Record<string, "allow-once" | "allow-always" | "deny"> = {

View File

@ -799,6 +799,30 @@ describe("/approve command", () => {
);
});
it("accepts bare approve text for Slack-style manual approvals", async () => {
const cfg = {
commands: { text: true },
channels: { slack: { allowFrom: ["*"] } },
} as OpenClawConfig;
const params = buildParams("approve abc allow-once", cfg, {
Provider: "slack",
Surface: "slack",
SenderId: "U123",
});
callGatewayMock.mockResolvedValue({ ok: true });
const result = await handleCommands(params);
expect(result.shouldContinue).toBe(false);
expect(result.reply?.text).toContain("Approval allow-once submitted");
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "exec.approval.resolve",
params: { id: "abc", decision: "allow-once" },
}),
);
});
it("accepts Telegram command mentions for /approve", async () => {
const cfg = createTelegramApproveCfg();
const params = buildParams("/approve@bot abc12345 allow-once", cfg, {
@ -2633,8 +2657,7 @@ describe("handleCommands subagents", () => {
});
createTaskRecord({
runtime: "subagent",
ownerKey: "agent:main:main",
scopeKind: "session",
requesterSessionKey: "agent:main:main",
childSessionKey: "agent:main:subagent:abc",
runId: "run-1",
task: "do thing",

View File

@ -234,6 +234,10 @@ describe("exec approval reply helpers", () => {
approvalId: "req-1",
decision: "deny",
});
expect(parseExecApprovalCommandText("approve req-1 allow-once")).toEqual({
approvalId: "req-1",
decision: "allow-once",
});
expect(parseExecApprovalCommandText("/approve@clover req-1 allow-once")).toEqual({
approvalId: "req-1",
decision: "allow-once",

View File

@ -140,7 +140,7 @@ export function parseExecApprovalCommandText(
): { approvalId: string; decision: ExecApprovalReplyDecision } | null {
const trimmed = raw.trim();
const match = trimmed.match(
/^\/approve(?:@[^\s]+)?\s+([A-Za-z0-9][A-Za-z0-9._:-]*)\s+(allow-once|allow-always|always|deny)\b/i,
/^\/?approve(?:@[^\s]+)?\s+([A-Za-z0-9][A-Za-z0-9._:-]*)\s+(allow-once|allow-always|always|deny)\b/i,
);
if (!match) {
return null;