refactor: centralize session key generation with new helper functions
This commit is contained in:
40
backend/app/services/openclaw/internal/session_keys.py
Normal file
40
backend/app/services/openclaw/internal/session_keys.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"""Deterministic session-key helpers for OpenClaw agents.
|
||||||
|
|
||||||
|
Session keys are part of Mission Control's contract with the OpenClaw gateway.
|
||||||
|
Centralize the string formats here to avoid drift across provisioning, DB workflows,
|
||||||
|
and API-facing services.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from app.services.openclaw.constants import AGENT_SESSION_PREFIX
|
||||||
|
from app.services.openclaw.shared import GatewayAgentIdentity
|
||||||
|
|
||||||
|
|
||||||
|
def gateway_main_session_key(gateway_id: UUID) -> str:
|
||||||
|
"""Return the deterministic session key for a gateway-main agent."""
|
||||||
|
return GatewayAgentIdentity.session_key_for_id(gateway_id)
|
||||||
|
|
||||||
|
|
||||||
|
def board_lead_session_key(board_id: UUID) -> str:
|
||||||
|
"""Return the deterministic session key for a board lead agent."""
|
||||||
|
return f"{AGENT_SESSION_PREFIX}:lead-{board_id}:main"
|
||||||
|
|
||||||
|
|
||||||
|
def board_agent_session_key(agent_id: UUID) -> str:
|
||||||
|
"""Return the deterministic session key for a non-lead, board-scoped agent."""
|
||||||
|
return f"{AGENT_SESSION_PREFIX}:mc-{agent_id}:main"
|
||||||
|
|
||||||
|
|
||||||
|
def board_scoped_session_key(
|
||||||
|
*,
|
||||||
|
agent_id: UUID,
|
||||||
|
board_id: UUID,
|
||||||
|
is_board_lead: bool,
|
||||||
|
) -> str:
|
||||||
|
"""Return the deterministic session key for a board-scoped agent."""
|
||||||
|
if is_board_lead:
|
||||||
|
return board_lead_session_key(board_id)
|
||||||
|
return board_agent_session_key(agent_id)
|
||||||
@@ -40,6 +40,10 @@ from app.services.openclaw.gateway_rpc import (
|
|||||||
)
|
)
|
||||||
from app.services.openclaw.internal.agent_key import agent_key as _agent_key
|
from app.services.openclaw.internal.agent_key import agent_key as _agent_key
|
||||||
from app.services.openclaw.internal.agent_key import slugify
|
from app.services.openclaw.internal.agent_key import slugify
|
||||||
|
from app.services.openclaw.internal.session_keys import (
|
||||||
|
board_agent_session_key,
|
||||||
|
board_lead_session_key,
|
||||||
|
)
|
||||||
from app.services.openclaw.shared import GatewayAgentIdentity
|
from app.services.openclaw.shared import GatewayAgentIdentity
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -251,8 +255,8 @@ def _session_key(agent: Agent) -> str:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if agent.is_board_lead and agent.board_id is not None:
|
if agent.is_board_lead and agent.board_id is not None:
|
||||||
return f"agent:lead-{agent.board_id}:main"
|
return board_lead_session_key(agent.board_id)
|
||||||
return f"agent:mc-{agent.id}:main"
|
return board_agent_session_key(agent.id)
|
||||||
|
|
||||||
|
|
||||||
def _render_agent_files(
|
def _render_agent_files(
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ from app.schemas.gateways import GatewayTemplatesSyncError, GatewayTemplatesSync
|
|||||||
from app.services.activity_log import record_activity
|
from app.services.activity_log import record_activity
|
||||||
from app.services.openclaw.constants import (
|
from app.services.openclaw.constants import (
|
||||||
_TOOLS_KV_RE,
|
_TOOLS_KV_RE,
|
||||||
AGENT_SESSION_PREFIX,
|
|
||||||
DEFAULT_HEARTBEAT_CONFIG,
|
DEFAULT_HEARTBEAT_CONFIG,
|
||||||
OFFLINE_AFTER,
|
OFFLINE_AFTER,
|
||||||
)
|
)
|
||||||
@@ -68,6 +67,10 @@ from app.services.openclaw.gateway_rpc import (
|
|||||||
)
|
)
|
||||||
from app.services.openclaw.internal.agent_key import agent_key as _agent_key
|
from app.services.openclaw.internal.agent_key import agent_key as _agent_key
|
||||||
from app.services.openclaw.internal.retry import GatewayBackoff
|
from app.services.openclaw.internal.retry import GatewayBackoff
|
||||||
|
from app.services.openclaw.internal.session_keys import (
|
||||||
|
board_agent_session_key,
|
||||||
|
board_lead_session_key,
|
||||||
|
)
|
||||||
from app.services.openclaw.policies import OpenClawAuthorizationPolicy
|
from app.services.openclaw.policies import OpenClawAuthorizationPolicy
|
||||||
from app.services.openclaw.provisioning import (
|
from app.services.openclaw.provisioning import (
|
||||||
OpenClawGatewayControlPlane,
|
OpenClawGatewayControlPlane,
|
||||||
@@ -139,7 +142,7 @@ class OpenClawProvisioningService(OpenClawDBService):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def lead_session_key(board: Board) -> str:
|
def lead_session_key(board: Board) -> str:
|
||||||
return f"agent:lead-{board.id}:main"
|
return board_lead_session_key(board.id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def lead_agent_name(_: Board) -> str:
|
def lead_agent_name(_: Board) -> str:
|
||||||
@@ -732,8 +735,8 @@ class AgentLifecycleService(OpenClawDBService):
|
|||||||
detail="Gateway main agent session key is required",
|
detail="Gateway main agent session key is required",
|
||||||
)
|
)
|
||||||
if agent.is_board_lead:
|
if agent.is_board_lead:
|
||||||
return f"{AGENT_SESSION_PREFIX}:lead-{agent.board_id}:main"
|
return board_lead_session_key(agent.board_id)
|
||||||
return f"{AGENT_SESSION_PREFIX}:mc-{agent.id}:main"
|
return board_agent_session_key(agent.id)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def workspace_path(cls, agent_name: str, workspace_root: str | None) -> str:
|
def workspace_path(cls, agent_name: str, workspace_root: str | None) -> str:
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ from typing import TYPE_CHECKING
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from sqlmodel import col
|
|
||||||
|
|
||||||
from app.models.agents import Agent
|
|
||||||
from app.models.boards import Board
|
from app.models.boards import Board
|
||||||
from app.schemas.gateway_api import (
|
from app.schemas.gateway_api import (
|
||||||
GatewayResolveQuery,
|
GatewayResolveQuery,
|
||||||
@@ -31,6 +29,7 @@ from app.services.openclaw.gateway_rpc import (
|
|||||||
send_message,
|
send_message,
|
||||||
)
|
)
|
||||||
from app.services.openclaw.policies import OpenClawAuthorizationPolicy
|
from app.services.openclaw.policies import OpenClawAuthorizationPolicy
|
||||||
|
from app.services.openclaw.shared import GatewayAgentIdentity
|
||||||
from app.services.organizations import require_board_access
|
from app.services.organizations import require_board_access
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -124,12 +123,7 @@ class GatewaySessionService(OpenClawDBService):
|
|||||||
await require_board_access(self.session, user=user, board=board, write=False)
|
await require_board_access(self.session, user=user, board=board, write=False)
|
||||||
gateway = await require_gateway_for_board(self.session, board)
|
gateway = await require_gateway_for_board(self.session, board)
|
||||||
config = gateway_client_config(gateway)
|
config = gateway_client_config(gateway)
|
||||||
main_agent = (
|
main_session = GatewayAgentIdentity.session_key(gateway)
|
||||||
await Agent.objects.filter_by(gateway_id=gateway.id)
|
|
||||||
.filter(col(Agent.board_id).is_(None))
|
|
||||||
.first(self.session)
|
|
||||||
)
|
|
||||||
main_session = main_agent.openclaw_session_id if main_agent else None
|
|
||||||
return (
|
return (
|
||||||
board,
|
board,
|
||||||
config,
|
config,
|
||||||
|
|||||||
47
backend/tests/test_session_keys.py
Normal file
47
backend/tests/test_session_keys.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# ruff: noqa: S101
|
||||||
|
"""Unit tests for deterministic OpenClaw session-key helpers."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from app.services.openclaw.internal.session_keys import (
|
||||||
|
board_agent_session_key,
|
||||||
|
board_lead_session_key,
|
||||||
|
board_scoped_session_key,
|
||||||
|
gateway_main_session_key,
|
||||||
|
)
|
||||||
|
from app.services.openclaw.shared import GatewayAgentIdentity
|
||||||
|
|
||||||
|
|
||||||
|
def test_gateway_main_session_key_matches_gateway_identity() -> None:
|
||||||
|
gateway_id = UUID("00000000-0000-0000-0000-000000000123")
|
||||||
|
assert gateway_main_session_key(gateway_id) == GatewayAgentIdentity.session_key_for_id(gateway_id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_board_lead_session_key_format() -> None:
|
||||||
|
board_id = UUID("00000000-0000-0000-0000-000000000456")
|
||||||
|
assert board_lead_session_key(board_id) == f"agent:lead-{board_id}:main"
|
||||||
|
|
||||||
|
|
||||||
|
def test_board_agent_session_key_format() -> None:
|
||||||
|
agent_id = UUID("00000000-0000-0000-0000-000000000789")
|
||||||
|
assert board_agent_session_key(agent_id) == f"agent:mc-{agent_id}:main"
|
||||||
|
|
||||||
|
|
||||||
|
def test_board_scoped_session_key_selects_lead() -> None:
|
||||||
|
agent_id = UUID("00000000-0000-0000-0000-000000000001")
|
||||||
|
board_id = UUID("00000000-0000-0000-0000-000000000002")
|
||||||
|
assert (
|
||||||
|
board_scoped_session_key(agent_id=agent_id, board_id=board_id, is_board_lead=True)
|
||||||
|
== board_lead_session_key(board_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_board_scoped_session_key_selects_non_lead() -> None:
|
||||||
|
agent_id = UUID("00000000-0000-0000-0000-000000000001")
|
||||||
|
board_id = UUID("00000000-0000-0000-0000-000000000002")
|
||||||
|
assert (
|
||||||
|
board_scoped_session_key(agent_id=agent_id, board_id=board_id, is_board_lead=False)
|
||||||
|
== board_agent_session_key(agent_id)
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user