UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

139 lines (138 loc) 4.94 kB
import { t as normalizeControlUiBasePath } from "./control-ui-shared-BiO6QP54.js"; import { i as expireDueOperatorApprovals, l as listPendingOperatorApprovals } from "./operator-approval-store-BxnBnd-1.js"; import { n as resolveApprovalSourceStreamKey } from "./approval-session-audience-ChaFPoso.js"; import { t as canAccessOperatorApproval } from "./operator-approval-authorization-DgI7PHhT.js"; //#region src/gateway/operator-approval-snapshot.ts /** Project one durable row into the reviewer-safe public approval shape. */ function projectOperatorApprovalSnapshot(record, controlUiBasePath) { const common = { id: record.id, status: record.status, presentation: record.presentation, urlPath: `${controlUiBasePath}/approve/${encodeURIComponent(record.id)}`, createdAtMs: record.createdAtMs, expiresAtMs: record.expiresAtMs }; if (record.status === "pending") return common; if (record.resolvedAtMs === null || record.terminalReason === null) return null; const terminal = { ...common, resolvedAtMs: record.resolvedAtMs, reason: record.terminalReason }; if (record.status === "allowed") { if (record.decision !== "allow-once" && record.decision !== "allow-always") return null; return { ...terminal, decision: record.decision }; } if (record.status === "denied") return { ...terminal, decision: "deny" }; return terminal; } //#endregion //#region src/gateway/operator-approval-session-events.ts const MAX_SESSION_APPROVAL_REPLAY = 1e3; function resolveApprovalSourceStreamKeyForRecord(record) { return record.audienceSessionKeys[0] ?? (record.source.sessionKey ? resolveApprovalSourceStreamKey(record.source.sessionKey, record.source.agentId) : null); } /** Project durable approval truth to exact, explicitly opted-in session audiences. */ function createOperatorApprovalSessionEventRuntime(params) { const controlUiBasePath = normalizeControlUiBasePath(params.controlUiBasePath); const now = params.now ?? Date.now; const canAccessRecord = (client, record) => canAccessOperatorApproval({ client, binding: { reviewerDeviceIds: record.reviewerDeviceIds } }); const authorizedRecipients = (sessionKey, record) => { const subscribed = params.sessionMessageSubscribers.getApprovals(sessionKey); if (subscribed.size === 0) return subscribed; const recipients = /* @__PURE__ */ new Set(); for (const client of params.clients) { const connId = client.connId; if (!client.invalidated && connId && subscribed.has(connId) && canAccessRecord(client, record)) recipients.add(connId); } return recipients; }; const publish = (event) => { const approval = projectOperatorApprovalSnapshot(event.record, controlUiBasePath); if (!approval || event.record.audienceSessionKeys.length === 0) return; const sourceStreamKey = resolveApprovalSourceStreamKeyForRecord(event.record); for (const sessionKey of event.record.audienceSessionKeys) { const recipients = authorizedRecipients(sessionKey, event.record); if (recipients.size === 0) continue; const common = { sessionKey, ...sourceStreamKey ? { sourceSessionKey: sourceStreamKey } : {}, updatedAtMs: event.record.updatedAtMs }; let payload; if (event.phase === "pending") { if (approval.status !== "pending") continue; payload = { ...common, phase: "pending", approval }; } else { if (approval.status === "pending") continue; payload = { ...common, phase: "terminal", approval }; } params.broadcastToConnIds("session.approval", payload, recipients); } }; return { publish, replay: (sessionKey, client) => { const snapshotAtMs = now(); const expired = expireDueOperatorApprovals({ nowMs: snapshotAtMs, databaseOptions: params.databaseOptions }); for (const record of expired.records) if (params.reconcileTerminal?.(record) !== true) publish({ phase: "terminal", record }); const approvals = []; const records = listPendingOperatorApprovals({ audienceSessionKey: sessionKey, recordFilter: (record) => canAccessRecord(client, record), limit: 1001, nowMs: snapshotAtMs, databaseOptions: params.databaseOptions }); const truncated = records.length > MAX_SESSION_APPROVAL_REPLAY; for (const record of records) { if (approvals.length === MAX_SESSION_APPROVAL_REPLAY) return { sessionKey, updatedAtMs: snapshotAtMs, approvals, truncated: true }; const approval = projectOperatorApprovalSnapshot(record, controlUiBasePath); if (approval?.status === "pending") { const sourceSessionKey = resolveApprovalSourceStreamKeyForRecord(record); approvals.push({ ...approval, ...sourceSessionKey ? { sourceSessionKey } : {} }); } } return { sessionKey, updatedAtMs: snapshotAtMs, approvals, truncated }; } }; } //#endregion export { createOperatorApprovalSessionEventRuntime };