openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
425 lines (424 loc) • 16.2 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { n as logError, t as logDebug } from "./logger-lqqYRtFw.js";
import "./logging-core-BH_8JYZP.js";
import "./string-coerce-runtime-CEGJWkQ_.js";
import { r as createChannelApprovalNativeRuntimeAdapter } from "./approval-handler-runtime-CFLINLFC.js";
import "./approval-handler-runtime-DF66dgbE.js";
import { t as inspectDiscordAccount } from "./account-inspect-DVZXXjia.js";
import { $ as createChannelMessage, A as Button, B as Separator, C as serializePayload, Ft as ButtonStyle, H as TextDisplay, J as createUserDmChannel, M as Container, R as Row, it as editChannelMessage, nt as deleteChannelMessage } from "./discord-DdqXEGEm.js";
import { F as createDiscordClient, b as stripUndefinedFields } from "./send.shared-SdWkx4sF.js";
import { i as isDiscordExecApprovalClientEnabled, t as shouldHandleDiscordApprovalRequest } from "./approval-shared-CvDEtoxb.js";
//#region extensions/discord/src/ui-colors.ts
const DEFAULT_DISCORD_ACCENT_COLOR = "#5865F2";
function normalizeDiscordAccentColor(raw) {
const trimmed = (raw ?? "").trim();
if (!trimmed) return null;
const normalized = trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
if (!/^#[0-9a-fA-F]{6}$/.test(normalized)) return null;
return normalized.toUpperCase();
}
function resolveDiscordAccentColor(params) {
return normalizeDiscordAccentColor(inspectDiscordAccount({
cfg: params.cfg,
accountId: params.accountId
}).config.ui?.components?.accentColor) ?? DEFAULT_DISCORD_ACCENT_COLOR;
}
//#endregion
//#region extensions/discord/src/ui.ts
var DiscordUiContainer = class extends Container {
constructor(params) {
const accentColor = normalizeDiscordAccentColor(params.accentColor) ?? resolveDiscordAccentColor({
cfg: params.cfg,
accountId: params.accountId
});
super(params.components, {
accentColor,
spoiler: params.spoiler
});
}
};
//#endregion
//#region extensions/discord/src/approval-handler.runtime.ts
function resolveHandlerContext(params) {
const context = params.context;
const accountId = normalizeOptionalString(params.accountId) ?? "";
if (!context?.token || !accountId) return null;
return {
accountId,
context
};
}
var ExecApprovalContainer = class extends DiscordUiContainer {
constructor(params) {
const components = [new TextDisplay(`## ${params.title}`)];
if (params.description) components.push(new TextDisplay(params.description));
components.push(new Separator({
divider: true,
spacing: "small"
}));
components.push(new TextDisplay(`### Command\n\`\`\`\n${params.commandPreview}\n\`\`\``));
if (params.commandSecondaryPreview) components.push(new TextDisplay(`### Shell Preview\n\`\`\`\n${params.commandSecondaryPreview}\n\`\`\``));
if (params.metadataLines?.length) components.push(new TextDisplay(params.metadataLines.join("\n")));
if (params.actionRow) components.push(params.actionRow);
if (params.footer) {
components.push(new Separator({
divider: false,
spacing: "small"
}));
components.push(new TextDisplay(`-# ${params.footer}`));
}
super({
cfg: params.cfg,
accountId: params.accountId,
components,
accentColor: params.accentColor
});
}
};
var ExecApprovalActionButton = class extends Button {
constructor(params) {
super();
this.customId = buildExecApprovalCustomId(params.approvalId, params.descriptor.decision);
this.label = params.descriptor.label;
this.style = params.descriptor.style === "success" ? ButtonStyle.Success : params.descriptor.style === "primary" ? ButtonStyle.Primary : params.descriptor.style === "danger" ? ButtonStyle.Danger : ButtonStyle.Secondary;
}
};
var ExecApprovalActionRow = class extends Row {
constructor(params) {
super(params.actions.map((descriptor) => new ExecApprovalActionButton({
approvalId: params.approvalId,
descriptor
})));
}
};
function createApprovalActionRow(view) {
return new ExecApprovalActionRow({
approvalId: view.approvalId,
actions: view.actions
});
}
function buildApprovalMetadataLines(metadata) {
return metadata.map((item) => `- ${item.label}: ${item.value}`);
}
function buildExecApprovalPayload(container) {
return { components: [container] };
}
function formatCommandPreview(commandText, maxChars) {
return (commandText.length > maxChars ? `${commandText.slice(0, maxChars)}...` : commandText).replace(/`/g, "`");
}
function formatOptionalCommandPreview(commandText, maxChars) {
if (!commandText) return null;
return formatCommandPreview(commandText, maxChars);
}
function resolveCommandPreviews(commandText, commandPreview, maxChars, secondaryMaxChars) {
return {
commandPreview: formatCommandPreview(commandText, maxChars),
commandSecondaryPreview: formatOptionalCommandPreview(commandPreview, secondaryMaxChars)
};
}
function createExecApprovalRequestContainer(params) {
const { commandPreview, commandSecondaryPreview } = resolveCommandPreviews(params.view.commandText, params.view.commandPreview, 1e3, 500);
const expiresAtSeconds = Math.max(0, Math.floor(params.view.expiresAtMs / 1e3));
return new ExecApprovalContainer({
cfg: params.cfg,
accountId: params.accountId,
title: "Exec Approval Required",
description: "A command needs your approval.",
commandPreview,
commandSecondaryPreview,
metadataLines: buildApprovalMetadataLines(params.view.metadata),
actionRow: params.actionRow,
footer: `Expires <t:${expiresAtSeconds}:R> · ID: ${params.view.approvalId}`,
accentColor: "#FFA500"
});
}
function createPluginApprovalRequestContainer(params) {
const expiresAtSeconds = Math.max(0, Math.floor(params.view.expiresAtMs / 1e3));
const severity = params.view.severity;
const accentColor = severity === "critical" ? "#ED4245" : severity === "info" ? "#5865F2" : "#FAA61A";
return new ExecApprovalContainer({
cfg: params.cfg,
accountId: params.accountId,
title: "Plugin Approval Required",
description: "A plugin action needs your approval.",
commandPreview: formatCommandPreview(params.view.title, 700),
commandSecondaryPreview: formatOptionalCommandPreview(params.view.description, 1e3),
metadataLines: buildApprovalMetadataLines(params.view.metadata),
actionRow: params.actionRow,
footer: `Expires <t:${expiresAtSeconds}:R> · ID: ${params.view.approvalId}`,
accentColor
});
}
function createExecResolvedContainer(params) {
const { commandPreview, commandSecondaryPreview } = resolveCommandPreviews(params.view.commandText, params.view.commandPreview, 500, 300);
const decisionLabel = params.view.decision === "allow-once" ? "Allowed (once)" : params.view.decision === "allow-always" ? "Allowed (always)" : "Denied";
const accentColor = params.view.decision === "deny" ? "#ED4245" : params.view.decision === "allow-always" ? "#5865F2" : "#57F287";
return new ExecApprovalContainer({
cfg: params.cfg,
accountId: params.accountId,
title: `Exec Approval: ${decisionLabel}`,
description: params.view.resolvedBy ? `Resolved by ${params.view.resolvedBy}` : "Resolved",
commandPreview,
commandSecondaryPreview,
metadataLines: buildApprovalMetadataLines(params.view.metadata),
footer: `ID: ${params.view.approvalId}`,
accentColor
});
}
function createPluginResolvedContainer(params) {
const decisionLabel = params.view.decision === "allow-once" ? "Allowed (once)" : params.view.decision === "allow-always" ? "Allowed (always)" : "Denied";
const accentColor = params.view.decision === "deny" ? "#ED4245" : params.view.decision === "allow-always" ? "#5865F2" : "#57F287";
return new ExecApprovalContainer({
cfg: params.cfg,
accountId: params.accountId,
title: `Plugin Approval: ${decisionLabel}`,
description: params.view.resolvedBy ? `Resolved by ${params.view.resolvedBy}` : "Resolved",
commandPreview: formatCommandPreview(params.view.title, 700),
commandSecondaryPreview: formatOptionalCommandPreview(params.view.description, 1e3),
metadataLines: buildApprovalMetadataLines(params.view.metadata),
footer: `ID: ${params.view.approvalId}`,
accentColor
});
}
function createExecExpiredContainer(params) {
const { commandPreview, commandSecondaryPreview } = resolveCommandPreviews(params.view.commandText, params.view.commandPreview, 500, 300);
return new ExecApprovalContainer({
cfg: params.cfg,
accountId: params.accountId,
title: "Exec Approval: Expired",
description: "This approval request has expired.",
commandPreview,
commandSecondaryPreview,
metadataLines: buildApprovalMetadataLines(params.view.metadata),
footer: `ID: ${params.view.approvalId}`,
accentColor: "#99AAB5"
});
}
function createPluginExpiredContainer(params) {
return new ExecApprovalContainer({
cfg: params.cfg,
accountId: params.accountId,
title: "Plugin Approval: Expired",
description: "This approval request has expired.",
commandPreview: formatCommandPreview(params.view.title, 700),
commandSecondaryPreview: formatOptionalCommandPreview(params.view.description, 1e3),
metadataLines: buildApprovalMetadataLines(params.view.metadata),
footer: `ID: ${params.view.approvalId}`,
accentColor: "#99AAB5"
});
}
function buildExecApprovalCustomId(approvalId, action) {
return [`execapproval:id=${encodeURIComponent(approvalId)}`, `action=${action}`].join(";");
}
async function updateMessage(params) {
try {
const { rest, request: discordRequest } = createDiscordClient({
cfg: params.cfg,
token: params.token,
accountId: params.accountId
});
const payload = buildExecApprovalPayload(params.container);
await discordRequest(() => editChannelMessage(rest, params.channelId, params.messageId, { body: stripUndefinedFields(serializePayload(payload)) }), "update-approval");
} catch (err) {
logError(`discord approvals: failed to update message: ${String(err)}`);
}
}
async function finalizeMessage(params) {
if (!params.cleanupAfterResolve) {
await updateMessage(params);
return;
}
try {
const { rest, request: discordRequest } = createDiscordClient({
cfg: params.cfg,
token: params.token,
accountId: params.accountId
});
await discordRequest(() => deleteChannelMessage(rest, params.channelId, params.messageId), "delete-approval");
} catch (err) {
logError(`discord approvals: failed to delete message: ${String(err)}`);
await updateMessage(params);
}
}
const discordApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdapter({
eventKinds: ["exec", "plugin"],
resolveApprovalKind: (request) => request.id.startsWith("plugin:") ? "plugin" : "exec",
availability: {
isConfigured: (params) => {
const resolved = resolveHandlerContext(params);
return resolved ? isDiscordExecApprovalClientEnabled({
cfg: params.cfg,
accountId: resolved.accountId,
configOverride: resolved.context.config
}) : false;
},
shouldHandle: (params) => {
const resolved = resolveHandlerContext(params);
return resolved ? shouldHandleDiscordApprovalRequest({
cfg: params.cfg,
accountId: resolved.accountId,
request: params.request,
configOverride: resolved.context.config
}) : false;
}
},
presentation: {
buildPendingPayload: ({ cfg, accountId, context, view }) => {
const resolved = resolveHandlerContext({
cfg,
accountId,
context
});
if (!resolved) return { body: {} };
const actionRow = createApprovalActionRow(view);
return { body: stripUndefinedFields(serializePayload(buildExecApprovalPayload(view.approvalKind === "plugin" ? createPluginApprovalRequestContainer({
view,
cfg,
accountId: resolved.accountId,
actionRow
}) : createExecApprovalRequestContainer({
view,
cfg,
accountId: resolved.accountId,
actionRow
})))) };
},
buildResolvedResult: ({ cfg, accountId, context, view }) => {
const resolvedContext = resolveHandlerContext({
cfg,
accountId,
context
});
if (!resolvedContext) return { kind: "delete" };
return {
kind: "update",
payload: view.approvalKind === "plugin" ? createPluginResolvedContainer({
view,
cfg,
accountId: resolvedContext.accountId
}) : createExecResolvedContainer({
view,
cfg,
accountId: resolvedContext.accountId
})
};
},
buildExpiredResult: ({ cfg, accountId, context, view }) => {
const resolvedContext = resolveHandlerContext({
cfg,
accountId,
context
});
if (!resolvedContext) return { kind: "delete" };
return {
kind: "update",
payload: view.approvalKind === "plugin" ? createPluginExpiredContainer({
view,
cfg,
accountId: resolvedContext.accountId
}) : createExecExpiredContainer({
view,
cfg,
accountId: resolvedContext.accountId
})
};
}
},
transport: {
prepareTarget: async ({ cfg, accountId, context, plannedTarget }) => {
const resolved = resolveHandlerContext({
cfg,
accountId,
context
});
if (!resolved) return null;
if (plannedTarget.surface === "origin") {
const destinationId = typeof plannedTarget.target.threadId === "string" && plannedTarget.target.threadId.trim().length > 0 ? plannedTarget.target.threadId.trim() : plannedTarget.target.to;
return {
dedupeKey: destinationId,
target: { discordChannelId: destinationId }
};
}
const { rest, request: discordRequest } = createDiscordClient({
cfg,
token: resolved.context.token,
accountId: resolved.accountId
});
const userId = plannedTarget.target.to;
const dmChannel = await discordRequest(() => createUserDmChannel(rest, userId), "dm-channel");
if (!dmChannel?.id) {
logError(`discord approvals: failed to create DM for user ${userId}`);
return null;
}
return {
dedupeKey: dmChannel.id,
target: {
discordChannelId: dmChannel.id,
recipientUserId: userId
}
};
},
deliverPending: async ({ cfg, accountId, context, plannedTarget, preparedTarget, pendingPayload }) => {
const resolved = resolveHandlerContext({
cfg,
accountId,
context
});
if (!resolved) return null;
const { rest, request: discordRequest } = createDiscordClient({
cfg,
token: resolved.context.token,
accountId: resolved.accountId
});
const message = await discordRequest(() => createChannelMessage(rest, preparedTarget.discordChannelId, { body: pendingPayload.body }), plannedTarget.surface === "origin" ? "send-approval-channel" : "send-approval");
if (!message?.id) {
if (plannedTarget.surface === "origin") logError("discord approvals: failed to send to channel");
else if (preparedTarget.recipientUserId) logError(`discord approvals: failed to send message to user ${preparedTarget.recipientUserId}`);
return null;
}
return {
discordMessageId: message.id,
discordChannelId: preparedTarget.discordChannelId
};
},
updateEntry: async ({ cfg, accountId, context, entry, payload, phase }) => {
const resolved = resolveHandlerContext({
cfg,
accountId,
context
});
if (!resolved) return;
const container = payload;
await finalizeMessage({
cfg,
accountId: resolved.accountId,
token: resolved.context.token,
cleanupAfterResolve: phase === "resolved" ? resolved.context.config.cleanupAfterResolve : false,
channelId: entry.discordChannelId,
messageId: entry.discordMessageId,
container
});
}
},
observe: {
onDuplicateSkipped: ({ preparedTarget, request }) => {
logDebug(`discord approvals: skipping duplicate approval ${request.id} for channel ${preparedTarget.dedupeKey}`);
},
onDelivered: ({ plannedTarget, preparedTarget, request }) => {
if (plannedTarget.surface === "origin") {
logDebug(`discord approvals: sent approval ${request.id} to channel ${preparedTarget.target.discordChannelId}`);
return;
}
logDebug(`discord approvals: sent approval ${request.id} to user ${plannedTarget.target.to}`);
},
onDeliveryError: ({ error, plannedTarget }) => {
if (plannedTarget.surface === "origin") {
logError(`discord approvals: failed to send to channel: ${String(error)}`);
return;
}
logError(`discord approvals: failed to notify user ${plannedTarget.target.to}: ${String(error)}`);
}
}
});
//#endregion
export { discordApprovalNativeRuntime as n, buildExecApprovalCustomId as t };