openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
222 lines (221 loc) • 10.2 kB
JavaScript
import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { O as parseAgentSessionKey, u as normalizeMainKey } from "./session-key-BnWWjqNc.js";
import { _ as resolveSessionAgentId } from "./agent-scope-DbtJyKUL.js";
import { n as resolvePersistedSessionStoreOwnerForKey } from "./session-store-owner-CR2Ag3xK.js";
import { l as isOperatorScope, t as ADMIN_SCOPE } from "./operator-scopes-Dw7Gu2cA.js";
import { r as logVerbose } from "./globals-CTaGxEqj.js";
import { U as withPluginCommandExecution } from "./runtime-BL4wZfTq.js";
import { f as withPluginRuntimeRegistryScope } from "./gateway-request-scope-BCMYlsDI.js";
import { i as canExposeSenderIsOwner, o as isTrustedReservedCommandOwner, t as isReservedCommandName } from "./command-registration-Ctsn0AES.js";
import { n as resolveCommandConversationResolution } from "./conversation-resolution-BCtsaj9X.js";
import { c as getCurrentPluginConversationBinding, f as requestPluginConversationBinding, s as detachPluginConversationBinding } from "./conversation-binding-DNzZGFgH.js";
import { t as pluginCommandSupportsChannel } from "./plugin-command-metadata-jSFxBwiS.js";
//#region src/agents/session-agent-binding.ts
/**
* Session-to-agent binding resolver.
*
* Derives the trusted active agent from explicit agent ids, agent session keys, or configured main-session aliases.
*/
/**
* Resolve the trusted active agent bound to a host-owned session reference.
*/
function resolveBoundAgentIdForSession(params) {
const config = params.config ?? {};
const agentId = normalizeOptionalString(params.agentId);
const sessionKey = normalizeOptionalString(params.sessionKey);
if (!agentId && !sessionKey) return;
if (agentId) return resolveSessionAgentId({
config,
sessionKey,
agentId
});
const persistedOwner = resolvePersistedSessionStoreOwnerForKey(config, sessionKey);
const loweredSessionKey = normalizeLowercaseStringOrEmpty(sessionKey);
const mainKey = normalizeMainKey(config.session?.mainKey);
return Boolean(parseAgentSessionKey(sessionKey)?.agentId) || persistedOwner.kind !== "none" || loweredSessionKey === "main" || loweredSessionKey === mainKey ? resolveSessionAgentId({
config,
sessionKey
}) : void 0;
}
//#endregion
//#region src/plugins/plugin-command-execution.ts
/** Exact-registry plugin command execution shared by focused and compatibility runtimes. */
const MAX_ARGS_LENGTH = 4096;
const blockedCompaction = (reason) => ({
compacted: false,
reason
});
function sanitizeArgs(args) {
if (!args) return;
let sanitized = "";
for (const char of truncateUtf16Safe(args, MAX_ARGS_LENGTH)) {
const code = char.charCodeAt(0);
if (!(code <= 31 && code !== 9 && code !== 10 || code === 127)) sanitized += char;
}
return sanitized;
}
function resolveBindingConversation(params) {
const channelPlugin = params.registry.channels.find((entry) => entry.plugin.id === params.channel)?.plugin;
if (!channelPlugin?.bindings?.resolveCommandConversation) return null;
return resolveCommandConversationResolution({
cfg: params.config,
plugin: channelPlugin,
channel: params.channel,
accountId: params.accountId,
threadId: params.messageThreadId,
threadParentId: params.threadParentId,
senderId: params.senderId,
originatingTo: params.originatingTo ?? params.from,
commandTo: params.to,
fallbackTo: params.to ?? params.from
});
}
function buildRuntimeContext(command, params, invocationSignal) {
const sessionKey = params.sessionKey?.trim();
const agentId = resolveBoundAgentIdForSession({
config: params.config,
agentId: params.agentId,
sessionKey
});
const compactCurrent = params.runtimeContext?.compactCurrent;
if (!sessionKey && !agentId) return;
return {
llm: { complete: async (request) => {
const { createRuntimeLlm } = await import("./runtime-llm.runtime.js");
return await createRuntimeLlm({
getConfig: () => params.config,
authority: {
caller: {
kind: "plugin",
id: command.pluginId,
name: command.pluginName
},
pluginIdForPolicy: command.pluginId,
requiresBoundAgent: true,
...sessionKey ? { sessionKey } : {},
...agentId ? { agentId } : {},
...params.authProfileId ? { preferredProfile: params.authProfileId } : {},
allowAgentIdOverride: false,
allowModelOverride: false,
allowComplete: true
}
}).complete(request);
} },
...compactCurrent && params.sessionTarget ? { compactCurrent: async () => {
if (invocationSignal.aborted) return blockedCompaction("command invocation closed");
const result = await compactCurrent(invocationSignal);
return invocationSignal.aborted ? blockedCompaction("command invocation closed") : result;
} } : {}
};
}
async function executeRegisteredPluginCommand(registry, params) {
const { command, args, senderId, channel, isAuthorizedSender, commandBody, config } = params;
if (!pluginCommandSupportsChannel(command, channel)) {
logVerbose(`Plugin command /${command.name} skipped on unsupported channel ${channel}`);
return { continueAgent: true };
}
if (command.requireAuth !== false && !isAuthorizedSender) {
logVerbose(`Plugin command /${command.name} blocked: unauthorized sender ${senderId || "<unknown>"}`);
return { text: "⚠️ This command requires authorization." };
}
if (command.requiredScopes !== void 0 && !Array.isArray(command.requiredScopes)) {
logVerbose(`Plugin command /${command.name} blocked: invalid requiredScopes configuration`);
return { text: "⚠️ This command has invalid gateway scope configuration." };
}
const requiredScopes = command.requiredScopes ?? [];
if (requiredScopes.find((scope) => !isOperatorScope(scope))) {
logVerbose(`Plugin command /${command.name} blocked: unknown gateway scope`);
return { text: "⚠️ This command has invalid gateway scope configuration." };
}
if (requiredScopes.length > 0) {
const scopes = Array.isArray(params.gatewayClientScopes) ? new Set(params.gatewayClientScopes) : void 0;
const hasAdmin = scopes?.has(ADMIN_SCOPE) === true;
const missingScope = scopes ? requiredScopes.find((scope) => !hasAdmin && !scopes.has(scope)) : requiredScopes[0];
if (missingScope && (scopes !== void 0 || params.senderIsOwner !== true)) {
logVerbose(`Plugin command /${command.name} blocked: missing gateway scope ${missingScope}`);
return { text: `⚠️ This command requires gateway scope: ${missingScope}.` };
}
}
const bindingConversation = resolveBindingConversation({
registry,
config,
channel,
senderId,
from: params.from,
to: params.to,
originatingTo: params.originatingTo,
accountId: params.accountId,
messageThreadId: params.messageThreadId,
threadParentId: params.threadParentId
});
const trustedReservedOwner = isTrustedReservedCommandOwner(command) && command.ownership === "reserved" && isReservedCommandName(command.name) && command.pluginId === normalizeLowercaseStringOrEmpty(command.name);
const senderIsOwner = canExposeSenderIsOwner(command) || trustedReservedOwner ? params.senderIsOwner : void 0;
const commandInvocationAbort = new AbortController();
const ctx = {
senderId,
channel,
channelId: params.channelId,
isAuthorizedSender,
...senderIsOwner === void 0 ? {} : { senderIsOwner },
gatewayClientScopes: params.gatewayClientScopes,
agentId: params.agentId,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
sessionTarget: params.sessionTarget,
sessionFile: params.sessionFile,
args: sanitizeArgs(args),
commandBody,
config,
from: params.from,
to: params.to,
accountId: bindingConversation?.accountId ?? params.accountId,
messageThreadId: params.messageThreadId,
threadParentId: params.threadParentId,
diagnosticsSessions: params.diagnosticsSessions,
runtimeContext: buildRuntimeContext(command, params, commandInvocationAbort.signal),
...trustedReservedOwner && params.diagnosticsUploadApproved !== void 0 ? { diagnosticsUploadApproved: params.diagnosticsUploadApproved } : {},
...trustedReservedOwner && params.diagnosticsPreviewOnly !== void 0 ? { diagnosticsPreviewOnly: params.diagnosticsPreviewOnly } : {},
...trustedReservedOwner && params.diagnosticsPrivateRouted !== void 0 ? { diagnosticsPrivateRouted: params.diagnosticsPrivateRouted } : {},
requestConversationBinding: async (bindingParams) => {
if (!command.pluginRoot || !bindingConversation) return {
status: "error",
message: "This command cannot bind the current conversation."
};
return requestPluginConversationBinding({
pluginId: command.pluginId,
pluginName: command.pluginName,
pluginRoot: command.pluginRoot,
requestedBySenderId: senderId,
conversation: bindingConversation,
binding: bindingParams
});
},
detachConversationBinding: async () => command.pluginRoot && bindingConversation ? detachPluginConversationBinding({
pluginRoot: command.pluginRoot,
conversation: bindingConversation
}) : { removed: false },
getCurrentConversationBinding: async () => command.pluginRoot && bindingConversation ? getCurrentPluginConversationBinding({
pluginRoot: command.pluginRoot,
conversation: bindingConversation
}) : null
};
try {
const execution = await withPluginCommandExecution(registry, () => withPluginRuntimeRegistryScope(registry, () => command.handler(ctx)));
if (!execution.admitted) return { text: "⚠️ This command is no longer available after the plugin registry changed. Please try again." };
const result = execution.value;
logVerbose(`Plugin command /${command.name} executed successfully for ${senderId || "unknown"}`);
if (!result || typeof result !== "object") {
logVerbose(`Plugin command /${command.name} returned no reply payload`);
return {};
}
return result;
} catch (error) {
logVerbose(`Plugin command /${command.name} error: ${error.message}`);
return { text: "⚠️ Command failed. Please try again later." };
} finally {
commandInvocationAbort.abort("command invocation closed");
}
}
//#endregion
export { resolveBoundAgentIdForSession as n, executeRegisteredPluginCommand as t };