UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

386 lines (385 loc) 14.2 kB
import { p as normalizeUniqueStringEntries } from "./string-normalization-WNUDCpXX.js"; import { n as listMessageReceiptPlatformIds, r as resolveMessageReceiptPrimaryId } from "./receipt-B3SXxhHV.js"; import "./string-coerce-runtime-CEGJWkQ_.js"; import { t as buildChannelApprovalNativeTargetKey } from "./approval-native-target-key-BgRxMHOp.js"; import { c as buildExecApprovalPendingReplyPayload } from "./exec-approval-reply-BXMsffTv.js"; import { r as createChannelApprovalNativeRuntimeAdapter } from "./approval-handler-runtime-CFLINLFC.js"; import { i as buildPluginApprovalResolvedReplyPayload, r as buildPluginApprovalPendingReplyPayload } from "./approval-renderers-Cf0JuAfU.js"; import "./approval-handler-runtime-DF66dgbE.js"; import "./approval-native-runtime-BeD_vgZw.js"; import "./approval-reply-runtime-CtMUsKCW.js"; import "./approval-runtime-B6CZRTwB.js"; import "./channel-outbound-B3_Zy-kG.js"; import { i as resolveMatrixAccount } from "./accounts-ChjotiNL.js"; import { c as shouldHandleMatrixApprovalRequest, r as isMatrixAnyApprovalClientEnabled } from "./exec-approvals-DAIt4CGn.js"; import { a as resolveMatrixTargetIdentity } from "./target-ids-BCKwsiMr.js"; import { a as sendMessageMatrix, c as sendSingleTextMessageMatrix, i as reactMatrixMessage } from "./send-DOJVJulA.js"; import { i as repairMatrixDirectRooms } from "./direct-management-CF8cefTG.js"; import { a as unregisterMatrixApprovalReactionTarget, n as listMatrixApprovalReactionBindings, r as registerMatrixApprovalReactionTarget, t as buildMatrixApprovalReactionHint } from "./approval-reactions-BdVPF-lO.js"; import { n as editMatrixMessage, t as deleteMatrixMessage } from "./messages-3TEvUpXM.js"; import { setTimeout } from "node:timers/promises"; //#region extensions/matrix/src/approval-handler.runtime.ts const MATRIX_APPROVAL_METADATA_KEY = "com.openclaw.approval"; const MATRIX_APPROVAL_DELIVERY_ATTEMPTS = 3; const MATRIX_APPROVAL_DELIVERY_RETRY_DELAY_MS = 250; function resolveHandlerContext(params) { const context = params.context; const accountId = params.accountId?.trim() || ""; if (!context?.client || !accountId) return null; return { accountId, context }; } function normalizePendingMessageIds(entry) { return normalizeUniqueStringEntries(entry.platformMessageIds); } function normalizeReactionTargetRef(params) { const roomId = params.roomId.trim(); const eventId = params.eventId.trim(); if (!roomId || !eventId) return null; return { roomId, eventId }; } function normalizeThreadId(value) { return (value == null ? "" : String(value).trim()) || void 0; } function isSingleMatrixMessageLimitError(error) { return error instanceof Error && error.message.includes("Matrix single-message text exceeds limit"); } async function retryMatrixApprovalDelivery(operation, params = {}) { let lastError; for (let attempt = 1; attempt <= MATRIX_APPROVAL_DELIVERY_ATTEMPTS; attempt += 1) try { return await operation(); } catch (error) { lastError = error; if (attempt === MATRIX_APPROVAL_DELIVERY_ATTEMPTS || params.shouldRetry?.(error) === false) break; await setTimeout(MATRIX_APPROVAL_DELIVERY_RETRY_DELAY_MS * attempt); } throw lastError; } async function prepareTarget(params) { const resolved = resolveHandlerContext(params); if (!resolved) return null; const target = resolveMatrixTargetIdentity(params.rawTarget.to); if (!target) return null; const threadId = normalizeThreadId(params.rawTarget.threadId); if (target.kind === "user") { const account = resolveMatrixAccount({ cfg: params.cfg, accountId: resolved.accountId }); const repairDirectRooms = resolved.context.deps?.repairDirectRooms ?? repairMatrixDirectRooms; const repaired = await retryMatrixApprovalDelivery(async () => await repairDirectRooms({ client: resolved.context.client, remoteUserId: target.id, encrypted: account.config.encryption === true })); if (!repaired.activeRoomId) return null; return { to: `room:${repaired.activeRoomId}`, roomId: repaired.activeRoomId, threadId }; } return { to: `room:${target.id}`, roomId: target.id, threadId }; } function buildMatrixApprovalMetadata(params) { const base = { version: 1, type: "approval.request", id: params.view.approvalId, state: "pending", kind: params.view.approvalKind, phase: params.view.phase, title: params.view.title, expiresAtMs: params.view.expiresAtMs, metadata: params.view.metadata, allowedDecisions: Array.from(params.allowedDecisions), actions: params.view.actions.map((action) => ({ decision: action.decision, label: action.label, style: action.style, command: action.command })), ...params.view.description != null ? { description: params.view.description } : {} }; if (params.view.approvalKind === "plugin") return { ...base, kind: "plugin", severity: params.view.severity, ...params.view.agentId != null ? { agentId: params.view.agentId } : {}, ...params.view.pluginId != null ? { pluginId: params.view.pluginId } : {}, ...params.view.toolName != null ? { toolName: params.view.toolName } : {} }; return { ...base, kind: "exec", commandText: params.view.commandText, ...params.view.ask != null ? { ask: params.view.ask } : {}, ...params.view.agentId != null ? { agentId: params.view.agentId } : {}, ...params.view.commandPreview != null ? { commandPreview: params.view.commandPreview } : {}, ...params.view.cwd != null ? { cwd: params.view.cwd } : {}, ...params.view.envKeys != null ? { envKeys: params.view.envKeys } : {}, ...params.view.host != null ? { host: params.view.host } : {}, ...params.view.nodeId != null ? { nodeId: params.view.nodeId } : {}, ...params.view.sessionKey != null ? { sessionKey: params.view.sessionKey } : {} }; } function buildPendingApprovalContent(params) { const allowedDecisions = params.view.actions.map((action) => action.decision); const payload = params.view.approvalKind === "plugin" ? buildPluginApprovalPendingReplyPayload({ request: { id: params.view.approvalId, request: { title: params.view.title, description: params.view.description ?? "", severity: params.view.severity, toolName: params.view.toolName ?? void 0, pluginId: params.view.pluginId ?? void 0, agentId: params.view.agentId ?? void 0 }, createdAtMs: 0, expiresAtMs: params.view.expiresAtMs }, nowMs: params.nowMs, allowedDecisions }) : buildExecApprovalPendingReplyPayload({ approvalId: params.view.approvalId, approvalSlug: params.view.approvalId.slice(0, 8), approvalCommandId: params.view.approvalId, ask: params.view.ask ?? void 0, agentId: params.view.agentId ?? void 0, allowedDecisions, command: params.view.commandText, cwd: params.view.cwd ?? void 0, host: params.view.host === "node" ? "node" : "gateway", nodeId: params.view.nodeId ?? void 0, sessionKey: params.view.sessionKey ?? void 0, expiresAtMs: params.view.expiresAtMs, nowMs: params.nowMs }); const hint = buildMatrixApprovalReactionHint(allowedDecisions); const text = payload.text ?? ""; return { approvalId: params.view.approvalId, text: hint ? text ? `${hint}\n\n${text}` : hint : text, allowedDecisions, extraContent: { [MATRIX_APPROVAL_METADATA_KEY]: buildMatrixApprovalMetadata({ view: params.view, allowedDecisions }) } }; } function buildResolvedApprovalText(view) { if (view.approvalKind === "plugin") return buildPluginApprovalResolvedReplyPayload({ resolved: { id: view.approvalId, decision: view.decision, resolvedBy: view.resolvedBy ?? void 0, ts: 0 } }).text ?? ""; return [ `Exec approval: ${view.decision === "allow-once" ? "Allowed once" : view.decision === "allow-always" ? "Allowed always" : "Denied"}`, "", "Command", buildMarkdownCodeBlock(view.commandText) ].join("\n"); } function buildMarkdownCodeBlock(text) { const longestFence = Math.max(...Array.from(text.matchAll(/`+/g), (match) => match[0].length), 0); const fence = "`".repeat(Math.max(3, longestFence + 1)); return [ fence, text, fence ].join("\n"); } const matrixApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdapter({ eventKinds: ["exec", "plugin"], availability: { isConfigured: ({ cfg, accountId, context }) => { const resolved = resolveHandlerContext({ cfg, accountId, context }); if (!resolved) return false; return isMatrixAnyApprovalClientEnabled({ cfg, accountId: resolved.accountId }); }, shouldHandle: ({ cfg, accountId, request, context }) => { const resolved = resolveHandlerContext({ cfg, accountId, context }); if (!resolved) return false; return shouldHandleMatrixApprovalRequest({ cfg, accountId: resolved.accountId, request }); } }, presentation: { buildPendingPayload: ({ view, nowMs }) => buildPendingApprovalContent({ view, nowMs }), buildResolvedResult: ({ view }) => ({ kind: "update", payload: buildResolvedApprovalText(view) }), buildExpiredResult: () => ({ kind: "delete" }) }, transport: { prepareTarget: ({ cfg, accountId, context, plannedTarget }) => { return prepareTarget({ cfg, accountId, context, rawTarget: plannedTarget.target }).then((preparedTarget) => preparedTarget ? { dedupeKey: buildChannelApprovalNativeTargetKey({ to: preparedTarget.roomId, threadId: preparedTarget.threadId }), target: preparedTarget } : null); }, deliverPending: async ({ cfg, accountId, context, preparedTarget, pendingPayload, view }) => { const resolved = resolveHandlerContext({ cfg, accountId, context }); if (!resolved) return null; const sendSingleTextMessage = resolved.context.deps?.sendSingleTextMessage ?? sendSingleTextMessageMatrix; const reactMessage = resolved.context.deps?.reactMessage ?? reactMatrixMessage; let result; try { result = await retryMatrixApprovalDelivery(async () => await sendSingleTextMessage(preparedTarget.to, pendingPayload.text, { cfg, accountId: resolved.accountId, client: resolved.context.client, threadId: preparedTarget.threadId, extraContent: pendingPayload.extraContent }), { shouldRetry: (error) => !isSingleMatrixMessageLimitError(error) }); } catch (error) { if (!isSingleMatrixMessageLimitError(error)) throw error; const sendMessage = resolved.context.deps?.sendMessage ?? sendMessageMatrix; result = await retryMatrixApprovalDelivery(async () => await sendMessage(preparedTarget.to, pendingPayload.text, { cfg, accountId: resolved.accountId, client: resolved.context.client, threadId: preparedTarget.threadId, extraContent: pendingPayload.extraContent })); } const receiptMessageIds = listMessageReceiptPlatformIds(result.receipt); const platformMessageIds = receiptMessageIds.length ? receiptMessageIds : [result.messageId.trim()].filter(Boolean); const reactionEventId = resolveMessageReceiptPrimaryId(result.receipt) || result.primaryMessageId?.trim() || platformMessageIds[0] || result.messageId.trim(); registerMatrixApprovalReactionTarget({ roomId: result.roomId, eventId: reactionEventId, approvalId: pendingPayload.approvalId, allowedDecisions: pendingPayload.allowedDecisions, ttlMs: view.expiresAtMs - Date.now() }); await Promise.allSettled(listMatrixApprovalReactionBindings(pendingPayload.allowedDecisions).map(async ({ emoji }) => { await reactMessage(result.roomId, reactionEventId, emoji, { cfg, accountId: resolved.accountId, client: resolved.context.client }); })); return { roomId: result.roomId, platformMessageIds, reactionEventId }; }, updateEntry: async ({ cfg, accountId, context, entry, payload }) => { const resolved = resolveHandlerContext({ cfg, accountId, context }); if (!resolved) return; const editMessage = resolved.context.deps?.editMessage ?? editMatrixMessage; const deleteMessage = resolved.context.deps?.deleteMessage ?? deleteMatrixMessage; const [primaryMessageId, ...staleMessageIds] = normalizePendingMessageIds(entry); if (!primaryMessageId) return; const text = payload; await Promise.allSettled([editMessage(entry.roomId, primaryMessageId, text, { cfg, accountId: resolved.accountId, client: resolved.context.client }), ...staleMessageIds.map(async (messageId) => { await deleteMessage(entry.roomId, messageId, { cfg, accountId: resolved.accountId, client: resolved.context.client, reason: "approval resolved" }); })]); }, deleteEntry: async ({ cfg, accountId, context, entry, phase }) => { const resolved = resolveHandlerContext({ cfg, accountId, context }); if (!resolved) return; const deleteMessage = resolved.context.deps?.deleteMessage ?? deleteMatrixMessage; await Promise.allSettled(normalizePendingMessageIds(entry).map(async (messageId) => { await deleteMessage(entry.roomId, messageId, { cfg, accountId: resolved.accountId, client: resolved.context.client, reason: phase === "expired" ? "approval expired" : "approval resolved" }); })); } }, interactions: { bindPending: (params) => { const target = normalizeReactionTargetRef({ roomId: params.entry.roomId, eventId: params.entry.reactionEventId }); if (!target) return null; registerMatrixApprovalReactionTarget({ roomId: target.roomId, eventId: target.eventId, approvalId: params.pendingPayload.approvalId, allowedDecisions: params.pendingPayload.allowedDecisions, ttlMs: params.view.expiresAtMs - Date.now() }); return target; }, unbindPending: (params) => { const target = normalizeReactionTargetRef(params.binding); if (!target) return; unregisterMatrixApprovalReactionTarget(target); }, cancelDelivered: (params) => { const target = normalizeReactionTargetRef({ roomId: params.entry.roomId, eventId: params.entry.reactionEventId }); if (!target) return; unregisterMatrixApprovalReactionTarget(target); } } }); //#endregion export { matrixApprovalNativeRuntime };