jarviscore-framework 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl

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.
Files changed (31) hide show
  1. examples/customagent_cognitive_discovery_example.py +49 -8
  2. examples/customagent_distributed_example.py +140 -1
  3. examples/fastapi_integration_example.py +70 -7
  4. jarviscore/__init__.py +1 -1
  5. jarviscore/core/mesh.py +149 -0
  6. jarviscore/data/examples/customagent_cognitive_discovery_example.py +49 -8
  7. jarviscore/data/examples/customagent_distributed_example.py +140 -1
  8. jarviscore/data/examples/fastapi_integration_example.py +70 -7
  9. jarviscore/docs/API_REFERENCE.md +547 -5
  10. jarviscore/docs/CHANGELOG.md +89 -0
  11. jarviscore/docs/CONFIGURATION.md +1 -1
  12. jarviscore/docs/CUSTOMAGENT_GUIDE.md +347 -2
  13. jarviscore/docs/TROUBLESHOOTING.md +1 -1
  14. jarviscore/docs/USER_GUIDE.md +286 -5
  15. jarviscore/p2p/coordinator.py +36 -7
  16. jarviscore/p2p/messages.py +13 -0
  17. jarviscore/p2p/peer_client.py +355 -23
  18. jarviscore/p2p/peer_tool.py +17 -11
  19. jarviscore/profiles/customagent.py +9 -2
  20. jarviscore/testing/__init__.py +35 -0
  21. jarviscore/testing/mocks.py +578 -0
  22. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/METADATA +2 -2
  23. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/RECORD +31 -24
  24. tests/test_17_session_context.py +489 -0
  25. tests/test_18_mesh_diagnostics.py +465 -0
  26. tests/test_19_async_requests.py +516 -0
  27. tests/test_20_load_balancing.py +546 -0
  28. tests/test_21_mock_testing.py +776 -0
  29. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/WHEEL +0 -0
  30. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/licenses/LICENSE +0 -0
  31. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/top_level.txt +0 -0
@@ -3,13 +3,13 @@ examples/calculator_agent_example.py,sha256=x7TrzE45WT_1DqwEnw8U3Fw56WpR9jBe3SLZ
3
3
  examples/cloud_deployment_example.py,sha256=R0WVtta6hi_yuhWVnnQ5X_wiztPSRLC2gfhoDMgUakw,5038
4
4
  examples/custom_profile_decorator.py,sha256=4EAgqXrT9an1f5AQ3cEWAhPvI_EeXbQq7O8ByunXb_M,4099
5
5
  examples/custom_profile_wrap.py,sha256=0NoQECVBEJOiL7Syr2QL73jnvj2XhGQZUhOvisqEQXw,5088
6
- examples/customagent_cognitive_discovery_example.py,sha256=3A5ibpTL5GCwJtnvx2sbp_Da59uK_xhBf83HvxjdQy0,12952
7
- examples/customagent_distributed_example.py,sha256=_vt_2sqSCIdHgwuV4VQFzHzmC3bgemPrHFifeA5tRNE,13677
6
+ examples/customagent_cognitive_discovery_example.py,sha256=UfdQm9j11G38IfHPyBguQSmxODU7A5Niod95aydNb7s,14835
7
+ examples/customagent_distributed_example.py,sha256=RA-zvRjPq2YrGfKU6_dP4ZuEVwivKdA3NNPty6j_GmA,18951
8
8
  examples/customagent_p2p_example.py,sha256=tWywC5abc1aYIyW2V6Xgr_XkjZdqzcOeLut3PnzUpB8,30669
9
- examples/fastapi_integration_example.py,sha256=e87ewAFQzTCrckZzkTpSl3Ajx30qL6Ws7ArfqussbSo,22462
9
+ examples/fastapi_integration_example.py,sha256=qWob72OD2BLf-SlpvwKjfzgyTZo97DL2ttp-Ox56280,24790
10
10
  examples/multi_agent_workflow.py,sha256=Sygx3iEBM9WzorVMXqtiwn4rLYrW9BsxsiQSKceuzsE,4303
11
11
  examples/research_agent_example.py,sha256=phJ5AHNnZ_pxCfiKvHoTp_IFwOAW7VD1fRNHlXvfgj4,2287
12
- jarviscore/__init__.py,sha256=S7nug9_fRGeOh5nNYBrlb-ty-uDt3cll9dvLbwCUJuY,3402
12
+ jarviscore/__init__.py,sha256=MFMKSUN3tqYyUAXHKrHWp6GyptOeg_wbg7CgbEfqZwA,3402
13
13
  jarviscore/adapter/__init__.py,sha256=fipq2XzgW3Us35tbFhs9B_ypoeEZeaP7bTsNPFFZVRk,1092
14
14
  jarviscore/adapter/decorator.py,sha256=ZY-WCF16EvtQWkra_6HNBKdTOJ0G5yvrD5PYKa6bcqk,12040
