refactor: share approval interactive renderers

This commit is contained in:
Peter Steinberger
2026-03-30 08:02:37 +09:00
parent cfac0e8698
commit 52fb4a149a
15 changed files with 289 additions and 491 deletions

View File

@@ -1,10 +1,45 @@
import { describe, expect, it } from "vitest";
import {
buildApprovalPendingReplyPayload,
buildPluginApprovalPendingReplyPayload,
buildPluginApprovalResolvedReplyPayload,
} from "./approval-renderers.js";
describe("plugin-sdk/approval-renderers", () => {
it("builds shared approval payloads with generic interactive commands", () => {
const payload = buildApprovalPendingReplyPayload({
approvalId: "plugin:approval-123",
approvalSlug: "plugin:a",
text: "Approval required @everyone",
});
expect(payload.text).toContain("@\u200beveryone");
expect(payload.interactive).toEqual({
blocks: [
{
type: "buttons",
buttons: [
{
label: "Allow Once",
value: "/approve plugin:approval-123 allow-once",
style: "success",
},
{
label: "Allow Always",
value: "/approve plugin:approval-123 always",
style: "primary",
},
{
label: "Deny",
value: "/approve plugin:approval-123 deny",
style: "danger",
},
],
},
],
});
});
it("builds plugin pending payloads with approval metadata and extra channel data", () => {
const payload = buildPluginApprovalPendingReplyPayload({
request: {
@@ -20,12 +55,36 @@ describe("plugin-sdk/approval-renderers", () => {
approvalSlug: "custom-slug",
channelData: {
telegram: {
buttons: [[{ text: "Allow Once", callback_data: "/approve id allow-once" }]],
quoteText: "quoted",
},
},
});
expect(payload.text).toContain("Plugin approval required");
expect(payload.interactive).toEqual({
blocks: [
{
type: "buttons",
buttons: [
{
label: "Allow Once",
value: "/approve plugin-approval-123 allow-once",
style: "success",
},
{
label: "Allow Always",
value: "/approve plugin-approval-123 always",
style: "primary",
},
{
label: "Deny",
value: "/approve plugin-approval-123 deny",
style: "danger",
},
],
},
],
});
expect(payload.channelData).toMatchObject({
execApproval: {
approvalId: "plugin-approval-123",
@@ -33,7 +92,7 @@ describe("plugin-sdk/approval-renderers", () => {
allowedDecisions: ["allow-once", "allow-always", "deny"],
},
telegram: {
buttons: [[{ text: "Allow Once", callback_data: "/approve id allow-once" }]],
quoteText: "quoted",
},
});
});

View File

@@ -1,5 +1,8 @@
import type { ReplyPayload } from "../auto-reply/types.js";
import type { ExecApprovalReplyDecision } from "../infra/exec-approval-reply.js";
import {
buildApprovalInteractiveReply,
type ExecApprovalReplyDecision,
} from "../infra/exec-approval-reply.js";
import {
buildPluginApprovalRequestMessage,
buildPluginApprovalResolvedMessage,
@@ -9,6 +12,39 @@ import {
const DEFAULT_ALLOWED_DECISIONS = ["allow-once", "allow-always", "deny"] as const;
function neutralizeApprovalText(value: string): string {
return value
.replace(/@everyone/gi, "@\u200beveryone")
.replace(/@here/gi, "@\u200bhere")
.replace(/<@/g, "<@\u200b")
.replace(/<#/g, "<#\u200b");
}
export function buildApprovalPendingReplyPayload(params: {
approvalId: string;
approvalSlug: string;
text: string;
allowedDecisions?: readonly ExecApprovalReplyDecision[];
channelData?: Record<string, unknown>;
}): ReplyPayload {
const allowedDecisions = params.allowedDecisions ?? DEFAULT_ALLOWED_DECISIONS;
return {
text: neutralizeApprovalText(params.text),
interactive: buildApprovalInteractiveReply({
approvalId: params.approvalId,
allowedDecisions,
}),
channelData: {
execApproval: {
approvalId: params.approvalId,
approvalSlug: params.approvalSlug,
allowedDecisions,
},
...params.channelData,
},
};
}
export function buildPluginApprovalPendingReplyPayload(params: {
request: PluginApprovalRequest;
nowMs: number;
@@ -17,17 +53,13 @@ export function buildPluginApprovalPendingReplyPayload(params: {
allowedDecisions?: readonly ExecApprovalReplyDecision[];
channelData?: Record<string, unknown>;
}): ReplyPayload {
return {
return buildApprovalPendingReplyPayload({
approvalId: params.request.id,
approvalSlug: params.approvalSlug ?? params.request.id.slice(0, 8),
text: params.text ?? buildPluginApprovalRequestMessage(params.request, params.nowMs),
channelData: {
execApproval: {
approvalId: params.request.id,
approvalSlug: params.approvalSlug ?? params.request.id.slice(0, 8),
allowedDecisions: params.allowedDecisions ?? DEFAULT_ALLOWED_DECISIONS,
},
...params.channelData,
},
};
allowedDecisions: params.allowedDecisions,
channelData: params.channelData,
});
}
export function buildPluginApprovalResolvedReplyPayload(params: {
@@ -37,10 +69,14 @@ export function buildPluginApprovalResolvedReplyPayload(params: {
}): ReplyPayload {
return params.channelData
? {
text: params.text ?? buildPluginApprovalResolvedMessage(params.resolved),
text: neutralizeApprovalText(
params.text ?? buildPluginApprovalResolvedMessage(params.resolved),
),
channelData: params.channelData,
}
: {
text: params.text ?? buildPluginApprovalResolvedMessage(params.resolved),
text: neutralizeApprovalText(
params.text ?? buildPluginApprovalResolvedMessage(params.resolved),
),
};
}

View File

@@ -33,6 +33,7 @@ export {
} from "../infra/plugin-approvals.js";
export { createApproverRestrictedNativeApprovalAdapter } from "./approval-delivery-helpers.js";
export {
buildApprovalPendingReplyPayload,
buildPluginApprovalPendingReplyPayload,
buildPluginApprovalResolvedReplyPayload,
} from "./approval-renderers.js";