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 type { OpenClawConfig } from "../../config/config.js"; import { loadSessionStore, resolveStorePath } from "../../config/sessions.js"; import type { DiscordExecApprovalConfig } from "../../config/types.discord.js"; import { buildGatewayConnectionDetails } from "../../gateway/call.js"; import { GatewayClient } from "../../gateway/client.js"; import type { EventFrame } from "../../gateway/protocol/index.js"; import type { ExecApprovalDecision, ExecApprovalRequest, ExecApprovalResolved, } from "../../infra/exec-approvals.js"; import { logDebug, logError } from "../../logger.js"; import { normalizeAccountId, resolveAgentIdFromSessionKey } from "../../routing/session-key.js"; import type { RuntimeEnv } from "../../runtime.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES, normalizeMessageChannel, } from "../../utils/message-channel.js"; 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; } 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; metadataLines?: string[]; actionRow?: Row