openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
248 lines (247 loc) • 10.2 kB
JavaScript
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { o as resolveChannelDmPolicy } from "./dm-access-DF6nqjVk.js";
import { t as ErrorCodes } from "./gateway-error-details-w0nAGBBp.js";
import { I as validateChannelsPairingApproveParams, L as validateChannelsPairingDismissParams, R as validateChannelsPairingListParams } from "./src-BiL5aQto.js";
import { d as errorShape } from "./error-codes-Bo8q2D1o.js";
import { i as listChannelPlugins } from "./registry-Cz6cv4VC.js";
import "./plugins-Cozj1Enf.js";
import { r as hasConfiguredCommandOwners } from "./doctor-command-owner-BMpVjvKQ.js";
import { f as notifyPairingApproved } from "./pairing-store-sqlite-CNgVQSBb.js";
import { a as dismissChannelPairingRequest, i as approveChannelPairingRequest, o as listChannelPairingRequests, t as CHANNEL_PAIRING_PENDING_TTL_MS, u as resolveChannelPairingRequestId } from "./pairing-store-CxXcVQPq.js";
import { t as formatForLog } from "./ws-log-Dkg9wKNU.js";
import { t as assertValidParams } from "./validation-pzrlzFvo.js";
import { t as bootstrapCommandOwnerFromPairing } from "./command-owner-XdlIjcLw.js";
import { t as resolveGatewayPluginConfig } from "./runtime-plugin-config-CKZ9Aehs.js";
//#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 = plugin.config.resolveAccount(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": async ({ params, respond, context }) => {
if (!assertValidParams(params, validateChannelsPairingListParams, "channels.pairing.list", respond)) return;
try {
const parsed = params;
const cfg = resolveGatewayPluginConfig({ config: context.getRuntimeConfig() });
const accounts = await listPairingAccounts({
cfg,
...parsed.channel ? { channel: parsed.channel } : {},
...parsed.accountId ? { accountId: parsed.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": async ({ params, respond, context }) => {
if (!assertValidParams(params, validateChannelsPairingApproveParams, "channels.pairing.approve", respond)) return;
const parsed = params;
let cfg;
let account;
try {
cfg = resolveGatewayPluginConfig({ config: context.getRuntimeConfig() });
account = await resolvePairingAccount({
cfg,
channel: parsed.channel,
accountId: parsed.accountId
});
} catch (error) {
respondPairingFailure(respond, error);
return;
}
if (!account?.plugin.pairing) {
invalidPairingAccount(respond, parsed.channel, parsed.accountId);
return;
}
try {
const approved = await approveChannelPairingRequest({
channel: account.plugin.id,
accountId: account.accountId,
requestId: parsed.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 (parsed.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 (parsed.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: parsed.requestId,
senderId: approved.entry.meta?.senderId ?? approved.id,
notification,
commandOwnerBootstrap
}, void 0);
} catch (error) {
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, formatForLog(error)));
}
},
"channels.pairing.dismiss": async ({ params, respond, context }) => {
if (!assertValidParams(params, validateChannelsPairingDismissParams, "channels.pairing.dismiss", respond)) return;
const parsed = params;
let account;
try {
account = await resolvePairingAccount({
cfg: resolveGatewayPluginConfig({ config: context.getRuntimeConfig() }),
channel: parsed.channel,
accountId: parsed.accountId
});
} catch (error) {
respondPairingFailure(respond, error);
return;
}
if (!account) {
invalidPairingAccount(respond, parsed.channel, parsed.accountId);
return;
}
try {
const dismissed = await dismissChannelPairingRequest({
channel: account.plugin.id,
accountId: account.accountId,
requestId: parsed.requestId
});
if (!dismissed) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "pending DM access request no longer exists"));
return;
}
respond(true, {
requestId: parsed.requestId,
senderId: dismissed.entry.meta?.senderId ?? dismissed.id
}, void 0);
} catch (error) {
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, formatForLog(error)));
}
}
};
//#endregion
export { channelPairingHandlers };