15
15
  jarviscore/adapter/wrapper.py,sha256=kU9nBiezU1M1dq9x5EjnAVHs_QjFFOFx7dR4TNyFdPk,9666
@@ -26,7 +26,7 @@ jarviscore/context/jarvis_context.py,sha256=6ai2TjDE5PRiBxF5lTdyBMoK3b8wv6cr0a6q
26
26
  jarviscore/context/memory.py,sha256=taonyl24ZUe-NZ5VtvrxljNv6f_BebuH6VE7l0B9S7A,4442
27
27
  jarviscore/core/__init__.py,sha256=30K2aqZckYTRZupn6X-mGV2QDSqWCgJ1cpN6Zk1gqlQ,177
28
28
  jarviscore/core/agent.py,sha256=j76BGNwN6ATnjWujvBSz8Dvojz7W9AEzC7gxfs9xde4,15561
29
- jarviscore/core/mesh.py,sha256=3d7CdWqZhED_t2tWAtB5nBjWuyxcTbMbCIy5tWpvLmU,23370
29
+ jarviscore/core/mesh.py,sha256=wPgrppzIXtqzhUdaCqNL4OvLpSBCUiAwxzcs-19Bpzg,29407
30
30
  jarviscore/core/profile.py,sha256=sTrGTxV9mAqbt5l3z0-BSNOeWzq8YDLR3mlaPFSgt1c,2190
31
31
  jarviscore/data/.env.example,sha256=TCPdye7tYNDOEpcgaEuzUsQ-H7m9G6rsyzNZV9IYQ9s,5156
32
32
  jarviscore/data/__init__.py,sha256=757nsqMkytYV0zXiM_mh3LqtGZZ1lgFuMzvaLrW51PM,151
@@ -35,20 +35,20 @@ jarviscore/data/examples/calculator_agent_example.py,sha256=x7TrzE45WT_1DqwEnw8U
35
35
  jarviscore/data/examples/cloud_deployment_example.py,sha256=R0WVtta6hi_yuhWVnnQ5X_wiztPSRLC2gfhoDMgUakw,5038
36
36
  jarviscore/data/examples/custom_profile_decorator.py,sha256=4EAgqXrT9an1f5AQ3cEWAhPvI_EeXbQq7O8ByunXb_M,4099
37
37
  jarviscore/data/examples/custom_profile_wrap.py,sha256=0NoQECVBEJOiL7Syr2QL73jnvj2XhGQZUhOvisqEQXw,5088
38
- jarviscore/data/examples/customagent_cognitive_discovery_example.py,sha256=3A5ibpTL5GCwJtnvx2sbp_Da59uK_xhBf83HvxjdQy0,12952
39
- jarviscore/data/examples/customagent_distributed_example.py,sha256=_vt_2sqSCIdHgwuV4VQFzHzmC3bgemPrHFifeA5tRNE,13677
38
+ jarviscore/data/examples/customagent_cognitive_discovery_example.py,sha256=UfdQm9j11G38IfHPyBguQSmxODU7A5Niod95aydNb7s,14835
39
+ jarviscore/data/examples/customagent_distributed_example.py,sha256=RA-zvRjPq2YrGfKU6_dP4ZuEVwivKdA3NNPty6j_GmA,18951
40
40
  jarviscore/data/examples/customagent_p2p_example.py,sha256=tWywC5abc1aYIyW2V6Xgr_XkjZdqzcOeLut3PnzUpB8,30669
41
- jarviscore/data/examples/fastapi_integration_example.py,sha256=e87ewAFQzTCrckZzkTpSl3Ajx30qL6Ws7ArfqussbSo,22462
41
+ jarviscore/data/examples/fastapi_integration_example.py,sha256=qWob72OD2BLf-SlpvwKjfzgyTZo97DL2ttp-Ox56280,24790
42
42
  jarviscore/data/examples/multi_agent_workflow.py,sha256=Sygx3iEBM9WzorVMXqtiwn4rLYrW9BsxsiQSKceuzsE,4303
43
43
  jarviscore/data/examples/research_agent_example.py,sha256=phJ5AHNnZ_pxCfiKvHoTp_IFwOAW7VD1fRNHlXvfgj4,2287
44
- jarviscore/docs/API_REFERENCE.md,sha256=kEhCehRupHmBiJfD0AYpEiyMKXKbUI70dELM__8eNWg,31573
44
+ jarviscore/docs/API_REFERENCE.md,sha256=duRyLZ9uDu4EgyTjOPThHpBdOp_l6K73yDJLtzXivdU,44404
45
45
  jarviscore/docs/AUTOAGENT_GUIDE.md,sha256=ftm8dymihs3Y9PZTZmSD9xRhlRFygfTZKm59Mz8zNRA,4618
