UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

187 lines (186 loc) • 8.8 kB
import { l as normalizeOptionalString } from "./string-coerce-6kR-86XC.mjs"; import { D as resolveExpiresAtMsFromDurationMs, F as resolveTimerTimeoutMs } from "./number-coercion-Dq1l5muL.mjs"; import { c as isRecord, i as asOptionalObjectRecord } from "./record-coerce-TIsquoVR.mjs"; import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs"; import { i as sanitizeExecApprovalWarningText, n as sanitizeExecApprovalDisplayText, t as exceedsApprovalTextLimit } from "./exec-approval-text-sanitize-C5Sh27u3.mjs"; import { t as sanitizeApprovalScope } from "./approval-scope-4KFvgPWW.mjs"; import { p as truncatePluginApprovalDetail } from "./plugin-approvals-BL3jD3Ct.mjs"; import { t as resolveExecApprovalCommandDisplay } from "./exec-approval-command-display-D1Zo4AoM.mjs"; import { randomUUID } from "node:crypto"; //#region src/infra/approval-presentation.ts const PLUGIN_EXTERNAL_RESOLUTION_LABEL_MAX_LENGTH = 80; function normalizeDecisionList(decisions) { const result = []; for (const decision of decisions) if (!result.includes(decision)) result.push(decision); if (!result.includes("deny")) result.push("deny"); return result; } function sanitizeOptionalSingleLine(value) { const normalized = normalizeOptionalString(value); return normalized ? sanitizeExecApprovalDisplayText(normalized) : null; } function normalizePluginExternalResolution(value) { if (!value) return null; const rawLabel = value.label?.trim(); const label = rawLabel ? sanitizeExecApprovalDisplayText(rawLabel) : ""; if (!label || exceedsApprovalTextLimit(label, PLUGIN_EXTERNAL_RESOLUTION_LABEL_MAX_LENGTH)) throw new Error("invalid external approval label"); const decisions = value.decisions ?? ["allow-once"]; if (decisions.length < 1 || decisions.length > 2 || decisions.some((decision) => decision !== "allow-once" && decision !== "allow-always") || new Set(decisions).size !== decisions.length) throw new Error("invalid external approval decisions"); return { label, decisions: [...decisions] }; } function buildExecApprovalPresentation(params) { if (!isRecord(params.request)) return null; const request = params.request; const { commandText, commandPreview } = resolveExecApprovalCommandDisplay(request); if (!commandText.trim()) return null; const warningText = typeof request.warningText === "string" && request.warningText.trim() ? sanitizeExecApprovalWarningText(request.warningText) : null; const scope = request.scope ? sanitizeApprovalScope(request.scope) : null; return { kind: "exec", commandText, commandPreview, warningText, host: sanitizeOptionalSingleLine(request.host), nodeId: sanitizeOptionalSingleLine(request.nodeId), agentId: sanitizeOptionalSingleLine(request.agentId), ...scope ? { scope } : {}, allowedDecisions: normalizeDecisionList(params.allowedDecisions) }; } function buildPluginApprovalPresentation(params) { if (!isRecord(params.request)) return null; const request = params.request; const rawTitle = normalizeOptionalString(request.title); const rawDescription = normalizeOptionalString(request.description); if (!rawTitle || !rawDescription) return null; const title = sanitizeExecApprovalDisplayText(rawTitle); const description = sanitizeExecApprovalWarningText(rawDescription); if (exceedsApprovalTextLimit(title, 80) || exceedsApprovalTextLimit(description, 512)) return null; const severity = request.severity === "info" || request.severity === "warning" || request.severity === "critical" ? request.severity : "warning"; const rawDetail = normalizeOptionalString(request.detail); const detail = rawDetail ? truncatePluginApprovalDetail(sanitizeExecApprovalWarningText(rawDetail)) : null; const scope = request.scope ? sanitizeApprovalScope(request.scope) : null; let externalResolution; try { externalResolution = normalizePluginExternalResolution(request.externalResolution); } catch { return null; } return { kind: "plugin", title, description, ...detail ? { detail } : {}, severity, pluginId: sanitizeOptionalSingleLine(request.pluginId), toolName: sanitizeOptionalSingleLine(request.toolName), agentId: sanitizeOptionalSingleLine(request.agentId), ...scope ? { scope } : {}, allowedDecisions: normalizeDecisionList(params.allowedDecisions), ...externalResolution ? { externalResolution: { label: externalResolution.label, decisions: [...externalResolution.decisions ?? ["allow-once"]] } } : {} }; } function buildSystemAgentApprovalPresentation(params) { if (!isRecord(params.request)) return null; const request = params.request; const title = normalizeOptionalString(request.title); const description = normalizeOptionalString(request.description); if (!title || !description || !/^[a-f0-9]{64}$/.test(request.proposalHash)) return null; return { kind: "system-agent", title: truncateUtf16Safe(sanitizeExecApprovalDisplayText(title), 80), description: truncateUtf16Safe(sanitizeExecApprovalWarningText(description), 512), proposalHash: request.proposalHash, agentId: sanitizeOptionalSingleLine(request.agentId), allowedDecisions: ["allow-once", "deny"] }; } /** Returns the safe cross-surface presentation, or null when no prompt can be rendered. */ function buildApprovalPresentation(params) { if (params.kind === "exec") return buildExecApprovalPresentation(params); return params.kind === "plugin" ? buildPluginApprovalPresentation(params) : buildSystemAgentApprovalPresentation(params); } //#endregion //#region src/gateway/exec-approval-registration.ts const EXPLICIT_APPROVAL_ID_INVALID_CHAR_PATTERN = /[^A-Za-z0-9._:-]/; /** Typed creation failure for an explicit approval id outside the shared safe format. */ var InvalidApprovalIdError = class extends Error { constructor() { super("approval id must be 1-128 characters using only letters, numbers, '.', '_', ':', or '-', and cannot be '.' or '..'"); this.code = "EXEC_APPROVAL_ID_INVALID"; this.reason = "INVALID_APPROVAL_ID"; this.name = "InvalidApprovalIdError"; } }; function createExecApprovalRecord(request, timeoutMs, id) { const now = Date.now(); const resolvedTimeoutMs = resolveTimerTimeoutMs(timeoutMs, 1); const expiresAtMs = resolveExpiresAtMsFromDurationMs(resolvedTimeoutMs, { nowMs: now }); if (expiresAtMs === void 0) throw new Error("approval expiry is unavailable"); const hasExplicitId = id !== null && id !== void 0 && id.length > 0; if (hasExplicitId && (id.length > 128 || id === "." || id === ".." || EXPLICIT_APPROVAL_ID_INVALID_CHAR_PATTERN.test(id))) throw new InvalidApprovalIdError(); return { id: hasExplicitId ? id : randomUUID(), request, createdAtMs: now, expiresAtMs }; } function prepareExecApprovalPresentation(kind, request, decisions) { const normalized = []; for (const decision of decisions ?? [ "allow-once", "allow-always", "deny" ]) if ((decision === "allow-once" || decision === "allow-always" || decision === "deny") && !normalized.includes(decision)) normalized.push(decision); if (!normalized.includes("deny")) normalized.push("deny"); const presentation = buildApprovalPresentation({ kind, request, allowedDecisions: normalized }); if (!presentation) throw new Error("approval cannot be persisted without a valid reviewer presentation"); return presentation; } function readRequestString(request, key) { const value = asOptionalObjectRecord(request)?.[key]; return typeof value === "string" && value.trim() ? value.trim() : null; } async function prepareExecApprovalRegistration(params) { const { record } = params; const source = { agentId: readRequestString(record.request, "agentId"), sessionKey: readRequestString(record.request, "sessionKey"), sessionId: readRequestString(record.request, "sessionId"), runId: readRequestString(record.request, "runId"), toolCallId: readRequestString(record.request, "toolCallId"), toolName: readRequestString(record.request, "toolName") }; let audienceSessionKeys = []; if (source.sessionKey) audienceSessionKeys = await params.resolveAudienceSessionKeys?.(source.sessionKey, source.agentId) ?? [source.sessionKey]; return { id: record.id, kind: params.kind, presentation: params.presentation, requester: { deviceId: record.requestedByDeviceId, clientId: record.requestedByClientId, deviceTokenAuth: record.requestedByDeviceTokenAuth === true }, reviewerDeviceIds: record.approvalReviewerDeviceIds, source, audienceSessionKeys, runtimeEpoch: params.runtimeEpoch, createdAtMs: record.createdAtMs, expiresAtMs: record.expiresAtMs, ...record.executionIdentityToken ? { executionIdentityToken: record.executionIdentityToken } : {} }; } //#endregion export { prepareExecApprovalRegistration as i, createExecApprovalRecord as n, prepareExecApprovalPresentation as r, InvalidApprovalIdError as t };