UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

205 lines (204 loc) • 7.97 kB
import { i as asOptionalObjectRecord } from "./record-coerce-TIsquoVR.mjs"; import { t as normalizeControlUiBasePath } from "./control-ui-shared-DqFhbHR8.mjs"; import { i as emitAgentEvent } from "./agent-events-CwU5G442.mjs"; import "./operator-scopes-D-CL26h0.mjs"; import "./method-scopes-C-MKLkWf.mjs"; import { c as listPendingOperatorApprovals, n as expireDueOperatorApprovals } from "./operator-approval-store-BsGORrwn.mjs"; import { n as resolveApprovalSourceStreamKey } from "./approval-session-audience-CfZ7knjN.mjs"; import { t as matchesOperatorApprovalReviewerBinding } from "./operator-approval-reviewer-binding-CRKs8ZbC.mjs"; import { r as canReviewOperatorApproval, t as canAccessOperatorApproval } from "./operator-approval-authorization-CSJhRQr6.mjs"; import { t as projectOperatorApprovalSnapshot } from "./operator-approval-snapshot-DiSAwAzh.mjs"; //#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 replayScopes = /* @__PURE__ */ new Map(); 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) => { for (const scope of replayScopes.values()) if (event.record.audienceSessionKeys.includes(scope.sessionKey) && (scope.reviewerDeviceId === void 0 || matchesOperatorApprovalReviewerBinding(event.record, scope.reviewerDeviceId))) { scope.revision += 1; scope.preparation = void 0; } const source = event.record.source; const pending = event.phase === "pending" && event.record.status === "pending"; const manager = params.getLiveManager?.(event.record.kind); const live = pending ? manager?.getLiveSnapshot(event.record.id) : void 0; const request = asOptionalObjectRecord(live?.request); const livePending = Boolean(manager?.runtimeEpoch === event.record.runtimeEpoch && live && live.resolvedAtMs === void 0 && live.expiresAtMs > now() && request?.runId === source.runId && request?.sessionId === source.sessionId && request?.sessionKey === source.sessionKey); if (params.getLiveManager && params.isCurrent?.() !== false && source.runId && source.sessionId && (pending ? livePending : event.record.status !== "pending")) emitAgentEvent({ runId: source.runId, sessionId: source.sessionId, ...source.sessionKey ? { sessionKey: source.sessionKey } : {}, stream: "execution", data: { approval: { id: event.record.id, state: pending ? "pending" : "resolved" } } }); 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); } }; const prepareReplay = async (scope) => { const { sessionKey, reviewerDeviceId } = scope; const snapshotAtMs = now(); const expired = await expireDueOperatorApprovals({ nowMs: snapshotAtMs, databaseOptions: params.databaseOptions }); for (const record of expired.records) if (await params.reconcileTerminal?.(record) !== true) publish({ phase: "terminal", record }); if (params.isCurrent?.() === false) throw new Error("Operator approval replay authority is no longer current"); const revision = scope.revision; const records = await listPendingOperatorApprovals({ audienceSessionKey: sessionKey, reviewerDeviceId, limit: 1001, nowMs: snapshotAtMs, databaseOptions: params.databaseOptions }); const isCurrent = () => revision === scope.revision; const approvals = []; const truncated = records.length > MAX_SESSION_APPROVAL_REPLAY; for (const record of records) { if (approvals.length === MAX_SESSION_APPROVAL_REPLAY) return { replay: { sessionKey, updatedAtMs: snapshotAtMs, approvals, truncated: true }, isCurrent }; const approval = projectOperatorApprovalSnapshot(record, controlUiBasePath); if (approval?.status === "pending") { const sourceSessionKey = resolveApprovalSourceStreamKeyForRecord(record); approvals.push({ ...approval, ...sourceSessionKey ? { sourceSessionKey } : {} }); } } return { replay: { sessionKey, updatedAtMs: snapshotAtMs, approvals, truncated }, isCurrent }; }; return { publish, replay: async (sessionKey, client) => { if (!canReviewOperatorApproval(client)) return { replay: { sessionKey, updatedAtMs: now(), approvals: [], truncated: false }, isCurrent: () => !canReviewOperatorApproval(client), release: () => {} }; const scopes = [...client?.connect.scopes ?? []]; const deviceId = client?.connect.device?.id; const reviewerDeviceId = scopes.includes("operator.admin") ? void 0 : deviceId?.trim(); const assertCurrent = () => { if (params.isCurrent?.() === false || client?.invalidated || !canReviewOperatorApproval(client) || scopes.join("\0") !== client?.connect.scopes?.join("\0") || !scopes.includes("operator.admin") && deviceId !== client?.connect.device?.id) throw new Error("Operator approval replay authority is no longer current"); }; assertCurrent(); const key = JSON.stringify([sessionKey, reviewerDeviceId]); let scope = replayScopes.get(key); if (!scope) { scope = { sessionKey, reviewerDeviceId, references: 0, revision: 0 }; replayScopes.set(key, scope); } const retained = scope; retained.references += 1; let released = false; const release = () => { if (released) return; released = true; retained.references -= 1; if (retained.references === 0) replayScopes.delete(key); }; try { let preparation = retained.preparation; if (!preparation) { preparation = prepareReplay(retained).finally(() => { if (retained.preparation === preparation) retained.preparation = void 0; }); retained.preparation = preparation; } const prepared = await preparation; assertCurrent(); return { replay: prepared.replay, isCurrent: () => { assertCurrent(); return !released && prepared.isCurrent(); }, release }; } catch (error) { release(); throw error; } } }; } //#endregion export { createOperatorApprovalSessionEventRuntime };