46
- jarviscore/docs/CHANGELOG.md,sha256=9nnQEVpfsdIkJKPPrjJsZoJI9VQ4clKGhLE1lrodeTs,4661
47
- jarviscore/docs/CONFIGURATION.md,sha256=cJwSmegYug2E3W2-dwXT1PwKc05dES6opbUNegSELwc,14910
48
- jarviscore/docs/CUSTOMAGENT_GUIDE.md,sha256=OYeID56PsOE4T-YktazT1_xfHUD2U2FOap_ZU-0poxE,72539
46
+ jarviscore/docs/CHANGELOG.md,sha256=_NIU7VQHiTB5HLcxw-HyhoDGup8uP2uCTcl6Nw5jQeY,7707
47
+ jarviscore/docs/CONFIGURATION.md,sha256=QiP2RVmZMhbV46S9W0FPKQ2y8P97ZntORtwxH-xPfvc,14910
48
+ jarviscore/docs/CUSTOMAGENT_GUIDE.md,sha256=hMbCD2R7h8gEUwl9usCc7_dNTWRRJ6NoI84Kip3yPX4,82420
49
49
  jarviscore/docs/GETTING_STARTED.md,sha256=w_t47M9kQcIG3N6tesMcjQgqpkV7PZOHAOXOSk8yGhE,15860
50
- jarviscore/docs/TROUBLESHOOTING.md,sha256=YFXc4pjm3_5Ol38r5fxXh1JFKBAlp14lOhIIe8zg12g,11544
51
- jarviscore/docs/USER_GUIDE.md,sha256=TpiRtyHG_bC0WT3JiwobbeKYaNVnF0u8UT-yZ30pMz8,20951
50
+ jarviscore/docs/TROUBLESHOOTING.md,sha256=iK4kz4x4AsChBPxeOvW6bzg5Gf1XwoZTQEEJYOivtr0,11544
51
+ jarviscore/docs/USER_GUIDE.md,sha256=uOYXABiOpr1e_4QM_8W2xIJtHbdZE6wOS87-U_srdTs,28426
52
52
  jarviscore/execution/__init__.py,sha256=yDAMehMO2dVvdKjxVx7zQV2AaxySmvymA24QF3O9tlY,1754
53
53
  jarviscore/execution/code_registry.py,sha256=C3_hAVXIeCG31qwSBUrmBBicmd2vnUrXJhJgj8MKlJw,9213
54
54
  jarviscore/execution/generator.py,sha256=zY7IxxDu4xoifeuCGZZN8_l8zQCsB5eUO9HGIiLIttw,8696
@@ -66,16 +66,18 @@ jarviscore/orchestration/engine.py,sha256=Xh9Fn3aVY7iwj7cAXdHcN90ogOiVFJlyJeoKnJ
66
66
  jarviscore/orchestration/status.py,sha256=XeKASMNQDwpJ6HpDw3m3eAAMNWsWCj4k9jrvMnLHPbo,2540
67
67
  jarviscore/p2p/__init__.py,sha256=k1KkBnbb74jrfWKLiKRsMbepy6xBfQZOGzOlDKvslp0,927
68
68
  jarviscore/p2p/broadcaster.py,sha256=Gaj0nCHDBQrVe4ob4GcaFSqnAaVe3ugEILslc5Mq1qE,14414
69
- jarviscore/p2p/coordinator.py,sha256=UtsSVSigIhs2Rcx1v5BVdLC11P649u71y8Bw4cU1fMk,30087
69
+ jarviscore/p2p/coordinator.py,sha256=MMvV7O30Ro0HSRzAT3mpo9p_0QmQoSJ8IJPRkMySOlo,31521
70
70
  jarviscore/p2p/keepalive.py,sha256=it0SyXVBmujfE79thrap-A1UxZW01p9ENEIglIbQoAM,14988
71
- jarviscore/p2p/messages.py,sha256=nd7ZutZ1Xge2C1KkRSwQK3RJ03ScxqESF3fl48hEkhI,2540
72
- jarviscore/p2p/peer_client.py,sha256=hC21-ecE6edcaxKXhxTnikt0aV5CV-TDNcCuvhSgAcI,32713
73
- jarviscore/p2p/peer_tool.py,sha256=qXiF0b2pTo8tJ8bUHxe_XbxE0UFgwu2wCpy-W82ocUo,9098
71
+ jarviscore/p2p/messages.py,sha256=ud856fEQQvZz0ngK8vVHKzJ5PlAZGZIRz3e_-DGyNQ4,3151
72
+ jarviscore/p2p/peer_client.py,sha256=ikyn5cPUKODBunEG1qFtd6b_9gaAXYS4Be0sAkR9UL4,44859
73
+ jarviscore/p2p/peer_tool.py,sha256=tMaMGbocGkluOfMEGQABPm37e8Tgr3_PAWZ0urGfFgw,9596
74
74
  jarviscore/p2p/swim_manager.py,sha256=mu9clcJ22gqSOoeWlr4QW9b4vhRvD6QEAlTjAVVZvV4,11126
