mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 15:54:32 +00:00
refactor(sessions): split access and resolution helpers
This commit is contained in:
240
src/agents/tools/sessions-access.ts
Normal file
240
src/agents/tools/sessions-access.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { isSubagentSessionKey, resolveAgentIdFromSessionKey } from "../../routing/session-key.js";
|
||||
import {
|
||||
listSpawnedSessionKeys,
|
||||
resolveInternalSessionKey,
|
||||
resolveMainSessionAlias,
|
||||
} from "./sessions-resolution.js";
|
||||
|
||||
export type SessionToolsVisibility = "self" | "tree" | "agent" | "all";
|
||||
|
||||
export type AgentToAgentPolicy = {
|
||||
enabled: boolean;
|
||||
matchesAllow: (agentId: string) => boolean;
|
||||
isAllowed: (requesterAgentId: string, targetAgentId: string) => boolean;
|
||||
};
|
||||
|
||||
export type SessionAccessAction = "history" | "send" | "list";
|
||||
|
||||
export type SessionAccessResult =
|
||||
| { allowed: true }
|
||||
| { allowed: false; error: string; status: "forbidden" };
|
||||
|
||||
export function resolveSessionToolsVisibility(cfg: OpenClawConfig): SessionToolsVisibility {
|
||||
const raw = (cfg.tools as { sessions?: { visibility?: unknown } } | undefined)?.sessions
|
||||
?.visibility;
|
||||
const value = typeof raw === "string" ? raw.trim().toLowerCase() : "";
|
||||
if (value === "self" || value === "tree" || value === "agent" || value === "all") {
|
||||
return value;
|
||||
}
|
||||
return "tree";
|
||||
}
|
||||
|
||||
export function resolveEffectiveSessionToolsVisibility(params: {
|
||||
cfg: OpenClawConfig;
|
||||
sandboxed: boolean;
|
||||
}): SessionToolsVisibility {
|
||||
const visibility = resolveSessionToolsVisibility(params.cfg);
|
||||
if (!params.sandboxed) {
|
||||
return visibility;
|
||||
}
|
||||
const sandboxClamp = params.cfg.agents?.defaults?.sandbox?.sessionToolsVisibility ?? "spawned";
|
||||
if (sandboxClamp === "spawned" && visibility !== "tree") {
|
||||
return "tree";
|
||||
}
|
||||
return visibility;
|
||||
}
|
||||
|
||||
export function resolveSandboxSessionToolsVisibility(cfg: OpenClawConfig): "spawned" | "all" {
|
||||
return cfg.agents?.defaults?.sandbox?.sessionToolsVisibility ?? "spawned";
|
||||
}
|
||||
|
||||
export function resolveSandboxedSessionToolContext(params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentSessionKey?: string;
|
||||
sandboxed?: boolean;
|
||||
}): {
|
||||
mainKey: string;
|
||||
alias: string;
|
||||
visibility: "spawned" | "all";
|
||||
requesterInternalKey: string | undefined;
|
||||
effectiveRequesterKey: string;
|
||||
restrictToSpawned: boolean;
|
||||
} {
|
||||
const { mainKey, alias } = resolveMainSessionAlias(params.cfg);
|
||||
const visibility = resolveSandboxSessionToolsVisibility(params.cfg);
|
||||
const requesterInternalKey =
|
||||
typeof params.agentSessionKey === "string" && params.agentSessionKey.trim()
|
||||
? resolveInternalSessionKey({
|
||||
key: params.agentSessionKey,
|
||||
alias,
|
||||
mainKey,
|
||||
})
|
||||
: undefined;
|
||||
const effectiveRequesterKey = requesterInternalKey ?? alias;
|
||||
const restrictToSpawned =
|
||||
params.sandboxed === true &&
|
||||
visibility === "spawned" &&
|
||||
!!requesterInternalKey &&
|
||||
!isSubagentSessionKey(requesterInternalKey);
|
||||
return {
|
||||
mainKey,
|
||||
alias,
|
||||
visibility,
|
||||
requesterInternalKey,
|
||||
effectiveRequesterKey,
|
||||
restrictToSpawned,
|
||||
};
|
||||
}
|
||||
|
||||
export function createAgentToAgentPolicy(cfg: OpenClawConfig): AgentToAgentPolicy {
|
||||
const routingA2A = cfg.tools?.agentToAgent;
|
||||
const enabled = routingA2A?.enabled === true;
|
||||
const allowPatterns = Array.isArray(routingA2A?.allow) ? routingA2A.allow : [];
|
||||
const matchesAllow = (agentId: string) => {
|
||||
if (allowPatterns.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return allowPatterns.some((pattern) => {
|
||||
const raw = String(pattern ?? "").trim();
|
||||
if (!raw) {
|
||||
return false;
|
||||
}
|
||||
if (raw === "*") {
|
||||
return true;
|
||||
}
|
||||
if (!raw.includes("*")) {
|
||||
return raw === agentId;
|
||||
}
|
||||
const escaped = raw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const re = new RegExp(`^${escaped.replaceAll("\\*", ".*")}$`, "i");
|
||||
return re.test(agentId);
|
||||
});
|
||||
};
|
||||
const isAllowed = (requesterAgentId: string, targetAgentId: string) => {
|
||||
if (requesterAgentId === targetAgentId) {
|
||||
return true;
|
||||
}
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
return matchesAllow(requesterAgentId) && matchesAllow(targetAgentId);
|
||||
};
|
||||
return { enabled, matchesAllow, isAllowed };
|
||||
}
|
||||
|
||||
function actionPrefix(action: SessionAccessAction): string {
|
||||
if (action === "history") {
|
||||
return "Session history";
|
||||
}
|
||||
if (action === "send") {
|
||||
return "Session send";
|
||||
}
|
||||
return "Session list";
|
||||
}
|
||||
|
||||
function a2aDisabledMessage(action: SessionAccessAction): string {
|
||||
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.";
|
||||
}
|
||||
return "Agent-to-agent listing is disabled. Set tools.agentToAgent.enabled=true to allow cross-agent visibility.";
|
||||
}
|
||||
|
||||
function a2aDeniedMessage(action: SessionAccessAction): string {
|
||||
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.";
|
||||
}
|
||||
return "Agent-to-agent listing denied by tools.agentToAgent.allow.";
|
||||
}
|
||||
|
||||
function crossVisibilityMessage(action: SessionAccessAction): string {
|
||||
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.";
|
||||
}
|
||||
return "Session list visibility is restricted. Set tools.sessions.visibility=all to allow cross-agent access.";
|
||||
}
|
||||
|
||||
function selfVisibilityMessage(action: SessionAccessAction): string {
|
||||
return `${actionPrefix(action)} visibility is restricted to the current session (tools.sessions.visibility=self).`;
|
||||
}
|
||||
|
||||
function treeVisibilityMessage(action: SessionAccessAction): string {
|
||||
return `${actionPrefix(action)} visibility is restricted to the current session tree (tools.sessions.visibility=tree).`;
|
||||
}
|
||||
|
||||
export async function createSessionVisibilityGuard(params: {
|
||||
action: SessionAccessAction;
|
||||
requesterSessionKey: string;
|
||||
visibility: SessionToolsVisibility;
|
||||
a2aPolicy: AgentToAgentPolicy;
|
||||
}): Promise<{
|
||||
check: (targetSessionKey: string) => SessionAccessResult;
|
||||
}> {
|
||||
const requesterAgentId = resolveAgentIdFromSessionKey(params.requesterSessionKey);
|
||||
const spawnedKeys =
|
||||
params.visibility === "tree"
|
||||
? await listSpawnedSessionKeys({ requesterSessionKey: params.requesterSessionKey })
|
||||
: null;
|
||||
|
||||
const check = (targetSessionKey: string): SessionAccessResult => {
|
||||
const targetAgentId = resolveAgentIdFromSessionKey(targetSessionKey);
|
||||
const isCrossAgent = targetAgentId !== requesterAgentId;
|
||||
if (isCrossAgent) {
|
||||
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" && targetSessionKey !== params.requesterSessionKey) {
|
||||
return {
|
||||
allowed: false,
|
||||
status: "forbidden",
|
||||
error: selfVisibilityMessage(params.action),
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
params.visibility === "tree" &&
|
||||
targetSessionKey !== params.requesterSessionKey &&
|
||||
!spawnedKeys?.has(targetSessionKey)
|
||||
) {
|
||||
return {
|
||||
allowed: false,
|
||||
status: "forbidden",
|
||||
error: treeVisibilityMessage(params.action),
|
||||
};
|
||||
}
|
||||
|
||||
return { allowed: true };
|
||||
};
|
||||
|
||||
return { check };
|
||||
}
|
||||
Reference in New Issue
Block a user