Merge pull request #2419 from Molecule-AI/staging

staging → main: auto-promote 665582b
This commit is contained in:
github-actions[bot] 2026-04-30 17:38:19 -07:00 committed by GitHub
commit 6445c0ad17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 58 additions and 7 deletions

View File

@ -188,14 +188,34 @@ async def tool_delegate_task(workspace_id: str, task: str) -> str:
if not workspace_id or not task:
return "Error: workspace_id and task are required"
# Discover the target
# Discover the target. We still call discover_peer because it
# enforces access control + tells us whether the peer is online,
# but we DO NOT use the peer's reported URL for routing — see below.
peer = await discover_peer(workspace_id)
if not peer:
return f"Error: workspace {workspace_id} not found or not accessible (check access control)"
target_url = peer.get("url", "")
if not target_url:
return f"Error: workspace {workspace_id} has no URL (may be offline)"
if (peer.get("status") or "").lower() == "offline":
return f"Error: workspace {workspace_id} is offline"
# Route through the platform's A2A proxy instead of POSTing
# directly to peer["url"]. The peer's URL is whatever it last
# registered — for in-container peers that's a Docker-internal
# hostname like ``http://ws-X-Y:8000`` which only resolves inside
# the platform's container DNS. External callers (the standalone
# molecule-mcp wrapper running on an operator's laptop) hit
# `[Errno 8] nodename nor servname` every time they try to reach
# an in-container peer that way. The platform's
# ``/workspaces/:peer-id/a2a`` proxy works for BOTH paths: it
# forwards over the Docker network for in-container peers and is
# the only path external runtimes can use, so unifying on it
# makes the universal-MCP path actually universal. In-container
# callers pay one extra HTTP hop on the same bridge — microseconds
# — in exchange for one consistent code path. Verified live on
# 2026-04-30 against workspace 8dad3e29 → 97ac32e9 (Claude Code
# Agent) which replied correctly through the proxy after failing
# via direct connect.
target_url = f"{PLATFORM_URL}/workspaces/{workspace_id}/a2a"
# Report delegation start — include the task text for traceability
peer_name = peer.get("name") or _peer_names.get(workspace_id) or workspace_id[:8]

View File

@ -230,13 +230,44 @@ class TestToolDelegateTask:
result = await a2a_tools.tool_delegate_task("ws-missing", "task")
assert "not found" in result or "Error" in result
async def test_peer_has_no_url_returns_error(self):
async def test_offline_peer_returns_error(self):
"""A peer with status=offline short-circuits before we hit the proxy."""
import a2a_tools
with patch("a2a_tools.discover_peer", return_value={"id": "ws-1", "url": ""}):
with patch("a2a_tools.discover_peer", return_value={"id": "ws-1", "status": "offline"}):
mc = _make_http_mock()
with patch("a2a_tools.httpx.AsyncClient", return_value=mc):
result = await a2a_tools.tool_delegate_task("ws-1", "task")
assert "no URL" in result or "Error" in result
assert "offline" in result.lower()
async def test_routes_through_platform_proxy_not_peer_url(self):
"""tool_delegate_task must POST to ${PLATFORM_URL}/workspaces/:peer-id/a2a,
NOT to peer["url"]. The peer's URL is a Docker-internal hostname for
in-container peers; external molecule-mcp callers cannot resolve it.
Routing through the platform proxy works for both."""
import a2a_tools
from a2a_client import PLATFORM_URL
peer = {
"id": "ws-target",
# Internal-only URL — must NOT be used.
"url": "http://ws-target-internal:8000",
"name": "Worker",
"status": "online",
}
captured = {}
async def fake_send(target_url, message):
captured["target_url"] = target_url
captured["message"] = message
return "ok"
with patch("a2a_tools.discover_peer", return_value=peer), \
patch("a2a_tools.send_a2a_message", side_effect=fake_send), \
patch("a2a_tools.report_activity", new=AsyncMock()):
await a2a_tools.tool_delegate_task("ws-target", "do thing")
assert captured["target_url"] == f"{PLATFORM_URL}/workspaces/ws-target/a2a"
# Sanity: definitely NOT the peer's reported URL
assert captured["target_url"] != peer["url"]
async def test_success_returns_result_text(self):
"""Happy path: peer found with URL, A2A returns a result."""