cloudagent-daemon 0.5.1__tar.gz → 0.5.2__tar.gz
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.
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/PKG-INFO +1 -1
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/pyproject.toml +1 -1
- cloudagent_daemon-0.5.2/src/cloudagent_daemon/__init__.py +1 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/claude_runner.py +51 -11
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/main_loop.py +24 -3
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/mcp_bridge.py +78 -15
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/outbound_queue.py +3 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/protocol.py +10 -1
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_mcp_bridge_round_trip.py +25 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_spawn_mcp_bridge_e2e.py +26 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_claude_runner.py +22 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_mcp_bridge_agent_retrieval.py +14 -2
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_mcp_bridge_send_message.py +5 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_outbound_queue.py +11 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/uv.lock +1 -1
- cloudagent_daemon-0.5.1/src/cloudagent_daemon/__init__.py +0 -1
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/.gitignore +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/CHANGELOG.md +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/LICENSE +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/README.md +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/scripts/release.sh +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/__main__.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/agent_download.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/agent_upload.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/agent_upload_denylist.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/auth_preflight.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/cli.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/config.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/context_fetch.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/delegation_report.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/doctor.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/frame_dispatch.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/http_outbound.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/injection_state.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/jsonl_logging.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/login.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/metrics.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/metrics_server.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/paths.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/qoder_credentials.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/readonly_bash_guard.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/runtime_config.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/runtime_detect.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/service.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/spawn_outbox.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/spawn_queue.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/sse_client.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/transport.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/unix_socket.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/workspace.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/ws_client.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/__init__.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/_fake_sse_server.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/conftest.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_e2e_deliver_to_spawn.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_e2e_sse.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_full_e2e.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_one_shot_cli.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_sse_handshake.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_sse_watchdog.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_ws_handshake.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_ws_reconnect.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/conftest.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_agent_download.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_agent_upload.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_auth_preflight.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_claude_runner_codex_contract.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_claude_runner_routing_contract.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_claude_runner_w3.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_cli_multi_profile.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_cli_smoke.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_config.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_context_fetch.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_delegation_report.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_doctor.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_frame_dispatch.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_http_outbound.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_injection_state.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_jsonl_logging.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_login.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_main_loop_assign.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_main_loop_preflight.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_metrics.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_metrics_server.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_multi_tool_wake.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_outbox_stuck_watchdog.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_packaging_mcp_pin.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_paths.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_protocol.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_qoder_credentials.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_readonly_bash_guard.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_runtime_detect.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_service.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_service_multi_profile.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_spawn_outbox.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_spawn_queue.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_sse_active_watchdog.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_start_guard.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_status_stop.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_workspace.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_workspace_root_task113.py +0 -0
- {cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_ws_client_insecure.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cloudagent-daemon
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Local daemon for cloudagent — bridges Claude Code, Codex, and Qoder CLI agents to the cloudagent backend.
|
|
5
5
|
Project-URL: Homepage, https://cloudagent.chat
|
|
6
6
|
Project-URL: Repository, https://codeup.aliyun.com/69bd0a4d29ad98af4065ce71/cloudagent
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cloudagent-daemon"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.2"
|
|
8
8
|
description = "Local daemon for cloudagent — bridges Claude Code, Codex, and Qoder CLI agents to the cloudagent backend."
|
|
9
9
|
authors = [{name = "chaoyin", email = "tudoucyx@gmail.com"}]
|
|
10
10
|
readme = "README.md"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.2"
|
|
@@ -391,6 +391,7 @@ class ClaudeRunner:
|
|
|
391
391
|
partial_publisher: PartialPublisher | None = None,
|
|
392
392
|
event_publisher: EventPublisher | None = None,
|
|
393
393
|
agent_to_session: "dict[str, str | None] | None" = None,
|
|
394
|
+
agent_to_delivery: "dict[str, str | None] | None" = None,
|
|
394
395
|
claude_permission_mode: str = "bypassPermissions",
|
|
395
396
|
runtime_binaries: dict[str, str] | None = None,
|
|
396
397
|
http: object | None = None,
|
|
@@ -440,6 +441,10 @@ class ClaudeRunner:
|
|
|
440
441
|
# unix socket ``get_current_session`` op instead of reading a file.
|
|
441
442
|
# None means feature disabled (legacy / test path).
|
|
442
443
|
self._agent_to_session: dict[str, str | None] | None = agent_to_session
|
|
444
|
+
# #112 — opaque backend delivery provenance for the currently running
|
|
445
|
+
# turn. The MCP bridge queries this map through daemon IPC and adds it
|
|
446
|
+
# to outbound calls without exposing a forgeable tool parameter.
|
|
447
|
+
self._agent_to_delivery: dict[str, str | None] | None = agent_to_delivery
|
|
443
448
|
|
|
444
449
|
# ------------------------------------------------------------------
|
|
445
450
|
# Public API
|
|
@@ -618,6 +623,14 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
618
623
|
# reading the file. Both paths coexist for backward compat.
|
|
619
624
|
if self._agent_to_session is not None:
|
|
620
625
|
self._agent_to_session[agent_id] = _effective_session_id
|
|
626
|
+
current_delivery_id = payload.get("delivery_id")
|
|
627
|
+
_effective_delivery_id = (
|
|
628
|
+
current_delivery_id
|
|
629
|
+
if isinstance(current_delivery_id, str) and current_delivery_id
|
|
630
|
+
else None
|
|
631
|
+
)
|
|
632
|
+
if self._agent_to_delivery is not None:
|
|
633
|
+
self._agent_to_delivery[agent_id] = _effective_delivery_id
|
|
621
634
|
|
|
622
635
|
# M7 W3 — localagent company-context injection. BEFORE spawning
|
|
623
636
|
# claude, if this deliver belongs to a task whose context has not
|
|
@@ -709,6 +722,8 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
709
722
|
# (c) IPC architecture upgrade — clear from memory map.
|
|
710
723
|
if self._agent_to_session is not None:
|
|
711
724
|
self._agent_to_session.pop(agent_id, None)
|
|
725
|
+
if self._agent_to_delivery is not None:
|
|
726
|
+
self._agent_to_delivery.pop(agent_id, None)
|
|
712
727
|
|
|
713
728
|
captured_session = attempt.captured_session_id
|
|
714
729
|
rc = attempt.exit_code
|
|
@@ -830,6 +845,11 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
830
845
|
"content": attempt.final_result_text,
|
|
831
846
|
"conversation_id": platform_conv_id,
|
|
832
847
|
"acknowledge_new_messages": True,
|
|
848
|
+
**(
|
|
849
|
+
{"delivery_id": _effective_delivery_id}
|
|
850
|
+
if _effective_delivery_id
|
|
851
|
+
else {}
|
|
852
|
+
),
|
|
833
853
|
},
|
|
834
854
|
timeout=15.0,
|
|
835
855
|
)
|
|
@@ -876,6 +896,8 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
876
896
|
payload, attempt.final_result_text
|
|
877
897
|
)
|
|
878
898
|
)
|
|
899
|
+
if _effective_delivery_id:
|
|
900
|
+
fallback_args["delivery_id"] = _effective_delivery_id
|
|
879
901
|
await self.outbound.send_tool_call(
|
|
880
902
|
agent_id=agent_id,
|
|
881
903
|
session_id=fallback_session,
|
|
@@ -1006,7 +1028,14 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
1006
1028
|
agent_id=agent_id,
|
|
1007
1029
|
session_id=fallback_session,
|
|
1008
1030
|
tool_name="send_message",
|
|
1009
|
-
args=
|
|
1031
|
+
args={
|
|
1032
|
+
**self._fallback_reply_args(payload, abort_body),
|
|
1033
|
+
**(
|
|
1034
|
+
{"delivery_id": _effective_delivery_id}
|
|
1035
|
+
if _effective_delivery_id
|
|
1036
|
+
else {}
|
|
1037
|
+
),
|
|
1038
|
+
},
|
|
1010
1039
|
timeout=15.0,
|
|
1011
1040
|
)
|
|
1012
1041
|
try:
|
|
@@ -1026,15 +1055,20 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
1026
1055
|
reason,
|
|
1027
1056
|
)
|
|
1028
1057
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
exit_code=rc,
|
|
1033
|
-
conversation_id=(
|
|
1058
|
+
final_status_kwargs: dict[str, Any] = {
|
|
1059
|
+
"exit_code": rc,
|
|
1060
|
+
"conversation_id": (
|
|
1034
1061
|
str(payload["conversation_id"])
|
|
1035
1062
|
if payload.get("conversation_id")
|
|
1036
1063
|
else None
|
|
1037
1064
|
),
|
|
1065
|
+
}
|
|
1066
|
+
if _effective_delivery_id:
|
|
1067
|
+
final_status_kwargs["delivery_id"] = _effective_delivery_id
|
|
1068
|
+
await self.outbound.send_agent_status(
|
|
1069
|
+
agent_id,
|
|
1070
|
+
final_status,
|
|
1071
|
+
**final_status_kwargs,
|
|
1038
1072
|
)
|
|
1039
1073
|
|
|
1040
1074
|
wake_secs = time.monotonic() - wake_start
|
|
@@ -1403,14 +1437,20 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
1403
1437
|
await self.outbound.send_ack(
|
|
1404
1438
|
seq=None, message_id=message_id, status="started"
|
|
1405
1439
|
)
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
"running",
|
|
1409
|
-
conversation_id=(
|
|
1440
|
+
running_status_kwargs: dict[str, Any] = {
|
|
1441
|
+
"conversation_id": (
|
|
1410
1442
|
str(payload["conversation_id"])
|
|
1411
1443
|
if payload.get("conversation_id")
|
|
1412
1444
|
else None
|
|
1413
|
-
)
|
|
1445
|
+
)
|
|
1446
|
+
}
|
|
1447
|
+
delivery_id = payload.get("delivery_id")
|
|
1448
|
+
if isinstance(delivery_id, str) and delivery_id:
|
|
1449
|
+
running_status_kwargs["delivery_id"] = delivery_id
|
|
1450
|
+
await self.outbound.send_agent_status(
|
|
1451
|
+
agent_id,
|
|
1452
|
+
"running",
|
|
1453
|
+
**running_status_kwargs,
|
|
1414
1454
|
)
|
|
1415
1455
|
|
|
1416
1456
|
# 1k74 — token-level streaming. Forward the deliver's session_id
|
|
@@ -637,6 +637,7 @@ async def _run_sse_post(
|
|
|
637
637
|
# IPC op instead of reading a file; file fallback kept for backward
|
|
638
638
|
# compat (old daemons without this map still work via the file).
|
|
639
639
|
_agent_to_session: dict[str, str | None] = {}
|
|
640
|
+
_agent_to_delivery: dict[str, str | None] = {}
|
|
640
641
|
|
|
641
642
|
# M7 W3 — localagent context injection + delegation reporting.
|
|
642
643
|
# ``injection_state`` is the file-backed (agent,task) dedup store
|
|
@@ -657,6 +658,7 @@ async def _run_sse_post(
|
|
|
657
658
|
partial_publisher=_publish_partial,
|
|
658
659
|
event_publisher=_publish_event,
|
|
659
660
|
agent_to_session=_agent_to_session,
|
|
661
|
+
agent_to_delivery=_agent_to_delivery,
|
|
660
662
|
claude_permission_mode=cfg.claude_permission_mode,
|
|
661
663
|
runtime_binaries=runtime_binaries,
|
|
662
664
|
http=http,
|
|
@@ -752,11 +754,17 @@ async def _run_sse_post(
|
|
|
752
754
|
a = req.get("args") or {}
|
|
753
755
|
agent_id_q = a.get("agent_id") or ""
|
|
754
756
|
sid = _agent_to_session.get(agent_id_q)
|
|
757
|
+
delivery_id = _agent_to_delivery.get(agent_id_q)
|
|
755
758
|
logger.info(
|
|
756
|
-
"main_loop.ipc_handler: get_current_session agent=%s sid=%s",
|
|
757
|
-
agent_id_q, sid,
|
|
759
|
+
"main_loop.ipc_handler: get_current_session agent=%s sid=%s delivery=%s",
|
|
760
|
+
agent_id_q, sid, delivery_id,
|
|
758
761
|
)
|
|
759
|
-
return {
|
|
762
|
+
return {
|
|
763
|
+
"result": {
|
|
764
|
+
"session_id": sid,
|
|
765
|
+
"delivery_id": delivery_id,
|
|
766
|
+
}
|
|
767
|
+
}
|
|
760
768
|
|
|
761
769
|
if op != "tool_call":
|
|
762
770
|
logger.warning(
|
|
@@ -981,6 +989,8 @@ async def _run_legacy_ws(
|
|
|
981
989
|
delegation_outbox = DelegationOutbox(
|
|
982
990
|
profile_delegation_outbox_path(profile)
|
|
983
991
|
)
|
|
992
|
+
agent_to_session_ws: dict[str, str | None] = {}
|
|
993
|
+
agent_to_delivery_ws: dict[str, str | None] = {}
|
|
984
994
|
|
|
985
995
|
runner = ClaudeRunner(
|
|
986
996
|
outbound=outbound,
|
|
@@ -991,6 +1001,8 @@ async def _run_legacy_ws(
|
|
|
991
1001
|
http=ws_http,
|
|
992
1002
|
injection_state=injection_state,
|
|
993
1003
|
delegation_outbox=delegation_outbox,
|
|
1004
|
+
agent_to_session=agent_to_session_ws,
|
|
1005
|
+
agent_to_delivery=agent_to_delivery_ws,
|
|
994
1006
|
)
|
|
995
1007
|
|
|
996
1008
|
# #25 — pre-flight auth self-check (parity with the SSE path). Detached
|
|
@@ -1071,6 +1083,15 @@ async def _run_legacy_ws(
|
|
|
1071
1083
|
sock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1072
1084
|
|
|
1073
1085
|
async def ipc_handler(req: dict) -> dict:
|
|
1086
|
+
if req.get("op") == "get_current_session":
|
|
1087
|
+
a = req.get("args") or {}
|
|
1088
|
+
agent_id_q = a.get("agent_id") or ""
|
|
1089
|
+
return {
|
|
1090
|
+
"result": {
|
|
1091
|
+
"session_id": agent_to_session_ws.get(agent_id_q),
|
|
1092
|
+
"delivery_id": agent_to_delivery_ws.get(agent_id_q),
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1074
1095
|
if req.get("op") != "tool_call":
|
|
1075
1096
|
return {"error": {"code": "UNKNOWN_OP", "message": str(req.get("op"))}}
|
|
1076
1097
|
a = req.get("args") or {}
|
|
@@ -121,6 +121,35 @@ def _read_identity(agent_id: str) -> dict:
|
|
|
121
121
|
return {}
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
async def _query_daemon_context(
|
|
125
|
+
agent_id: str, socket_path: Path
|
|
126
|
+
) -> tuple[str | None, str | None]:
|
|
127
|
+
"""Return the daemon-owned ``(session_id, delivery_id)`` for this turn.
|
|
128
|
+
|
|
129
|
+
Old daemons return no delivery id (or reject the IPC op), which cleanly
|
|
130
|
+
degrades to ``(None, None)``. The delivery id is intentionally absent from
|
|
131
|
+
every MCP tool schema: the bridge attaches it after the model has chosen
|
|
132
|
+
its ordinary tool arguments.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
async with UnixSocketClient(socket_path) as ipc:
|
|
136
|
+
result = await ipc.call(
|
|
137
|
+
"get_current_session",
|
|
138
|
+
{"agent_id": agent_id},
|
|
139
|
+
timeout=5.0,
|
|
140
|
+
)
|
|
141
|
+
sid = result.get("session_id")
|
|
142
|
+
delivery_id = result.get("delivery_id")
|
|
143
|
+
return (
|
|
144
|
+
sid if isinstance(sid, str) and sid else None,
|
|
145
|
+
delivery_id
|
|
146
|
+
if isinstance(delivery_id, str) and delivery_id
|
|
147
|
+
else None,
|
|
148
|
+
)
|
|
149
|
+
except Exception: # noqa: BLE001
|
|
150
|
+
return None, None
|
|
151
|
+
|
|
152
|
+
|
|
124
153
|
async def _query_daemon_session(
|
|
125
154
|
agent_id: str, socket_path: Path
|
|
126
155
|
) -> str | None:
|
|
@@ -134,19 +163,8 @@ async def _query_daemon_session(
|
|
|
134
163
|
Returns ``None`` on any failure (socket missing, timeout, old daemon
|
|
135
164
|
without the op) so the caller can fall back to the file path.
|
|
136
165
|
"""
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
result = await ipc.call(
|
|
140
|
-
"get_current_session",
|
|
141
|
-
{"agent_id": agent_id},
|
|
142
|
-
timeout=5.0,
|
|
143
|
-
)
|
|
144
|
-
sid = result.get("session_id")
|
|
145
|
-
return sid if isinstance(sid, str) and sid else None
|
|
146
|
-
except Exception: # noqa: BLE001
|
|
147
|
-
# Socket missing, old daemon (UNKNOWN_OP), or timeout — fall
|
|
148
|
-
# through to file-based path. Not an error: backward compat.
|
|
149
|
-
return None
|
|
166
|
+
sid, _delivery_id = await _query_daemon_context(agent_id, socket_path)
|
|
167
|
+
return sid
|
|
150
168
|
|
|
151
169
|
|
|
152
170
|
def _read_current_session(
|
|
@@ -190,6 +208,7 @@ async def _ipc_tool_call(
|
|
|
190
208
|
tool_name: str,
|
|
191
209
|
tool_args: dict,
|
|
192
210
|
session_id: str | None,
|
|
211
|
+
delivery_id: str | None = None,
|
|
193
212
|
) -> dict:
|
|
194
213
|
"""Single round-trip: open socket → send tool_call → return result."""
|
|
195
214
|
# 1k #55 — log the tool dispatch BEFORE we touch the socket so we can
|
|
@@ -200,6 +219,9 @@ async def _ipc_tool_call(
|
|
|
200
219
|
tool_name, agent_id, session_id,
|
|
201
220
|
)
|
|
202
221
|
try:
|
|
222
|
+
effective_tool_args = dict(tool_args)
|
|
223
|
+
if delivery_id:
|
|
224
|
+
effective_tool_args["delivery_id"] = delivery_id
|
|
203
225
|
async with UnixSocketClient(socket_path) as ipc:
|
|
204
226
|
logger.info(
|
|
205
227
|
"mcp_bridge: ipc connected socket=%s tool=%s agent=%s session=%s",
|
|
@@ -211,7 +233,7 @@ async def _ipc_tool_call(
|
|
|
211
233
|
"agent_id": agent_id,
|
|
212
234
|
"session_id": session_id,
|
|
213
235
|
"tool_name": tool_name,
|
|
214
|
-
"tool_args":
|
|
236
|
+
"tool_args": effective_tool_args,
|
|
215
237
|
},
|
|
216
238
|
)
|
|
217
239
|
logger.info(
|
|
@@ -227,11 +249,30 @@ async def _ipc_tool_call(
|
|
|
227
249
|
raise
|
|
228
250
|
|
|
229
251
|
|
|
252
|
+
# Resolve the module-level helper at CALL time, not import time.
|
|
253
|
+
#
|
|
254
|
+
# ``build_server``'s provenance-stamping closure shadows the name
|
|
255
|
+
# ``_ipc_tool_call`` locally, so it needs another way to reach the module
|
|
256
|
+
# helper. A plain alias (``_raw_ipc_tool_call = _ipc_tool_call``) binds the
|
|
257
|
+
# original function object once, at import — which silently defeats every
|
|
258
|
+
# test that does ``monkeypatch.setattr(bridge, "_ipc_tool_call", fake)``:
|
|
259
|
+
# the patch rebinds the module attribute, the frozen alias keeps pointing at
|
|
260
|
+
# the real implementation, and the "unit" test goes off and opens a real unix
|
|
261
|
+
# socket. That is exactly how 10 tests started failing with FileNotFoundError.
|
|
262
|
+
#
|
|
263
|
+
# Looking the attribute up through the module at call time keeps the closure
|
|
264
|
+
# working AND lets the patch take effect. Production behaviour is unchanged:
|
|
265
|
+
# unpatched, this returns the same function the alias used to hold.
|
|
266
|
+
def _module_ipc_tool_call(): # type: ignore[no-untyped-def]
|
|
267
|
+
return sys.modules[__name__]._ipc_tool_call # type: ignore[attr-defined,no-any-return]
|
|
268
|
+
|
|
269
|
+
|
|
230
270
|
def build_server(
|
|
231
271
|
agent_id: str,
|
|
232
272
|
socket_path: Path = DEFAULT_SOCKET_PATH,
|
|
233
273
|
identity: dict | None = None,
|
|
234
274
|
session_id: str | None = None,
|
|
275
|
+
delivery_id: str | None = None,
|
|
235
276
|
tool_profile: str = "full",
|
|
236
277
|
) -> FastMCP:
|
|
237
278
|
"""Construct a FastMCP server filtered by identity and tool profile.
|
|
@@ -269,6 +310,25 @@ def build_server(
|
|
|
269
310
|
|
|
270
311
|
server = FastMCP(name=f"cloudagent-bridge-{agent_id}")
|
|
271
312
|
|
|
313
|
+
# Keep the opaque provenance out of every public MCP schema. The nested
|
|
314
|
+
# wrapper shadows the module helper for all tool closures below and stamps
|
|
315
|
+
# the daemon-provided value only after the model's arguments are built.
|
|
316
|
+
async def _ipc_tool_call( # type: ignore[no-redef]
|
|
317
|
+
socket_path: Path,
|
|
318
|
+
agent_id: str,
|
|
319
|
+
tool_name: str,
|
|
320
|
+
tool_args: dict,
|
|
321
|
+
session_id: str | None,
|
|
322
|
+
) -> dict:
|
|
323
|
+
return await _module_ipc_tool_call()(
|
|
324
|
+
socket_path,
|
|
325
|
+
agent_id,
|
|
326
|
+
tool_name,
|
|
327
|
+
tool_args,
|
|
328
|
+
session_id,
|
|
329
|
+
delivery_id,
|
|
330
|
+
)
|
|
331
|
+
|
|
272
332
|
# ---- Common tools (all agents) ----------------------------------------
|
|
273
333
|
|
|
274
334
|
@server.tool(
|
|
@@ -924,7 +984,9 @@ async def run_bridge(
|
|
|
924
984
|
# freshly-written current-session.txt). Fall back to file on any
|
|
925
985
|
# failure so old daemons without the ``get_current_session`` op
|
|
926
986
|
# keep working.
|
|
927
|
-
current_session_id = await
|
|
987
|
+
current_session_id, current_delivery_id = await _query_daemon_context(
|
|
988
|
+
agent_id, socket_path
|
|
989
|
+
)
|
|
928
990
|
if current_session_id is not None:
|
|
929
991
|
logger.info(
|
|
930
992
|
"mcp_bridge: agent=%s current_session=%s (via socket IPC)",
|
|
@@ -951,6 +1013,7 @@ async def run_bridge(
|
|
|
951
1013
|
socket_path=socket_path,
|
|
952
1014
|
identity=identity,
|
|
953
1015
|
session_id=current_session_id,
|
|
1016
|
+
delivery_id=current_delivery_id,
|
|
954
1017
|
tool_profile=tool_profile,
|
|
955
1018
|
)
|
|
956
1019
|
logger.info(
|
|
@@ -156,6 +156,7 @@ class OutboundQueue:
|
|
|
156
156
|
status: str,
|
|
157
157
|
exit_code: int | None = None,
|
|
158
158
|
conversation_id: str | None = None,
|
|
159
|
+
delivery_id: str | None = None,
|
|
159
160
|
) -> None:
|
|
160
161
|
"""Send an ``agent_status`` frame."""
|
|
161
162
|
payload: dict = {"agent_id": agent_id, "status": status}
|
|
@@ -163,6 +164,8 @@ class OutboundQueue:
|
|
|
163
164
|
payload["exit_code"] = exit_code
|
|
164
165
|
if conversation_id:
|
|
165
166
|
payload["conversation_id"] = conversation_id
|
|
167
|
+
if delivery_id:
|
|
168
|
+
payload["delivery_id"] = delivery_id
|
|
166
169
|
|
|
167
170
|
await self.send_raw({
|
|
168
171
|
"v": 1,
|
|
@@ -63,6 +63,10 @@ class DeliverPayload(BaseModel):
|
|
|
63
63
|
model: str | None = None
|
|
64
64
|
reasoning_effort: str | None = None
|
|
65
65
|
runtime_env: dict[str, str] | None = None
|
|
66
|
+
run_mode: str | None = None
|
|
67
|
+
origin: dict[str, Any] | None = None
|
|
68
|
+
conversation_id: str | None = None
|
|
69
|
+
delivery_id: str | None = None
|
|
66
70
|
|
|
67
71
|
|
|
68
72
|
class DeliveryAckPayload(BaseModel):
|
|
@@ -95,9 +99,14 @@ class AgentSessionStartPayload(BaseModel):
|
|
|
95
99
|
|
|
96
100
|
class AgentStatusPayload(BaseModel):
|
|
97
101
|
agent_id: str
|
|
98
|
-
status: Literal[
|
|
102
|
+
status: Literal[
|
|
103
|
+
"running", "idle", "crashed", "timeout", "quarantined",
|
|
104
|
+
"backpressure_dropped",
|
|
105
|
+
]
|
|
99
106
|
exit_code: int | None = None
|
|
100
107
|
error: str | None = None
|
|
108
|
+
conversation_id: str | None = None
|
|
109
|
+
delivery_id: str | None = None
|
|
101
110
|
|
|
102
111
|
|
|
103
112
|
class PingPayload(BaseModel):
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_mcp_bridge_round_trip.py
RENAMED
|
@@ -405,6 +405,31 @@ async def test_platform_send_tunnels_via_ipc_with_session_id(
|
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
|
|
408
|
+
async def test_bridge_stamps_delivery_id_outside_tool_schema(
|
|
409
|
+
ipc_server: tuple[Path, list[dict]],
|
|
410
|
+
) -> None:
|
|
411
|
+
"""#112: provenance is daemon-owned context, not a model-selectable
|
|
412
|
+
``platform_send`` argument, but it must cross IPC on the actual call."""
|
|
413
|
+
sock, received = ipc_server
|
|
414
|
+
server = build_server(
|
|
415
|
+
agent_id="a_platform_provenance",
|
|
416
|
+
socket_path=sock,
|
|
417
|
+
identity={"is_chief": False},
|
|
418
|
+
session_id="sess_plat_provenance",
|
|
419
|
+
delivery_id="del_plat_provenance",
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
tool = next(t for t in await server.list_tools() if t.name == "platform_send")
|
|
423
|
+
assert "delivery_id" not in tool.inputSchema.get("properties", {})
|
|
424
|
+
await server.call_tool(
|
|
425
|
+
"platform_send",
|
|
426
|
+
{"conversation_id": "conv_x1", "content": "带可信上下文"},
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
req = received[0]
|
|
430
|
+
assert req["args"]["tool_args"]["delivery_id"] == "del_plat_provenance"
|
|
431
|
+
|
|
432
|
+
|
|
408
433
|
# ---------------------------------------------------------------------------
|
|
409
434
|
# 1k #55 — multi-tool-wake regression
|
|
410
435
|
# ---------------------------------------------------------------------------
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_spawn_mcp_bridge_e2e.py
RENAMED
|
@@ -27,6 +27,7 @@ import pytest
|
|
|
27
27
|
import pytest_asyncio
|
|
28
28
|
|
|
29
29
|
from cloudagent_daemon.mcp_bridge import (
|
|
30
|
+
_query_daemon_context,
|
|
30
31
|
_query_daemon_session,
|
|
31
32
|
_read_current_session,
|
|
32
33
|
build_server,
|
|
@@ -181,6 +182,31 @@ async def test_query_daemon_session_returns_registered_session(
|
|
|
181
182
|
assert sid == "sess_from_memory_abc"
|
|
182
183
|
|
|
183
184
|
|
|
185
|
+
async def test_query_daemon_context_returns_delivery_provenance(
|
|
186
|
+
short_sock_dir: Path,
|
|
187
|
+
) -> None:
|
|
188
|
+
sock = short_sock_dir / "ctx.sock"
|
|
189
|
+
|
|
190
|
+
async def handler(req: dict) -> dict:
|
|
191
|
+
assert req.get("op") == "get_current_session"
|
|
192
|
+
return {
|
|
193
|
+
"result": {
|
|
194
|
+
"session_id": "sess_ctx",
|
|
195
|
+
"delivery_id": "del_ctx",
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
server = UnixSocketServer(sock, handler)
|
|
200
|
+
await server.start()
|
|
201
|
+
try:
|
|
202
|
+
assert await _query_daemon_context("agent_ctx", sock) == (
|
|
203
|
+
"sess_ctx",
|
|
204
|
+
"del_ctx",
|
|
205
|
+
)
|
|
206
|
+
finally:
|
|
207
|
+
await server.stop()
|
|
208
|
+
|
|
209
|
+
|
|
184
210
|
async def test_query_daemon_session_returns_none_when_not_registered(
|
|
185
211
|
daemon_session_server: tuple[Path, dict, list],
|
|
186
212
|
) -> None:
|
|
@@ -130,6 +130,7 @@ class FakeOutbound:
|
|
|
130
130
|
) -> None:
|
|
131
131
|
self.calls: list[tuple] = []
|
|
132
132
|
self.status_conversation_ids: list[str | None] = []
|
|
133
|
+
self.status_delivery_ids: list[str | None] = []
|
|
133
134
|
# 1k #63 — record send_tool_call invocations so tests can assert
|
|
134
135
|
# the daemon-side fallback fires (or doesn't) and inspect its
|
|
135
136
|
# args. The result is hard-coded ok=True by default; tests that
|
|
@@ -156,9 +157,11 @@ class FakeOutbound:
|
|
|
156
157
|
status: str,
|
|
157
158
|
exit_code: int | None = None,
|
|
158
159
|
conversation_id: str | None = None,
|
|
160
|
+
delivery_id: str | None = None,
|
|
159
161
|
) -> None:
|
|
160
162
|
self.calls.append(("agent_status", agent_id, status, exit_code))
|
|
161
163
|
self.status_conversation_ids.append(conversation_id)
|
|
164
|
+
self.status_delivery_ids.append(delivery_id)
|
|
162
165
|
|
|
163
166
|
async def send_agent_session_start(
|
|
164
167
|
self,
|
|
@@ -626,6 +629,25 @@ async def test_agent_status_carries_deliver_conversation(fake_claude, tmp_path):
|
|
|
626
629
|
assert outbound.status_conversation_ids == ["conv_status", "conv_status"]
|
|
627
630
|
|
|
628
631
|
|
|
632
|
+
async def test_agent_status_carries_delivery_provenance(fake_claude, tmp_path):
|
|
633
|
+
mcp_cfg = tmp_path / "mcp.json"
|
|
634
|
+
mcp_cfg.write_text("{}")
|
|
635
|
+
ws = FakeWorkspace(FakeAgentWorkspace(mcp_path=mcp_cfg, last_session=None))
|
|
636
|
+
outbound = FakeOutbound()
|
|
637
|
+
runner = _build_runner(
|
|
638
|
+
fake_claude(stdout_lines=[], exit_code=0),
|
|
639
|
+
ws,
|
|
640
|
+
outbound,
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
await runner.run_one(
|
|
644
|
+
"agent-A",
|
|
645
|
+
{**_make_payload(), "delivery_id": "del_status"},
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
assert outbound.status_delivery_ids == ["del_status", "del_status"]
|
|
649
|
+
|
|
650
|
+
|
|
629
651
|
async def test_emits_status_crashed_on_nonzero_exit(fake_claude, tmp_path):
|
|
630
652
|
"""Non-zero exit → final agent_status is crashed with the exit code."""
|
|
631
653
|
mcp_cfg = tmp_path / "mcp.json"
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_mcp_bridge_agent_retrieval.py
RENAMED
|
@@ -29,9 +29,21 @@ _CHIEF_ONLY = {
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def _build(monkeypatch, is_chief, captured=None):
|
|
32
|
-
|
|
32
|
+
# ``delivery_id`` is the 6th positional the provenance closure now passes
|
|
33
|
+
# (#112). Accepting it keeps this stub in step with the real helper — a
|
|
34
|
+
# 5-arg stub raises TypeError inside the tool and surfaces as an opaque
|
|
35
|
+
# ToolError, which reads like a bridge bug rather than a stale stub.
|
|
36
|
+
async def _fake_ipc(
|
|
37
|
+
socket_path, agent_id, tool_name, tool_args, session_id, delivery_id=None
|
|
38
|
+
):
|
|
33
39
|
if captured is not None:
|
|
34
|
-
captured.append(
|
|
40
|
+
captured.append(
|
|
41
|
+
{
|
|
42
|
+
"tool_name": tool_name,
|
|
43
|
+
"tool_args": tool_args,
|
|
44
|
+
"delivery_id": delivery_id,
|
|
45
|
+
}
|
|
46
|
+
)
|
|
35
47
|
return {"ok": True}
|
|
36
48
|
|
|
37
49
|
monkeypatch.setattr(bridge, "_ipc_tool_call", _fake_ipc)
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_mcp_bridge_send_message.py
RENAMED
|
@@ -21,12 +21,16 @@ def _build_staff_server(monkeypatch, captured: list[dict]):
|
|
|
21
21
|
"""Build a non-chief :class:`FastMCP` with the IPC layer monkeypatched
|
|
22
22
|
so we can introspect the args ``send_message`` passes downstream."""
|
|
23
23
|
|
|
24
|
+
# ``delivery_id`` is the 6th positional the provenance closure now passes
|
|
25
|
+
# (#112). Capturing it keeps this stub in step with the real helper and
|
|
26
|
+
# lets callers assert the daemon-side value actually reaches the IPC layer.
|
|
24
27
|
async def _fake_ipc_tool_call(
|
|
25
28
|
socket_path: Path,
|
|
26
29
|
agent_id: str,
|
|
27
30
|
tool_name: str,
|
|
28
31
|
tool_args: dict,
|
|
29
32
|
session_id: str | None,
|
|
33
|
+
delivery_id: str | None = None,
|
|
30
34
|
) -> dict[str, Any]:
|
|
31
35
|
captured.append(
|
|
32
36
|
{
|
|
@@ -35,6 +39,7 @@ def _build_staff_server(monkeypatch, captured: list[dict]):
|
|
|
35
39
|
"tool_name": tool_name,
|
|
36
40
|
"tool_args": tool_args,
|
|
37
41
|
"session_id": session_id,
|
|
42
|
+
"delivery_id": delivery_id,
|
|
38
43
|
}
|
|
39
44
|
)
|
|
40
45
|
return {"ok": True}
|
|
@@ -108,6 +108,17 @@ async def test_send_agent_status_includes_conversation_id():
|
|
|
108
108
|
assert ws.sent[0]["payload"]["conversation_id"] == "conv-1"
|
|
109
109
|
|
|
110
110
|
|
|
111
|
+
async def test_send_agent_status_includes_delivery_id():
|
|
112
|
+
q, ws = _make_queue()
|
|
113
|
+
await q.send_agent_status(
|
|
114
|
+
agent_id="agent-1",
|
|
115
|
+
status="idle",
|
|
116
|
+
delivery_id="del-1",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
assert ws.sent[0]["payload"]["delivery_id"] == "del-1"
|
|
120
|
+
|
|
121
|
+
|
|
111
122
|
async def test_send_tool_call_returns_tool_result():
|
|
112
123
|
q, ws = _make_queue()
|
|
113
124
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/agent_upload_denylist.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/delegation_report.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/injection_state.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/qoder_credentials.py
RENAMED
|
File without changes
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/src/cloudagent_daemon/readonly_bash_guard.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/integration/test_e2e_deliver_to_spawn.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_claude_runner_codex_contract.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_outbox_stuck_watchdog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_service_multi_profile.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.1 → cloudagent_daemon-0.5.2}/tests/unit/test_workspace_root_task113.py
RENAMED
|
File without changes
|
|
File without changes
|