mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Linting: Check
This commit is contained in:
@@ -17,6 +17,7 @@ Tools:
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from fastapi import status
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from roboco.config import settings
|
||||
@@ -64,7 +65,7 @@ def create_journal_mcp_server(agent_id: str) -> FastMCP:
|
||||
mcp = FastMCP(f"roboco-journal-{agent_id}", json_response=True)
|
||||
|
||||
# Store agent context
|
||||
mcp.agent_id = agent_id # type: ignore
|
||||
mcp.agent_id = agent_id
|
||||
|
||||
# =========================================================================
|
||||
# GENERAL ENTRY
|
||||
@@ -251,7 +252,8 @@ def create_journal_mcp_server(agent_id: str) -> FastMCP:
|
||||
Returns:
|
||||
Created decision log entry
|
||||
"""
|
||||
if len(options) < 2:
|
||||
two = 2
|
||||
if len(options) < two:
|
||||
return _format_error_response(
|
||||
"INVALID_OPTIONS",
|
||||
"Decision log requires at least 2 options",
|
||||
@@ -464,7 +466,7 @@ def create_journal_mcp_server(agent_id: str) -> FastMCP:
|
||||
headers={"X-Agent-Id": agent_id},
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
if resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"SEARCH_FAILED",
|
||||
"Failed to search journal",
|
||||
@@ -513,8 +515,16 @@ def create_journal_mcp_server(agent_id: str) -> FastMCP:
|
||||
headers={"X-Agent-Id": agent_id},
|
||||
)
|
||||
|
||||
stats = stats_resp.json() if stats_resp.status_code == 200 else {}
|
||||
growth = growth_resp.json() if growth_resp.status_code == 200 else {}
|
||||
stats = (
|
||||
stats_resp.json()
|
||||
if stats_resp.status_code == status.HTTP_200_OK
|
||||
else {}
|
||||
)
|
||||
growth = (
|
||||
growth_resp.json()
|
||||
if growth_resp.status_code == status.HTTP_200_OK
|
||||
else {}
|
||||
)
|
||||
|
||||
return {
|
||||
"total_entries": stats.get("total_entries", 0),
|
||||
@@ -548,9 +558,13 @@ def create_journal_mcp_server(agent_id: str) -> FastMCP:
|
||||
List recent journal entries.
|
||||
|
||||
Args:
|
||||
entry_type: Optional filter by type (general, task_reflection, decision_log, learning, struggle)
|
||||
task_id: Optional filter by related task
|
||||
limit: Maximum entries to return
|
||||
entry_type:
|
||||
Optional filter by type
|
||||
(general, task_reflection, decision_log, learning, struggle)
|
||||
task_id:
|
||||
Optional filter by related task
|
||||
limit:
|
||||
Maximum entries to return
|
||||
|
||||
Returns:
|
||||
Recent journal entries
|
||||
@@ -568,7 +582,7 @@ def create_journal_mcp_server(agent_id: str) -> FastMCP:
|
||||
headers={"X-Agent-Id": agent_id},
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
if resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"LIST_FAILED",
|
||||
"Failed to list entries",
|
||||
@@ -591,7 +605,9 @@ def create_journal_mcp_server(agent_id: str) -> FastMCP:
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
two = 2
|
||||
|
||||
if len(sys.argv) < two:
|
||||
print("Usage: python journal_server.py <agent_id>")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
+105
-78
@@ -16,6 +16,7 @@ from datetime import UTC, datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from fastapi import status
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from roboco.agents_config import CHANNEL_ACCESS
|
||||
@@ -81,7 +82,7 @@ def create_message_mcp_server(agent_id: str) -> FastMCP:
|
||||
mcp = FastMCP(f"roboco-message-{agent_id}", json_response=True)
|
||||
|
||||
# Store agent context
|
||||
mcp.agent_id = agent_id # type: ignore
|
||||
mcp.agent_id = agent_id
|
||||
|
||||
# =========================================================================
|
||||
# CHANNEL LISTING
|
||||
@@ -155,7 +156,7 @@ def create_message_mcp_server(agent_id: str) -> FastMCP:
|
||||
params={"slug": channel_slug},
|
||||
)
|
||||
|
||||
if channels_resp.status_code != 200:
|
||||
if channels_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response("API_ERROR", "Failed to fetch channels")
|
||||
|
||||
channels = channels_resp.json()
|
||||
@@ -175,7 +176,7 @@ def create_message_mcp_server(agent_id: str) -> FastMCP:
|
||||
},
|
||||
)
|
||||
|
||||
if messages_resp.status_code != 200:
|
||||
if messages_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response("API_ERROR", "Failed to fetch messages")
|
||||
|
||||
messages = messages_resp.json()
|
||||
@@ -192,6 +193,76 @@ def create_message_mcp_server(agent_id: str) -> FastMCP:
|
||||
# SEND MESSAGE
|
||||
# =========================================================================
|
||||
|
||||
def _validate_message_send(
|
||||
channel_slug: str,
|
||||
content: str,
|
||||
message_type: str,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Validate message send parameters. Returns error dict or None if valid."""
|
||||
valid_types = [
|
||||
"reasoning",
|
||||
"dialogue",
|
||||
"decision",
|
||||
"action",
|
||||
"blocker",
|
||||
"technical",
|
||||
]
|
||||
if message_type not in valid_types:
|
||||
return _format_error_response(
|
||||
"INVALID_TYPE",
|
||||
f"Invalid message type '{message_type}'. Must be one of: {valid_types}",
|
||||
)
|
||||
|
||||
if not _check_channel_access(agent_id, channel_slug, "write"):
|
||||
return _format_error_response(
|
||||
"ACCESS_DENIED",
|
||||
f"You don't have write access to #{channel_slug}",
|
||||
{
|
||||
"your_writable_channels": [
|
||||
ch
|
||||
for ch in CHANNEL_ACCESS
|
||||
if _check_channel_access(agent_id, ch, "write")
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
if agent_id in CHANNEL_ACCESS.get(channel_slug, {}).get("silent", []):
|
||||
return _format_error_response(
|
||||
"SILENT_OBSERVER",
|
||||
"You are a silent observer on this channel and cannot post messages.",
|
||||
)
|
||||
|
||||
if not content or not content.strip():
|
||||
return _format_error_response(
|
||||
"EMPTY_CONTENT",
|
||||
"Message content cannot be empty.",
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
async def _get_or_create_session(
|
||||
client: httpx.AsyncClient,
|
||||
channel_id: str,
|
||||
) -> str | dict[str, Any]:
|
||||
"""Get or create session for channel. Returns session_id or error dict."""
|
||||
session_resp = await client.get(
|
||||
f"{_get_api_url()}/channels/{channel_id}/session",
|
||||
)
|
||||
|
||||
if session_resp.status_code == status.HTTP_200_OK:
|
||||
return session_resp.json()["id"]
|
||||
|
||||
create_resp = await client.post(
|
||||
f"{_get_api_url()}/sessions",
|
||||
json={"channel_id": channel_id},
|
||||
)
|
||||
if create_resp.status_code in [status.HTTP_200_OK, status.HTTP_201_CREATED]:
|
||||
return create_resp.json()["id"]
|
||||
|
||||
return _format_error_response(
|
||||
"SESSION_ERROR", "Failed to get or create session"
|
||||
)
|
||||
|
||||
@mcp.tool()
|
||||
async def roboco_message_send(
|
||||
channel_slug: str,
|
||||
@@ -220,56 +291,23 @@ def create_message_mcp_server(agent_id: str) -> FastMCP:
|
||||
Returns:
|
||||
Sent message with confirmation
|
||||
"""
|
||||
# Validate message type
|
||||
valid_types = [
|
||||
"reasoning",
|
||||
"dialogue",
|
||||
"decision",
|
||||
"action",
|
||||
"blocker",
|
||||
"technical",
|
||||
]
|
||||
if message_type not in valid_types:
|
||||
return _format_error_response(
|
||||
"INVALID_TYPE",
|
||||
f"Invalid message type '{message_type}'. Must be one of: {valid_types}",
|
||||
)
|
||||
|
||||
# Check write access
|
||||
if not _check_channel_access(agent_id, channel_slug, "write"):
|
||||
return _format_error_response(
|
||||
"ACCESS_DENIED",
|
||||
f"You don't have write access to #{channel_slug}",
|
||||
{
|
||||
"your_writable_channels": [
|
||||
ch
|
||||
for ch in CHANNEL_ACCESS
|
||||
if _check_channel_access(agent_id, ch, "write")
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
# Silent observers cannot write even if in read list
|
||||
if agent_id in CHANNEL_ACCESS.get(channel_slug, {}).get("silent", []):
|
||||
return _format_error_response(
|
||||
"SILENT_OBSERVER",
|
||||
"You are a silent observer on this channel and cannot post messages.",
|
||||
)
|
||||
|
||||
if not content or not content.strip():
|
||||
return _format_error_response(
|
||||
"EMPTY_CONTENT",
|
||||
"Message content cannot be empty.",
|
||||
)
|
||||
# Validate inputs
|
||||
if validation_error := _validate_message_send(
|
||||
channel_slug, content, message_type
|
||||
):
|
||||
return validation_error
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
# Get channel and active session
|
||||
# Get channel
|
||||
channels_resp = await client.get(
|
||||
f"{_get_api_url()}/channels",
|
||||
params={"slug": channel_slug},
|
||||
)
|
||||
|
||||
if channels_resp.status_code != 200 or not channels_resp.json():
|
||||
if (
|
||||
channels_resp.status_code != status.HTTP_200_OK
|
||||
or not channels_resp.json()
|
||||
):
|
||||
return _format_error_response(
|
||||
"NOT_FOUND", f"Channel #{channel_slug} not found"
|
||||
)
|
||||
@@ -277,26 +315,13 @@ def create_message_mcp_server(agent_id: str) -> FastMCP:
|
||||
channel = channels_resp.json()[0]
|
||||
channel_id = channel["id"]
|
||||
|
||||
# Get or create session for the channel
|
||||
session_resp = await client.get(
|
||||
f"{_get_api_url()}/channels/{channel_id}/session",
|
||||
)
|
||||
# Get or create session
|
||||
session_result = await _get_or_create_session(client, channel_id)
|
||||
if isinstance(session_result, dict):
|
||||
return session_result # Error response
|
||||
session_id = session_result
|
||||
|
||||
if session_resp.status_code != 200:
|
||||
# Create a new session
|
||||
create_resp = await client.post(
|
||||
f"{_get_api_url()}/sessions",
|
||||
json={"channel_id": channel_id},
|
||||
)
|
||||
if create_resp.status_code not in [200, 201]:
|
||||
return _format_error_response(
|
||||
"SESSION_ERROR", "Failed to get or create session"
|
||||
)
|
||||
session_id = create_resp.json()["id"]
|
||||
else:
|
||||
session_id = session_resp.json()["id"]
|
||||
|
||||
# Build message payload
|
||||
# Build and send message
|
||||
message_data = {
|
||||
"session_id": session_id,
|
||||
"type": message_type,
|
||||
@@ -307,28 +332,28 @@ def create_message_mcp_server(agent_id: str) -> FastMCP:
|
||||
"task_id": task_id,
|
||||
}
|
||||
|
||||
# Send message
|
||||
send_resp = await client.post(
|
||||
f"{_get_api_url()}/messages",
|
||||
json=message_data,
|
||||
headers={"X-Agent-Id": agent_id},
|
||||
)
|
||||
|
||||
if send_resp.status_code not in [200, 201]:
|
||||
if send_resp.status_code not in [
|
||||
status.HTTP_200_OK,
|
||||
status.HTTP_201_CREATED,
|
||||
]:
|
||||
return _format_error_response(
|
||||
"SEND_FAILED",
|
||||
"Failed to send message",
|
||||
{"api_error": send_resp.text},
|
||||
)
|
||||
|
||||
message = send_resp.json()
|
||||
|
||||
return {
|
||||
"status": "sent",
|
||||
"message": message,
|
||||
"channel": channel_slug,
|
||||
"guidance": "Message sent successfully.",
|
||||
}
|
||||
return {
|
||||
"status": "sent",
|
||||
"message": send_resp.json(),
|
||||
"channel": channel_slug,
|
||||
"guidance": "Message sent successfully.",
|
||||
}
|
||||
|
||||
# =========================================================================
|
||||
# GET MESSAGE
|
||||
@@ -348,12 +373,12 @@ def create_message_mcp_server(agent_id: str) -> FastMCP:
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(f"{_get_api_url()}/messages/{message_id}")
|
||||
|
||||
if resp.status_code == 404:
|
||||
if resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response(
|
||||
"NOT_FOUND", f"Message {message_id} not found"
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
if resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response("API_ERROR", "Failed to fetch message")
|
||||
|
||||
message = resp.json()
|
||||
@@ -475,7 +500,9 @@ def create_message_mcp_server(agent_id: str) -> FastMCP:
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
two = 2
|
||||
|
||||
if len(sys.argv) < two:
|
||||
print("Usage: python message_server.py <agent_id>")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
+14
-11
@@ -14,6 +14,7 @@ Tools:
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from fastapi import status
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from roboco.agents_config import (
|
||||
@@ -104,7 +105,7 @@ def create_notify_mcp_server(agent_id: str) -> FastMCP:
|
||||
mcp = FastMCP(f"roboco-notify-{agent_id}", json_response=True)
|
||||
|
||||
# Store agent context
|
||||
mcp.agent_id = agent_id # type: ignore
|
||||
mcp.agent_id = agent_id
|
||||
|
||||
# =========================================================================
|
||||
# LIST NOTIFICATIONS
|
||||
@@ -140,7 +141,7 @@ def create_notify_mcp_server(agent_id: str) -> FastMCP:
|
||||
headers={"X-Agent-Id": agent_id},
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
if resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"API_ERROR", "Failed to fetch notifications"
|
||||
)
|
||||
@@ -194,16 +195,16 @@ def create_notify_mcp_server(agent_id: str) -> FastMCP:
|
||||
headers={"X-Agent-Id": agent_id},
|
||||
)
|
||||
|
||||
if resp.status_code == 404:
|
||||
if resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", "Notification not found")
|
||||
|
||||
if resp.status_code == 403:
|
||||
if resp.status_code == status.HTTP_403_FORBIDDEN:
|
||||
return _format_error_response(
|
||||
"NOT_RECIPIENT",
|
||||
"You are not a recipient of this notification",
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
if resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"API_ERROR", "Failed to fetch notification"
|
||||
)
|
||||
@@ -246,22 +247,22 @@ def create_notify_mcp_server(agent_id: str) -> FastMCP:
|
||||
headers={"X-Agent-Id": agent_id},
|
||||
)
|
||||
|
||||
if resp.status_code == 404:
|
||||
if resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", "Notification not found")
|
||||
|
||||
if resp.status_code == 403:
|
||||
if resp.status_code == status.HTTP_403_FORBIDDEN:
|
||||
return _format_error_response(
|
||||
"NOT_RECIPIENT",
|
||||
"You are not a recipient of this notification",
|
||||
)
|
||||
|
||||
if resp.status_code == 400:
|
||||
if resp.status_code == status.HTTP_400_BAD_REQUEST:
|
||||
return _format_error_response(
|
||||
"NO_ACK_REQUIRED",
|
||||
"This notification does not require acknowledgment",
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
if resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"API_ERROR", "Failed to acknowledge notification"
|
||||
)
|
||||
@@ -367,7 +368,7 @@ def create_notify_mcp_server(agent_id: str) -> FastMCP:
|
||||
headers={"X-Agent-Id": agent_id},
|
||||
)
|
||||
|
||||
if resp.status_code not in [200, 201]:
|
||||
if resp.status_code not in [status.HTTP_200_OK, status.HTTP_201_CREATED]:
|
||||
return _format_error_response(
|
||||
"SEND_FAILED",
|
||||
"Failed to send notification",
|
||||
@@ -477,7 +478,9 @@ def create_notify_mcp_server(agent_id: str) -> FastMCP:
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
two = 2
|
||||
|
||||
if len(sys.argv) < two:
|
||||
print("Usage: python notify_server.py <agent_id>")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
+90
-77
@@ -23,6 +23,7 @@ Tools:
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from fastapi import status
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from roboco.config import settings
|
||||
@@ -169,7 +170,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
mcp = FastMCP(f"roboco-task-{agent_id}", json_response=True)
|
||||
|
||||
# Store agent context
|
||||
mcp.agent_id = agent_id # type: ignore
|
||||
mcp.agent_id = agent_id
|
||||
|
||||
# =========================================================================
|
||||
# TASK SCANNING
|
||||
@@ -199,7 +200,11 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
f"{_get_api_url()}/tasks",
|
||||
params={"assigned_to": agent_id, "status": "paused"},
|
||||
)
|
||||
paused_tasks = paused_resp.json() if paused_resp.status_code == 200 else []
|
||||
paused_tasks = (
|
||||
paused_resp.json()
|
||||
if paused_resp.status_code == status.HTTP_200_OK
|
||||
else []
|
||||
)
|
||||
|
||||
# Get assigned tasks (claimed, in_progress)
|
||||
assigned_resp = await client.get(
|
||||
@@ -207,7 +212,9 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
params={"assigned_to": agent_id},
|
||||
)
|
||||
assigned_data = (
|
||||
assigned_resp.json() if assigned_resp.status_code == 200 else []
|
||||
assigned_resp.json()
|
||||
if assigned_resp.status_code == status.HTTP_200_OK
|
||||
else []
|
||||
)
|
||||
assigned_tasks = [
|
||||
t
|
||||
@@ -225,7 +232,9 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
params=params,
|
||||
)
|
||||
available_tasks = (
|
||||
available_resp.json() if available_resp.status_code == 200 else []
|
||||
available_resp.json()
|
||||
if available_resp.status_code == status.HTTP_200_OK
|
||||
else []
|
||||
)
|
||||
|
||||
# Determine guidance
|
||||
@@ -275,7 +284,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
|
||||
if resp.status_code == 404:
|
||||
if resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response(
|
||||
"NOT_FOUND",
|
||||
f"Task {task_id} not found",
|
||||
@@ -312,7 +321,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
f"{_get_api_url()}/tasks",
|
||||
params={"assigned_to": agent_id},
|
||||
)
|
||||
if active_resp.status_code == 200:
|
||||
if active_resp.status_code == status.HTTP_200_OK:
|
||||
active_tasks = active_resp.json()
|
||||
# Check for non-waiting active tasks
|
||||
blocking_tasks = [
|
||||
@@ -340,7 +349,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
|
||||
# Get the task to check status
|
||||
task_resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
if task_resp.status_code == 404:
|
||||
if task_resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", f"Task {task_id} not found")
|
||||
|
||||
task = task_resp.json()
|
||||
@@ -358,7 +367,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
json={"agent_id": agent_id},
|
||||
)
|
||||
|
||||
if claim_resp.status_code != 200:
|
||||
if claim_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"CLAIM_FAILED",
|
||||
"Failed to claim task",
|
||||
@@ -374,7 +383,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
proj_resp = await client.get(
|
||||
f"{_get_api_url()}/projects/{claimed_task['project_id']}"
|
||||
)
|
||||
if proj_resp.status_code == 200:
|
||||
if proj_resp.status_code == status.HTTP_200_OK:
|
||||
project = proj_resp.json()
|
||||
|
||||
return _format_task_response(
|
||||
@@ -419,7 +428,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
async with httpx.AsyncClient() as client:
|
||||
# Verify task state and ownership
|
||||
task_resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
if task_resp.status_code == 404:
|
||||
if task_resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", f"Task {task_id} not found")
|
||||
|
||||
task = task_resp.json()
|
||||
@@ -461,7 +470,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
json={"plan": plan_data},
|
||||
)
|
||||
|
||||
if update_resp.status_code != 200:
|
||||
if update_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"UPDATE_FAILED",
|
||||
"Failed to save plan",
|
||||
@@ -490,6 +499,41 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
# TASK START
|
||||
# =========================================================================
|
||||
|
||||
def _validate_task_start(task: dict[str, Any]) -> dict[str, Any] | None:
|
||||
"""Validate task can be started. Returns error dict or None if valid."""
|
||||
if task.get("assigned_to") != agent_id:
|
||||
return _format_error_response(
|
||||
"NOT_OWNER", "You are not assigned to this task"
|
||||
)
|
||||
|
||||
task_status = task.get("status")
|
||||
if task_status not in ["claimed", "paused"]:
|
||||
return _format_error_response(
|
||||
"INVALID_STATE",
|
||||
f"Cannot start task in '{task_status}' status. Task must be 'claimed' or 'paused'.",
|
||||
{"current_status": task_status},
|
||||
)
|
||||
|
||||
if task_status == "claimed" and not task.get("plan"):
|
||||
return _format_error_response(
|
||||
"NO_PLAN",
|
||||
"Cannot start without a plan. Call roboco_task_plan first.",
|
||||
)
|
||||
|
||||
plan = task.get("plan", {})
|
||||
unanswered = [
|
||||
q for q in plan.get("open_questions", []) if not q.get("answered")
|
||||
]
|
||||
if unanswered:
|
||||
return _format_error_response(
|
||||
"UNANSWERED_QUESTIONS",
|
||||
f"Cannot start with {len(unanswered)} unanswered question(s). "
|
||||
"Get answers first, then update the plan.",
|
||||
{"questions": [q.get("question") for q in unanswered]},
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
@mcp.tool()
|
||||
async def roboco_task_start(task_id: str) -> dict[str, Any]:
|
||||
"""
|
||||
@@ -508,67 +552,34 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
"""
|
||||
async with httpx.AsyncClient() as client:
|
||||
task_resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
if task_resp.status_code == 404:
|
||||
if task_resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", f"Task {task_id} not found")
|
||||
|
||||
task = task_resp.json()
|
||||
|
||||
if task.get("assigned_to") != agent_id:
|
||||
return _format_error_response(
|
||||
"NOT_OWNER",
|
||||
"You are not assigned to this task",
|
||||
)
|
||||
|
||||
if task.get("status") not in ["claimed", "paused"]:
|
||||
return _format_error_response(
|
||||
"INVALID_STATE",
|
||||
f"Cannot start task in '{task.get('status')}' status. "
|
||||
"Task must be 'claimed' or 'paused'.",
|
||||
{"current_status": task.get("status")},
|
||||
)
|
||||
|
||||
# Check for plan (if claimed)
|
||||
if task.get("status") == "claimed" and not task.get("plan"):
|
||||
return _format_error_response(
|
||||
"NO_PLAN",
|
||||
"Cannot start without a plan. Call roboco_task_plan first.",
|
||||
)
|
||||
|
||||
# Check for unanswered questions
|
||||
plan = task.get("plan", {})
|
||||
unanswered = [
|
||||
q for q in plan.get("open_questions", []) if not q.get("answered")
|
||||
]
|
||||
if unanswered:
|
||||
return _format_error_response(
|
||||
"UNANSWERED_QUESTIONS",
|
||||
f"Cannot start with {len(unanswered)} unanswered question(s). "
|
||||
"Get answers first, then update the plan.",
|
||||
{"questions": [q.get("question") for q in unanswered]},
|
||||
)
|
||||
if validation_error := _validate_task_start(task):
|
||||
return validation_error
|
||||
|
||||
# Start the task
|
||||
start_resp = await client.post(f"{_get_api_url()}/tasks/{task_id}/start")
|
||||
|
||||
if start_resp.status_code != 200:
|
||||
if start_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"START_FAILED",
|
||||
"Failed to start task",
|
||||
{"api_error": start_resp.text},
|
||||
)
|
||||
|
||||
started_task = start_resp.json()
|
||||
|
||||
return _format_task_response(
|
||||
started_task,
|
||||
"EXECUTE",
|
||||
"Task started. Work through your plan step by step:\n"
|
||||
"1. Implement each sub-task\n"
|
||||
"2. Commit frequently with clear messages\n"
|
||||
"3. Call roboco_task_progress to update status\n"
|
||||
"4. If blocked, call roboco_task_block immediately\n"
|
||||
"5. When done, call roboco_task_submit_verification",
|
||||
)
|
||||
return _format_task_response(
|
||||
start_resp.json(),
|
||||
"EXECUTE",
|
||||
"Task started. Work through your plan step by step:\n"
|
||||
"1. Implement each sub-task\n"
|
||||
"2. Commit frequently with clear messages\n"
|
||||
"3. Call roboco_task_progress to update status\n"
|
||||
"4. If blocked, call roboco_task_block immediately\n"
|
||||
"5. When done, call roboco_task_submit_verification",
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# PROGRESS UPDATES
|
||||
@@ -593,7 +604,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
"""
|
||||
async with httpx.AsyncClient() as client:
|
||||
task_resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
if task_resp.status_code == 404:
|
||||
if task_resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", f"Task {task_id} not found")
|
||||
|
||||
task = task_resp.json()
|
||||
@@ -619,7 +630,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
},
|
||||
)
|
||||
|
||||
if progress_resp.status_code != 200:
|
||||
if progress_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"UPDATE_FAILED",
|
||||
"Failed to update progress",
|
||||
@@ -668,7 +679,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
task_resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
if task_resp.status_code == 404:
|
||||
if task_resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", f"Task {task_id} not found")
|
||||
|
||||
task = task_resp.json()
|
||||
@@ -694,7 +705,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
},
|
||||
)
|
||||
|
||||
if block_resp.status_code != 200:
|
||||
if block_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response("BLOCK_FAILED", "Failed to block task")
|
||||
|
||||
blocked_task = block_resp.json()
|
||||
@@ -727,7 +738,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
"""
|
||||
async with httpx.AsyncClient() as client:
|
||||
task_resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
if task_resp.status_code == 404:
|
||||
if task_resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", f"Task {task_id} not found")
|
||||
|
||||
task = task_resp.json()
|
||||
@@ -747,7 +758,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
f"{_get_api_url()}/tasks/{task_id}/unblock"
|
||||
)
|
||||
|
||||
if unblock_resp.status_code != 200:
|
||||
if unblock_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"UNBLOCK_FAILED", "Failed to unblock task"
|
||||
)
|
||||
@@ -789,7 +800,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
"""
|
||||
async with httpx.AsyncClient() as client:
|
||||
task_resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
if task_resp.status_code == 404:
|
||||
if task_resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", f"Task {task_id} not found")
|
||||
|
||||
task = task_resp.json()
|
||||
@@ -819,7 +830,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
# Pause the task
|
||||
pause_resp = await client.post(f"{_get_api_url()}/tasks/{task_id}/pause")
|
||||
|
||||
if pause_resp.status_code != 200:
|
||||
if pause_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response("PAUSE_FAILED", "Failed to pause task")
|
||||
|
||||
paused_task = pause_resp.json()
|
||||
@@ -854,7 +865,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
"""
|
||||
async with httpx.AsyncClient() as client:
|
||||
task_resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
if task_resp.status_code == 404:
|
||||
if task_resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", f"Task {task_id} not found")
|
||||
|
||||
task = task_resp.json()
|
||||
@@ -880,7 +891,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
|
||||
verify_resp = await client.post(f"{_get_api_url()}/tasks/{task_id}/verify")
|
||||
|
||||
if verify_resp.status_code != 200:
|
||||
if verify_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"VERIFY_FAILED", "Failed to submit for verification"
|
||||
)
|
||||
@@ -929,7 +940,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
task_resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
if task_resp.status_code == 404:
|
||||
if task_resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", f"Task {task_id} not found")
|
||||
|
||||
task = task_resp.json()
|
||||
@@ -957,7 +968,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
# Submit for QA
|
||||
qa_resp = await client.post(f"{_get_api_url()}/tasks/{task_id}/submit-qa")
|
||||
|
||||
if qa_resp.status_code != 200:
|
||||
if qa_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"SUBMIT_FAILED", "Failed to submit for QA"
|
||||
)
|
||||
@@ -1001,7 +1012,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
task_resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
if task_resp.status_code == 404:
|
||||
if task_resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", f"Task {task_id} not found")
|
||||
|
||||
task = task_resp.json()
|
||||
@@ -1024,7 +1035,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
json={"notes": qa_notes},
|
||||
)
|
||||
|
||||
if pass_resp.status_code != 200:
|
||||
if pass_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response("QA_FAILED", "Failed to pass QA")
|
||||
|
||||
passed_task = pass_resp.json()
|
||||
@@ -1072,7 +1083,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
task_resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
if task_resp.status_code == 404:
|
||||
if task_resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", f"Task {task_id} not found")
|
||||
|
||||
task = task_resp.json()
|
||||
@@ -1092,7 +1103,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
json={"notes": full_notes},
|
||||
)
|
||||
|
||||
if fail_resp.status_code != 200:
|
||||
if fail_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response("QA_FAILED", "Failed to fail QA")
|
||||
|
||||
failed_task = fail_resp.json()
|
||||
@@ -1126,7 +1137,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
"""
|
||||
async with httpx.AsyncClient() as client:
|
||||
task_resp = await client.get(f"{_get_api_url()}/tasks/{task_id}")
|
||||
if task_resp.status_code == 404:
|
||||
if task_resp.status_code == status.HTTP_404_NOT_FOUND:
|
||||
return _format_error_response("NOT_FOUND", f"Task {task_id} not found")
|
||||
|
||||
task = task_resp.json()
|
||||
@@ -1141,7 +1152,7 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
f"{_get_api_url()}/tasks/{task_id}/complete"
|
||||
)
|
||||
|
||||
if complete_resp.status_code != 200:
|
||||
if complete_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response(
|
||||
"COMPLETE_FAILED", "Failed to complete task"
|
||||
)
|
||||
@@ -1164,7 +1175,9 @@ def create_task_mcp_server(agent_id: str) -> FastMCP:
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
two = 2
|
||||
|
||||
if len(sys.argv) < two:
|
||||
print("Usage: python task_server.py <agent_id>")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user