agent-relay 3.2.2 → 3.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/agent-relay-broker-darwin-arm64 +0 -0
- package/bin/agent-relay-broker-darwin-x64 +0 -0
- package/bin/agent-relay-broker-linux-arm64 +0 -0
- package/bin/agent-relay-broker-linux-x64 +0 -0
- package/dist/index.cjs +1358 -941
- package/dist/src/cli/commands/agent-management.d.ts +2 -2
- package/dist/src/cli/commands/agent-management.d.ts.map +1 -1
- package/dist/src/cli/commands/agent-management.js +41 -240
- package/dist/src/cli/commands/agent-management.js.map +1 -1
- package/dist/src/cli/commands/messaging.d.ts +1 -1
- package/dist/src/cli/commands/messaging.d.ts.map +1 -1
- package/dist/src/cli/commands/messaging.js +14 -5
- package/dist/src/cli/commands/messaging.js.map +1 -1
- package/dist/src/cli/lib/agent-management-listing.d.ts +4 -1
- package/dist/src/cli/lib/agent-management-listing.d.ts.map +1 -1
- package/dist/src/cli/lib/agent-management-listing.js +27 -2
- package/dist/src/cli/lib/agent-management-listing.js.map +1 -1
- package/package.json +11 -10
- package/packages/acp-bridge/package.json +2 -2
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/memory/package.json +2 -2
- package/packages/openclaw/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/sdk/ADAPTER_REVIEW.md +109 -0
- package/packages/sdk/dist/client.d.ts +66 -0
- package/packages/sdk/dist/client.d.ts.map +1 -1
- package/packages/sdk/dist/client.js +230 -0
- package/packages/sdk/dist/client.js.map +1 -1
- package/packages/sdk/dist/communicate/a2a-bridge.d.ts +25 -0
- package/packages/sdk/dist/communicate/a2a-bridge.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-bridge.js +89 -0
- package/packages/sdk/dist/communicate/a2a-bridge.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-server.d.ts +31 -0
- package/packages/sdk/dist/communicate/a2a-server.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-server.js +220 -0
- package/packages/sdk/dist/communicate/a2a-server.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-transport.d.ts +48 -0
- package/packages/sdk/dist/communicate/a2a-transport.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-transport.js +302 -0
- package/packages/sdk/dist/communicate/a2a-transport.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-types.d.ts +107 -0
- package/packages/sdk/dist/communicate/a2a-types.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-types.js +209 -0
- package/packages/sdk/dist/communicate/a2a-types.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts +28 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.js +47 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/crewai.d.ts +42 -0
- package/packages/sdk/dist/communicate/adapters/crewai.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/crewai.js +95 -0
- package/packages/sdk/dist/communicate/adapters/crewai.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.d.ts +53 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.js +77 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/index.d.ts +7 -0
- package/packages/sdk/dist/communicate/adapters/index.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/index.js +7 -0
- package/packages/sdk/dist/communicate/adapters/index.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.d.ts +40 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.js +77 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts +25 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.js +70 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/pi.d.ts +45 -0
- package/packages/sdk/dist/communicate/adapters/pi.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/pi.js +59 -0
- package/packages/sdk/dist/communicate/adapters/pi.js.map +1 -0
- package/packages/sdk/dist/communicate/core.d.ts +58 -0
- package/packages/sdk/dist/communicate/core.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/core.js +128 -0
- package/packages/sdk/dist/communicate/core.js.map +1 -0
- package/packages/sdk/dist/communicate/index.d.ts +4 -0
- package/packages/sdk/dist/communicate/index.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/index.js +4 -0
- package/packages/sdk/dist/communicate/index.js.map +1 -0
- package/packages/sdk/dist/communicate/transport.d.ts +36 -0
- package/packages/sdk/dist/communicate/transport.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/transport.js +371 -0
- package/packages/sdk/dist/communicate/transport.js.map +1 -0
- package/packages/sdk/dist/communicate/types.d.ts +58 -0
- package/packages/sdk/dist/communicate/types.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/types.js +66 -0
- package/packages/sdk/dist/communicate/types.js.map +1 -0
- package/packages/sdk/dist/workflows/builder.d.ts +35 -5
- package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/builder.js +81 -7
- package/packages/sdk/dist/workflows/builder.js.map +1 -1
- package/packages/sdk/dist/workflows/cli.js +14 -1
- package/packages/sdk/dist/workflows/cli.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.d.ts +10 -2
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +95 -1
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/dist/workflows/types.d.ts +11 -0
- package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
- package/packages/sdk/examples/communicate/claude_sdk_example.ts +5 -0
- package/packages/sdk/examples/communicate/pi_example.ts +8 -0
- package/packages/sdk/package.json +48 -2
- package/packages/sdk/src/__tests__/builder-deterministic.test.ts +132 -0
- package/packages/sdk/src/__tests__/communicate/a2a-bridge.test.ts +211 -0
- package/packages/sdk/src/__tests__/communicate/a2a-server.test.ts +359 -0
- package/packages/sdk/src/__tests__/communicate/a2a-transport.test.ts +537 -0
- package/packages/sdk/src/__tests__/communicate/a2a-types.test.ts +297 -0
- package/packages/sdk/src/__tests__/communicate/adapters/claude-sdk.test.ts +163 -0
- package/packages/sdk/src/__tests__/communicate/adapters/crewai.test.ts +219 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-crewai.test.ts +101 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-google-adk.test.ts +166 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-langgraph.test.ts +181 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-openai-agents.test.ts +137 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-pi.test.ts +140 -0
- package/packages/sdk/src/__tests__/communicate/adapters/google-adk.test.ts +200 -0
- package/packages/sdk/src/__tests__/communicate/adapters/langgraph.test.ts +162 -0
- package/packages/sdk/src/__tests__/communicate/adapters/openai-agents.test.ts +166 -0
- package/packages/sdk/src/__tests__/communicate/adapters/pi.test.ts +140 -0
- package/packages/sdk/src/__tests__/communicate/core.test.ts +574 -0
- package/packages/sdk/src/__tests__/communicate/integration/cross-framework.test.ts +353 -0
- package/packages/sdk/src/__tests__/communicate/transport.test.ts +613 -0
- package/packages/sdk/src/__tests__/start-from.test.ts +346 -0
- package/packages/sdk/src/client.ts +301 -0
- package/packages/sdk/src/communicate/a2a-bridge.ts +111 -0
- package/packages/sdk/src/communicate/a2a-server.ts +277 -0
- package/packages/sdk/src/communicate/a2a-transport.ts +395 -0
- package/packages/sdk/src/communicate/a2a-types.ts +338 -0
- package/packages/sdk/src/communicate/adapters/claude-sdk.ts +85 -0
- package/packages/sdk/src/communicate/adapters/crewai.ts +141 -0
- package/packages/sdk/src/communicate/adapters/google-adk.ts +139 -0
- package/packages/sdk/src/communicate/adapters/index.ts +6 -0
- package/packages/sdk/src/communicate/adapters/langgraph.ts +112 -0
- package/packages/sdk/src/communicate/adapters/openai-agents.ts +113 -0
- package/packages/sdk/src/communicate/adapters/pi.ts +105 -0
- package/packages/sdk/src/communicate/core.ts +157 -0
- package/packages/sdk/src/communicate/index.ts +3 -0
- package/packages/sdk/src/communicate/transport.ts +489 -0
- package/packages/sdk/src/communicate/types.ts +106 -0
- package/packages/sdk/src/examples/workflows/fix-dashboard-user-registration.yaml +182 -0
- package/packages/sdk/src/workflows/builder.ts +97 -9
- package/packages/sdk/src/workflows/cli.ts +16 -1
- package/packages/sdk/src/workflows/runner.ts +110 -1
- package/packages/sdk/src/workflows/types.ts +14 -0
- package/packages/sdk/tsconfig.build.json +1 -7
- package/packages/sdk/tsconfig.json +1 -7
- package/packages/sdk-py/README.md +67 -25
- package/packages/sdk-py/examples/communicate/agno_example.py +8 -0
- package/packages/sdk-py/examples/communicate/claude_sdk_example.py +6 -0
- package/packages/sdk-py/examples/communicate/crewai_example.py +7 -0
- package/packages/sdk-py/examples/communicate/google_adk_example.py +7 -0
- package/packages/sdk-py/examples/communicate/openai_agents_example.py +8 -0
- package/packages/sdk-py/examples/communicate/swarms_example.py +7 -0
- package/packages/sdk-py/pyproject.toml +12 -1
- package/packages/sdk-py/src/agent_relay/__init__.py +8 -0
- package/packages/sdk-py/src/agent_relay/builder.py +65 -26
- package/packages/sdk-py/src/agent_relay/communicate/__init__.py +6 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_bridge.py +138 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_server.py +242 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_transport.py +366 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_types.py +294 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/__init__.py +10 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/agno.py +74 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/claude_sdk.py +78 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/crewai.py +143 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/google_adk.py +69 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/openai_agents.py +86 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/pi.py +175 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/swarms.py +44 -0
- package/packages/sdk-py/src/agent_relay/communicate/core.py +293 -0
- package/packages/sdk-py/src/agent_relay/communicate/transport.py +502 -0
- package/packages/sdk-py/src/agent_relay/communicate/types.py +89 -0
- package/packages/sdk-py/src/agent_relay/types.py +2 -1
- package/packages/sdk-py/tests/communicate/__init__.py +0 -0
- package/packages/sdk-py/tests/communicate/adapters/__init__.py +0 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_agno.py +154 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_claude_sdk.py +428 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_crewai.py +234 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_google_adk.py +182 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_langgraph.py +262 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_openai_agents.py +88 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_pi.py +156 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_swarms.py +239 -0
- package/packages/sdk-py/tests/communicate/adapters/test_agno.py +140 -0
- package/packages/sdk-py/tests/communicate/adapters/test_claude_sdk.py +147 -0
- package/packages/sdk-py/tests/communicate/adapters/test_crewai.py +136 -0
- package/packages/sdk-py/tests/communicate/adapters/test_google_adk.py +125 -0
- package/packages/sdk-py/tests/communicate/adapters/test_openai_agents.py +99 -0
- package/packages/sdk-py/tests/communicate/adapters/test_pi.py +270 -0
- package/packages/sdk-py/tests/communicate/adapters/test_swarms.py +113 -0
- package/packages/sdk-py/tests/communicate/conftest.py +555 -0
- package/packages/sdk-py/tests/communicate/integration/__init__.py +1 -0
- package/packages/sdk-py/tests/communicate/integration/test_cross_framework.py +331 -0
- package/packages/sdk-py/tests/communicate/integration/test_end_to_end.py +151 -0
- package/packages/sdk-py/tests/communicate/test_a2a_bridge.py +363 -0
- package/packages/sdk-py/tests/communicate/test_a2a_server.py +346 -0
- package/packages/sdk-py/tests/communicate/test_a2a_transport.py +561 -0
- package/packages/sdk-py/tests/communicate/test_a2a_types.py +342 -0
- package/packages/sdk-py/tests/communicate/test_auto_detect.py +67 -0
- package/packages/sdk-py/tests/communicate/test_core.py +331 -0
- package/packages/sdk-py/tests/communicate/test_transport.py +373 -0
- package/packages/sdk-py/tests/communicate/test_types.py +285 -0
- package/packages/sdk-py/tests/test_builder_deterministic.py +118 -0
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts +0 -14
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js +0 -1476
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js +0 -152
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts +0 -16
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js +0 -640
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/facade.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/facade.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/facade.test.js +0 -305
- package/packages/sdk/dist/__tests__/facade.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/integration.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/integration.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/integration.test.js +0 -205
- package/packages/sdk/dist/__tests__/integration.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/pty.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/pty.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/pty.test.js +0 -20
- package/packages/sdk/dist/__tests__/pty.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/quickstart.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/quickstart.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/quickstart.test.js +0 -176
- package/packages/sdk/dist/__tests__/quickstart.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/spawn-from-env.test.js +0 -222
- package/packages/sdk/dist/__tests__/spawn-from-env.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/unit.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/unit.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/unit.test.js +0 -357
- package/packages/sdk/dist/__tests__/unit.test.js.map +0 -1
- package/packages/sdk-py/agent_relay/__init__.py +0 -21
- package/packages/sdk-py/agent_relay/models.py +0 -398
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""E2E test: Agno Python adapter against live Relaycast."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
from types import ModuleType
|
|
11
|
+
from unittest.mock import MagicMock
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from agent_relay.communicate.core import Relay
|
|
16
|
+
from agent_relay.communicate.types import RelayConfig
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _live_config() -> RelayConfig:
|
|
20
|
+
return RelayConfig.resolve(
|
|
21
|
+
workspace=os.environ.get("RELAY_WORKSPACE"),
|
|
22
|
+
api_key=os.environ.get("RELAY_API_KEY"),
|
|
23
|
+
base_url=os.environ.get("RELAY_BASE_URL"),
|
|
24
|
+
channels=["general"],
|
|
25
|
+
auto_cleanup=False,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _unique_name(prefix: str = "e2e-agno") -> str:
|
|
30
|
+
return f"{prefix}-{int(time.time() * 1000)}-{uuid.uuid4().hex[:6]}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _install_agno_module(monkeypatch):
|
|
34
|
+
"""Inject a fake 'agno' module tree so the adapter resolves correctly."""
|
|
35
|
+
agno_mod = ModuleType("agno")
|
|
36
|
+
agno_agent_mod = ModuleType("agno.agent")
|
|
37
|
+
agno_agent_mod.Agent = type("Agent", (), {})
|
|
38
|
+
agno_mod.agent = agno_agent_mod
|
|
39
|
+
monkeypatch.setitem(sys.modules, "agno", agno_mod)
|
|
40
|
+
monkeypatch.setitem(sys.modules, "agno.agent", agno_agent_mod)
|
|
41
|
+
return agno_agent_mod
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _make_mock_agent(name: str, AgentCls):
|
|
45
|
+
agent = MagicMock(spec=AgentCls)
|
|
46
|
+
agent.name = name
|
|
47
|
+
agent.tools = []
|
|
48
|
+
agent.instructions = "You are a helpful Agno agent."
|
|
49
|
+
type(agent).__module__ = "agno.agent"
|
|
50
|
+
return agent
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.asyncio
|
|
54
|
+
async def test_agno_e2e(monkeypatch):
|
|
55
|
+
"""Full round-trip: real Relay, mock Agno Agent, live Relaycast API."""
|
|
56
|
+
agno_agent_mod = _install_agno_module(monkeypatch)
|
|
57
|
+
from agent_relay.communicate.adapters.agno import on_relay
|
|
58
|
+
|
|
59
|
+
sender_name = _unique_name("e2e-agno-s")
|
|
60
|
+
receiver_name = _unique_name("e2e-agno-r")
|
|
61
|
+
config = _live_config()
|
|
62
|
+
|
|
63
|
+
sender_relay = Relay(sender_name, config)
|
|
64
|
+
receiver_relay = Relay(receiver_name, config)
|
|
65
|
+
|
|
66
|
+
sender_agent = _make_mock_agent(sender_name, agno_agent_mod.Agent)
|
|
67
|
+
receiver_agent = _make_mock_agent(receiver_name, agno_agent_mod.Agent)
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
# Step 1: on_relay injects 4 tools
|
|
71
|
+
wrapped_s = on_relay(sender_agent, sender_relay)
|
|
72
|
+
wrapped_r = on_relay(receiver_agent, receiver_relay)
|
|
73
|
+
assert wrapped_s is sender_agent
|
|
74
|
+
assert wrapped_r is receiver_agent
|
|
75
|
+
assert len(sender_agent.tools) == 4
|
|
76
|
+
tool_names = {fn.__name__ for fn in sender_agent.tools}
|
|
77
|
+
assert tool_names == {"relay_send", "relay_inbox", "relay_post", "relay_agents"}
|
|
78
|
+
|
|
79
|
+
# Step 2: relay_agents tool lists the registered agent
|
|
80
|
+
agents_fn = next(f for f in sender_agent.tools if f.__name__ == "relay_agents")
|
|
81
|
+
result = await agents_fn()
|
|
82
|
+
assert isinstance(result, str)
|
|
83
|
+
assert sender_name in result
|
|
84
|
+
|
|
85
|
+
# Step 3: relay_post tool posts to a channel
|
|
86
|
+
post_fn = next(f for f in sender_agent.tools if f.__name__ == "relay_post")
|
|
87
|
+
post_result = await post_fn("general", f"agno-e2e-{uuid.uuid4().hex[:8]}")
|
|
88
|
+
assert post_result == "Message posted"
|
|
89
|
+
|
|
90
|
+
# Step 4: relay_send + relay_inbox across two agents
|
|
91
|
+
send_fn = next(f for f in sender_agent.tools if f.__name__ == "relay_send")
|
|
92
|
+
inbox_fn = next(f for f in receiver_agent.tools if f.__name__ == "relay_inbox")
|
|
93
|
+
|
|
94
|
+
# Ensure receiver is connected first
|
|
95
|
+
await receiver_relay.agents()
|
|
96
|
+
|
|
97
|
+
dm_text = f"agno-dm-{uuid.uuid4().hex[:8]}"
|
|
98
|
+
await send_fn(receiver_name, dm_text)
|
|
99
|
+
|
|
100
|
+
deadline = asyncio.get_event_loop().time() + 15.0
|
|
101
|
+
found = False
|
|
102
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
103
|
+
result = await inbox_fn()
|
|
104
|
+
if sender_name in result and dm_text in result:
|
|
105
|
+
found = True
|
|
106
|
+
break
|
|
107
|
+
await asyncio.sleep(0.5)
|
|
108
|
+
assert found, f"DM from {sender_name} not received within timeout"
|
|
109
|
+
|
|
110
|
+
# Step 5: instructions wrapper returns base instructions when no pending msgs
|
|
111
|
+
assert callable(receiver_agent.instructions)
|
|
112
|
+
instr = await receiver_agent.instructions()
|
|
113
|
+
assert "You are a helpful Agno agent." in instr
|
|
114
|
+
|
|
115
|
+
# Step 6: instructions wrapper prepends messages when present
|
|
116
|
+
instr_text = f"instr-msg-{uuid.uuid4().hex[:8]}"
|
|
117
|
+
await sender_relay.send(receiver_name, instr_text)
|
|
118
|
+
|
|
119
|
+
deadline = asyncio.get_event_loop().time() + 15.0
|
|
120
|
+
found_instr = False
|
|
121
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
122
|
+
msgs = await receiver_relay.peek()
|
|
123
|
+
if any(m.text == instr_text for m in msgs):
|
|
124
|
+
found_instr = True
|
|
125
|
+
break
|
|
126
|
+
await asyncio.sleep(0.5)
|
|
127
|
+
assert found_instr, "Instruction message did not arrive in receiver buffer"
|
|
128
|
+
|
|
129
|
+
instr_with_msgs = await receiver_agent.instructions()
|
|
130
|
+
assert sender_name in instr_with_msgs
|
|
131
|
+
assert instr_text in instr_with_msgs
|
|
132
|
+
assert "You are a helpful Agno agent." in instr_with_msgs
|
|
133
|
+
|
|
134
|
+
finally:
|
|
135
|
+
await asyncio.gather(sender_relay.close(), receiver_relay.close())
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.mark.asyncio
|
|
139
|
+
async def test_agno_cleanup(monkeypatch):
|
|
140
|
+
"""relay.close() disconnects the transport cleanly."""
|
|
141
|
+
agno_agent_mod = _install_agno_module(monkeypatch)
|
|
142
|
+
from agent_relay.communicate.adapters.agno import on_relay
|
|
143
|
+
|
|
144
|
+
agent_name = _unique_name("e2e-agno-cleanup")
|
|
145
|
+
config = _live_config()
|
|
146
|
+
relay = Relay(agent_name, config)
|
|
147
|
+
mock_agent = _make_mock_agent(agent_name, agno_agent_mod.Agent)
|
|
148
|
+
|
|
149
|
+
on_relay(mock_agent, relay)
|
|
150
|
+
agents = await relay.agents()
|
|
151
|
+
assert agent_name in agents
|
|
152
|
+
|
|
153
|
+
await relay.close()
|
|
154
|
+
assert not relay._connected
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"""E2E test: Claude Agent SDK Python adapter against live Relaycast."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
from types import ModuleType, SimpleNamespace
|
|
11
|
+
from unittest.mock import MagicMock
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from agent_relay.communicate.core import Relay
|
|
16
|
+
from agent_relay.communicate.types import Message, RelayConfig
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _live_config() -> RelayConfig:
|
|
20
|
+
return RelayConfig.resolve(
|
|
21
|
+
workspace=os.environ.get("RELAY_WORKSPACE"),
|
|
22
|
+
api_key=os.environ.get("RELAY_API_KEY"),
|
|
23
|
+
base_url=os.environ.get("RELAY_BASE_URL"),
|
|
24
|
+
channels=[],
|
|
25
|
+
auto_cleanup=False,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _unique_name(prefix: str = "e2e-claude-py") -> str:
|
|
30
|
+
return f"{prefix}-{int(time.time() * 1000)}-{uuid.uuid4().hex[:8]}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _install_claude_sdk_module(monkeypatch):
|
|
34
|
+
"""Inject a fake 'claude_agent_sdk' module so the adapter can import HookResult."""
|
|
35
|
+
types_mod = ModuleType("claude_agent_sdk.types")
|
|
36
|
+
types_mod.HookResult = type(
|
|
37
|
+
"HookResult",
|
|
38
|
+
(),
|
|
39
|
+
{
|
|
40
|
+
"__init__": lambda self, system_message=None, should_continue=False: (
|
|
41
|
+
setattr(self, "system_message", system_message)
|
|
42
|
+
or setattr(self, "should_continue", should_continue)
|
|
43
|
+
),
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
sdk_mod = ModuleType("claude_agent_sdk")
|
|
47
|
+
sdk_mod.types = types_mod
|
|
48
|
+
monkeypatch.setitem(sys.modules, "claude_agent_sdk", sdk_mod)
|
|
49
|
+
monkeypatch.setitem(sys.modules, "claude_agent_sdk.types", types_mod)
|
|
50
|
+
return types_mod
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _make_mock_options(name: str = "TestAgent") -> SimpleNamespace:
|
|
54
|
+
"""Create a mock Claude SDK options object."""
|
|
55
|
+
return SimpleNamespace(name=name, hooks=None, mcp_servers=[])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TestClaudeSdkAdapterE2E:
|
|
59
|
+
"""E2E tests for the Claude SDK adapter with a real Relay connection."""
|
|
60
|
+
|
|
61
|
+
@pytest.mark.asyncio
|
|
62
|
+
async def test_on_relay_injects_mcp_and_hooks(self, monkeypatch):
|
|
63
|
+
"""on_relay() injects the relaycast MCP config and wraps hooks."""
|
|
64
|
+
types_mod = _install_claude_sdk_module(monkeypatch)
|
|
65
|
+
|
|
66
|
+
from agent_relay.communicate.adapters.claude_sdk import on_relay
|
|
67
|
+
|
|
68
|
+
config = _live_config()
|
|
69
|
+
agent_name = _unique_name()
|
|
70
|
+
relay = Relay(agent_name, config)
|
|
71
|
+
options = _make_mock_options(agent_name)
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
result = on_relay(agent_name, options, relay)
|
|
75
|
+
|
|
76
|
+
assert result is options
|
|
77
|
+
mcp_names = [s["name"] for s in options.mcp_servers]
|
|
78
|
+
assert "relaycast" in mcp_names
|
|
79
|
+
assert callable(options.hooks.post_tool_use)
|
|
80
|
+
assert callable(options.hooks.stop)
|
|
81
|
+
finally:
|
|
82
|
+
await relay.close()
|
|
83
|
+
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_on_relay_preserves_existing_mcp_servers(self, monkeypatch):
|
|
86
|
+
"""on_relay() appends relaycast MCP without clobbering existing servers."""
|
|
87
|
+
_install_claude_sdk_module(monkeypatch)
|
|
88
|
+
|
|
89
|
+
from agent_relay.communicate.adapters.claude_sdk import on_relay
|
|
90
|
+
|
|
91
|
+
config = _live_config()
|
|
92
|
+
agent_name = _unique_name()
|
|
93
|
+
relay = Relay(agent_name, config)
|
|
94
|
+
existing_mcp = {"name": "custom-tool", "command": "custom", "args": []}
|
|
95
|
+
options = SimpleNamespace(
|
|
96
|
+
name=agent_name,
|
|
97
|
+
hooks=None,
|
|
98
|
+
mcp_servers=[existing_mcp],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
on_relay(agent_name, options, relay)
|
|
103
|
+
|
|
104
|
+
assert len(options.mcp_servers) == 2
|
|
105
|
+
names = [s["name"] for s in options.mcp_servers]
|
|
106
|
+
assert "custom-tool" in names
|
|
107
|
+
assert "relaycast" in names
|
|
108
|
+
finally:
|
|
109
|
+
await relay.close()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class TestRelayRegistrationE2E:
|
|
113
|
+
"""Verify Relay registration against live Relaycast via Claude adapter path."""
|
|
114
|
+
|
|
115
|
+
@pytest.mark.asyncio
|
|
116
|
+
async def test_relay_registers_agent(self):
|
|
117
|
+
"""Relay.agents() includes the registered agent after on_relay wrapping."""
|
|
118
|
+
config = _live_config()
|
|
119
|
+
agent_name = _unique_name()
|
|
120
|
+
relay = Relay(agent_name, config)
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
agents = await relay.agents()
|
|
124
|
+
assert isinstance(agents, list)
|
|
125
|
+
assert agent_name in agents
|
|
126
|
+
finally:
|
|
127
|
+
await relay.close()
|
|
128
|
+
|
|
129
|
+
@pytest.mark.asyncio
|
|
130
|
+
async def test_two_agents_register_simultaneously(self):
|
|
131
|
+
"""Two agents with unique names can coexist."""
|
|
132
|
+
config = _live_config()
|
|
133
|
+
name_a = _unique_name("e2e-claude-a")
|
|
134
|
+
name_b = _unique_name("e2e-claude-b")
|
|
135
|
+
relay_a = Relay(name_a, config)
|
|
136
|
+
relay_b = Relay(name_b, config)
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
agents_a = await relay_a.agents()
|
|
140
|
+
agents_b = await relay_b.agents()
|
|
141
|
+
assert name_a in agents_a
|
|
142
|
+
assert name_b in agents_b
|
|
143
|
+
finally:
|
|
144
|
+
await asyncio.gather(relay_a.close(), relay_b.close())
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class TestRelayToolFunctionsE2E:
|
|
148
|
+
"""Test relay operations (send, inbox, agents, post) against the real API."""
|
|
149
|
+
|
|
150
|
+
@pytest.mark.asyncio
|
|
151
|
+
async def test_send_and_inbox(self):
|
|
152
|
+
"""relay.send() delivers a DM that relay.inbox() can retrieve."""
|
|
153
|
+
config = _live_config()
|
|
154
|
+
sender_name = _unique_name("e2e-claude-sender")
|
|
155
|
+
receiver_name = _unique_name("e2e-claude-recv")
|
|
156
|
+
sender = Relay(sender_name, config)
|
|
157
|
+
receiver = Relay(receiver_name, config)
|
|
158
|
+
text = f"claude-e2e-{uuid.uuid4().hex[:8]}"
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
await sender.agents()
|
|
162
|
+
await receiver.agents()
|
|
163
|
+
|
|
164
|
+
await sender.send(receiver_name, text)
|
|
165
|
+
|
|
166
|
+
deadline = asyncio.get_event_loop().time() + 15.0
|
|
167
|
+
found = False
|
|
168
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
169
|
+
messages = await receiver.inbox()
|
|
170
|
+
for msg in messages:
|
|
171
|
+
if msg.sender == sender_name and msg.text == text:
|
|
172
|
+
found = True
|
|
173
|
+
break
|
|
174
|
+
if found:
|
|
175
|
+
break
|
|
176
|
+
await asyncio.sleep(0.5)
|
|
177
|
+
|
|
178
|
+
assert found, f"DM from {sender_name} not received within timeout"
|
|
179
|
+
finally:
|
|
180
|
+
await asyncio.gather(sender.close(), receiver.close())
|
|
181
|
+
|
|
182
|
+
@pytest.mark.asyncio
|
|
183
|
+
async def test_post_to_channel(self):
|
|
184
|
+
"""relay.post() succeeds when posting to a channel."""
|
|
185
|
+
config = _live_config()
|
|
186
|
+
agent_name = _unique_name("e2e-claude-post")
|
|
187
|
+
relay = Relay(agent_name, config)
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
await relay.agents()
|
|
191
|
+
await relay.join("general")
|
|
192
|
+
await relay.post("general", f"claude-e2e-test-{uuid.uuid4().hex[:8]}")
|
|
193
|
+
finally:
|
|
194
|
+
await relay.close()
|
|
195
|
+
|
|
196
|
+
@pytest.mark.asyncio
|
|
197
|
+
async def test_agents_list(self):
|
|
198
|
+
"""relay.agents() returns a list containing the registered agent."""
|
|
199
|
+
config = _live_config()
|
|
200
|
+
agent_name = _unique_name("e2e-claude-list")
|
|
201
|
+
relay = Relay(agent_name, config)
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
agents = await relay.agents()
|
|
205
|
+
assert isinstance(agents, list)
|
|
206
|
+
assert agent_name in agents
|
|
207
|
+
finally:
|
|
208
|
+
await relay.close()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class TestHooksE2E:
|
|
212
|
+
"""Test Claude SDK hook wrappers with a real Relay connection."""
|
|
213
|
+
|
|
214
|
+
@pytest.mark.asyncio
|
|
215
|
+
async def test_post_tool_use_hook_returns_none_when_no_messages(self, monkeypatch):
|
|
216
|
+
"""post_tool_use hook returns None when inbox is empty."""
|
|
217
|
+
_install_claude_sdk_module(monkeypatch)
|
|
218
|
+
|
|
219
|
+
from agent_relay.communicate.adapters.claude_sdk import on_relay
|
|
220
|
+
|
|
221
|
+
config = _live_config()
|
|
222
|
+
agent_name = _unique_name("e2e-claude-hook")
|
|
223
|
+
relay = Relay(agent_name, config)
|
|
224
|
+
options = _make_mock_options(agent_name)
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
on_relay(agent_name, options, relay)
|
|
228
|
+
await relay.agents()
|
|
229
|
+
|
|
230
|
+
# Drain any pre-existing messages
|
|
231
|
+
await relay.inbox()
|
|
232
|
+
|
|
233
|
+
result = await options.hooks.post_tool_use()
|
|
234
|
+
assert result is None
|
|
235
|
+
finally:
|
|
236
|
+
await relay.close()
|
|
237
|
+
|
|
238
|
+
@pytest.mark.asyncio
|
|
239
|
+
async def test_stop_hook_returns_none_when_no_messages(self, monkeypatch):
|
|
240
|
+
"""stop hook returns None when inbox is empty."""
|
|
241
|
+
_install_claude_sdk_module(monkeypatch)
|
|
242
|
+
|
|
243
|
+
from agent_relay.communicate.adapters.claude_sdk import on_relay
|
|
244
|
+
|
|
245
|
+
config = _live_config()
|
|
246
|
+
agent_name = _unique_name("e2e-claude-stop")
|
|
247
|
+
relay = Relay(agent_name, config)
|
|
248
|
+
options = _make_mock_options(agent_name)
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
on_relay(agent_name, options, relay)
|
|
252
|
+
await relay.agents()
|
|
253
|
+
await relay.inbox()
|
|
254
|
+
|
|
255
|
+
result = await options.hooks.stop()
|
|
256
|
+
assert result is None
|
|
257
|
+
finally:
|
|
258
|
+
await relay.close()
|
|
259
|
+
|
|
260
|
+
@pytest.mark.asyncio
|
|
261
|
+
async def test_stop_hook_returns_messages_with_should_continue(self, monkeypatch):
|
|
262
|
+
"""stop hook returns HookResult with should_continue=True when inbox has messages."""
|
|
263
|
+
types_mod = _install_claude_sdk_module(monkeypatch)
|
|
264
|
+
|
|
265
|
+
from agent_relay.communicate.adapters.claude_sdk import on_relay
|
|
266
|
+
|
|
267
|
+
config = _live_config()
|
|
268
|
+
sender_name = _unique_name("e2e-claude-hook-s")
|
|
269
|
+
receiver_name = _unique_name("e2e-claude-hook-r")
|
|
270
|
+
sender = Relay(sender_name, config)
|
|
271
|
+
receiver = Relay(receiver_name, config)
|
|
272
|
+
options = _make_mock_options(receiver_name)
|
|
273
|
+
text = f"hook-test-{uuid.uuid4().hex[:8]}"
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
on_relay(options, receiver, name=receiver_name)
|
|
277
|
+
await sender.agents()
|
|
278
|
+
await receiver.agents()
|
|
279
|
+
# Drain any stale messages
|
|
280
|
+
await receiver.inbox()
|
|
281
|
+
|
|
282
|
+
await sender.send(receiver_name, text)
|
|
283
|
+
|
|
284
|
+
# Wait for message to arrive
|
|
285
|
+
deadline = asyncio.get_event_loop().time() + 15.0
|
|
286
|
+
found = False
|
|
287
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
288
|
+
msgs = await receiver.peek()
|
|
289
|
+
for m in msgs:
|
|
290
|
+
if m.sender == sender_name and m.text == text:
|
|
291
|
+
found = True
|
|
292
|
+
break
|
|
293
|
+
if found:
|
|
294
|
+
break
|
|
295
|
+
await asyncio.sleep(0.5)
|
|
296
|
+
|
|
297
|
+
assert found, f"DM from {sender_name} not received within timeout"
|
|
298
|
+
|
|
299
|
+
result = await options.hooks.stop()
|
|
300
|
+
assert result is not None
|
|
301
|
+
assert result.should_continue is True
|
|
302
|
+
assert sender_name in result.system_message
|
|
303
|
+
assert text in result.system_message
|
|
304
|
+
finally:
|
|
305
|
+
await asyncio.gather(sender.close(), receiver.close())
|
|
306
|
+
|
|
307
|
+
@pytest.mark.asyncio
|
|
308
|
+
async def test_post_tool_use_hook_drains_inbox(self, monkeypatch):
|
|
309
|
+
"""post_tool_use hook returns HookResult with system_message containing DM text."""
|
|
310
|
+
types_mod = _install_claude_sdk_module(monkeypatch)
|
|
311
|
+
|
|
312
|
+
from agent_relay.communicate.adapters.claude_sdk import on_relay
|
|
313
|
+
|
|
314
|
+
config = _live_config()
|
|
315
|
+
sender_name = _unique_name("e2e-claude-ptu-s")
|
|
316
|
+
receiver_name = _unique_name("e2e-claude-ptu-r")
|
|
317
|
+
sender = Relay(sender_name, config)
|
|
318
|
+
receiver = Relay(receiver_name, config)
|
|
319
|
+
options = _make_mock_options(receiver_name)
|
|
320
|
+
text = f"ptu-test-{uuid.uuid4().hex[:8]}"
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
on_relay(options, receiver, name=receiver_name)
|
|
324
|
+
await sender.agents()
|
|
325
|
+
await receiver.agents()
|
|
326
|
+
await receiver.inbox()
|
|
327
|
+
|
|
328
|
+
await sender.send(receiver_name, text)
|
|
329
|
+
|
|
330
|
+
deadline = asyncio.get_event_loop().time() + 15.0
|
|
331
|
+
found = False
|
|
332
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
333
|
+
msgs = await receiver.peek()
|
|
334
|
+
for m in msgs:
|
|
335
|
+
if m.sender == sender_name and m.text == text:
|
|
336
|
+
found = True
|
|
337
|
+
break
|
|
338
|
+
if found:
|
|
339
|
+
break
|
|
340
|
+
await asyncio.sleep(0.5)
|
|
341
|
+
|
|
342
|
+
assert found, f"DM from {sender_name} not received within timeout"
|
|
343
|
+
|
|
344
|
+
result = await options.hooks.post_tool_use()
|
|
345
|
+
assert result is not None
|
|
346
|
+
assert sender_name in result.system_message
|
|
347
|
+
assert text in result.system_message
|
|
348
|
+
finally:
|
|
349
|
+
await asyncio.gather(sender.close(), receiver.close())
|
|
350
|
+
|
|
351
|
+
@pytest.mark.asyncio
|
|
352
|
+
async def test_hooks_chain_with_original_hooks(self, monkeypatch):
|
|
353
|
+
"""on_relay preserves and chains original hooks."""
|
|
354
|
+
_install_claude_sdk_module(monkeypatch)
|
|
355
|
+
|
|
356
|
+
from agent_relay.communicate.adapters.claude_sdk import on_relay
|
|
357
|
+
|
|
358
|
+
config = _live_config()
|
|
359
|
+
agent_name = _unique_name("e2e-claude-chain")
|
|
360
|
+
relay = Relay(agent_name, config)
|
|
361
|
+
|
|
362
|
+
orig_called = {"post": False, "stop": False}
|
|
363
|
+
|
|
364
|
+
async def orig_post_tool(*a, **kw):
|
|
365
|
+
orig_called["post"] = True
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
async def orig_stop(*a, **kw):
|
|
369
|
+
orig_called["stop"] = True
|
|
370
|
+
return None
|
|
371
|
+
|
|
372
|
+
options = SimpleNamespace(
|
|
373
|
+
name=agent_name,
|
|
374
|
+
hooks=SimpleNamespace(post_tool_use=orig_post_tool, stop=orig_stop),
|
|
375
|
+
mcp_servers=[],
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
on_relay(agent_name, options, relay)
|
|
380
|
+
await relay.agents()
|
|
381
|
+
await relay.inbox()
|
|
382
|
+
|
|
383
|
+
await options.hooks.post_tool_use()
|
|
384
|
+
await options.hooks.stop()
|
|
385
|
+
|
|
386
|
+
assert orig_called["post"], "Original post_tool_use was not called"
|
|
387
|
+
assert orig_called["stop"], "Original stop was not called"
|
|
388
|
+
finally:
|
|
389
|
+
await relay.close()
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class TestCleanupE2E:
|
|
393
|
+
"""Verify cleanup against live API."""
|
|
394
|
+
|
|
395
|
+
@pytest.mark.xfail(reason="Agent removal propagation depends on server-side TTL", strict=False)
|
|
396
|
+
@pytest.mark.asyncio
|
|
397
|
+
async def test_relay_close_disconnects(self):
|
|
398
|
+
"""After relay.close(), the agent eventually disappears from the agent list."""
|
|
399
|
+
config = _live_config()
|
|
400
|
+
agent_name = _unique_name("e2e-claude-cleanup")
|
|
401
|
+
relay = Relay(agent_name, config)
|
|
402
|
+
probe = Relay(_unique_name("e2e-claude-probe"), config)
|
|
403
|
+
|
|
404
|
+
try:
|
|
405
|
+
agents = await relay.agents()
|
|
406
|
+
assert agent_name in agents
|
|
407
|
+
|
|
408
|
+
await relay.close()
|
|
409
|
+
|
|
410
|
+
# Allow rate-limit budget to recover before polling
|
|
411
|
+
await asyncio.sleep(5)
|
|
412
|
+
|
|
413
|
+
deadline = asyncio.get_event_loop().time() + 25.0
|
|
414
|
+
absent = False
|
|
415
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
416
|
+
try:
|
|
417
|
+
current = await probe.agents()
|
|
418
|
+
except Exception:
|
|
419
|
+
await asyncio.sleep(2.0)
|
|
420
|
+
continue
|
|
421
|
+
if agent_name not in current:
|
|
422
|
+
absent = True
|
|
423
|
+
break
|
|
424
|
+
await asyncio.sleep(2.0)
|
|
425
|
+
|
|
426
|
+
assert absent, f"Agent {agent_name} still present after close"
|
|
427
|
+
finally:
|
|
428
|
+
await probe.close()
|