feat: add backend API endpoints for agent file import and editing
Co-authored-by: abhi1693 <5083532+abhi1693@users.noreply.github.com>
This commit is contained in:
@@ -1538,6 +1538,191 @@ async def update_agent_soul(
|
|||||||
return OkResponse()
|
return OkResponse()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/boards/{board_id}/agents/{agent_id}/files",
|
||||||
|
response_model=list[dict[str, object]],
|
||||||
|
tags=AGENT_BOARD_TAGS,
|
||||||
|
summary="List agent files",
|
||||||
|
description="List available agent markdown files from the gateway workspace.",
|
||||||
|
operation_id="agent_board_list_files",
|
||||||
|
openapi_extra=_agent_board_openapi_hints(
|
||||||
|
intent="agent_files_list",
|
||||||
|
when_to_use=[
|
||||||
|
"Discover available agent files before reading",
|
||||||
|
"Check which files can be edited",
|
||||||
|
],
|
||||||
|
routing_examples=[
|
||||||
|
{
|
||||||
|
"input": {
|
||||||
|
"intent": "list agent configuration files",
|
||||||
|
"required_privilege": "board_lead_or_same_actor",
|
||||||
|
},
|
||||||
|
"decision": "agent_board_list_files",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
side_effects=["No persisted side effects"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def list_agent_files(
|
||||||
|
agent_id: str,
|
||||||
|
board: Board = BOARD_DEP,
|
||||||
|
session: AsyncSession = SESSION_DEP,
|
||||||
|
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||||
|
) -> list[dict[str, object]]:
|
||||||
|
"""List available agent files.
|
||||||
|
|
||||||
|
Allowed for board lead or for an agent listing its own files.
|
||||||
|
"""
|
||||||
|
_guard_board_access(agent_ctx, board)
|
||||||
|
OpenClawAuthorizationPolicy.require_board_lead_or_same_actor(
|
||||||
|
actor_agent=agent_ctx.agent,
|
||||||
|
target_agent_id=agent_id,
|
||||||
|
)
|
||||||
|
coordination = GatewayCoordinationService(session)
|
||||||
|
return await coordination.list_agent_files(
|
||||||
|
board=board,
|
||||||
|
target_agent_id=agent_id,
|
||||||
|
correlation_id=f"files.list:{board.id}:{agent_id}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/boards/{board_id}/agents/{agent_id}/files/{filename}",
|
||||||
|
response_model=str,
|
||||||
|
tags=AGENT_BOARD_TAGS,
|
||||||
|
summary="Read agent file",
|
||||||
|
description="Fetch content of a specific agent markdown file.",
|
||||||
|
operation_id="agent_board_read_file",
|
||||||
|
openapi_extra=_agent_board_openapi_hints(
|
||||||
|
intent="agent_file_read",
|
||||||
|
when_to_use=[
|
||||||
|
"Read IDENTITY.md, BOOTSTRAP.md, or other agent configuration files",
|
||||||
|
"Review current agent file content before editing",
|
||||||
|
],
|
||||||
|
routing_examples=[
|
||||||
|
{
|
||||||
|
"input": {
|
||||||
|
"intent": "read agent configuration file",
|
||||||
|
"required_privilege": "board_lead_or_same_actor",
|
||||||
|
},
|
||||||
|
"decision": "agent_board_read_file",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
side_effects=["No persisted side effects"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def read_agent_file(
|
||||||
|
agent_id: str,
|
||||||
|
filename: str,
|
||||||
|
board: Board = BOARD_DEP,
|
||||||
|
session: AsyncSession = SESSION_DEP,
|
||||||
|
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||||
|
) -> str:
|
||||||
|
"""Read content of an agent file.
|
||||||
|
|
||||||
|
Allowed for board lead or for an agent reading its own files.
|
||||||
|
"""
|
||||||
|
_guard_board_access(agent_ctx, board)
|
||||||
|
OpenClawAuthorizationPolicy.require_board_lead_or_same_actor(
|
||||||
|
actor_agent=agent_ctx.agent,
|
||||||
|
target_agent_id=agent_id,
|
||||||
|
)
|
||||||
|
coordination = GatewayCoordinationService(session)
|
||||||
|
return await coordination.get_agent_file(
|
||||||
|
board=board,
|
||||||
|
target_agent_id=agent_id,
|
||||||
|
filename=filename,
|
||||||
|
correlation_id=f"file.read:{board.id}:{agent_id}:{filename}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put(
|
||||||
|
"/boards/{board_id}/agents/{agent_id}/files/{filename}",
|
||||||
|
response_model=OkResponse,
|
||||||
|
tags=AGENT_LEAD_TAGS,
|
||||||
|
summary="Update agent file",
|
||||||
|
description=(
|
||||||
|
"Write content to an agent markdown file and persist it for reprovisioning.\n\n"
|
||||||
|
"Use this when agent configuration files need updates."
|
||||||
|
),
|
||||||
|
operation_id="agent_lead_update_file",
|
||||||
|
responses={
|
||||||
|
200: {"description": "File updated"},
|
||||||
|
403: {
|
||||||
|
"model": LLMErrorResponse,
|
||||||
|
"description": "Caller is not board lead",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
"model": LLMErrorResponse,
|
||||||
|
"description": "Board or target agent not found",
|
||||||
|
},
|
||||||
|
422: {
|
||||||
|
"model": LLMErrorResponse,
|
||||||
|
"description": "File content is invalid or empty",
|
||||||
|
},
|
||||||
|
502: {
|
||||||
|
"model": LLMErrorResponse,
|
||||||
|
"description": "Gateway sync failed",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
openapi_extra={
|
||||||
|
"x-llm-intent": "agent_file_authoring",
|
||||||
|
"x-when-to-use": [
|
||||||
|
"Update agent configuration files like IDENTITY.md or BOOTSTRAP.md",
|
||||||
|
"Import existing agent files into mission control",
|
||||||
|
],
|
||||||
|
"x-when-not-to-use": [
|
||||||
|
"Use dedicated SOUL endpoint for SOUL.md updates",
|
||||||
|
"Use task comments for transient guidance",
|
||||||
|
],
|
||||||
|
"x-required-actor": "board_lead",
|
||||||
|
"x-prerequisites": [
|
||||||
|
"Authenticated board lead",
|
||||||
|
"Non-empty file content",
|
||||||
|
"Target agent scoped to board",
|
||||||
|
],
|
||||||
|
"x-side-effects": [
|
||||||
|
"Updates file content in gateway workspace",
|
||||||
|
"Persists to database for certain files (IDENTITY.md, SOUL.md)",
|
||||||
|
"Creates activity log entry",
|
||||||
|
],
|
||||||
|
"x-routing-policy": [
|
||||||
|
"Use when updating agent configuration files",
|
||||||
|
"Prefer dedicated SOUL endpoint for SOUL.md",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def update_agent_file(
|
||||||
|
agent_id: str,
|
||||||
|
filename: str,
|
||||||
|
payload: dict[str, str],
|
||||||
|
board: Board = BOARD_DEP,
|
||||||
|
session: AsyncSession = SESSION_DEP,
|
||||||
|
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
|
||||||
|
) -> OkResponse:
|
||||||
|
"""Update an agent file in gateway and optionally persist to DB.
|
||||||
|
|
||||||
|
Lead-only endpoint. Persists IDENTITY.md and SOUL.md for reprovisioning.
|
||||||
|
"""
|
||||||
|
_guard_board_access(agent_ctx, board)
|
||||||
|
_require_board_lead(agent_ctx)
|
||||||
|
|
||||||
|
content = payload.get("content", "")
|
||||||
|
reason = payload.get("reason")
|
||||||
|
|
||||||
|
coordination = GatewayCoordinationService(session)
|
||||||
|
await coordination.update_agent_file(
|
||||||
|
board=board,
|
||||||
|
target_agent_id=agent_id,
|
||||||
|
filename=filename,
|
||||||
|
content=content,
|
||||||
|
reason=reason,
|
||||||
|
actor_agent_id=agent_ctx.agent.id,
|
||||||
|
correlation_id=f"file.write:{board.id}:{agent_id}:{filename}",
|
||||||
|
)
|
||||||
|
return OkResponse()
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/boards/{board_id}/agents/{agent_id}",
|
"/boards/{board_id}/agents/{agent_id}",
|
||||||
response_model=OkResponse,
|
response_model=OkResponse,
|
||||||
|
|||||||
101
backend/app/schemas/agent_files.py
Normal file
101
backend/app/schemas/agent_files.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
"""Pydantic schemas for agent file operations."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
from sqlmodel import SQLModel
|
||||||
|
from sqlmodel._compat import SQLModelConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AgentFileRead(SQLModel):
|
||||||
|
"""Response model for reading an agent file."""
|
||||||
|
|
||||||
|
model_config = SQLModelConfig(
|
||||||
|
json_schema_extra={
|
||||||
|
"x-llm-intent": "agent_file_content",
|
||||||
|
"x-when-to-use": [
|
||||||
|
"Retrieve content of an agent markdown file",
|
||||||
|
"Read IDENTITY.md, SOUL.md, BOOTSTRAP.md, or other agent files",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
name: str = Field(
|
||||||
|
description="File name (e.g., IDENTITY.md, SOUL.md, BOOTSTRAP.md)",
|
||||||
|
examples=["IDENTITY.md", "SOUL.md"],
|
||||||
|
)
|
||||||
|
content: str = Field(
|
||||||
|
description="File content",
|
||||||
|
examples=["# IDENTITY.md\n\n## Core\n- Name: Agent Name"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AgentFileUpdate(SQLModel):
|
||||||
|
"""Request model for updating an agent file."""
|
||||||
|
|
||||||
|
model_config = SQLModelConfig(
|
||||||
|
json_schema_extra={
|
||||||
|
"x-llm-intent": "agent_file_update",
|
||||||
|
"x-when-to-use": [
|
||||||
|
"Update an agent markdown file",
|
||||||
|
"Modify IDENTITY.md or other editable agent files",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
content: str = Field(
|
||||||
|
description="New file content",
|
||||||
|
examples=["# IDENTITY.md\n\n## Core\n- Name: Updated Agent Name"],
|
||||||
|
)
|
||||||
|
reason: str | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="Optional reason for the update",
|
||||||
|
examples=["Updated agent role and communication style"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AgentFileImport(SQLModel):
|
||||||
|
"""Request model for importing an agent file."""
|
||||||
|
|
||||||
|
model_config = SQLModelConfig(
|
||||||
|
json_schema_extra={
|
||||||
|
"x-llm-intent": "agent_file_import",
|
||||||
|
"x-when-to-use": [
|
||||||
|
"Import existing agent markdown file into mission control",
|
||||||
|
"Upload IDENTITY.md, SOUL.md, or other agent files",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
name: str = Field(
|
||||||
|
description="File name (e.g., IDENTITY.md, SOUL.md, BOOTSTRAP.md)",
|
||||||
|
examples=["IDENTITY.md", "SOUL.md"],
|
||||||
|
)
|
||||||
|
content: str = Field(
|
||||||
|
description="File content to import",
|
||||||
|
examples=["# IDENTITY.md\n\n## Core\n- Name: Agent Name"],
|
||||||
|
)
|
||||||
|
reason: str | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="Optional reason for the import",
|
||||||
|
examples=["Importing existing agent configuration"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AgentFileListItem(SQLModel):
|
||||||
|
"""Agent file list item."""
|
||||||
|
|
||||||
|
model_config = SQLModelConfig(
|
||||||
|
json_schema_extra={
|
||||||
|
"x-llm-intent": "agent_file_list_item",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
name: str = Field(
|
||||||
|
description="File name",
|
||||||
|
examples=["IDENTITY.md", "SOUL.md", "BOOTSTRAP.md"],
|
||||||
|
)
|
||||||
|
editable: bool = Field(
|
||||||
|
description="Whether the file can be edited via the API",
|
||||||
|
examples=[True, False],
|
||||||
|
)
|
||||||
@@ -408,6 +408,281 @@ class GatewayCoordinationService(AbstractGatewayMessagingService):
|
|||||||
actor_agent_id,
|
actor_agent_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def list_agent_files(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
board: Board,
|
||||||
|
target_agent_id: str,
|
||||||
|
correlation_id: str | None = None,
|
||||||
|
) -> list[dict[str, object]]:
|
||||||
|
"""List available agent files from the gateway."""
|
||||||
|
trace_id = GatewayDispatchService.resolve_trace_id(
|
||||||
|
correlation_id, prefix="coord.files.list"
|
||||||
|
)
|
||||||
|
self.logger.log(
|
||||||
|
TRACE_LEVEL,
|
||||||
|
"gateway.coordination.files_list.start trace_id=%s board_id=%s target_agent_id=%s",
|
||||||
|
trace_id,
|
||||||
|
board.id,
|
||||||
|
target_agent_id,
|
||||||
|
)
|
||||||
|
target = await self._board_agent_or_404(board=board, agent_id=target_agent_id)
|
||||||
|
_gateway, config = await GatewayDispatchService(
|
||||||
|
self.session
|
||||||
|
).require_gateway_config_for_board(board)
|
||||||
|
try:
|
||||||
|
|
||||||
|
async def _do_list() -> object:
|
||||||
|
return await openclaw_call(
|
||||||
|
"agents.files.list",
|
||||||
|
{"agentId": agent_key(target)},
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = await self._with_gateway_retry(_do_list)
|
||||||
|
except (OpenClawGatewayError, TimeoutError) as exc:
|
||||||
|
self.logger.error(
|
||||||
|
"gateway.coordination.files_list.failed trace_id=%s board_id=%s "
|
||||||
|
"target_agent_id=%s error=%s",
|
||||||
|
trace_id,
|
||||||
|
board.id,
|
||||||
|
target_agent_id,
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
raise map_gateway_error_to_http_exception(GatewayOperation.FILES_LIST, exc) from exc
|
||||||
|
except Exception as exc: # pragma: no cover - defensive guard
|
||||||
|
self.logger.critical(
|
||||||
|
"gateway.coordination.files_list.failed_unexpected trace_id=%s board_id=%s "
|
||||||
|
"target_agent_id=%s error_type=%s error=%s",
|
||||||
|
trace_id,
|
||||||
|
board.id,
|
||||||
|
target_agent_id,
|
||||||
|
exc.__class__.__name__,
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Parse the file list from gateway response
|
||||||
|
if isinstance(payload, dict):
|
||||||
|
files = payload.get("files", [])
|
||||||
|
elif isinstance(payload, list):
|
||||||
|
files = payload
|
||||||
|
else:
|
||||||
|
files = []
|
||||||
|
|
||||||
|
# Define editable files
|
||||||
|
editable_files = {
|
||||||
|
"IDENTITY.md",
|
||||||
|
"SOUL.md",
|
||||||
|
"BOOTSTRAP.md",
|
||||||
|
"AGENTS.md",
|
||||||
|
"TOOLS.md",
|
||||||
|
"HEARTBEAT.md",
|
||||||
|
}
|
||||||
|
|
||||||
|
result = []
|
||||||
|
if isinstance(files, list):
|
||||||
|
for file in files:
|
||||||
|
if isinstance(file, str):
|
||||||
|
result.append({"name": file, "editable": file in editable_files})
|
||||||
|
elif isinstance(file, dict):
|
||||||
|
name = file.get("name", "")
|
||||||
|
if isinstance(name, str):
|
||||||
|
result.append({"name": name, "editable": name in editable_files})
|
||||||
|
|
||||||
|
self.logger.info(
|
||||||
|
"gateway.coordination.files_list.success trace_id=%s board_id=%s target_agent_id=%s "
|
||||||
|
"file_count=%s",
|
||||||
|
trace_id,
|
||||||
|
board.id,
|
||||||
|
target_agent_id,
|
||||||
|
len(result),
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def get_agent_file(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
board: Board,
|
||||||
|
target_agent_id: str,
|
||||||
|
filename: str,
|
||||||
|
correlation_id: str | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Get content of an agent file from the gateway."""
|
||||||
|
trace_id = GatewayDispatchService.resolve_trace_id(
|
||||||
|
correlation_id, prefix="coord.file.read"
|
||||||
|
)
|
||||||
|
self.logger.log(
|
||||||
|
TRACE_LEVEL,
|
||||||
|
"gateway.coordination.file_read.start trace_id=%s board_id=%s target_agent_id=%s "
|
||||||
|
"filename=%s",
|
||||||
|
trace_id,
|
||||||
|
board.id,
|
||||||
|
target_agent_id,
|
||||||
|
filename,
|
||||||
|
)
|
||||||
|
target = await self._board_agent_or_404(board=board, agent_id=target_agent_id)
|
||||||
|
_gateway, config = await GatewayDispatchService(
|
||||||
|
self.session
|
||||||
|
).require_gateway_config_for_board(board)
|
||||||
|
try:
|
||||||
|
|
||||||
|
async def _do_get() -> object:
|
||||||
|
return await openclaw_call(
|
||||||
|
"agents.files.get",
|
||||||
|
{"agentId": agent_key(target), "name": filename},
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = await self._with_gateway_retry(_do_get)
|
||||||
|
except (OpenClawGatewayError, TimeoutError) as exc:
|
||||||
|
self.logger.error(
|
||||||
|
"gateway.coordination.file_read.failed trace_id=%s board_id=%s "
|
||||||
|
"target_agent_id=%s filename=%s error=%s",
|
||||||
|
trace_id,
|
||||||
|
board.id,
|
||||||
|
target_agent_id,
|
||||||
|
filename,
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
raise map_gateway_error_to_http_exception(GatewayOperation.FILE_READ, exc) from exc
|
||||||
|
except Exception as exc: # pragma: no cover - defensive guard
|
||||||
|
self.logger.critical(
|
||||||
|
"gateway.coordination.file_read.failed_unexpected trace_id=%s board_id=%s "
|
||||||
|
"target_agent_id=%s filename=%s error_type=%s error=%s",
|
||||||
|
trace_id,
|
||||||
|
board.id,
|
||||||
|
target_agent_id,
|
||||||
|
filename,
|
||||||
|
exc.__class__.__name__,
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
content = self._gateway_file_content(payload)
|
||||||
|
if content is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||||
|
detail="Invalid gateway response",
|
||||||
|
)
|
||||||
|
self.logger.info(
|
||||||
|
"gateway.coordination.file_read.success trace_id=%s board_id=%s target_agent_id=%s "
|
||||||
|
"filename=%s",
|
||||||
|
trace_id,
|
||||||
|
board.id,
|
||||||
|
target_agent_id,
|
||||||
|
filename,
|
||||||
|
)
|
||||||
|
return content
|
||||||
|
|
||||||
|
async def update_agent_file(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
board: Board,
|
||||||
|
target_agent_id: str,
|
||||||
|
filename: str,
|
||||||
|
content: str,
|
||||||
|
reason: str | None,
|
||||||
|
actor_agent_id: UUID,
|
||||||
|
correlation_id: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Update an agent file in the gateway and optionally persist to DB."""
|
||||||
|
trace_id = GatewayDispatchService.resolve_trace_id(
|
||||||
|
correlation_id, prefix="coord.file.write"
|
||||||
|
)
|
||||||
|
self.logger.log(
|
||||||
|
TRACE_LEVEL,
|
||||||
|
"gateway.coordination.file_write.start trace_id=%s board_id=%s target_agent_id=%s "
|
||||||
|
"filename=%s actor_agent_id=%s",
|
||||||
|
trace_id,
|
||||||
|
board.id,
|
||||||
|
target_agent_id,
|
||||||
|
filename,
|
||||||
|
actor_agent_id,
|
||||||
|
)
|
||||||
|
target = await self._board_agent_or_404(board=board, agent_id=target_agent_id)
|
||||||
|
normalized_content = content.strip()
|
||||||
|
if not normalized_content:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
||||||
|
detail="content is required",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update database fields for specific files
|
||||||
|
if filename == "SOUL.md":
|
||||||
|
target.soul_template = normalized_content
|
||||||
|
target.updated_at = utcnow()
|
||||||
|
self.session.add(target)
|
||||||
|
await self.session.commit()
|
||||||
|
elif filename == "IDENTITY.md":
|
||||||
|
target.identity_template = normalized_content
|
||||||
|
target.updated_at = utcnow()
|
||||||
|
self.session.add(target)
|
||||||
|
await self.session.commit()
|
||||||
|
|
||||||
|
_gateway, config = await GatewayDispatchService(
|
||||||
|
self.session
|
||||||
|
).require_gateway_config_for_board(board)
|
||||||
|
try:
|
||||||
|
|
||||||
|
async def _do_set() -> object:
|
||||||
|
return await openclaw_call(
|
||||||
|
"agents.files.set",
|
||||||
|
{
|
||||||
|
"agentId": agent_key(target),
|
||||||
|
"name": filename,
|
||||||
|
"content": normalized_content,
|
||||||
|
},
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._with_gateway_retry(_do_set)
|
||||||
|
except (OpenClawGatewayError, TimeoutError) as exc:
|
||||||
|
self.logger.error(
|
||||||
|
"gateway.coordination.file_write.failed trace_id=%s board_id=%s "
|
||||||
|
"target_agent_id=%s filename=%s actor_agent_id=%s error=%s",
|
||||||
|
trace_id,
|
||||||
|
board.id,
|
||||||
|
target_agent_id,
|
||||||
|
filename,
|
||||||
|
actor_agent_id,
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
raise map_gateway_error_to_http_exception(GatewayOperation.FILE_WRITE, exc) from exc
|
||||||
|
except Exception as exc: # pragma: no cover - defensive guard
|
||||||
|
self.logger.critical(
|
||||||
|
"gateway.coordination.file_write.failed_unexpected trace_id=%s board_id=%s "
|
||||||
|
"target_agent_id=%s filename=%s actor_agent_id=%s error_type=%s error=%s",
|
||||||
|
trace_id,
|
||||||
|
board.id,
|
||||||
|
target_agent_id,
|
||||||
|
filename,
|
||||||
|
actor_agent_id,
|
||||||
|
exc.__class__.__name__,
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
reason_text = (reason or "").strip()
|
||||||
|
note = f"{filename} updated for {target.name}."
|
||||||
|
if reason_text:
|
||||||
|
note = f"{note} Reason: {reason_text}"
|
||||||
|
record_activity(
|
||||||
|
self.session,
|
||||||
|
event_type="agent.file.updated",
|
||||||
|
message=note,
|
||||||
|
agent_id=actor_agent_id,
|
||||||
|
)
|
||||||
|
await self.session.commit()
|
||||||
|
self.logger.info(
|
||||||
|
"gateway.coordination.file_write.success trace_id=%s board_id=%s target_agent_id=%s "
|
||||||
|
"filename=%s actor_agent_id=%s",
|
||||||
|
trace_id,
|
||||||
|
board.id,
|
||||||
|
target_agent_id,
|
||||||
|
filename,
|
||||||
|
actor_agent_id,
|
||||||
|
)
|
||||||
|
|
||||||
async def ask_user_via_gateway_main(
|
async def ask_user_via_gateway_main(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ class GatewayOperation(str, Enum):
|
|||||||
NUDGE_AGENT = "nudge_agent"
|
NUDGE_AGENT = "nudge_agent"
|
||||||
SOUL_READ = "soul_read"
|
SOUL_READ = "soul_read"
|
||||||
SOUL_WRITE = "soul_write"
|
SOUL_WRITE = "soul_write"
|
||||||
|
FILES_LIST = "files_list"
|
||||||
|
FILE_READ = "file_read"
|
||||||
|
FILE_WRITE = "file_write"
|
||||||
ASK_USER_DISPATCH = "ask_user_dispatch"
|
ASK_USER_DISPATCH = "ask_user_dispatch"
|
||||||
LEAD_MESSAGE_DISPATCH = "lead_message_dispatch"
|
LEAD_MESSAGE_DISPATCH = "lead_message_dispatch"
|
||||||
LEAD_BROADCAST_DISPATCH = "lead_broadcast_dispatch"
|
LEAD_BROADCAST_DISPATCH = "lead_broadcast_dispatch"
|
||||||
@@ -42,6 +45,18 @@ _GATEWAY_ERROR_POLICIES: dict[GatewayOperation, GatewayErrorPolicy] = {
|
|||||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||||
detail_template="Gateway SOUL update failed: {error}",
|
detail_template="Gateway SOUL update failed: {error}",
|
||||||
),
|
),
|
||||||
|
GatewayOperation.FILES_LIST: GatewayErrorPolicy(
|
||||||
|
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||||
|
detail_template="Gateway files list failed: {error}",
|
||||||
|
),
|
||||||
|
GatewayOperation.FILE_READ: GatewayErrorPolicy(
|
||||||
|
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||||
|
detail_template="Gateway file read failed: {error}",
|
||||||
|
),
|
||||||
|
GatewayOperation.FILE_WRITE: GatewayErrorPolicy(
|
||||||
|
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||||
|
detail_template="Gateway file update failed: {error}",
|
||||||
|
),
|
||||||
GatewayOperation.ASK_USER_DISPATCH: GatewayErrorPolicy(
|
GatewayOperation.ASK_USER_DISPATCH: GatewayErrorPolicy(
|
||||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||||
detail_template="Gateway ask-user dispatch failed: {error}",
|
detail_template="Gateway ask-user dispatch failed: {error}",
|
||||||
|
|||||||
Reference in New Issue
Block a user