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 { logDebug, logError } from "../../logger.js";
|
||||||
import type { RuntimeEnv } from "../../runtime.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";
|
const EXEC_APPROVAL_VALUE_PREFIX = "execapproval";
|
||||||
|
|
||||||
export type ExecApprovalRequest = {
|
export type ExecApprovalRequest = {
|
||||||
@ -60,8 +60,12 @@ export function parseApprovalValue(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExecApprovalActionId(): string {
|
export function getExecApprovalActionIdPrefix(): string {
|
||||||
return EXEC_APPROVAL_ACTION_ID;
|
return EXEC_APPROVAL_ACTION_ID_PREFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function matchesExecApprovalActionId(actionId: string): boolean {
|
||||||
|
return actionId.startsWith(EXEC_APPROVAL_ACTION_ID_PREFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatApprovalBlocks(request: ExecApprovalRequest) {
|
function formatApprovalBlocks(request: ExecApprovalRequest) {
|
||||||
@ -121,20 +125,20 @@ function formatApprovalBlocks(request: ExecApprovalRequest) {
|
|||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
text: { type: "plain_text", text: "✓ Allow once", emoji: true },
|
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"),
|
value: encodeApprovalValue(request.id, "allow-once"),
|
||||||
style: "primary",
|
style: "primary",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
text: { type: "plain_text", text: "✓✓ Always allow", emoji: true },
|
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"),
|
value: encodeApprovalValue(request.id, "allow-always"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
text: { type: "plain_text", text: "✗ Deny", emoji: true },
|
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"),
|
value: encodeApprovalValue(request.id, "deny"),
|
||||||
style: "danger",
|
style: "danger",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -28,7 +28,8 @@ import { normalizeAllowList } from "./allow-list.js";
|
|||||||
import type { MonitorSlackOpts } from "./types.js";
|
import type { MonitorSlackOpts } from "./types.js";
|
||||||
import {
|
import {
|
||||||
SlackExecApprovalHandler,
|
SlackExecApprovalHandler,
|
||||||
getExecApprovalActionId,
|
getExecApprovalActionIdPrefix,
|
||||||
|
matchesExecApprovalActionId,
|
||||||
parseApprovalValue,
|
parseApprovalValue,
|
||||||
} from "./exec-approvals.js";
|
} from "./exec-approvals.js";
|
||||||
|
|
||||||
@ -234,43 +235,46 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
(
|
(
|
||||||
app as unknown as {
|
app as unknown as {
|
||||||
action: (
|
action: (
|
||||||
id: string,
|
id: string | RegExp,
|
||||||
handler: (args: {
|
handler: (args: {
|
||||||
ack: () => Promise<void>;
|
ack: () => Promise<void>;
|
||||||
body: { user?: { id?: string } };
|
body: { user?: { id?: string } };
|
||||||
action: { value?: string };
|
action: { action_id?: string; value?: string };
|
||||||
respond: (payload: { text: string; response_type?: string }) => Promise<void>;
|
respond: (payload: { text: string; response_type?: string }) => Promise<void>;
|
||||||
}) => Promise<void>,
|
}) => Promise<void>,
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
).action(getExecApprovalActionId(), async ({ ack, body, action, respond }) => {
|
).action(
|
||||||
await ack();
|
new RegExp(`^${getExecApprovalActionIdPrefix()}_`),
|
||||||
const parsed = parseApprovalValue(action?.value);
|
async ({ ack, body, action, respond }) => {
|
||||||
if (!parsed) {
|
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({
|
await respond({
|
||||||
text: "This approval button is no longer valid.",
|
text: `Submitting decision: **${decisionLabel}**...`,
|
||||||
response_type: "ephemeral",
|
response_type: "ephemeral",
|
||||||
});
|
});
|
||||||
return;
|
const ok = await execApprovalHandler!.resolveApproval(parsed.approvalId, parsed.action);
|
||||||
}
|
if (!ok) {
|
||||||
const decisionLabel =
|
await respond({
|
||||||
parsed.action === "allow-once"
|
text: "Failed to submit approval. The request may have expired or already been resolved.",
|
||||||
? "Allowed (once)"
|
response_type: "ephemeral",
|
||||||
: 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",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user