UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

485 lines (484 loc) • 21.1 kB
import { Et as _enum, Jn as string, Lt as boolean, Nt as array, qn as strictObject, xn as literal } from "./schemas-BOYIvvln.mjs"; import { t as freezeJsonSnapshot } from "./immutable-data-MyNs7ITg.mjs"; import { a as getProcessGatewayPluginMetadataSnapshot } from "./current-plugin-metadata-state-LHE4BTlf.mjs"; import { a as composeSessionSourceAssertion } from "./session-source-authority-h1UO3u0I.mjs"; import { n as GatewayOperatorRoleDefinitionSchema } from "./zod-schema.gateway-BpIrk2MA.mjs"; import { n as isBrowserOperatorUiClient } from "./message-channel-AvA3r_Xp.mjs"; import { t as GATEWAY_OWNER_PROFILE_ID } from "./user-profile-constants-DfyZS95p.mjs"; import { z as onUserProfilesChanged } from "./user-profiles-internal-B_G1rF82.mjs"; import { o as createAdmittedRunOperatorAuthority, t as assertAdmittedRunOperatorAuthority } from "./admitted-run-context-D0yzk2io.mjs"; import { n as prepareOperatorModelPolicy, r as readOperatorModelPolicyMembership, t as captureOperatorModelPolicySnapshot } from "./operator-model-policy-dg8HlQ48.mjs"; import { a as roleScopesAllow, t as intersectOperatorScopes } from "./operator-scope-compat-Ci6GBcmU.mjs"; import { s as prepareUserProfileIdentity } from "./user-profile-list-B5oc-3kB.mjs"; import { d as readGatewayDeviceSourceIdentity, f as retainGatewayDeviceRevocation, s as onGatewayDeviceSourceRevoked, u as readGatewayDeviceSourceAuthority } from "./device-revocation-Cyf_TEpJ.mjs"; import { a as onOperatorRolePolicyChanged, f as resolveOperatorRolePolicyForAssignment, u as resolveGatewayOperatorRoleActor } from "./operator-role-policy-BPiQfn95.mjs"; import { r as sourceRolePolicy } from "./operator-role-source-policy-BO9lXFYe.mjs"; import { isDeepStrictEqual } from "node:util"; //#region src/gateway/operator-run-recovery-source.ts const MAX_RECOVERY_SOURCE_BYTES = 65536; const boundedString = string().min(1).max(4096); const refs = strictObject({ exact: array(boundedString).max(4096), wildcards: array(boundedString).max(256) }); const snapshotSchema = strictObject({ profileId: boundedString, scopes: array(boundedString).max(64), assignedRole: boundedString.nullable(), githubLogin: boundedString.nullable(), rolePolicy: GatewayOperatorRoleDefinitionSchema.omit({ modelPolicy: true }).optional(), modelPolicy: strictObject({ models: array(strictObject({ provider: boundedString, model: boundedString })).max(4096), allowed: refs, denied: refs }).optional(), grant: strictObject({ pluginId: boundedString, grantId: boundedString }).nullable(), aliasBindingIds: array(boundedString).max(256), authPolicy: strictObject({ generation: literal(""), grantGeneration: boundedString, role: literal("operator"), authMethod: _enum([ "token", "password", "device-token", "tailscale", "trusted-proxy" ]), authModeOverride: _enum([ "none", "token", "password", "trusted-proxy" ]).optional(), verifiedIdentity: boundedString.optional(), browserOrigin: strictObject({ requestHost: boundedString.optional(), origin: boundedString.optional(), isLocalClient: boolean().optional() }).optional() }), sharedGeneration: boundedString.optional(), device: strictObject({ deviceId: boundedString, key: string().regex(/^[a-f0-9]{64}$/) }).optional(), controlUiAdmin: boolean(), localOperator: boolean(), sourceIngress: _enum(["control-ui", "internal"]) }); const sourceSchema = strictObject({ version: literal(1), agentId: boundedString, sessionKey: boundedString, sessionId: boundedString, lifecycleRevision: boundedString.optional(), sourceRunId: boundedString, snapshot: snapshotSchema }); function decodeGatewayOperatorRecoverySource(value) { const json = JSON.stringify(value); if (!json || Buffer.byteLength(json, "utf8") > MAX_RECOVERY_SOURCE_BYTES) throw new Error("Restart recovery operator source exceeds its storage bound."); const parsed = sourceSchema.safeParse(value); if (!parsed.success) throw new Error("Restart recovery operator source is invalid; start a new user turn."); return freezeJsonSnapshot(parsed.data); } /** Capture only handshake-attested input; internal callers cannot manufacture a durable source. */ function captureGatewayOperatorRecoverySnapshot(params) { const { client, authority, identity } = params; if (!client.internal?.authenticatedOperator || client.internal.syntheticClient || !client.authPolicy?.authMethod || authority.gatewayAccessGrant === void 0) return; authority.assertCurrent(); const device = client.internal.operatorDeviceTokenIdentity ?? void 0; if (client.connect.device && client.internal.operatorDeviceTokenIdentity === void 0) return; const sharedGeneration = client.usesSharedGatewayAuth ? client.sharedGatewaySessionGeneration : void 0; if (client.usesSharedGatewayAuth && sharedGeneration === void 0) return; const modelPolicy = captureOperatorModelPolicySnapshot(params.modelPolicy); if (params.modelPolicy && !modelPolicy) return; const profile = identity.readCurrentProfile(); const source = { profileId: authority.profileId, scopes: [...authority.scopes], assignedRole: profile.assignedRole, githubLogin: profile.githubLogin ?? null, rolePolicy: sourceRolePolicy(resolveOperatorRolePolicyForAssignment(profile.profileId, profile.assignedRole, params.config, profile.githubLogin ?? null)), modelPolicy, grant: authority.gatewayAccessGrant, aliasBindingIds: identity.emailBindingIds, authPolicy: { ...client.authPolicy, generation: "" }, sharedGeneration, device, controlUiAdmin: client.internal.controlUiAdmin === true, localOperator: client.internal.isLocalClient === true, sourceIngress: isBrowserOperatorUiClient(client.connect.client) ? "control-ui" : "internal" }; const json = JSON.stringify(source); if (Buffer.byteLength(json, "utf8") > MAX_RECOVERY_SOURCE_BYTES) return; const parsed = snapshotSchema.safeParse(JSON.parse(json)); return parsed.success ? freezeJsonSnapshot(parsed.data) : void 0; } //#endregion //#region src/gateway/operator-run-authority.ts const operatorSources = /* @__PURE__ */ new WeakMap(); function getOperatorSources(client) { let sources = operatorSources.get(client); if (!sources) { sources = /* @__PURE__ */ new Set(); operatorSources.set(client, sources); } return sources; } /** A captured transport principal keeps its source's comparison identity, not new authority. */ function transferGatewayOperatorSourceIdentity(source, target) { operatorSources.set(target, getOperatorSources(source)); } function retainOperatorSource(client, owners, membership) { const sources = getOperatorSources(client); let source = membership === void 0 ? void 0 : [...sources].find((entry) => entry.membership === membership && entry.owners.every((owner, index) => owner === owners[index])); if (!source) { source = { owners, membership, token: Object.freeze({}), references: 0 }; sources.add(source); } const retained = source; const bucket = sources; retained.references += 1; return { token: retained.token, release: () => { if (--retained.references === 0) bucket.delete(retained); } }; } function prepareRunRolePolicy(role) { return role ? { sessionAccessCap: role.sessions.others, sandboxRequired: role.sandbox === "required", agents: role.agents } : void 0; } function intersectOperatorRunModelPolicy(original, current) { return original && current && readOperatorModelPolicyMembership(original) !== readOperatorModelPolicyMembership(current) ? Object.freeze({ models: Object.freeze(current.models.filter(original.allows)), allows: (ref) => original.allows(ref) && current.allows(ref) }) : current ?? original; } /** Keep the admission ceiling while refreshing only committed config/metadata views. */ function prepareRunModelPolicy(params) { let { cfg, metadata } = params; const original = prepareOperatorModelPolicy({ cfg, policy: params.initialPolicy, manifestPlugins: metadata ?? [] }); let policy = original; return { original, readCurrent: () => { const nextConfig = params.getConfig(); const nextMetadata = getProcessGatewayPluginMetadataSnapshot(); if (nextConfig !== cfg || nextMetadata !== metadata) { const current = prepareOperatorModelPolicy({ cfg: nextConfig, policy: params.resolvePolicy(nextConfig), manifestPlugins: nextMetadata ?? [] }); policy = intersectOperatorRunModelPolicy(original, current); cfg = nextConfig; metadata = nextMetadata; } return policy; } }; } /** Bridge a prepared linked principal while retaining its exact channel admission capability. */ function captureChannelOperatorRunAuthority(input) { const params = { ...input }; params.assertCurrent(); const initialConfig = params.getRuntimeConfig(); const metadata = getProcessGatewayPluginMetadataSnapshot(); const resolveRole = (cfg) => resolveOperatorRolePolicyForAssignment(params.profileId, params.assignedRole, cfg, params.githubLogin ?? null); const role = resolveRole(initialConfig); const modelPolicy = prepareRunModelPolicy({ cfg: initialConfig, metadata, initialPolicy: role?.modelPolicy, getConfig: params.getRuntimeConfig, resolvePolicy: (cfg) => resolveRole(cfg)?.modelPolicy }); return createAdmittedRunOperatorAuthority({ profileId: params.profileId, scopes: params.scopes, rolePolicy: prepareRunRolePolicy(sourceRolePolicy(role)), gatewayAccessGrant: params.gatewayAccessGrant, assertCurrent: params.assertCurrent, readCurrentRoleAssignment: () => { params.assertCurrent(); return params.assignedRole; }, readCurrentGithubLogin: () => { params.assertCurrent(); return params.githubLogin ?? null; }, get modelPolicy() { return modelPolicy.readCurrent(); }, onModelPolicyChanged: (listener) => { const releaseProfile = onUserProfilesChanged(listener); const releasePolicy = onOperatorRolePolicyChanged((change) => { if (change.kind === "config" || change.profileId === params.profileId) listener(); }); params.signal?.addEventListener("abort", listener, { once: true }); return () => { releaseProfile(); releasePolicy(); params.signal?.removeEventListener("abort", listener); }; } }); } /** Transfers the original operator restriction into accepted work, independently of its request. */ async function captureGatewayOperatorRunAuthority(input) { const params = { ...input }; const inherited = params.client?.internal?.operatorRunAuthority; if (inherited !== void 0) { assertAdmittedRunOperatorAuthority(inherited); inherited.assertCurrent(); const scopes = intersectOperatorScopes(inherited.scopes, params.client?.connect.scopes ?? []); return { authority: roleScopesAllow({ role: "operator", requestedScopes: inherited.scopes, allowedScopes: scopes }) ? inherited : createAdmittedRunOperatorAuthority({ ...inherited, scopes, get modelPolicy() { return inherited.modelPolicy; } }), release: inherited.retain?.() ?? (() => {}) }; } const actor = resolveGatewayOperatorRoleActor(params.client); const client = params.client; const isAuthenticatedOwner = (currentActor) => client?.internal?.authenticatedOperator === true && (currentActor === void 0 || currentActor.kind === "system") && client.connect.role === "operator" && Boolean(client.connId) && !client.invalidated && !client.connectionSignal?.aborted && !client.internal.syntheticClient && !client.internal.agentRuntimeIdentity && !client.internal.agentToolCaller && client.authenticatedUserProfile?.profileId === "gateway-owner"; const authenticatedOwner = isAuthenticatedOwner(actor); if (!client || actor?.kind !== "operator" && !authenticatedOwner) return; const profileId = actor?.kind === "operator" ? actor.profileId : GATEWAY_OWNER_PROFILE_ID; const connectionId = client.connId; const connectionSignal = client.connectionSignal; if (params.hasCurrentClientAuthority?.() === false) throw new Error("Gateway caller authority is no longer active."); const releaseDevice = retainGatewayDeviceRevocation(params.hasCurrentClientAuthority); const isSourceCurrent = readGatewayDeviceSourceAuthority(params.hasCurrentClientAuthority); const resolveGatewayContext = params.context.resolveGatewayContext; const gatewayContext = resolveGatewayContext?.(); const getConfig = params.context.getCommittedRuntimeConfig ?? params.context.getRuntimeConfig; const initialModelConfig = getConfig(); const initialModelMetadata = getProcessGatewayPluginMetadataSnapshot(); const isGatewayCurrent = () => !resolveGatewayContext || gatewayContext !== void 0 && resolveGatewayContext() === gatewayContext; const sourceAuthority = params.sourceAuthority !== void 0 ? params.sourceAuthority : client.internal?.operatorAccessAuthority; const sourceAuthorities = [sourceAuthority, params.invocationAuthority]; const scopes = Object.freeze([...client.connect.scopes ?? []]); const sourceOwners = [ gatewayContext ?? params.context, resolveGatewayContext, getConfig, readGatewayDeviceSourceIdentity(params.hasCurrentClientAuthority) ?? (params.hasCurrentClientAuthority ? Object.freeze({}) : void 0), sourceAuthority, params.invocationAuthority ]; let releaseSource; let references = 1; let revoked = false; const revocation = new AbortController(); const subscriptions = []; let preparedProfile; const assertProfileCurrent = () => { try { const profile = preparedProfile?.readCurrentProfile(); if (!profile || profile.profileId !== profileId) throw new Error("operator profile is unavailable"); return profile; } catch (error) { throw new Error("operator source identity changed; start a new request", { cause: error }); } }; const resolveCurrentRole = (cfg = getConfig()) => { const profile = assertProfileCurrent(); return resolveOperatorRolePolicyForAssignment(profileId, profile.assignedRole, cfg, profile.githubLogin ?? null); }; const assertRoleCurrent = () => { const policy = resolveCurrentRole(); if (policy && !roleScopesAllow({ role: "operator", requestedScopes: scopes, allowedScopes: policy.scopes })) throw new Error("Your operator role changed; reconnect before continuing."); }; const revoke = (reason) => { if (references > 0 && isGatewayCurrent()) { revoked = true; revocation.abort(reason); } }; const recheck = (check) => { if (!revoked && references > 0 && isGatewayCurrent()) try { check(); } catch (error) { revoke(error); } }; const assertSourceCurrent = composeSessionSourceAssertion(sourceAuthorities.map((authority) => composeSessionSourceAssertion([authority?.assertCurrent], (assertSource) => { authority?.signal?.throwIfAborted(); assertSource(); authority?.signal?.throwIfAborted(); })), (assertSources) => { if (revoked || references === 0) throw new Error("operator execution authority is no longer active"); if (isSourceCurrent?.() === false || !isGatewayCurrent()) throw new Error("operator source authority is no longer active"); assertSources(); if (revoked || references === 0) throw new Error("operator execution authority is no longer active"); }); const assertCurrent = composeSessionSourceAssertion([assertSourceCurrent], (assertSource) => { try { assertSource(); assertRoleCurrent(); } catch (error) { revoked = true; throw error; } }); const releaseHold = () => { let released = false; return () => { if (!released) { released = true; if (--references === 0) { releaseSource?.(); releaseSource = void 0; for (const unsubscribe of subscriptions.splice(0)) unsubscribe?.(); preparedProfile?.release(); releaseDevice?.(); } } }; }; const release = releaseHold(); try { const initialRoleConfig = { gateway: { roles: structuredClone(initialModelConfig.gateway?.roles) } }; const preparationConfigs = [initialRoleConfig]; let onConfigChange = () => { preparationConfigs.push({ gateway: { roles: structuredClone(getConfig().gateway?.roles) } }); }; let profileChangedDuringPreparation = false; let onProfileChange = () => { profileChangedDuringPreparation = true; }; subscriptions.push(onGatewayDeviceSourceRevoked(params.hasCurrentClientAuthority, () => revoke(/* @__PURE__ */ new Error("operator source authority is no longer active"))), onUserProfilesChanged(() => onProfileChange()), onOperatorRolePolicyChanged((change) => { if (change.kind === "assignment" && change.profileId === profileId) revoke(/* @__PURE__ */ new Error("Your operator role changed; reconnect before continuing.")); else if (change.kind === "config" && change.context === (gatewayContext ?? params.context)) onConfigChange(); })); for (const authority of sourceAuthorities) { const sourceSignal = authority?.signal; if (sourceSignal) { const onAbort = () => revoke(sourceSignal.reason); sourceSignal.addEventListener("abort", onAbort, { once: true }); subscriptions.push(() => sourceSignal.removeEventListener("abort", onAbort)); } } assertSourceCurrent(); preparedProfile = await prepareUserProfileIdentity(profileId); const currentActor = resolveGatewayOperatorRoleActor(params.client); const originalActorCurrent = authenticatedOwner ? isAuthenticatedOwner(currentActor) && client.connId === connectionId && client.connectionSignal === connectionSignal : currentActor?.kind === "operator" && currentActor.profileId === profileId; if (params.client !== client || !originalActorCurrent || params.hasCurrentClientAuthority?.() === false || !isGatewayCurrent() || !roleScopesAllow({ role: "operator", requestedScopes: scopes, allowedScopes: client.connect.scopes ?? [] })) throw new Error("Gateway caller authority is no longer active."); const capturedProfile = assertProfileCurrent(); const capturedAssignedRole = capturedProfile.assignedRole; const capturedGithubLogin = capturedProfile.githubLogin ?? null; const capturedRole = structuredClone(resolveCurrentRole()); const capturedSourcePolicy = sourceRolePolicy(capturedRole); if (preparationConfigs.some((config) => !isDeepStrictEqual(capturedSourcePolicy, sourceRolePolicy(resolveOperatorRolePolicyForAssignment(profileId, capturedAssignedRole, config, capturedGithubLogin))))) revoke(/* @__PURE__ */ new Error("Your operator role changed; reconnect before continuing.")); const assertCapturedRoleCurrent = () => { const current = assertProfileCurrent(); const policy = resolveOperatorRolePolicyForAssignment(profileId, current.assignedRole, getConfig(), current.githubLogin ?? null); if (current.assignedRole !== capturedAssignedRole || !isDeepStrictEqual(capturedSourcePolicy, sourceRolePolicy(policy))) throw new Error("Your operator role changed; reconnect before continuing."); }; onConfigChange = () => recheck(assertCapturedRoleCurrent); onProfileChange = () => recheck(assertCapturedRoleCurrent); if (profileChangedDuringPreparation) onProfileChange(); assertCurrent(); const modelPolicy = prepareRunModelPolicy({ cfg: initialModelConfig, metadata: initialModelMetadata, initialPolicy: resolveOperatorRolePolicyForAssignment(profileId, capturedAssignedRole, initialRoleConfig, capturedGithubLogin)?.modelPolicy, getConfig, resolvePolicy: (cfg) => resolveCurrentRole(cfg)?.modelPolicy }); preparationConfigs.length = 0; const source = retainOperatorSource(client, sourceOwners, readOperatorModelPolicyMembership(modelPolicy.original)); releaseSource = source.release; const grant = sourceAuthority === null || sourceAuthority === void 0 && authenticatedOwner ? null : sourceAuthority?.gatewayAccessGrant; const admission = { profileId, scopes, gatewayAccessGrant: grant ? Object.freeze({ ...grant }) : grant, assertCurrent }; const recoverySnapshot = captureGatewayOperatorRecoverySnapshot({ client, authority: admission, modelPolicy: modelPolicy.original, identity: preparedProfile, config: getConfig() }); assertCurrent(); return { authority: createAdmittedRunOperatorAuthority({ ...admission, recoverySnapshot, rolePolicy: prepareRunRolePolicy(capturedSourcePolicy), readCurrentRoleAssignment: () => { assertCurrent(); return assertProfileCurrent().assignedRole; }, readCurrentGithubLogin: () => { assertCurrent(); return assertProfileCurrent().githubLogin ?? null; }, source: source.token, signal: revocation.signal, retain: () => { assertCurrent(); references += 1; return releaseHold(); }, get modelPolicy() { return modelPolicy.readCurrent(); }, onModelPolicyChanged: (listener) => onOperatorRolePolicyChanged((change) => { if (change.kind === "config" && change.context === (gatewayContext ?? params.context)) listener(); }) }), release }; } catch (error) { release(); throw error; } } //#endregion export { decodeGatewayOperatorRecoverySource as a, transferGatewayOperatorSourceIdentity as i, captureGatewayOperatorRunAuthority as n, intersectOperatorRunModelPolicy as r, captureChannelOperatorRunAuthority as t };