fix(slack): use unique action_ids for approval buttons
Slack requires unique action_id per button in a block. Changed from single shared ID to per-action IDs (allow_once, allow_always, deny) and use regex matching in the action handler.
This commit is contained in:
parent
26cd21e73b
commit
07ae264704
@ -8,7 +8,7 @@ import type { ExecApprovalDecision } from "../../infra/exec-approvals.js";
|
||||
import { logDebug, logError } from "../../logger.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
|
||||
const EXEC_APPROVAL_ACTION_ID = "clawdbot_execapproval";
|
||||
const EXEC_APPROVAL_ACTION_ID_PREFIX = "clawdbot_execapproval";
|
||||
const EXEC_APPROVAL_VALUE_PREFIX = "execapproval";
|
||||
|
||||
export type ExecApprovalRequest = {
|
||||
@ -60,8 +60,12 @@ export function parseApprovalValue(
|
||||
}
|
||||
}
|
||||
|
||||
export function getExecApprovalActionId(): string {
|
||||
return EXEC_APPROVAL_ACTION_ID;
|
||||
export function getExecApprovalActionIdPrefix(): string {
|
||||
return EXEC_APPROVAL_ACTION_ID_PREFIX;
|
||||
}
|
||||
|
||||
export function matchesExecApprovalActionId(actionId: string): boolean {
|
||||
return actionId.startsWith(EXEC_APPROVAL_ACTION_ID_PREFIX);
|
||||
}
|
||||
|
||||
function formatApprovalBlocks(request: ExecApprovalRequest) {
|
||||
@ -121,20 +125,20 @@ function formatApprovalBlocks(request: ExecApprovalRequest) {
|
||||
{
|
||||
type: "button",
|
||||
text: { type: "plain_text", text: "✓ Allow once", emoji: true },
|
||||
action_id: EXEC_APPROVAL_ACTION_ID,
|
||||
action_id: `${EXEC_APPROVAL_ACTION_ID_PREFIX}_allow_once`,
|
||||
value: encodeApprovalValue(request.id, "allow-once"),
|
||||
style: "primary",
|
||||
},
|
||||
{
|
||||
type: "button",
|
||||
text: { type: "plain_text", text: "✓✓ Always allow", emoji: true },
|
||||
action_id: EXEC_APPROVAL_ACTION_ID,
|
||||
action_id: `${EXEC_APPROVAL_ACTION_ID_PREFIX}_allow_always`,
|
||||
value: encodeApprovalValue(request.id, "allow-always"),
|
||||
},
|
||||
{
|
||||
type: "button",
|
||||
text: { type: "plain_text", text: "✗ Deny", emoji: true },
|
||||
action_id: EXEC_APPROVAL_ACTION_ID,
|
||||
action_id: `${EXEC_APPROVAL_ACTION_ID_PREFIX}_deny`,
|
||||
value: encodeApprovalValue(request.id, "deny"),
|
||||
style: "danger",
|
||||
},
|
||||
|
||||
@ -28,7 +28,8 @@ import { normalizeAllowList } from "./allow-list.js";
|
||||
import type { MonitorSlackOpts } from "./types.js";
|
||||
import {
|
||||
SlackExecApprovalHandler,
|
||||
getExecApprovalActionId,
|
||||
getExecApprovalActionIdPrefix,
|
||||
matchesExecApprovalActionId,
|
||||
parseApprovalValue,
|
||||
} from "./exec-approvals.js";
|
||||
|
||||
@ -234,43 +235,46 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
(
|
||||
app as unknown as {
|
||||
action: (
|
||||
id: string,
|
||||
id: string | RegExp,
|
||||
handler: (args: {
|
||||
ack: () => Promise<void>;
|
||||
body: { user?: { id?: string } };
|
||||
action: { value?: string };
|
||||
action: { action_id?: string; value?: string };
|
||||
respond: (payload: { text: string; response_type?: string }) => Promise<void>;
|
||||
}) => Promise<void>,
|
||||
) => void;
|
||||
}
|
||||
).action(getExecApprovalActionId(), async ({ ack, body, action, respond }) => {
|
||||
await ack();
|
||||
const parsed = parseApprovalValue(action?.value);
|
||||
if (!parsed) {
|
||||
).action(
|
||||
new RegExp(`^${getExecApprovalActionIdPrefix()}_`),
|
||||
async ({ ack, body, action, respond }) => {
|
||||
await ack();
|
||||
const parsed = parseApprovalValue(action?.value);
|
||||
if (!parsed) {
|
||||
await respond({
|
||||
text: "This approval button is no longer valid.",
|
||||
response_type: "ephemeral",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const decisionLabel =
|
||||
parsed.action === "allow-once"
|
||||
? "Allowed (once)"
|
||||
: parsed.action === "allow-always"
|
||||
? "Allowed (always)"
|
||||
: "Denied";
|
||||
await respond({
|
||||
text: "This approval button is no longer valid.",
|
||||
text: `Submitting decision: **${decisionLabel}**...`,
|
||||
response_type: "ephemeral",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const decisionLabel =
|
||||
parsed.action === "allow-once"
|
||||
? "Allowed (once)"
|
||||
: parsed.action === "allow-always"
|
||||
? "Allowed (always)"
|
||||
: "Denied";
|
||||
await respond({
|
||||
text: `Submitting decision: **${decisionLabel}**...`,
|
||||
response_type: "ephemeral",
|
||||
});
|
||||
const ok = await execApprovalHandler!.resolveApproval(parsed.approvalId, parsed.action);
|
||||
if (!ok) {
|
||||
await respond({
|
||||
text: "Failed to submit approval. The request may have expired or already been resolved.",
|
||||
response_type: "ephemeral",
|
||||
});
|
||||
}
|
||||
});
|
||||
const ok = await execApprovalHandler!.resolveApproval(parsed.approvalId, parsed.action);
|
||||
if (!ok) {
|
||||
await respond({
|
||||
text: "Failed to submit approval. The request may have expired or already been resolved.",
|
||||
response_type: "ephemeral",
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user