import { Button, Row, Separator, TextDisplay, serializePayload, type ButtonInteraction, type ComponentData, type MessagePayloadObject, type TopLevelComponents, } from "@buape/carbon"; import { ButtonStyle, Routes } from "discord-api-types/v10"; import { normalizeMessageChannel } from "openclaw/plugin-sdk/channel-runtime"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { loadSessionStore, resolveStorePath } from "openclaw/plugin-sdk/config-runtime"; import type { DiscordExecApprovalConfig } from "openclaw/plugin-sdk/config-runtime"; import { GatewayClient } from "openclaw/plugin-sdk/gateway-runtime"; import { createOperatorApprovalsGatewayClient } from "openclaw/plugin-sdk/gateway-runtime"; import type { EventFrame } from "openclaw/plugin-sdk/gateway-runtime"; import { resolveExecApprovalCommandDisplay } from "openclaw/plugin-sdk/infra-runtime"; import { getExecApprovalApproverDmNoticeText } from "openclaw/plugin-sdk/infra-runtime"; import type { ExecApprovalDecision, ExecApprovalRequest, ExecApprovalResolved, } from "openclaw/plugin-sdk/infra-runtime"; import { normalizeAccountId, resolveAgentIdFromSessionKey } from "openclaw/plugin-sdk/routing"; import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; import { compileSafeRegex, testRegexWithBoundedInput } from "openclaw/plugin-sdk/security-runtime"; import { logDebug, logError } from "openclaw/plugin-sdk/text-runtime"; import { createDiscordClient, stripUndefinedFields } from "../send.shared.js"; import { DiscordUiContainer } from "../ui.js"; const EXEC_APPROVAL_KEY = "execapproval"; export type { ExecApprovalRequest, ExecApprovalResolved }; /** Extract Discord channel ID from a session key like "agent:main:discord:channel:123456789" */ export function extractDiscordChannelId(sessionKey?: string | null): string | null { if (!sessionKey) { return null; } // Session key format: agent::discord:channel: or agent::discord:group: const match = sessionKey.match(/discord:(?:channel|group):(\d+)/); return match ? match[1] : null; } function buildDiscordApprovalDmRedirectNotice(): { content: string } { return { content: getExecApprovalApproverDmNoticeText(), }; } type PendingApproval = { discordMessageId: string; discordChannelId: string; timeoutId: NodeJS.Timeout; }; function encodeCustomIdValue(value: string): string { return encodeURIComponent(value); } function decodeCustomIdValue(value: string): string { try { return decodeURIComponent(value); } catch { return value; } } export function buildExecApprovalCustomId( approvalId: string, action: ExecApprovalDecision, ): string { return [`${EXEC_APPROVAL_KEY}:id=${encodeCustomIdValue(approvalId)}`, `action=${action}`].join( ";", ); } export function parseExecApprovalData( data: ComponentData, ): { approvalId: string; action: ExecApprovalDecision } | null { if (!data || typeof data !== "object") { return null; } const coerce = (value: unknown) => typeof value === "string" || typeof value === "number" ? String(value) : ""; const rawId = coerce(data.id); const rawAction = coerce(data.action); if (!rawId || !rawAction) { return null; } const action = rawAction as ExecApprovalDecision; if (action !== "allow-once" && action !== "allow-always" && action !== "deny") { return null; } return { approvalId: decodeCustomIdValue(rawId), action, }; } type ExecApprovalContainerParams = { cfg: OpenClawConfig; accountId: string; title: string; description?: string; commandPreview: string; commandSecondaryPreview?: string | null; metadataLines?: string[]; actionRow?: Row