75
75
  jarviscore/profiles/__init__.py,sha256=gg2_T7ONl5se6rCSXLo-OlA3yOzmyHZPaNLzB6zjolE,328
76
76
  jarviscore/profiles/autoagent.py,sha256=1nJAVf1oU9lLO47BP1xFGBDZtypXXkwKy6kZjtpdlX0,10424
77
- jarviscore/profiles/customagent.py,sha256=zGt0RMnMbyyWeXtbQkHdvk1-dMweuNVjPTnH0-mZdaU,13906
78
- jarviscore_framework-0.3.1.dist-info/licenses/LICENSE,sha256=SjsXanvmQJFYz_SVFa17O85-bKIa_aG99wrkPpWtypo,1101
77
+ jarviscore/profiles/customagent.py,sha256=--oI_pgKFXprFJXAFD-7Ot9XSYsucr_1L87TOEsJ06g,14279
78
+ jarviscore/testing/__init__.py,sha256=LdPqVN9xfeTBlc4znGcb6kwcTmUdG4Xt7NT77Uu3qOI,869
79
+ jarviscore/testing/mocks.py,sha256=QO-9k5URcRTBRtGCe9v7U8rYT4UalpiQuIidLb51pOE,18949
80
+ jarviscore_framework-0.3.2.dist-info/licenses/LICENSE,sha256=SjsXanvmQJFYz_SVFa17O85-bKIa_aG99wrkPpWtypo,1101
79
81
  test_logs/code_registry/functions/data_generator-558779ed_560ebc37.py,sha256=ua0Lueqe1mWCeMpKTMaumfPS-ZrWBFF_Zx6TU5QVjNo,132
80
82
  test_logs/code_registry/functions/data_generator-5ed3609e_560ebc37.py,sha256=ua0Lueqe1mWCeMpKTMaumfPS-ZrWBFF_Zx6TU5QVjNo,132
81
83
  test_logs/code_registry/functions/data_generator-66da0356_43970bb9.py,sha256=vOkjCOcfXHLJX2TkR3et9vWSwEhCmOn4DdJHMGfTMVY,733
@@ -130,6 +132,11 @@ tests/test_13_dx_improvements.py,sha256=t3xoWdRBq5o2j90i-kZCJYc9AwW9F0lsB-9Y4VhI
130
132
  tests/test_14_cloud_deployment.py,sha256=EaqJ9Cai9Ki61dhmeqNgMfEkWGnL8-aQg52CKZ6335I,16402
131
133
  tests/test_15_llm_cognitive_discovery.py,sha256=AvBtuIPgiI5Z4skpoEjBOP6XwJukAJy7nR08HP-Gl5M,26331
132
134
  tests/test_16_unified_dx_flow.py,sha256=fRqIwhXfkioEY1Wri2vNuxUvLYDy4FvwdRkOqAnffXs,38249
135
+ tests/test_17_session_context.py,sha256=8fDdv8Ct3NNgoXgutp2Bk2CJMiN0oCMCqqUhBqyMYds,17018
136
+ tests/test_18_mesh_diagnostics.py,sha256=-8cQMdGC7kgi8Cmafe3odz0wlYbDw17CxsJFJEVNClU,14043
137
+ tests/test_19_async_requests.py,sha256=lCWTdynGHSloY73sH4UYw6xUOkeekYu_HqeUasVVipI,17381
138
+ tests/test_20_load_balancing.py,sha256=a321d8PmwuE5EFN7PBoXol7yJPDtz9IeyjLxuQS_QnM,19573
139
+ tests/test_21_mock_testing.py,sha256=JFvS6a7U8Tsz8GtGYIHBGp0jz_LUL71P6iqD8r_-N88,25469
133
140
  tests/test_agent.py,sha256=qx9SFDTP4DlcQi6hV8y6LZyEYX6IB8D3VnM7fODnW9s,5182
134
141
  tests/test_autoagent.py,sha256=_mzinLdQwskOn6a-yGqdfoOsqw2f52XSyTCmj8hLqlg,4628
135
142
  tests/test_autoagent_day4.py,sha256=TTb0kSImF9stMsq4cMlkGahf9UBpYjoNXAkgnkKuuQA,4719
@@ -141,7 +148,7 @@ tests/test_llm_fallback.py,sha256=CNajpKkQ6MO503dRbgaP2cz9kXHwUGKo5381tHKTe4c,57
141
148
  tests/test_mesh.py,sha256=JmAAvZaduQ8dHThFDBYgUnjiz8a3r4Kt1atvdH1JO5I,11857
142
149
  tests/test_p2p_integration.py,sha256=F9B21eWlwRzSRphm2Kacs9nM1FgSbSzi6RSLPDvvt2U,10995
143
150
  tests/test_remote_sandbox.py,sha256=80ebc0pWInauWnywsQ0VSzlk8OexSCgGL7BcJUCPkR8,3268
