UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

245 lines (244 loc) • 10 kB
import { l as normalizeOptionalString } from "./string-coerce-6kR-86XC.mjs"; import { a as asOptionalRecord } from "./record-coerce-TIsquoVR.mjs"; import { o as resolveChannelDmPolicy } from "./dm-access-oFJ9Wmpg.mjs"; import { I as validateChannelsPairingApproveParams, L as validateChannelsPairingDismissParams, R as validateChannelsPairingListParams } from "./src-D2gvuSyP.mjs"; import { t as ErrorCodes } from "./gateway-error-details-DIefe7Zy.mjs"; import { f as errorShape } from "./error-codes-DI20vDEB.mjs"; import { r as listChannelPlugins } from "./registry-B56V4gqI.mjs"; import "./plugins-CBMHLsl7.mjs"; import { r as hasConfiguredCommandOwners } from "./doctor-command-owner-CcdaMg7Q.mjs"; import { f as notifyPairingApproved } from "./pairing-store-sqlite-Ge7q10AO.mjs"; import { a as dismissChannelPairingRequest, i as approveChannelPairingRequest, o as listChannelPairingRequests, t as CHANNEL_PAIRING_PENDING_TTL_MS, u as resolveChannelPairingRequestId } from "./pairing-store-DKAYw0R1.mjs"; import { n as resolveChannelAccount } from "./account-resolution-DCHDXWZ0.mjs"; import { t as bootstrapCommandOwnerFromPairing } from "./command-owner-DAR6AvYS.mjs"; import { t as formatForLog } from "./ws-log-O15wBxel.mjs"; import { n as defineValidatedGatewayHandler } from "./validation-B7TobU6-.mjs"; import { n as respondUnavailableOnThrow, t as respondUnavailable } from "./response-DiopswxY.mjs"; //#region src/gateway/server-methods/channel-pairing.ts var InvalidPairingTargetError = class extends Error {}; function normalizeFilter(value) { return normalizeOptionalString(value)?.toLowerCase(); } function resolvePairingPolicy(params) { const securityPolicy = params.plugin.security?.resolveDmPolicy?.({ cfg: params.cfg, accountId: params.accountId, account: params.account })?.policy; if (securityPolicy) return securityPolicy; const account = asOptionalRecord(params.account); return resolveChannelDmPolicy({ account, parent: asOptionalRecord(account?.config), defaultPolicy: "pairing" }); } function resolvePairingAccountLabel(plugin, account, cfg) { const described = plugin.config.describeAccount?.(account, cfg); return normalizeOptionalString(described?.name) ?? normalizeOptionalString(asOptionalRecord(account)?.name); } async function listPairingAccounts(params) { const requestedChannel = normalizeFilter(params.channel); const requestedAccount = normalizeFilter(params.accountId); const pairingPlugins = listChannelPlugins().filter((plugin) => plugin.pairing); if (requestedChannel && !pairingPlugins.some((plugin) => plugin.id === requestedChannel)) throw new InvalidPairingTargetError(`unknown pairing channel: ${params.channel}`); const accounts = []; for (const plugin of pairingPlugins) { if (requestedChannel && plugin.id !== requestedChannel) continue; for (const accountId of plugin.config.listAccountIds(params.cfg)) { if (requestedAccount && accountId.toLowerCase() !== requestedAccount) continue; const account = await resolveChannelAccount({ plugin, cfg: params.cfg, accountId }); if (!(plugin.config.isConfigured ? await plugin.config.isConfigured(account, params.cfg) : asOptionalRecord(account)?.configured !== false) || resolvePairingPolicy({ plugin, cfg: params.cfg, accountId, account }) !== "pairing") continue; const accountLabel = resolvePairingAccountLabel(plugin, account, params.cfg); accounts.push({ plugin, accountId, ...accountLabel ? { accountLabel } : {} }); } } return accounts; } async function resolvePairingAccount(params) { const accounts = await listPairingAccounts(params); return accounts.length === 1 ? accounts[0] ?? null : null; } function publicAccount(account) { const adapter = account.plugin.pairing; if (!adapter) throw new Error(`Channel ${account.plugin.id} does not support pairing`); return { channel: account.plugin.id, channelLabel: account.plugin.meta.label, accountId: account.accountId, ...account.accountLabel ? { accountLabel: account.accountLabel } : {}, notifySupported: Boolean(adapter.notifyApproval) }; } function publicRequest(params) { const adapter = params.account.plugin.pairing; if (!adapter) throw new Error(`Channel ${params.account.plugin.id} does not support pairing`); const metadata = params.request.meta ? Object.fromEntries(Object.entries(params.request.meta).filter(([key, value]) => key !== "accountId" && key !== "senderId" && value)) : void 0; const createdAtMs = Date.parse(params.request.createdAt); const senderId = params.request.meta?.senderId ?? params.request.id; return { requestId: resolveChannelPairingRequestId(params.account.plugin.id, params.request), channel: params.account.plugin.id, channelLabel: params.account.plugin.meta.label, accountId: params.account.accountId, ...params.account.accountLabel ? { accountLabel: params.account.accountLabel } : {}, senderId, senderLabel: adapter.idLabel, ...metadata && Object.keys(metadata).length > 0 ? { metadata } : {}, createdAt: params.request.createdAt, lastSeenAt: params.request.lastSeenAt, expiresAt: new Date(createdAtMs + CHANNEL_PAIRING_PENDING_TTL_MS).toISOString(), notifySupported: Boolean(adapter.notifyApproval) }; } function invalidPairingAccount(respond, channel, accountId) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `channel account does not use DM pairing: ${channel}:${accountId}`)); } function respondPairingFailure(respond, error) { respond(false, void 0, errorShape(error instanceof InvalidPairingTargetError ? ErrorCodes.INVALID_REQUEST : ErrorCodes.UNAVAILABLE, formatForLog(error))); } const channelPairingHandlers = { "channels.pairing.list": defineValidatedGatewayHandler("channels.pairing.list", validateChannelsPairingListParams, async ({ params, respond, context }) => { try { const cfg = context.getRuntimeConfig(); const accounts = await listPairingAccounts({ cfg, ...params.channel ? { channel: params.channel } : {}, ...params.accountId ? { accountId: params.accountId } : {} }); const requests = []; for (const account of accounts) { const pending = await listChannelPairingRequests(account.plugin.id, process.env, account.accountId); requests.push(...pending.map((request) => publicRequest({ account, request }))); } respond(true, { accounts: accounts.map(publicAccount), requests, commandOwnerConfigured: hasConfiguredCommandOwners(cfg), limits: { pendingPerAccount: 3, ttlMs: CHANNEL_PAIRING_PENDING_TTL_MS } }, void 0); } catch (error) { respondPairingFailure(respond, error); } }), "channels.pairing.approve": defineValidatedGatewayHandler("channels.pairing.approve", validateChannelsPairingApproveParams, async ({ params, respond, context }) => { let cfg; let account; try { cfg = context.getRuntimeConfig(); account = await resolvePairingAccount({ cfg, channel: params.channel, accountId: params.accountId }); } catch (error) { respondPairingFailure(respond, error); return; } if (!account?.plugin.pairing) { invalidPairingAccount(respond, params.channel, params.accountId); return; } try { const approved = await approveChannelPairingRequest({ channel: account.plugin.id, accountId: account.accountId, requestId: params.requestId, pairingAdapter: account.plugin.pairing }); if (!approved) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "pending DM access request no longer exists")); return; } let commandOwnerBootstrap = "not-requested"; if (params.bootstrapCommandOwner === true) try { commandOwnerBootstrap = (await bootstrapCommandOwnerFromPairing({ channel: account.plugin.id, id: approved.id })).status; } catch (error) { context.logGateway.warn(`DM pairing command-owner bootstrap failed channel=${account.plugin.id} account=${account.accountId}: ${formatForLog(error)}`); commandOwnerBootstrap = "unavailable"; } let notification = "not-requested"; if (params.notify === true) { if (!account.plugin.pairing.notifyApproval) notification = "unsupported"; else try { await notifyPairingApproved({ channelId: account.plugin.id, accountId: account.accountId, id: approved.id, cfg, pairingAdapter: account.plugin.pairing, ...approved.entry.meta ? { meta: approved.entry.meta } : {} }); notification = "sent"; } catch (error) { context.logGateway.warn(`DM pairing approval notification failed channel=${account.plugin.id} account=${account.accountId}: ${formatForLog(error)}`); notification = "failed"; } } respond(true, { requestId: params.requestId, senderId: approved.entry.meta?.senderId ?? approved.id, notification, commandOwnerBootstrap }, void 0); } catch (error) { respondUnavailable(respond, error); } }), "channels.pairing.dismiss": defineValidatedGatewayHandler("channels.pairing.dismiss", validateChannelsPairingDismissParams, async ({ params, respond, context }) => { let account; try { account = await resolvePairingAccount({ cfg: context.getRuntimeConfig(), channel: params.channel, accountId: params.accountId }); } catch (error) { respondPairingFailure(respond, error); return; } if (!account) { invalidPairingAccount(respond, params.channel, params.accountId); return; } await respondUnavailableOnThrow(respond, async () => { const dismissed = await dismissChannelPairingRequest({ channel: account.plugin.id, accountId: account.accountId, requestId: params.requestId }); if (!dismissed) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "pending DM access request no longer exists")); return; } respond(true, { requestId: params.requestId, senderId: dismissed.entry.meta?.senderId ?? dismissed.id }, void 0); }); }) }; //#endregion export { channelPairingHandlers };