fix(python-sdk): track recursive subagent notifications
This commit is contained in:
@@ -198,6 +198,65 @@ for line in sys.stdin:
|
||||
]
|
||||
|
||||
|
||||
def test_session_run_collects_nested_subagent_tree_without_polluting_root_events(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
script = tmp_path / "fake_runtime.py"
|
||||
script.write_text(
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
|
||||
for line in sys.stdin:
|
||||
msg = json.loads(line)
|
||||
method = msg.get("method")
|
||||
if method == "initialize":
|
||||
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
|
||||
elif method == "session/prompt":
|
||||
root = (msg.get("params") or {})["sessionId"]
|
||||
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.started", "params": {"parentSessionId": root, "childSessionId": "child"}}), flush=True)
|
||||
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": "child", "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "child response"}]}}}}), flush=True)
|
||||
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.started", "params": {"parentSessionId": "child", "childSessionId": "grandchild"}}), flush=True)
|
||||
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": "grandchild", "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "grandchild response"}]}}}}), flush=True)
|
||||
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.finished", "params": {"parentSessionId": "child", "childSessionId": "grandchild", "status": "ok"}}), flush=True)
|
||||
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.finished", "params": {"parentSessionId": root, "childSessionId": "child", "status": "ok"}}), flush=True)
|
||||
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": root, "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "root response"}]}}}}), flush=True)
|
||||
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": root, "status": "ok"}}), flush=True)
|
||||
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
|
||||
elif method == "shutdown":
|
||||
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
|
||||
break
|
||||
""".strip()
|
||||
)
|
||||
|
||||
seen: list[str] = []
|
||||
with DeepSeekHarness(
|
||||
launch_args_override=(sys.executable, str(script)),
|
||||
cwd=str(tmp_path),
|
||||
) as harness:
|
||||
result = harness.run(
|
||||
"delegate recursively",
|
||||
session_id="main",
|
||||
on_notification=lambda notification: seen.append(notification.method),
|
||||
)
|
||||
assert harness.client._notifications.qsize() == 0
|
||||
|
||||
assert result.status == "ok"
|
||||
assert result.final_response == "root response"
|
||||
assert [event["data"]["content"][0]["text"] for event in result.events] == ["root response"]
|
||||
assert [notification.method for notification in result.notifications] == [
|
||||
"subagent.started",
|
||||
"session.event",
|
||||
"subagent.started",
|
||||
"session.event",
|
||||
"subagent.finished",
|
||||
"subagent.finished",
|
||||
"session.event",
|
||||
"session.finished",
|
||||
]
|
||||
assert seen == [notification.method for notification in result.notifications]
|
||||
|
||||
|
||||
def test_session_run_ignores_notifications_for_other_sessions(tmp_path: Path) -> None:
|
||||
script = tmp_path / "fake_runtime.py"
|
||||
script.write_text(
|
||||
@@ -356,6 +415,33 @@ def test_client_keeps_unmatched_notifications_available_globally_while_subscribe
|
||||
assert notification.payload["sessionId"] == "other"
|
||||
|
||||
|
||||
def test_session_subscription_keeps_descendant_relationships_across_subscriptions() -> None:
|
||||
client = HarnessClient()
|
||||
with client.subscribe_session_notifications("main") as first:
|
||||
client._handle_message({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "subagent.started",
|
||||
"params": {"parentSessionId": "main", "childSessionId": "child"},
|
||||
})
|
||||
assert first.next().payload["childSessionId"] == "child"
|
||||
|
||||
with client.subscribe_session_notifications("main") as second:
|
||||
client._handle_message({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "subagent.started",
|
||||
"params": {"parentSessionId": "child", "childSessionId": "grandchild"},
|
||||
})
|
||||
client._handle_message({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "session.event",
|
||||
"params": {"sessionId": "grandchild", "event": {"type": "assistant/message"}},
|
||||
})
|
||||
assert second.next().payload["childSessionId"] == "grandchild"
|
||||
assert second.next().payload["sessionId"] == "grandchild"
|
||||
|
||||
assert client._notifications.qsize() == 0
|
||||
|
||||
|
||||
def test_client_contains_notification_filter_failure_to_its_subscription(tmp_path: Path) -> None:
|
||||
script = tmp_path / "fake_bridge.py"
|
||||
script.write_text(
|
||||
|
||||
Reference in New Issue
Block a user