144
- jarviscore_framework-0.3.1.dist-info/METADATA,sha256=4dF2CJq_tMCtHyWoIVbr_Z9Igx-PKh_eP86jKMAAyxc,5876
145
- jarviscore_framework-0.3.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
146
- jarviscore_framework-0.3.1.dist-info/top_level.txt,sha256=aTco8nlqDftlvhB43Je0xXmb-Pw5qYgj-phawjHX4VY,36
147
- jarviscore_framework-0.3.1.dist-info/RECORD,,
151
+ jarviscore_framework-0.3.2.dist-info/METADATA,sha256=AOWvJ2tgtGxED2sKHjX2AsNsfk-kbhqDS-gZ7KiWBcs,5876
152
+ jarviscore_framework-0.3.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
153
+ jarviscore_framework-0.3.2.dist-info/top_level.txt,sha256=aTco8nlqDftlvhB43Je0xXmb-Pw5qYgj-phawjHX4VY,36
154
+ jarviscore_framework-0.3.2.dist-info/RECORD,,
@@ -0,0 +1,489 @@
1
+ """
2
+ Test 17: Session Context Propagation (Feature F6)
3
+
4
+ Tests the context propagation feature:
5
+ - Context parameter in notify(), request(), respond(), broadcast()
6
+ - Auto-propagation of context in respond()
7
+ - Context accessible via IncomingMessage.context
8
+
9
+ Run with: pytest tests/test_17_session_context.py -v -s
10
+ """
11
+ import asyncio
12
+ import sys
13
+ import pytest
14
+ import logging
15
+ from unittest.mock import AsyncMock, MagicMock, patch
16
+
17
+ sys.path.insert(0, '.')
18
+
19
+ # Setup logging
20
+ logging.basicConfig(level=logging.INFO)
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ # =============================================================================
25
+ # TEST: CONTEXT IN OUTGOING MESSAGES
26
+ # =============================================================================
27
+
28
+ class TestContextInNotify:
29
+ """Test context parameter in notify()."""
30
+
31
+ @pytest.mark.asyncio
32
+ async def test_notify_with_context(self):
33
+ """Test notify() accepts and sends context."""
34
+ from jarviscore.testing import MockPeerClient
35
+
36
+ client = MockPeerClient(
37
+ agent_id="sender-1",
38
+ agent_role="sender",
39
+ mock_peers=[{"role": "receiver", "capabilities": ["receiving"]}]
40
+ )
41
+
42
+ context = {"mission_id": "mission-123", "priority": "high"}
43
+ result = await client.notify("receiver", {"event": "test"}, context=context)
44
+
45
+ assert result is True
46
+ notifications = client.get_sent_notifications()
47
+ assert len(notifications) == 1
48
+ assert notifications[0]["context"] == context
49
+ assert notifications[0]["message"] == {"event": "test"}
50
+
51
+ @pytest.mark.asyncio
52
+ async def test_notify_without_context(self):
53
+ """Test notify() works without context (None by default)."""
54
+ from jarviscore.testing import MockPeerClient
55
+
56
+ client = MockPeerClient(
57
+ agent_id="sender-1",
58
+ agent_role="sender",
59
+ mock_peers=[{"role": "receiver", "capabilities": ["receiving"]}]
60
+ )
61
+
62
+ result = await client.notify("receiver", {"event": "test"})
63
+
64
+ assert result is True
65
+ notifications = client.get_sent_notifications()
66
+ assert len(notifications) == 1
67
+ assert notifications[0]["context"] is None
68
+
69
+
70
+ class TestContextInRequest:
71
+ """Test context parameter in request()."""
72
+
73
+ @pytest.mark.asyncio
74
+ async def test_request_with_context(self):
75
+ """Test request() accepts and sends context."""
76
+ from jarviscore.testing import MockPeerClient
77
+
78
+ client = MockPeerClient(
79
+ agent_id="sender-1",
80
+ agent_role="sender",
81
+ mock_peers=[{"role": "analyst", "capabilities": ["analysis"]}]
82
+ )
83
+ client.set_mock_response("analyst", {"result": "analyzed"})
84
+
85
+ context = {"mission_id": "mission-456", "trace_id": "trace-abc"}
86
+ response = await client.request("analyst", {"query": "test"}, context=context)
87
+
88
+ assert response == {"result": "analyzed"}
89
+ requests = client.get_sent_requests()
90
+ assert len(requests) == 1
91
+ assert requests[0]["context"] == context
92
+
93
+ @pytest.mark.asyncio
94
+ async def test_request_without_context(self):
95
+ """Test request() works without context."""
96
+ from jarviscore.testing import MockPeerClient
97
+
98
+ client = MockPeerClient(
99
+ agent_id="sender-1",
100
+ agent_role="sender",
101
+ mock_peers=[{"role": "analyst", "capabilities": ["analysis"]}]
102
+ )
103
+
104
+ response = await client.request("analyst", {"query": "test"})
105
+
106
+ assert response is not None
107
+ requests = client.get_sent_requests()
108
+ assert len(requests) == 1
109
+ assert requests[0]["context"] is None
110
+
111
+
112
+ class TestContextInBroadcast:
113
+ """Test context parameter in broadcast()."""
114
+
115
+ @pytest.mark.asyncio
116
+ async def test_broadcast_with_context(self):
117
+ """Test broadcast() accepts and sends context to all peers."""
118
+ from jarviscore.testing import MockPeerClient
119
+
120
+ client = MockPeerClient(
121
+ agent_id="broadcaster-1",
122
+ agent_role="broadcaster",
123
+ mock_peers=[
124
+ {"role": "peer1", "capabilities": ["cap1"]},
125
+ {"role": "peer2", "capabilities": ["cap2"]}
126
+ ]
127
+ )
128
+
129
+ context = {"broadcast_id": "bc-789", "source": "alert_system"}
130
+ count = await client.broadcast({"alert": "important"}, context=context)
131
+
132
+ assert count == 2
133
+ broadcasts = client.get_sent_broadcasts()
134
+ assert len(broadcasts) == 1
135
+ assert broadcasts[0]["context"] == context
136
+
137
+
138
+ # =============================================================================
139
+ # TEST: CONTEXT IN INCOMING MESSAGES
140
+ # =============================================================================
141
+
142
+ class TestContextInIncomingMessage:
143
+ """Test context accessible in IncomingMessage."""
144
+
145
+ def test_incoming_message_has_context_field(self):
146
+ """Test IncomingMessage dataclass has context field."""
147
+ from jarviscore.p2p.messages import IncomingMessage, MessageType
148
+
149
+ msg = IncomingMessage(
150
+ sender="sender-1",
151
+ sender_node="localhost:7946",
152
+ type=MessageType.NOTIFY,
153
+ data={"event": "test"},
154
+ context={"mission_id": "m-123"}
155
+ )
156
+
157
+ assert msg.context == {"mission_id": "m-123"}
158
+
159
+ def test_incoming_message_context_default_none(self):
160
+ """Test IncomingMessage context defaults to None."""
161
+ from jarviscore.p2p.messages import IncomingMessage, MessageType
162
+
163
+ msg = IncomingMessage(
164
+ sender="sender-1",
165
+ sender_node="localhost:7946",
166
+ type=MessageType.NOTIFY,
167
+ data={"event": "test"}
168
+ )
169
+
170
+ assert msg.context is None
171
+
172
+
173
+ class TestContextInOutgoingMessage:
174
+ """Test context in OutgoingMessage dataclass."""
175
+
176
+ def test_outgoing_message_has_context_field(self):
177
+ """Test OutgoingMessage dataclass has context field."""
178
+ from jarviscore.p2p.messages import OutgoingMessage, MessageType
179
+
180
+ msg = OutgoingMessage(
181
+ target="receiver",
182
+ type=MessageType.REQUEST,
183
+ data={"query": "test"},
184
+ context={"priority": "high"}
185
+ )
186
+
187
+ assert msg.context == {"priority": "high"}
188
+
189
+
190
+ # =============================================================================
191
+ # TEST: CONTEXT AUTO-PROPAGATION IN RESPOND
192
+ # =============================================================================
193
+
194
+ class TestContextAutoPropagation:
195
+ """Test context auto-propagation in respond()."""
196
+
197
+ @pytest.mark.asyncio
198
+ async def test_respond_auto_propagates_context(self):
199
+ """Test respond() auto-propagates request context if not overridden."""
200
+ from jarviscore.p2p.peer_client import PeerClient
201
+ from jarviscore.p2p.messages import IncomingMessage, MessageType
202
+
203
+ # Create a mock coordinator
204
+ mock_coordinator = MagicMock()
205
+ mock_coordinator._send_p2p_message = AsyncMock(return_value=True)
206
+ mock_coordinator._remote_agent_registry = {}
207
+
208
+ # Create mock agent registry with a sender agent
209
+ class MockAgent:
210
+ def __init__(self):
211
+ self.agent_id = "sender-1"
212
+ self.role = "sender"
213
+ self.capabilities = ["sending"]
214
+ self.peers = MagicMock()
215
+ self.peers._deliver_message = AsyncMock()
216
+
217
+ mock_sender = MockAgent()
218
+ agent_registry = {"sender": [mock_sender]}
219
+
220
+ # Create the responder PeerClient
221
+ client = PeerClient(
222
+ coordinator=mock_coordinator,
223
+ agent_id="responder-1",
224
+ agent_role="responder",
225
+ agent_registry=agent_registry,
226
+ node_id="localhost:7946"
227
+ )
228
+
229
+ # Create incoming request with context
230
+ incoming = IncomingMessage(
231
+ sender="sender-1",
232
+ sender_node="localhost:7946",
233
+ type=MessageType.REQUEST,
234
+ data={"query": "test"},
235
+ correlation_id="corr-123",
236
+ context={"mission_id": "m-999", "trace_id": "t-abc"}
237
+ )
238
+
239
+ # Respond without explicitly providing context
240
+ result = await client.respond(incoming, {"answer": "42"})
241
+
242
+ assert result is True
243
+
244
+ # Verify the response was delivered with propagated context
245
+ mock_sender.peers._deliver_message.assert_called_once()
246
+ delivered_msg = mock_sender.peers._deliver_message.call_args[0][0]
247
+ assert delivered_msg.context == {"mission_id": "m-999", "trace_id": "t-abc"}
248
+
249
+ @pytest.mark.asyncio
250
+ async def test_respond_override_context(self):
251
+ """Test respond() can override context."""
252
+ from jarviscore.p2p.peer_client import PeerClient
253
+ from jarviscore.p2p.messages import IncomingMessage, MessageType
254
+
255
+ mock_coordinator = MagicMock()
256
+ mock_coordinator._send_p2p_message = AsyncMock(return_value=True)
257
+ mock_coordinator._remote_agent_registry = {}
258
+
259
+ class MockAgent:
260
+ def __init__(self):
261
+ self.agent_id = "sender-1"
262
+ self.role = "sender"
263
+ self.capabilities = ["sending"]
264
+ self.peers = MagicMock()
265
+ self.peers._deliver_message = AsyncMock()
266
+
267
+ mock_sender = MockAgent()
268
+ agent_registry = {"sender": [mock_sender]}
269
+
270
+ client = PeerClient(
271
+ coordinator=mock_coordinator,
272
+ agent_id="responder-1",
273
+ agent_role="responder",
274
+ agent_registry=agent_registry,
275
+ node_id="localhost:7946"
276
+ )
277
+
278
+ # Incoming with original context
279
+ incoming = IncomingMessage(
280
+ sender="sender-1",
281
+ sender_node="localhost:7946",
282
+ type=MessageType.REQUEST,
283
+ data={"query": "test"},
284
+ correlation_id="corr-456",
285
+ context={"original": "context"}
286
+ )
287
+
288
+ # Respond with overridden context
289
+ result = await client.respond(
290
+ incoming,
291
+ {"answer": "42"},
292
+ context={"overridden": "context", "status": "complete"}
293
+ )
294
+
295
+ assert result is True
296
+ delivered_msg = mock_sender.peers._deliver_message.call_args[0][0]
297
+ assert delivered_msg.context == {"overridden": "context", "status": "complete"}
298
+
299
+
300
+ # =============================================================================
301
+ # TEST: CONTEXT THROUGH LOCAL DELIVERY
302
+ # =============================================================================
303
+
304
+ class TestContextLocalDelivery:
305
+ """Test context flows through local message delivery."""
306
+
307
+ @pytest.mark.asyncio
308
+ async def test_context_in_local_notify(self):
309
+ """Test context preserved in local notify delivery."""
310
+ from jarviscore.p2p.peer_client import PeerClient
311
+ from jarviscore.p2p.messages import MessageType
312
+
313
+ mock_coordinator = MagicMock()
314
+ mock_coordinator._remote_agent_registry = {}
315
+
316
+ # Create receiver agent with PeerClient
317
+ class MockReceiverAgent:
318
+ def __init__(self):
319
+ self.agent_id = "receiver-1"
320
+ self.role = "receiver"
321
+ self.capabilities = ["receiving"]
322
+ self.peers = None
323
+
324
+ receiver = MockReceiverAgent()
325
+ receiver_client = PeerClient(
326
+ coordinator=mock_coordinator,
327
+ agent_id="receiver-1",
328
+ agent_role="receiver",
329
+ agent_registry={},
330
+ node_id="localhost:7946"
331
+ )
332
+ receiver.peers = receiver_client
333
+
334
+ # Create sender with receiver in registry
335
+ agent_registry = {"receiver": [receiver]}
336
+ sender_client = PeerClient(
337
+ coordinator=mock_coordinator,
338
+ agent_id="sender-1",
339
+ agent_role="sender",
340
+ agent_registry=agent_registry,
341
+ node_id="localhost:7946"
342
+ )
343
+
344
+ # Send notify with context
345
+ context = {"session_id": "sess-123", "user_id": "user-456"}
346
+ result = await sender_client.notify("receiver", {"event": "test"}, context=context)
347
+
348
+ assert result is True
349
+
350
+ # Receive and check context
351
+ msg = await receiver_client.receive(timeout=1)
352
+ assert msg is not None
353
+ assert msg.type == MessageType.NOTIFY
354
+ assert msg.context == context
355
+
356
+
357
+ # =============================================================================
358
+ # TEST: CONTEXT IN MOCK PEER CLIENT
359
+ # =============================================================================
360
+
361
+ class TestMockPeerClientContext:
362
+ """Test MockPeerClient supports context in all operations."""
363
+
364
+ @pytest.mark.asyncio
365
+ async def test_mock_inject_message_with_context(self):
366
+ """Test MockPeerClient.inject_message() supports context."""
367
+ from jarviscore.testing import MockPeerClient
368
+ from jarviscore.p2p.messages import MessageType
369
+
370
+ client = MockPeerClient()
371
+
372
+ context = {"injected": "context", "test_id": "inject-1"}
373
+ client.inject_message(
374
+ sender="injector",
375
+ message_type=MessageType.NOTIFY,
376
+ data={"injected": True},
377
+ context=context
378
+ )
379
+
380
+ msg = await client.receive(timeout=1)
381
+ assert msg is not None
382
+ assert msg.context == context
383
+ assert msg.data == {"injected": True}
384
+
385
+ @pytest.mark.asyncio
386
+ async def test_mock_tracks_context_in_sent_messages(self):
387
+ """Test MockPeerClient tracks context in all sent messages."""
388
+ from jarviscore.testing import MockPeerClient
389
+
390
+ client = MockPeerClient(
391
+ mock_peers=[{"role": "target", "capabilities": []}]
392
+ )
393
+
394
+ # Send notification with context
395
+ await client.notify("target", {"n": 1}, context={"ctx": "notify"})
396
+ assert client.get_sent_notifications()[0]["context"] == {"ctx": "notify"}
397
+
398
+ # Send request with context
399
+ await client.request("target", {"r": 2}, context={"ctx": "request"})
400
+ assert client.get_sent_requests()[0]["context"] == {"ctx": "request"}
401
+
402
+ # Send broadcast with context
403
+ await client.broadcast({"b": 3}, context={"ctx": "broadcast"})
404
+ assert client.get_sent_broadcasts()[0]["context"] == {"ctx": "broadcast"}
405
+
406
+
407
+ # =============================================================================
408
+ # TEST: CONTEXT IN CUSTOM AGENT HANDLERS
409
+ # =============================================================================
410
+
411
+ class TestContextInCustomAgentHandlers:
412
+ """Test context accessible in CustomAgent message handlers."""
413
+
414
+ @pytest.mark.asyncio
415
+ async def test_context_in_on_peer_request(self):
416
+ """Test context is accessible in on_peer_request handler."""
417
+ from jarviscore.profiles import CustomAgent
418
+ from jarviscore.p2p.messages import IncomingMessage, MessageType
419
+
420
+ received_context = None
421
+
422
+ class TestAgent(CustomAgent):
423
+ role = "context_handler"
424
+ capabilities = ["handling"]
425
+
426
+ async def on_peer_request(self, msg):
427
+ nonlocal received_context
428
+ received_context = msg.context
429
+ return {"received_context": msg.context}
430
+
431
+ agent = TestAgent()
432
+ agent._logger = MagicMock()
433
+ agent.peers = MagicMock()
434
+ agent.peers.respond = AsyncMock()
435
+
436
+ msg = IncomingMessage(
437
+ sender="sender",
438
+ sender_node="localhost:7946",
439
+ type=MessageType.REQUEST,
440
+ data={"query": "test"},
441
+ correlation_id="corr-123",
442
+ context={"mission_id": "m-handler", "stage": "processing"}
443
+ )
444
+
445
+ await agent._dispatch_message(msg)
446
+
447
+ assert received_context == {"mission_id": "m-handler", "stage": "processing"}
448
+
449
+ @pytest.mark.asyncio
450
+ async def test_context_in_on_peer_notify(self):
451
+ """Test context is accessible in on_peer_notify handler."""
452
+ from jarviscore.profiles import CustomAgent
453
+ from jarviscore.p2p.messages import IncomingMessage, MessageType
454
+
455
+ received_context = None
456
+
457
+ class TestAgent(CustomAgent):
458
+ role = "context_handler"
459
+ capabilities = ["handling"]
460
+
461
+ async def on_peer_notify(self, msg):
462
+ nonlocal received_context
463
+ received_context = msg.context
464
+
465
+ async def on_peer_request(self, msg):
466
+ return {}
467
+
468
+ agent = TestAgent()
469
+ agent._logger = MagicMock()
470
+
471
+ msg = IncomingMessage(
472
+ sender="sender",
473
+ sender_node="localhost:7946",
474
+ type=MessageType.NOTIFY,
475
+ data={"event": "test"},
476
+ context={"notification_source": "event_system"}
477
+ )
478
+
479
+ await agent._dispatch_message(msg)
480
+
481
+ assert received_context == {"notification_source": "event_system"}
482
+
483
+
484
+ # =============================================================================
485
+ # RUN TESTS
486
+ # =============================================================================
487
+
488
+ if __name__ == "__main__":
489
+ pytest.main([__file__, "-v", "-s"])