UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

135 lines (134 loc) 6.62 kB
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { o as isRecord } from "./record-coerce-DHZ4bFlT.js"; import { i as formatErrorMessage } from "./errors-BXgSefBE.js"; import { t as createSubsystemLogger } from "./subsystem-BzXSmsuh.js"; import { t as validateJsonSchemaValue } from "./schema-validator-CHU-4IBb.js"; import { o as WRITE_SCOPE, t as ADMIN_SCOPE } from "./operator-scopes-CS3xdS-V.js"; import { o as getActivePluginRegistry } from "./runtime-B2ROiq5l.js"; import { t as isPluginJsonValue } from "./host-hook-json-CRVrIqU9.js"; import { in as errorShape, rn as ErrorCodes } from "./schema-BwaBORnA.js"; import { At as validatePluginsUiDescriptorsParams, Ot as validatePluginsSessionActionParams, kt as validatePluginsSessionActionResult, t as formatValidationErrors } from "./src-oj0IwW6K.js"; //#region src/gateway/server-methods/plugin-host-hooks.ts const log = createSubsystemLogger("gateway/plugin-host-hooks"); function formatSessionActionPayloadSchemaErrors(errors) { return errors.map((error) => error.text).join("; "); } /** Ensures plugin action result extension fields stay JSON-compatible on the wire. */ function validatePluginSessionActionJsonFields(result) { for (const field of [ "result", "reply", "details" ]) if (result[field] !== void 0 && !isPluginJsonValue(result[field])) return `plugin session action ${field} must be JSON-compatible`; } /** Gateway handlers for plugin-declared Control UI descriptors and session actions. */ const pluginHostHookHandlers = { "plugins.uiDescriptors": ({ params, respond }) => { if (!validatePluginsUiDescriptorsParams(params)) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `invalid plugins.uiDescriptors params: ${formatValidationErrors(validatePluginsUiDescriptorsParams.errors)}`)); return; } respond(true, { ok: true, descriptors: (getActivePluginRegistry()?.controlUiDescriptors ?? []).map((entry) => Object.assign({}, entry.descriptor, { pluginId: entry.pluginId, pluginName: entry.pluginName })) }, void 0); }, "plugins.sessionAction": async ({ params, client, respond }) => { if (!validatePluginsSessionActionParams(params)) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `invalid plugins.sessionAction params: ${formatValidationErrors(validatePluginsSessionActionParams.errors)}`)); return; } const pluginId = normalizeOptionalString(params.pluginId); const actionId = normalizeOptionalString(params.actionId); const sessionKey = normalizeOptionalString(params.sessionKey); if (!pluginId || !actionId) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "plugins.sessionAction pluginId and actionId must be non-empty")); return; } const registry = getActivePluginRegistry(); const pluginLoaded = Boolean(registry?.plugins.some((plugin) => plugin.id === pluginId && plugin.status === "loaded")); const registration = (registry?.sessionActions ?? []).find((entry) => entry.pluginId === pluginId && entry.action.id === actionId); if (!registration || !pluginLoaded) { respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, `unknown plugin session action: ${pluginId}/${actionId}`)); return; } const scopes = Array.isArray(client?.connect.scopes) ? client.connect.scopes : []; const hasAdmin = scopes.includes(ADMIN_SCOPE); const missingScope = (registration.action.requiredScopes && registration.action.requiredScopes.length > 0 ? registration.action.requiredScopes : [WRITE_SCOPE]).find((scope) => !hasAdmin && !scopes.includes(scope) && !(scope === "operator.read" && scopes.includes("operator.write"))); if (missingScope) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `missing scope: ${missingScope}`)); return; } try { if (params.payload !== void 0 && !isPluginJsonValue(params.payload)) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "plugin session action payload must be JSON-compatible")); return; } if (registration.action.schema !== void 0) { if (typeof registration.action.schema !== "boolean" && !isRecord(registration.action.schema)) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "plugin session action schema must be an object or boolean")); return; } const validation = validateJsonSchemaValue({ schema: registration.action.schema, cacheKey: `plugin-session-action:${pluginId}:${actionId}`, value: params.payload }); if (!validation.ok) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `plugin session action payload does not match schema: ${formatSessionActionPayloadSchemaErrors(validation.errors)}`)); return; } } const result = await registration.action.handler({ pluginId, actionId, ...sessionKey ? { sessionKey } : {}, ...params.payload !== void 0 ? { payload: params.payload } : {}, client: { ...client?.connId ? { connId: client.connId } : {}, scopes: [...scopes] } }); if (result !== void 0 && !isRecord(result)) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "plugin session action result must be an object")); return; } const wireResult = result?.ok === false ? result : { ok: true, ...result }; if (!validatePluginsSessionActionResult(wireResult)) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `invalid plugin session action result: ${formatValidationErrors(validatePluginsSessionActionResult.errors)}`)); return; } const jsonFieldError = result ? validatePluginSessionActionJsonFields(result) : void 0; if (jsonFieldError) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, jsonFieldError)); return; } if (!wireResult.ok) { respond(true, { ok: false, error: wireResult.error, ...wireResult.code !== void 0 ? { code: wireResult.code } : {}, ...wireResult.details !== void 0 ? { details: wireResult.details } : {} }, void 0); return; } respond(true, { ok: true, ...wireResult.result !== void 0 ? { result: wireResult.result } : {}, ...wireResult.continueAgent !== void 0 ? { continueAgent: wireResult.continueAgent } : {}, ...wireResult.reply !== void 0 ? { reply: wireResult.reply } : {} }); } catch (error) { log.warn(`plugin session action failed plugin=${pluginId} action=${actionId}: ${formatErrorMessage(error)}`); respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, "plugin session action failed")); } } }; //#endregion export { pluginHostHookHandlers };