feat: enhance agent and board APIs with role-based tags and improved documentation
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import cast
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
@@ -77,6 +79,11 @@ TASK_STATUS_QUERY = Query(default=None, alias="status")
|
||||
IS_CHAT_QUERY = Query(default=None)
|
||||
APPROVAL_STATUS_QUERY = Query(default=None, alias="status")
|
||||
|
||||
AGENT_LEAD_TAGS = cast("list[str | Enum]", ["agent-lead"])
|
||||
AGENT_MAIN_TAGS = cast("list[str | Enum]", ["agent-main"])
|
||||
AGENT_BOARD_TAGS = cast("list[str | Enum]", ["agent-lead", "agent-worker"])
|
||||
AGENT_ALL_ROLE_TAGS = cast("list[str | Enum]", ["agent-lead", "agent-worker", "agent-main"])
|
||||
|
||||
|
||||
def _coerce_agent_items(items: Sequence[Any]) -> list[Agent]:
|
||||
agents: list[Agent] = []
|
||||
@@ -142,12 +149,20 @@ def _guard_task_access(agent_ctx: AgentAuthContext, task: Task) -> None:
|
||||
OpenClawAuthorizationPolicy.require_board_write_access(allowed=allowed)
|
||||
|
||||
|
||||
@router.get("/boards", response_model=DefaultLimitOffsetPage[BoardRead])
|
||||
@router.get(
|
||||
"/boards",
|
||||
response_model=DefaultLimitOffsetPage[BoardRead],
|
||||
tags=AGENT_ALL_ROLE_TAGS,
|
||||
)
|
||||
async def list_boards(
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> LimitOffsetPage[BoardRead]:
|
||||
"""List boards visible to the authenticated agent."""
|
||||
"""List boards visible to the authenticated agent.
|
||||
|
||||
Board-scoped agents typically see only their assigned board.
|
||||
Main agents may see multiple boards when permitted by auth scope.
|
||||
"""
|
||||
statement = select(Board)
|
||||
if agent_ctx.agent.board_id:
|
||||
statement = statement.where(col(Board.id) == agent_ctx.agent.board_id)
|
||||
@@ -155,23 +170,34 @@ async def list_boards(
|
||||
return await paginate(session, statement)
|
||||
|
||||
|
||||
@router.get("/boards/{board_id}", response_model=BoardRead)
|
||||
@router.get("/boards/{board_id}", response_model=BoardRead, tags=AGENT_ALL_ROLE_TAGS)
|
||||
def get_board(
|
||||
board: Board = BOARD_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> Board:
|
||||
"""Return a board if the authenticated agent can access it."""
|
||||
"""Return one board if the authenticated agent can access it.
|
||||
|
||||
Use this when an agent needs board metadata (objective, status, target date)
|
||||
before planning or posting updates.
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
return board
|
||||
|
||||
|
||||
@router.get("/agents", response_model=DefaultLimitOffsetPage[AgentRead])
|
||||
@router.get(
|
||||
"/agents",
|
||||
response_model=DefaultLimitOffsetPage[AgentRead],
|
||||
tags=AGENT_ALL_ROLE_TAGS,
|
||||
)
|
||||
async def list_agents(
|
||||
board_id: UUID | None = BOARD_ID_QUERY,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> LimitOffsetPage[AgentRead]:
|
||||
"""List agents, optionally filtered to a board."""
|
||||
"""List agents visible to the caller, optionally filtered by board.
|
||||
|
||||
Useful for lead delegation and workload balancing.
|
||||
"""
|
||||
statement = select(Agent)
|
||||
if agent_ctx.agent.board_id:
|
||||
if board_id:
|
||||
@@ -195,14 +221,23 @@ async def list_agents(
|
||||
return await paginate(session, statement, transformer=_transform)
|
||||
|
||||
|
||||
@router.get("/boards/{board_id}/tasks", response_model=DefaultLimitOffsetPage[TaskRead])
|
||||
@router.get(
|
||||
"/boards/{board_id}/tasks",
|
||||
response_model=DefaultLimitOffsetPage[TaskRead],
|
||||
tags=AGENT_BOARD_TAGS,
|
||||
)
|
||||
async def list_tasks(
|
||||
filters: AgentTaskListFilters = TASK_LIST_FILTERS_DEP,
|
||||
board: Board = BOARD_DEP,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> LimitOffsetPage[TaskRead]:
|
||||
"""List tasks on a board with optional status and assignment filters."""
|
||||
"""List tasks on a board with status/assignment filters.
|
||||
|
||||
Common patterns:
|
||||
- worker: fetch assigned inbox/in-progress tasks
|
||||
- lead: fetch unassigned inbox tasks for delegation
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
return await tasks_api.list_tasks(
|
||||
status_filter=filters.status_filter,
|
||||
@@ -214,13 +249,16 @@ async def list_tasks(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/boards/{board_id}/tags", response_model=list[TagRef])
|
||||
@router.get("/boards/{board_id}/tags", response_model=list[TagRef], tags=AGENT_BOARD_TAGS)
|
||||
async def list_tags(
|
||||
board: Board = BOARD_DEP,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> list[TagRef]:
|
||||
"""List tags available to the board's organization."""
|
||||
"""List available tags for the board's organization.
|
||||
|
||||
Use returned ids in task create/update payloads (`tag_ids`).
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
tags = (
|
||||
await session.exec(
|
||||
@@ -240,14 +278,18 @@ async def list_tags(
|
||||
]
|
||||
|
||||
|
||||
@router.post("/boards/{board_id}/tasks", response_model=TaskRead)
|
||||
@router.post("/boards/{board_id}/tasks", response_model=TaskRead, tags=AGENT_LEAD_TAGS)
|
||||
async def create_task(
|
||||
payload: TaskCreate,
|
||||
board: Board = BOARD_DEP,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> TaskRead:
|
||||
"""Create a task on the board as the lead agent."""
|
||||
"""Create a task as the board lead.
|
||||
|
||||
Lead-only endpoint. Supports dependency-aware creation via
|
||||
`depends_on_task_ids` and optional `tag_ids`.
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
_require_board_lead(agent_ctx)
|
||||
data = payload.model_dump(exclude={"depends_on_task_ids", "tag_ids"})
|
||||
@@ -343,14 +385,21 @@ async def create_task(
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/boards/{board_id}/tasks/{task_id}", response_model=TaskRead)
|
||||
@router.patch(
|
||||
"/boards/{board_id}/tasks/{task_id}",
|
||||
response_model=TaskRead,
|
||||
tags=AGENT_BOARD_TAGS,
|
||||
)
|
||||
async def update_task(
|
||||
payload: TaskUpdate,
|
||||
task: Task = TASK_DEP,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> TaskRead:
|
||||
"""Update a task after board-level access checks."""
|
||||
"""Update a task after board-level authorization checks.
|
||||
|
||||
Supports status, assignment, dependencies, and optional inline comment.
|
||||
"""
|
||||
_guard_task_access(agent_ctx, task)
|
||||
return await tasks_api.update_task(
|
||||
payload=payload,
|
||||
@@ -363,13 +412,17 @@ async def update_task(
|
||||
@router.get(
|
||||
"/boards/{board_id}/tasks/{task_id}/comments",
|
||||
response_model=DefaultLimitOffsetPage[TaskCommentRead],
|
||||
tags=AGENT_BOARD_TAGS,
|
||||
)
|
||||
async def list_task_comments(
|
||||
task: Task = TASK_DEP,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> LimitOffsetPage[TaskCommentRead]:
|
||||
"""List comments for a task visible to the authenticated agent."""
|
||||
"""List task comments visible to the authenticated agent.
|
||||
|
||||
Read this before posting updates to avoid duplicate or low-value comments.
|
||||
"""
|
||||
_guard_task_access(agent_ctx, task)
|
||||
return await tasks_api.list_task_comments(
|
||||
task=task,
|
||||
@@ -380,6 +433,7 @@ async def list_task_comments(
|
||||
@router.post(
|
||||
"/boards/{board_id}/tasks/{task_id}/comments",
|
||||
response_model=TaskCommentRead,
|
||||
tags=AGENT_BOARD_TAGS,
|
||||
)
|
||||
async def create_task_comment(
|
||||
payload: TaskCommentCreate,
|
||||
@@ -387,7 +441,10 @@ async def create_task_comment(
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> ActivityEvent:
|
||||
"""Create a task comment on behalf of the authenticated agent."""
|
||||
"""Create a task comment as the authenticated agent.
|
||||
|
||||
This is the primary collaboration/log surface for task progress.
|
||||
"""
|
||||
_guard_task_access(agent_ctx, task)
|
||||
return await tasks_api.create_task_comment(
|
||||
payload=payload,
|
||||
@@ -400,6 +457,7 @@ async def create_task_comment(
|
||||
@router.get(
|
||||
"/boards/{board_id}/memory",
|
||||
response_model=DefaultLimitOffsetPage[BoardMemoryRead],
|
||||
tags=AGENT_BOARD_TAGS,
|
||||
)
|
||||
async def list_board_memory(
|
||||
is_chat: bool | None = IS_CHAT_QUERY,
|
||||
@@ -407,7 +465,10 @@ async def list_board_memory(
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> LimitOffsetPage[BoardMemoryRead]:
|
||||
"""List board memory entries with optional chat filtering."""
|
||||
"""List board memory with optional chat filtering.
|
||||
|
||||
Use `is_chat=false` for durable context and `is_chat=true` for board chat.
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
return await board_memory_api.list_board_memory(
|
||||
is_chat=is_chat,
|
||||
@@ -417,14 +478,17 @@ async def list_board_memory(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/boards/{board_id}/memory", response_model=BoardMemoryRead)
|
||||
@router.post("/boards/{board_id}/memory", response_model=BoardMemoryRead, tags=AGENT_BOARD_TAGS)
|
||||
async def create_board_memory(
|
||||
payload: BoardMemoryCreate,
|
||||
board: Board = BOARD_DEP,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> BoardMemory:
|
||||
"""Create a board memory entry."""
|
||||
"""Create a board memory entry.
|
||||
|
||||
Use tags to indicate purpose (e.g. `chat`, `decision`, `plan`, `handoff`).
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
return await board_memory_api.create_board_memory(
|
||||
payload=payload,
|
||||
@@ -437,6 +501,7 @@ async def create_board_memory(
|
||||
@router.get(
|
||||
"/boards/{board_id}/approvals",
|
||||
response_model=DefaultLimitOffsetPage[ApprovalRead],
|
||||
tags=AGENT_BOARD_TAGS,
|
||||
)
|
||||
async def list_approvals(
|
||||
status_filter: ApprovalStatus | None = APPROVAL_STATUS_QUERY,
|
||||
@@ -444,7 +509,10 @@ async def list_approvals(
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> LimitOffsetPage[ApprovalRead]:
|
||||
"""List approvals for a board."""
|
||||
"""List approvals for a board.
|
||||
|
||||
Use status filtering to process pending approvals efficiently.
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
return await approvals_api.list_approvals(
|
||||
status_filter=status_filter,
|
||||
@@ -454,14 +522,17 @@ async def list_approvals(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/boards/{board_id}/approvals", response_model=ApprovalRead)
|
||||
@router.post("/boards/{board_id}/approvals", response_model=ApprovalRead, tags=AGENT_BOARD_TAGS)
|
||||
async def create_approval(
|
||||
payload: ApprovalCreate,
|
||||
board: Board = BOARD_DEP,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> ApprovalRead:
|
||||
"""Create a board approval request."""
|
||||
"""Create an approval request for risky or low-confidence actions.
|
||||
|
||||
Include `task_id` or `task_ids` to scope the decision precisely.
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
return await approvals_api.create_approval(
|
||||
payload=payload,
|
||||
@@ -471,14 +542,21 @@ async def create_approval(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/boards/{board_id}/onboarding", response_model=BoardOnboardingRead)
|
||||
@router.post(
|
||||
"/boards/{board_id}/onboarding",
|
||||
response_model=BoardOnboardingRead,
|
||||
tags=AGENT_BOARD_TAGS,
|
||||
)
|
||||
async def update_onboarding(
|
||||
payload: BoardOnboardingAgentUpdate,
|
||||
board: Board = BOARD_DEP,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> BoardOnboardingSession:
|
||||
"""Apply onboarding updates for a board."""
|
||||
"""Apply board onboarding updates from an agent workflow.
|
||||
|
||||
Used during structured objective/success-metric intake loops.
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
return await onboarding_api.agent_onboarding_update(
|
||||
payload=payload,
|
||||
@@ -488,13 +566,16 @@ async def update_onboarding(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/agents", response_model=AgentRead)
|
||||
@router.post("/agents", response_model=AgentRead, tags=AGENT_LEAD_TAGS)
|
||||
async def create_agent(
|
||||
payload: AgentCreate,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> AgentRead:
|
||||
"""Create an agent on the caller's board."""
|
||||
"""Create a new board agent as lead.
|
||||
|
||||
The new agent is always forced onto the caller's board (`board_id` override).
|
||||
"""
|
||||
lead = _require_board_lead(agent_ctx)
|
||||
payload = AgentCreate(
|
||||
**{**payload.model_dump(), "board_id": lead.board_id},
|
||||
@@ -506,7 +587,11 @@ async def create_agent(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/boards/{board_id}/agents/{agent_id}/nudge", response_model=OkResponse)
|
||||
@router.post(
|
||||
"/boards/{board_id}/agents/{agent_id}/nudge",
|
||||
response_model=OkResponse,
|
||||
tags=AGENT_LEAD_TAGS,
|
||||
)
|
||||
async def nudge_agent(
|
||||
payload: AgentNudge,
|
||||
agent_id: str,
|
||||
@@ -514,7 +599,10 @@ async def nudge_agent(
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> OkResponse:
|
||||
"""Send a direct nudge message to a board agent."""
|
||||
"""Send a direct nudge to one board agent.
|
||||
|
||||
Lead-only endpoint for stale or blocked in-progress work.
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
_require_board_lead(agent_ctx)
|
||||
coordination = GatewayCoordinationService(session)
|
||||
@@ -528,13 +616,16 @@ async def nudge_agent(
|
||||
return OkResponse()
|
||||
|
||||
|
||||
@router.post("/heartbeat", response_model=AgentRead)
|
||||
@router.post("/heartbeat", response_model=AgentRead, tags=AGENT_ALL_ROLE_TAGS)
|
||||
async def agent_heartbeat(
|
||||
payload: AgentHeartbeatCreate,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> AgentRead:
|
||||
"""Record heartbeat status for the authenticated agent."""
|
||||
"""Record heartbeat status for the authenticated agent.
|
||||
|
||||
Heartbeats are identity-bound to the token's agent id.
|
||||
"""
|
||||
# Heartbeats must apply to the authenticated agent; agent names are not unique.
|
||||
return await agents_api.heartbeat_agent(
|
||||
agent_id=str(agent_ctx.agent.id),
|
||||
@@ -544,14 +635,21 @@ async def agent_heartbeat(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/boards/{board_id}/agents/{agent_id}/soul", response_model=str)
|
||||
@router.get(
|
||||
"/boards/{board_id}/agents/{agent_id}/soul",
|
||||
response_model=str,
|
||||
tags=AGENT_BOARD_TAGS,
|
||||
)
|
||||
async def get_agent_soul(
|
||||
agent_id: str,
|
||||
board: Board = BOARD_DEP,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> str:
|
||||
"""Fetch the target agent's SOUL.md content from the gateway."""
|
||||
"""Fetch an agent's SOUL.md content.
|
||||
|
||||
Allowed for board lead, or for an agent reading its own SOUL.
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
OpenClawAuthorizationPolicy.require_board_lead_or_same_actor(
|
||||
actor_agent=agent_ctx.agent,
|
||||
@@ -565,7 +663,11 @@ async def get_agent_soul(
|
||||
)
|
||||
|
||||
|
||||
@router.put("/boards/{board_id}/agents/{agent_id}/soul", response_model=OkResponse)
|
||||
@router.put(
|
||||
"/boards/{board_id}/agents/{agent_id}/soul",
|
||||
response_model=OkResponse,
|
||||
tags=AGENT_LEAD_TAGS,
|
||||
)
|
||||
async def update_agent_soul(
|
||||
agent_id: str,
|
||||
payload: SoulUpdateRequest,
|
||||
@@ -573,7 +675,10 @@ async def update_agent_soul(
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> OkResponse:
|
||||
"""Update an agent's SOUL.md content in DB and gateway."""
|
||||
"""Update an agent's SOUL.md template in DB and gateway.
|
||||
|
||||
Lead-only endpoint. Persists as `soul_template` for future reprovisioning.
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
_require_board_lead(agent_ctx)
|
||||
coordination = GatewayCoordinationService(session)
|
||||
@@ -589,14 +694,21 @@ async def update_agent_soul(
|
||||
return OkResponse()
|
||||
|
||||
|
||||
@router.delete("/boards/{board_id}/agents/{agent_id}", response_model=OkResponse)
|
||||
@router.delete(
|
||||
"/boards/{board_id}/agents/{agent_id}",
|
||||
response_model=OkResponse,
|
||||
tags=AGENT_LEAD_TAGS,
|
||||
)
|
||||
async def delete_board_agent(
|
||||
agent_id: str,
|
||||
board: Board = BOARD_DEP,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> OkResponse:
|
||||
"""Delete a board agent as the board lead."""
|
||||
"""Delete a board agent as board lead.
|
||||
|
||||
Cleans up runtime/session state through lifecycle services.
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
_require_board_lead(agent_ctx)
|
||||
service = AgentLifecycleService(session)
|
||||
@@ -609,6 +721,7 @@ async def delete_board_agent(
|
||||
@router.post(
|
||||
"/boards/{board_id}/gateway/main/ask-user",
|
||||
response_model=GatewayMainAskUserResponse,
|
||||
tags=AGENT_LEAD_TAGS,
|
||||
)
|
||||
async def ask_user_via_gateway_main(
|
||||
payload: GatewayMainAskUserRequest,
|
||||
@@ -616,7 +729,10 @@ async def ask_user_via_gateway_main(
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> GatewayMainAskUserResponse:
|
||||
"""Route a lead's ask-user request through the dedicated gateway agent."""
|
||||
"""Ask the human via gateway-main external channels.
|
||||
|
||||
Lead-only endpoint for situations where board chat is not responsive.
|
||||
"""
|
||||
_guard_board_access(agent_ctx, board)
|
||||
_require_board_lead(agent_ctx)
|
||||
coordination = GatewayCoordinationService(session)
|
||||
@@ -630,6 +746,7 @@ async def ask_user_via_gateway_main(
|
||||
@router.post(
|
||||
"/gateway/boards/{board_id}/lead/message",
|
||||
response_model=GatewayLeadMessageResponse,
|
||||
tags=AGENT_MAIN_TAGS,
|
||||
)
|
||||
async def message_gateway_board_lead(
|
||||
board_id: UUID,
|
||||
@@ -637,7 +754,7 @@ async def message_gateway_board_lead(
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> GatewayLeadMessageResponse:
|
||||
"""Send a gateway-main message to a single board lead agent."""
|
||||
"""Send a gateway-main control message to one board lead."""
|
||||
coordination = GatewayCoordinationService(session)
|
||||
return await coordination.message_gateway_board_lead(
|
||||
actor_agent=agent_ctx.agent,
|
||||
@@ -649,13 +766,14 @@ async def message_gateway_board_lead(
|
||||
@router.post(
|
||||
"/gateway/leads/broadcast",
|
||||
response_model=GatewayLeadBroadcastResponse,
|
||||
tags=AGENT_MAIN_TAGS,
|
||||
)
|
||||
async def broadcast_gateway_lead_message(
|
||||
payload: GatewayLeadBroadcastRequest,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||
) -> GatewayLeadBroadcastResponse:
|
||||
"""Broadcast a gateway-main message to multiple board leads."""
|
||||
"""Broadcast a gateway-main control message to multiple board leads."""
|
||||
coordination = GatewayCoordinationService(session)
|
||||
return await coordination.broadcast_gateway_lead_message(
|
||||
actor_agent=agent_ctx.agent,
|
||||
|
||||
Reference in New Issue
Block a user