UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

216 lines (215 loc) 10.1 kB
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { d as normalizeTrimmedStringList } from "./string-normalization-WNUDCpXX.js"; import { f as resolveAgentIdFromSessionKey } from "./session-key-B_NoIfpX.js"; import { o as callGateway } from "./call-B5-GYOlf.js"; //#region src/plugin-sdk/session-visibility.ts let callGatewayForListSpawned = callGateway; /** Test hook: must stay aligned with `sessions-resolution` `testing.setDepsForTest`. */ const sessionVisibilityGatewayTesting = { setCallGatewayForListSpawned(overrides) { callGatewayForListSpawned = overrides ?? callGateway; } }; /** List sessions spawned by the requester through the gateway session list method. */ async function listSpawnedSessionKeys(params) { const limit = typeof params.limit === "number" && Number.isFinite(params.limit) ? Math.max(1, Math.floor(params.limit)) : void 0; try { const list = await callGatewayForListSpawned({ method: "sessions.list", params: { includeGlobal: false, includeUnknown: false, ...limit !== void 0 ? { limit } : {}, spawnedBy: params.requesterSessionKey } }); const keys = normalizeTrimmedStringList((Array.isArray(list?.sessions) ? list.sessions : []).map((entry) => entry?.key)); return new Set(keys); } catch { return /* @__PURE__ */ new Set(); } } /** Resolve configured session-tool visibility, defaulting invalid or missing values to tree. */ function resolveSessionToolsVisibility(cfg) { const raw = cfg.tools?.sessions?.visibility; const value = normalizeLowercaseStringOrEmpty(raw); if (value === "self" || value === "tree" || value === "agent" || value === "all") return value; return "tree"; } /** Resolve visibility after applying sandbox clamps for spawned-session-only agents. */ function resolveEffectiveSessionToolsVisibility(params) { const visibility = resolveSessionToolsVisibility(params.cfg); if (!params.sandboxed) return visibility; if ((params.cfg.agents?.defaults?.sandbox?.sessionToolsVisibility ?? "spawned") === "spawned" && visibility !== "tree") return "tree"; return visibility; } /** Resolve sandbox-specific session visibility clamp for agent defaults. */ function resolveSandboxSessionToolsVisibility(cfg) { return cfg.agents?.defaults?.sandbox?.sessionToolsVisibility ?? "spawned"; } function compileAgentAllowPattern(pattern) { const raw = normalizeOptionalString(pattern) ?? ""; if (!raw) return { kind: "deny" }; if (raw === "*") return { kind: "all" }; if (!raw.includes("*")) return { kind: "exact", value: raw }; const parts = raw.toLowerCase().split("*"); return { kind: "wildcard", first: parts[0] ?? "", last: parts[parts.length - 1] ?? "", interior: parts.slice(1, -1).filter(Boolean) }; } /** * Linear-time case-insensitive glob matcher for precompiled `*` patterns. * Checks prefix, suffix, then ordered interior segments without entering the * regex engine, avoiding polynomial backtracking on repeated wildcards. */ function matchesCompiledWildcard(pattern, lower) { let pos = 0; if (pattern.first) { if (!lower.startsWith(pattern.first)) return false; pos = pattern.first.length; } const endBound = pattern.last ? lower.length - pattern.last.length : lower.length; if (pattern.last && (!lower.endsWith(pattern.last) || endBound < pos)) return false; for (const part of pattern.interior) { const idx = lower.indexOf(part, pos); if (idx === -1 || idx + part.length > endBound) return false; pos = idx + part.length; } return true; } /** Compile agent-to-agent allow rules into reusable matching predicates. */ function createAgentToAgentPolicy(cfg) { const routingA2A = cfg.tools?.agentToAgent; const enabled = routingA2A?.enabled === true; const allowPatterns = (Array.isArray(routingA2A?.allow) ? routingA2A.allow : []).map((pattern) => compileAgentAllowPattern(pattern)); const hasWildcardPatterns = allowPatterns.some((pattern) => pattern.kind === "wildcard"); const matchesAllow = (agentId) => { if (allowPatterns.length === 0) return true; const lowerAgentId = hasWildcardPatterns ? agentId.toLowerCase() : ""; return allowPatterns.some((pattern) => { if (pattern.kind === "all") return true; if (pattern.kind === "deny") return false; if (pattern.kind === "exact") return pattern.value === agentId; return matchesCompiledWildcard(pattern, lowerAgentId); }); }; const isAllowed = (requesterAgentId, targetAgentId) => { if (requesterAgentId === targetAgentId) return true; if (!enabled) return false; return matchesAllow(requesterAgentId) && matchesAllow(targetAgentId); }; return { enabled, matchesAllow, isAllowed }; } function actionPrefix(action) { if (action === "history") return "Session history"; if (action === "send") return "Session send"; if (action === "status") return "Session status"; return "Session list"; } function a2aDisabledMessage(action) { if (action === "history") return "Agent-to-agent history is disabled. Set tools.agentToAgent.enabled=true to allow cross-agent access."; if (action === "send") return "Agent-to-agent messaging is disabled. Set tools.agentToAgent.enabled=true to allow cross-agent sends."; if (action === "status") return "Agent-to-agent status is disabled. Set tools.agentToAgent.enabled=true to allow cross-agent access."; return "Agent-to-agent listing is disabled. Set tools.agentToAgent.enabled=true to allow cross-agent visibility."; } function a2aDeniedMessage(action) { if (action === "history") return "Agent-to-agent history denied by tools.agentToAgent.allow."; if (action === "send") return "Agent-to-agent messaging denied by tools.agentToAgent.allow."; if (action === "status") return "Agent-to-agent status denied by tools.agentToAgent.allow."; return "Agent-to-agent listing denied by tools.agentToAgent.allow."; } function crossVisibilityMessage(action) { if (action === "history") return "Session history visibility is restricted. Set tools.sessions.visibility=all to allow cross-agent access."; if (action === "send") return "Session send visibility is restricted. Set tools.sessions.visibility=all to allow cross-agent access."; if (action === "status") return "Session status visibility is restricted. Set tools.sessions.visibility=all to allow cross-agent access."; return "Session list visibility is restricted. Set tools.sessions.visibility=all to allow cross-agent access."; } function selfVisibilityMessage(action) { return `${actionPrefix(action)} visibility is restricted to the current session (tools.sessions.visibility=self).`; } function treeVisibilityMessage(action) { return `${actionPrefix(action)} visibility is restricted to the current session tree (tools.sessions.visibility=tree).`; } /** Create a direct session-key visibility checker for one requester/action pair. */ function createSessionVisibilityChecker(params) { const spawnedKeys = params.spawnedKeys; const rowChecker = createSessionVisibilityRowChecker({ action: params.action, requesterSessionKey: params.requesterSessionKey, visibility: params.visibility, a2aPolicy: params.a2aPolicy }); const check = (targetSessionKey) => { const isSpawnedSession = spawnedKeys?.has(targetSessionKey) === true; return rowChecker.check({ key: targetSessionKey, spawnedBy: isSpawnedSession ? params.requesterSessionKey : void 0 }); }; return { check }; } function rowOwnedByRequester(row, requesterSessionKey) { return row.ownerSessionKey === requesterSessionKey || row.spawnedBy === requesterSessionKey || row.parentSessionKey === requesterSessionKey; } /** Create a row-aware visibility checker that can use owner/spawn metadata. */ function createSessionVisibilityRowChecker(params) { const requesterAgentId = resolveAgentIdFromSessionKey(params.requesterSessionKey); const check = (row) => { const targetSessionKey = row.key; const targetAgentId = row.agentId ?? resolveAgentIdFromSessionKey(targetSessionKey); const isRequesterSession = targetSessionKey === params.requesterSessionKey || targetSessionKey === "current"; const isRequesterOwned = rowOwnedByRequester(row, params.requesterSessionKey); if (!isRequesterSession && isRequesterOwned && (params.visibility === "tree" || params.visibility === "all")) return { allowed: true }; if (targetAgentId !== requesterAgentId) { if (params.visibility !== "all") return { allowed: false, status: "forbidden", error: crossVisibilityMessage(params.action) }; if (!params.a2aPolicy.enabled) return { allowed: false, status: "forbidden", error: a2aDisabledMessage(params.action) }; if (!params.a2aPolicy.isAllowed(requesterAgentId, targetAgentId)) return { allowed: false, status: "forbidden", error: a2aDeniedMessage(params.action) }; return { allowed: true }; } if (params.visibility === "self" && !isRequesterSession) return { allowed: false, status: "forbidden", error: selfVisibilityMessage(params.action) }; if (params.visibility === "tree" && !isRequesterSession && !isRequesterOwned) return { allowed: false, status: "forbidden", error: treeVisibilityMessage(params.action) }; return { allowed: true }; }; return { check }; } /** Create a visibility guard, loading spawned-session ownership when direct keys need it. */ async function createSessionVisibilityGuard(params) { const spawnedKeys = params.action !== "list" && (params.visibility === "tree" || params.visibility === "all") ? await listSpawnedSessionKeys({ requesterSessionKey: params.requesterSessionKey }) : null; return createSessionVisibilityChecker({ action: params.action, requesterSessionKey: params.requesterSessionKey, visibility: params.visibility, a2aPolicy: params.a2aPolicy, spawnedKeys }); } //#endregion export { listSpawnedSessionKeys as a, resolveSessionToolsVisibility as c, createSessionVisibilityRowChecker as i, sessionVisibilityGatewayTesting as l, createSessionVisibilityChecker as n, resolveEffectiveSessionToolsVisibility as o, createSessionVisibilityGuard as r, resolveSandboxSessionToolsVisibility as s, createAgentToAgentPolicy as t };