Merge master into ishan/fix-activity-clerkprovider
This commit is contained in:
@@ -5,7 +5,6 @@ from typing import Any, cast
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import func
|
||||
from sqlmodel import col, select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
@@ -43,13 +42,13 @@ from app.schemas.board_onboarding import BoardOnboardingAgentUpdate, BoardOnboar
|
||||
from app.schemas.boards import BoardRead
|
||||
from app.schemas.common import OkResponse
|
||||
from app.schemas.gateway_coordination import (
|
||||
GatewayBoardEnsureRequest,
|
||||
GatewayBoardEnsureResponse,
|
||||
GatewayLeadBroadcastBoardResult,
|
||||
GatewayLeadBroadcastRequest,
|
||||
GatewayLeadBroadcastResponse,
|
||||
GatewayLeadMessageRequest,
|
||||
GatewayLeadMessageResponse,
|
||||
GatewayMainAskUserRequest,
|
||||
GatewayMainAskUserResponse,
|
||||
)
|
||||
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||
from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate
|
||||
@@ -82,13 +81,6 @@ async def _gateway_config(session: AsyncSession, board: Board) -> GatewayClientC
|
||||
return GatewayClientConfig(url=gateway.url, token=gateway.token)
|
||||
|
||||
|
||||
def _slugify(value: str) -> str:
|
||||
import re
|
||||
|
||||
slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
|
||||
return slug or "board"
|
||||
|
||||
|
||||
async def _require_gateway_main(
|
||||
session: AsyncSession,
|
||||
agent: Agent,
|
||||
@@ -498,87 +490,97 @@ async def agent_heartbeat(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/gateway/boards/ensure", response_model=GatewayBoardEnsureResponse)
|
||||
async def ensure_gateway_board(
|
||||
payload: GatewayBoardEnsureRequest,
|
||||
@router.post(
|
||||
"/boards/{board_id}/gateway/main/ask-user",
|
||||
response_model=GatewayMainAskUserResponse,
|
||||
)
|
||||
async def ask_user_via_gateway_main(
|
||||
payload: GatewayMainAskUserRequest,
|
||||
board: Board = Depends(get_board_or_404),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
||||
) -> GatewayBoardEnsureResponse:
|
||||
gateway, config = await _require_gateway_main(session, agent_ctx.agent)
|
||||
) -> GatewayMainAskUserResponse:
|
||||
import json
|
||||
|
||||
requested_name = payload.name.strip()
|
||||
requested_slug = _slugify(payload.slug.strip() if payload.slug else requested_name)
|
||||
_guard_board_access(agent_ctx, board)
|
||||
if not agent_ctx.agent.is_board_lead:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Try slug match first, then case-insensitive name match.
|
||||
existing = (
|
||||
await session.exec(
|
||||
select(Board)
|
||||
.where(col(Board.gateway_id) == gateway.id)
|
||||
.where(col(Board.slug) == requested_slug)
|
||||
if not board.gateway_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Board is not attached to a gateway",
|
||||
)
|
||||
).first()
|
||||
if existing is None:
|
||||
existing = (
|
||||
await session.exec(
|
||||
select(Board)
|
||||
.where(col(Board.gateway_id) == gateway.id)
|
||||
.where(func.lower(col(Board.name)) == requested_name.lower())
|
||||
)
|
||||
).first()
|
||||
|
||||
created = False
|
||||
board = existing
|
||||
if board is None:
|
||||
slug = requested_slug
|
||||
suffix = 2
|
||||
while True:
|
||||
conflict = (
|
||||
await session.exec(
|
||||
select(Board.id)
|
||||
.where(col(Board.gateway_id) == gateway.id)
|
||||
.where(col(Board.slug) == slug)
|
||||
)
|
||||
).first()
|
||||
if conflict is None:
|
||||
break
|
||||
slug = f"{requested_slug}-{suffix}"
|
||||
suffix += 1
|
||||
|
||||
board = Board(
|
||||
name=requested_name,
|
||||
slug=slug,
|
||||
gateway_id=gateway.id,
|
||||
board_type=payload.board_type,
|
||||
objective=payload.objective.strip() if payload.objective else None,
|
||||
success_metrics=payload.success_metrics,
|
||||
target_date=payload.target_date,
|
||||
goal_confirmed=False,
|
||||
goal_source="gateway_main_agent",
|
||||
gateway = await session.get(Gateway, board.gateway_id)
|
||||
if gateway is None or not gateway.url:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Gateway is not configured for this board",
|
||||
)
|
||||
session.add(board)
|
||||
await session.commit()
|
||||
await session.refresh(board)
|
||||
created = True
|
||||
main_session_key = (gateway.main_session_key or "").strip()
|
||||
if not main_session_key:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Gateway main session key is required",
|
||||
)
|
||||
config = GatewayClientConfig(url=gateway.url, token=gateway.token)
|
||||
|
||||
lead, lead_created = await ensure_board_lead_agent(
|
||||
session,
|
||||
board=board,
|
||||
gateway=gateway,
|
||||
config=config,
|
||||
user=None,
|
||||
agent_name=payload.lead_agent_name.strip() if payload.lead_agent_name else None,
|
||||
identity_profile=payload.lead_identity_profile,
|
||||
action="provision",
|
||||
correlation = payload.correlation_id.strip() if payload.correlation_id else ""
|
||||
correlation_line = f"Correlation ID: {correlation}\n" if correlation else ""
|
||||
preferred_channel = (payload.preferred_channel or "").strip()
|
||||
channel_line = f"Preferred channel: {preferred_channel}\n" if preferred_channel else ""
|
||||
|
||||
tags = payload.reply_tags or ["gateway_main", "user_reply"]
|
||||
tags_json = json.dumps(tags)
|
||||
reply_source = payload.reply_source or "user_via_gateway_main"
|
||||
base_url = settings.base_url or "http://localhost:8000"
|
||||
|
||||
message = (
|
||||
"LEAD REQUEST: ASK USER\n"
|
||||
f"Board: {board.name}\n"
|
||||
f"Board ID: {board.id}\n"
|
||||
f"From lead: {agent_ctx.agent.name}\n"
|
||||
f"{correlation_line}"
|
||||
f"{channel_line}\n"
|
||||
f"{payload.content.strip()}\n\n"
|
||||
"Please reach the user via your configured OpenClaw channel(s) (Slack/SMS/etc).\n"
|
||||
"If you cannot reach them there, post the question in Mission Control board chat as a fallback.\n\n"
|
||||
"When you receive the answer, reply in Mission Control by writing a NON-chat memory item on this board:\n"
|
||||
f"POST {base_url}/api/v1/agent/boards/{board.id}/memory\n"
|
||||
f'Body: {{"content":"<answer>","tags":{tags_json},"source":"{reply_source}"}}\n'
|
||||
"Do NOT reply in OpenClaw chat."
|
||||
)
|
||||
|
||||
return GatewayBoardEnsureResponse(
|
||||
created=created,
|
||||
lead_created=lead_created,
|
||||
try:
|
||||
await ensure_session(main_session_key, config=config, label="Main Agent")
|
||||
await send_message(message, session_key=main_session_key, config=config, deliver=True)
|
||||
except OpenClawGatewayError as exc:
|
||||
record_activity(
|
||||
session,
|
||||
event_type="gateway.lead.ask_user.failed",
|
||||
message=f"Lead user question failed for {board.name}: {exc}",
|
||||
agent_id=agent_ctx.agent.id,
|
||||
)
|
||||
await session.commit()
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc
|
||||
|
||||
record_activity(
|
||||
session,
|
||||
event_type="gateway.lead.ask_user.sent",
|
||||
message=f"Lead requested user info via gateway main for board: {board.name}.",
|
||||
agent_id=agent_ctx.agent.id,
|
||||
)
|
||||
|
||||
main_agent = (
|
||||
await session.exec(select(Agent).where(col(Agent.openclaw_session_id) == main_session_key))
|
||||
).first()
|
||||
|
||||
await session.commit()
|
||||
|
||||
return GatewayMainAskUserResponse(
|
||||
board_id=board.id,
|
||||
lead_agent_id=lead.id,
|
||||
board_name=board.name,
|
||||
board_slug=board.slug,
|
||||
lead_agent_name=lead.name,
|
||||
main_agent_id=main_agent.id if main_agent else None,
|
||||
main_agent_name=main_agent.name if main_agent else None,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
from uuid import UUID
|
||||
|
||||
@@ -9,29 +8,6 @@ from sqlmodel import Field, SQLModel
|
||||
from app.schemas.common import NonEmptyStr
|
||||
|
||||
|
||||
class GatewayBoardEnsureRequest(SQLModel):
|
||||
name: NonEmptyStr
|
||||
slug: str | None = None
|
||||
board_type: Literal["goal", "general"] = "goal"
|
||||
objective: str | None = None
|
||||
success_metrics: dict[str, object] | None = None
|
||||
target_date: datetime | None = None
|
||||
lead_agent_name: str | None = None
|
||||
lead_identity_profile: dict[str, str] | None = None
|
||||
|
||||
|
||||
class GatewayBoardEnsureResponse(SQLModel):
|
||||
created: bool = False
|
||||
lead_created: bool = False
|
||||
board_id: UUID
|
||||
lead_agent_id: UUID | None = None
|
||||
|
||||
# Convenience fields for callers that don't want to re-fetch.
|
||||
board_name: str
|
||||
board_slug: str
|
||||
lead_agent_name: str | None = None
|
||||
|
||||
|
||||
class GatewayLeadMessageRequest(SQLModel):
|
||||
kind: Literal["question", "handoff"] = "question"
|
||||
correlation_id: str | None = None
|
||||
@@ -72,3 +48,20 @@ class GatewayLeadBroadcastResponse(SQLModel):
|
||||
sent: int = 0
|
||||
failed: int = 0
|
||||
results: list[GatewayLeadBroadcastBoardResult] = Field(default_factory=list)
|
||||
|
||||
|
||||
class GatewayMainAskUserRequest(SQLModel):
|
||||
correlation_id: str | None = None
|
||||
content: NonEmptyStr
|
||||
preferred_channel: str | None = None
|
||||
|
||||
# How the main agent should reply back into Mission Control (defaults interpreted by templates).
|
||||
reply_tags: list[str] = Field(default_factory=lambda: ["gateway_main", "user_reply"])
|
||||
reply_source: str | None = "user_via_gateway_main"
|
||||
|
||||
|
||||
class GatewayMainAskUserResponse(SQLModel):
|
||||
ok: bool = True
|
||||
board_id: UUID
|
||||
main_agent_id: UUID | None = None
|
||||
main_agent_name: str | None = None
|
||||
|
||||
@@ -86,6 +86,49 @@ def _slugify(value: str) -> str:
|
||||
return slug or uuid4().hex
|
||||
|
||||
|
||||
def _agent_id_from_session_key(session_key: str | None) -> str | None:
|
||||
value = (session_key or "").strip()
|
||||
if not value:
|
||||
return None
|
||||
if not value.startswith("agent:"):
|
||||
return None
|
||||
parts = value.split(":")
|
||||
if len(parts) < 2:
|
||||
return None
|
||||
agent_id = parts[1].strip()
|
||||
return agent_id or None
|
||||
|
||||
|
||||
def _extract_agent_id(payload: object) -> str | None:
|
||||
def _from_list(items: object) -> str | None:
|
||||
if not isinstance(items, list):
|
||||
return None
|
||||
for item in items:
|
||||
if isinstance(item, str) and item.strip():
|
||||
return item.strip()
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
for key in ("id", "agentId", "agent_id"):
|
||||
raw = item.get(key)
|
||||
if isinstance(raw, str) and raw.strip():
|
||||
return raw.strip()
|
||||
return None
|
||||
|
||||
if isinstance(payload, list):
|
||||
return _from_list(payload)
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
for key in ("defaultId", "default_id", "defaultAgentId", "default_agent_id"):
|
||||
raw = payload.get(key)
|
||||
if isinstance(raw, str) and raw.strip():
|
||||
return raw.strip()
|
||||
for key in ("agents", "items", "list", "data"):
|
||||
agent_id = _from_list(payload.get(key))
|
||||
if agent_id:
|
||||
return agent_id
|
||||
return None
|
||||
|
||||
|
||||
def _agent_key(agent: Agent) -> str:
|
||||
session_key = agent.openclaw_session_id or ""
|
||||
if session_key.startswith("agent:"):
|
||||
@@ -383,24 +426,18 @@ def _render_agent_files(
|
||||
|
||||
async def _gateway_default_agent_id(
|
||||
config: GatewayClientConfig,
|
||||
*,
|
||||
fallback_session_key: str | None = None,
|
||||
) -> str | None:
|
||||
try:
|
||||
payload = await openclaw_call("agents.list", config=config)
|
||||
except OpenClawGatewayError:
|
||||
return None
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
default_id = payload.get("defaultId") or payload.get("default_id")
|
||||
if isinstance(default_id, str) and default_id:
|
||||
return default_id
|
||||
agents = payload.get("agents") or []
|
||||
if isinstance(agents, list) and agents:
|
||||
first = agents[0]
|
||||
if isinstance(first, dict):
|
||||
agent_id = first.get("id")
|
||||
if isinstance(agent_id, str) and agent_id:
|
||||
return agent_id
|
||||
return None
|
||||
return _agent_id_from_session_key(fallback_session_key)
|
||||
|
||||
agent_id = _extract_agent_id(payload)
|
||||
if agent_id:
|
||||
return agent_id
|
||||
return _agent_id_from_session_key(fallback_session_key)
|
||||
|
||||
|
||||
async def _patch_gateway_agent_list(
|
||||
@@ -585,7 +622,10 @@ async def provision_main_agent(
|
||||
client_config = GatewayClientConfig(url=gateway.url, token=gateway.token)
|
||||
await ensure_session(gateway.main_session_key, config=client_config, label="Main Agent")
|
||||
|
||||
agent_id = await _gateway_default_agent_id(client_config)
|
||||
agent_id = await _gateway_default_agent_id(
|
||||
client_config,
|
||||
fallback_session_key=gateway.main_session_key,
|
||||
)
|
||||
if not agent_id:
|
||||
raise OpenClawGatewayError("Unable to resolve gateway main agent id")
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import TypeVar
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from sqlmodel import col, select
|
||||
@@ -19,12 +22,94 @@ from app.services.agent_provisioning import provision_agent, provision_main_agen
|
||||
|
||||
_TOOLS_KV_RE = re.compile(r"^(?P<key>[A-Z0-9_]+)=(?P<value>.*)$")
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def _slugify(value: str) -> str:
|
||||
slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
|
||||
return slug or uuid4().hex
|
||||
|
||||
|
||||
def _is_transient_gateway_error(exc: Exception) -> bool:
|
||||
if not isinstance(exc, OpenClawGatewayError):
|
||||
return False
|
||||
message = str(exc).lower()
|
||||
if not message:
|
||||
return False
|
||||
if "unsupported file" in message:
|
||||
return False
|
||||
if "received 1012" in message or "service restart" in message:
|
||||
return True
|
||||
if "http 503" in message or ("503" in message and "websocket" in message):
|
||||
return True
|
||||
if "temporar" in message:
|
||||
return True
|
||||
if "timeout" in message or "timed out" in message:
|
||||
return True
|
||||
if "connection closed" in message or "connection reset" in message:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def _with_gateway_retry(
|
||||
fn: Callable[[], Awaitable[T]],
|
||||
*,
|
||||
attempts: int = 3,
|
||||
base_delay_s: float = 0.75,
|
||||
) -> T:
|
||||
for attempt in range(attempts):
|
||||
try:
|
||||
return await fn()
|
||||
except Exception as exc:
|
||||
if attempt >= attempts - 1 or not _is_transient_gateway_error(exc):
|
||||
raise
|
||||
await asyncio.sleep(base_delay_s * (2**attempt))
|
||||
raise AssertionError("unreachable")
|
||||
|
||||
|
||||
def _agent_id_from_session_key(session_key: str | None) -> str | None:
|
||||
value = (session_key or "").strip()
|
||||
if not value:
|
||||
return None
|
||||
if not value.startswith("agent:"):
|
||||
return None
|
||||
parts = value.split(":")
|
||||
if len(parts) < 2:
|
||||
return None
|
||||
agent_id = parts[1].strip()
|
||||
return agent_id or None
|
||||
|
||||
|
||||
def _extract_agent_id(payload: object) -> str | None:
|
||||
def _from_list(items: object) -> str | None:
|
||||
if not isinstance(items, list):
|
||||
return None
|
||||
for item in items:
|
||||
if isinstance(item, str) and item.strip():
|
||||
return item.strip()
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
for key in ("id", "agentId", "agent_id"):
|
||||
raw = item.get(key)
|
||||
if isinstance(raw, str) and raw.strip():
|
||||
return raw.strip()
|
||||
return None
|
||||
|
||||
if isinstance(payload, list):
|
||||
return _from_list(payload)
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
for key in ("defaultId", "default_id", "defaultAgentId", "default_agent_id"):
|
||||
raw = payload.get(key)
|
||||
if isinstance(raw, str) and raw.strip():
|
||||
return raw.strip()
|
||||
for key in ("agents", "items", "list", "data"):
|
||||
agent_id = _from_list(payload.get(key))
|
||||
if agent_id:
|
||||
return agent_id
|
||||
return None
|
||||
|
||||
|
||||
def _gateway_agent_id(agent: Agent) -> str:
|
||||
session_key = agent.openclaw_session_id or ""
|
||||
if session_key.startswith("agent:"):
|
||||
@@ -94,24 +179,34 @@ async def _get_existing_auth_token(
|
||||
return token or None
|
||||
|
||||
|
||||
async def _gateway_default_agent_id(config: GatewayClientConfig) -> str | None:
|
||||
try:
|
||||
payload = await openclaw_call("agents.list", config=config)
|
||||
except OpenClawGatewayError:
|
||||
return None
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
default_id = payload.get("defaultId") or payload.get("default_id")
|
||||
if isinstance(default_id, str) and default_id:
|
||||
return default_id
|
||||
agents = payload.get("agents") or []
|
||||
if isinstance(agents, list) and agents:
|
||||
first = agents[0]
|
||||
if isinstance(first, dict):
|
||||
agent_id = first.get("id")
|
||||
if isinstance(agent_id, str) and agent_id:
|
||||
async def _gateway_default_agent_id(
|
||||
config: GatewayClientConfig,
|
||||
*,
|
||||
fallback_session_key: str | None = None,
|
||||
) -> str | None:
|
||||
last_error: OpenClawGatewayError | None = None
|
||||
# Gateways may reject WS connects transiently under load (HTTP 503).
|
||||
for attempt in range(3):
|
||||
try:
|
||||
payload = await openclaw_call("agents.list", config=config)
|
||||
agent_id = _extract_agent_id(payload)
|
||||
if agent_id:
|
||||
return agent_id
|
||||
return None
|
||||
break
|
||||
except OpenClawGatewayError as exc:
|
||||
last_error = exc
|
||||
message = str(exc).lower()
|
||||
if (
|
||||
"503" not in message
|
||||
and "temporar" not in message
|
||||
and "rejected" not in message
|
||||
and "timeout" not in message
|
||||
):
|
||||
break
|
||||
await asyncio.sleep(0.5 * (2**attempt))
|
||||
|
||||
_ = last_error
|
||||
return _agent_id_from_session_key(fallback_session_key)
|
||||
|
||||
|
||||
async def sync_gateway_templates(
|
||||
@@ -226,16 +321,19 @@ async def sync_gateway_templates(
|
||||
)
|
||||
|
||||
try:
|
||||
await provision_agent(
|
||||
agent,
|
||||
board,
|
||||
gateway,
|
||||
auth_token,
|
||||
user,
|
||||
action="update",
|
||||
force_bootstrap=force_bootstrap,
|
||||
reset_session=reset_sessions,
|
||||
)
|
||||
async def _do_provision() -> None:
|
||||
await provision_agent(
|
||||
agent,
|
||||
board,
|
||||
gateway,
|
||||
auth_token,
|
||||
user,
|
||||
action="update",
|
||||
force_bootstrap=force_bootstrap,
|
||||
reset_session=reset_sessions,
|
||||
)
|
||||
|
||||
await _with_gateway_retry(_do_provision)
|
||||
result.agents_updated += 1
|
||||
except Exception as exc: # pragma: no cover - gateway/network dependent
|
||||
result.agents_skipped += 1
|
||||
@@ -262,7 +360,10 @@ async def sync_gateway_templates(
|
||||
)
|
||||
return result
|
||||
|
||||
main_gateway_agent_id = await _gateway_default_agent_id(client_config)
|
||||
main_gateway_agent_id = await _gateway_default_agent_id(
|
||||
client_config,
|
||||
fallback_session_key=gateway.main_session_key,
|
||||
)
|
||||
if not main_gateway_agent_id:
|
||||
result.errors.append(
|
||||
GatewayTemplatesSyncError(
|
||||
@@ -277,25 +378,57 @@ async def sync_gateway_templates(
|
||||
agent_gateway_id=main_gateway_agent_id, config=client_config
|
||||
)
|
||||
if not main_token:
|
||||
result.errors.append(
|
||||
GatewayTemplatesSyncError(
|
||||
agent_id=main_agent.id,
|
||||
agent_name=main_agent.name,
|
||||
message="Skipping main agent: unable to read AUTH_TOKEN from TOOLS.md.",
|
||||
if rotate_tokens:
|
||||
raw_token = generate_agent_token()
|
||||
main_agent.agent_token_hash = hash_agent_token(raw_token)
|
||||
main_agent.updated_at = utcnow()
|
||||
session.add(main_agent)
|
||||
await session.commit()
|
||||
await session.refresh(main_agent)
|
||||
main_token = raw_token
|
||||
else:
|
||||
result.errors.append(
|
||||
GatewayTemplatesSyncError(
|
||||
agent_id=main_agent.id,
|
||||
agent_name=main_agent.name,
|
||||
message="Skipping main agent: unable to read AUTH_TOKEN from TOOLS.md.",
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
if main_agent.agent_token_hash and not verify_agent_token(
|
||||
main_token, main_agent.agent_token_hash
|
||||
):
|
||||
if rotate_tokens:
|
||||
raw_token = generate_agent_token()
|
||||
main_agent.agent_token_hash = hash_agent_token(raw_token)
|
||||
main_agent.updated_at = utcnow()
|
||||
session.add(main_agent)
|
||||
await session.commit()
|
||||
await session.refresh(main_agent)
|
||||
main_token = raw_token
|
||||
else:
|
||||
result.errors.append(
|
||||
GatewayTemplatesSyncError(
|
||||
agent_id=main_agent.id,
|
||||
agent_name=main_agent.name,
|
||||
message="Warning: AUTH_TOKEN in TOOLS.md does not match backend token hash (main agent auth may be broken).",
|
||||
)
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
try:
|
||||
await provision_main_agent(
|
||||
main_agent,
|
||||
gateway,
|
||||
main_token,
|
||||
user,
|
||||
action="update",
|
||||
force_bootstrap=force_bootstrap,
|
||||
reset_session=reset_sessions,
|
||||
)
|
||||
async def _do_provision_main() -> None:
|
||||
await provision_main_agent(
|
||||
main_agent,
|
||||
gateway,
|
||||
main_token,
|
||||
user,
|
||||
action="update",
|
||||
force_bootstrap=force_bootstrap,
|
||||
reset_session=reset_sessions,
|
||||
)
|
||||
|
||||
await _with_gateway_retry(_do_provision_main)
|
||||
result.main_updated = True
|
||||
except Exception as exc: # pragma: no cover - gateway/network dependent
|
||||
result.errors.append(
|
||||
|
||||
Reference in New Issue
Block a user