From 79bd1a8d43e2d5021eaa1e1509f0b28d181f1e1a Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 12:18:53 -0800 Subject: [PATCH 01/14] initial commit --- dimos/manipulation/test_manipulation_module.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dimos/manipulation/test_manipulation_module.py b/dimos/manipulation/test_manipulation_module.py index d2c2a347c9..c30ba9b55c 100644 --- a/dimos/manipulation/test_manipulation_module.py +++ b/dimos/manipulation/test_manipulation_module.py @@ -149,7 +149,7 @@ def test_plan_to_joints(self, module, joint_state_zeros): """Test planning to a joint configuration.""" module._on_joint_state(joint_state_zeros) - target = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1] + target = JointState(position=[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]) success = module.plan_to_joints(target) assert success is True @@ -203,7 +203,7 @@ def test_trajectory_name_translation(self, module, joint_state_zeros): """Test that trajectory joint names are translated for coordinator.""" module._on_joint_state(joint_state_zeros) - success = module.plan_to_joints([0.05] * 7) + success = module.plan_to_joints(JointState(position=[0.05] * 7)) assert success is True traj = module._planned_trajectories["test_arm"] @@ -224,7 +224,7 @@ def test_execute_with_mock_coordinator(self, module, joint_state_zeros): """Test execute sends trajectory to coordinator.""" module._on_joint_state(joint_state_zeros) - success = module.plan_to_joints([0.05] * 7) + success = module.plan_to_joints(JointState(position=[0.05] * 7)) assert success is True # Mock the coordinator client @@ -253,7 +253,7 @@ def test_execute_rejected_by_coordinator(self, module, joint_state_zeros): """Test handling of coordinator rejection.""" module._on_joint_state(joint_state_zeros) - module.plan_to_joints([0.05] * 7) + module.plan_to_joints(JointState(position=[0.05] * 7)) # Mock coordinator to reject mock_client = MagicMock() @@ -273,7 +273,7 @@ def test_state_transitions_during_execution(self, module, joint_state_zeros): module._on_joint_state(joint_state_zeros) # Plan - should go through PLANNING -> COMPLETED - module.plan_to_joints([0.05] * 7) + module.plan_to_joints(JointState(position=[0.05] * 7)) assert module._state == ManipulationState.COMPLETED # Reset works from COMPLETED @@ -281,7 +281,7 @@ def test_state_transitions_during_execution(self, module, joint_state_zeros): assert module._state == ManipulationState.IDLE # Plan again - module.plan_to_joints([0.05] * 7) + module.plan_to_joints(JointState(position=[0.05] * 7)) # Mock coordinator mock_client = MagicMock() From ed88757e5881e96c97751d9eb44bac0f216ea786 Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 13:51:11 -0800 Subject: [PATCH 02/14] lazyloader to defer heavy imports --- dimos/teleop/quest/__init__.py | 63 ++++++++++++++-------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/dimos/teleop/quest/__init__.py b/dimos/teleop/quest/__init__.py index 83daf4347b..fc289e53b8 100644 --- a/dimos/teleop/quest/__init__.py +++ b/dimos/teleop/quest/__init__.py @@ -14,41 +14,30 @@ """Quest teleoperation module.""" -from dimos.teleop.quest.quest_extensions import ( - ArmTeleopModule, - TwistTeleopModule, - VisualizingTeleopModule, - arm_teleop_module, - twist_teleop_module, - visualizing_teleop_module, -) -from dimos.teleop.quest.quest_teleop_module import ( - Hand, - QuestTeleopConfig, - QuestTeleopModule, - QuestTeleopStatus, - quest_teleop_module, -) -from dimos.teleop.quest.quest_types import ( - Buttons, - QuestControllerState, - ThumbstickState, -) +import lazy_loader as lazy -__all__ = [ - "ArmTeleopModule", - "Buttons", - "Hand", - "QuestControllerState", - "QuestTeleopConfig", - "QuestTeleopModule", - "QuestTeleopStatus", - "ThumbstickState", - "TwistTeleopModule", - "VisualizingTeleopModule", - # Blueprints - "arm_teleop_module", - "quest_teleop_module", - "twist_teleop_module", - "visualizing_teleop_module", -] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "quest_types": [ + "Buttons", + "QuestControllerState", + "ThumbstickState", + ], + "quest_teleop_module": [ + "Hand", + "QuestTeleopConfig", + "QuestTeleopModule", + "QuestTeleopStatus", + "quest_teleop_module", + ], + "quest_extensions": [ + "ArmTeleopModule", + "TwistTeleopModule", + "VisualizingTeleopModule", + "arm_teleop_module", + "twist_teleop_module", + "visualizing_teleop_module", + ], + }, +) From 8d1f4a2ddfec02c8dbccc89844f3e2535bd01627 Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 13:51:48 -0800 Subject: [PATCH 03/14] Increase test timeout to prevent flaky failures --- dimos/e2e_tests/test_control_coordinator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dimos/e2e_tests/test_control_coordinator.py b/dimos/e2e_tests/test_control_coordinator.py index 3b212a9060..c522c93e3d 100644 --- a/dimos/e2e_tests/test_control_coordinator.py +++ b/dimos/e2e_tests/test_control_coordinator.py @@ -48,7 +48,7 @@ def test_coordinator_starts_and_responds_to_rpc(self, lcm_spy, start_blueprint) # Wait for joint state to be published (proves tick loop is running) lcm_spy.wait_for_saved_topic( joint_state_topic, - timeout=10.0, + timeout=15.0, ) # Create RPC client and query @@ -82,7 +82,7 @@ def test_coordinator_executes_trajectory(self, lcm_spy, start_blueprint) -> None # Wait for it to be ready lcm_spy.wait_for_saved_topic( - "/coordinator/joint_state#sensor_msgs.JointState", timeout=10.0 + "/coordinator/joint_state#sensor_msgs.JointState", timeout=20.0 ) # Create RPC client @@ -166,7 +166,7 @@ def test_coordinator_cancel_trajectory(self, lcm_spy, start_blueprint) -> None: # Start coordinator start_blueprint("coordinator-mock") lcm_spy.wait_for_saved_topic( - "/coordinator/joint_state#sensor_msgs.JointState", timeout=10.0 + "/coordinator/joint_state#sensor_msgs.JointState", timeout=20.0 ) client = RPCClient(None, ControlCoordinator) @@ -211,7 +211,7 @@ def test_dual_arm_coordinator(self, lcm_spy, start_blueprint) -> None: # Start dual-arm mock coordinator start_blueprint("coordinator-dual-mock") lcm_spy.wait_for_saved_topic( - "/coordinator/joint_state#sensor_msgs.JointState", timeout=10.0 + "/coordinator/joint_state#sensor_msgs.JointState", timeout=20.0 ) client = RPCClient(None, ControlCoordinator) From a36e96915a7e5b57f4f1cb9d52ba0b8780b241fd Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 13:52:13 -0800 Subject: [PATCH 04/14] Fix lcmspy global totals test flaking from LCM discovery packets --- dimos/utils/cli/lcmspy/test_lcmspy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dimos/utils/cli/lcmspy/test_lcmspy.py b/dimos/utils/cli/lcmspy/test_lcmspy.py index 14877bbb9a..3f7ba63b67 100644 --- a/dimos/utils/cli/lcmspy/test_lcmspy.py +++ b/dimos/utils/cli/lcmspy/test_lcmspy.py @@ -176,7 +176,8 @@ def test_lcmspy_global_totals() -> None: spy.msg("/imu", b"imu data") # The spy itself should have accumulated all messages - assert len(spy.message_history) == 3 + # (may be > 3 due to async LCM discovery packets on the subscription thread) + assert len(spy.message_history) >= 3 # Check global statistics global_freq = spy.freq(1.0) From 838be8b2a7cc17dee99746957641b6041daa9209 Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 14:07:03 -0800 Subject: [PATCH 05/14] Timeout to 15s --- dimos/e2e_tests/test_control_coordinator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dimos/e2e_tests/test_control_coordinator.py b/dimos/e2e_tests/test_control_coordinator.py index c522c93e3d..40f653c50a 100644 --- a/dimos/e2e_tests/test_control_coordinator.py +++ b/dimos/e2e_tests/test_control_coordinator.py @@ -82,7 +82,7 @@ def test_coordinator_executes_trajectory(self, lcm_spy, start_blueprint) -> None # Wait for it to be ready lcm_spy.wait_for_saved_topic( - "/coordinator/joint_state#sensor_msgs.JointState", timeout=20.0 + "/coordinator/joint_state#sensor_msgs.JointState", timeout=15.0 ) # Create RPC client @@ -166,7 +166,7 @@ def test_coordinator_cancel_trajectory(self, lcm_spy, start_blueprint) -> None: # Start coordinator start_blueprint("coordinator-mock") lcm_spy.wait_for_saved_topic( - "/coordinator/joint_state#sensor_msgs.JointState", timeout=20.0 + "/coordinator/joint_state#sensor_msgs.JointState", timeout=15.0 ) client = RPCClient(None, ControlCoordinator) @@ -211,7 +211,7 @@ def test_dual_arm_coordinator(self, lcm_spy, start_blueprint) -> None: # Start dual-arm mock coordinator start_blueprint("coordinator-dual-mock") lcm_spy.wait_for_saved_topic( - "/coordinator/joint_state#sensor_msgs.JointState", timeout=20.0 + "/coordinator/joint_state#sensor_msgs.JointState", timeout=15.0 ) client = RPCClient(None, ControlCoordinator) From a9df78bf2d61e2ae74c625a7d1b346fba8b77ab7 Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 14:30:31 -0800 Subject: [PATCH 06/14] lazy imports not necessary --- dimos/teleop/quest/__init__.py | 63 ++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/dimos/teleop/quest/__init__.py b/dimos/teleop/quest/__init__.py index fc289e53b8..83daf4347b 100644 --- a/dimos/teleop/quest/__init__.py +++ b/dimos/teleop/quest/__init__.py @@ -14,30 +14,41 @@ """Quest teleoperation module.""" -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submod_attrs={ - "quest_types": [ - "Buttons", - "QuestControllerState", - "ThumbstickState", - ], - "quest_teleop_module": [ - "Hand", - "QuestTeleopConfig", - "QuestTeleopModule", - "QuestTeleopStatus", - "quest_teleop_module", - ], - "quest_extensions": [ - "ArmTeleopModule", - "TwistTeleopModule", - "VisualizingTeleopModule", - "arm_teleop_module", - "twist_teleop_module", - "visualizing_teleop_module", - ], - }, +from dimos.teleop.quest.quest_extensions import ( + ArmTeleopModule, + TwistTeleopModule, + VisualizingTeleopModule, + arm_teleop_module, + twist_teleop_module, + visualizing_teleop_module, +) +from dimos.teleop.quest.quest_teleop_module import ( + Hand, + QuestTeleopConfig, + QuestTeleopModule, + QuestTeleopStatus, + quest_teleop_module, ) +from dimos.teleop.quest.quest_types import ( + Buttons, + QuestControllerState, + ThumbstickState, +) + +__all__ = [ + "ArmTeleopModule", + "Buttons", + "Hand", + "QuestControllerState", + "QuestTeleopConfig", + "QuestTeleopModule", + "QuestTeleopStatus", + "ThumbstickState", + "TwistTeleopModule", + "VisualizingTeleopModule", + # Blueprints + "arm_teleop_module", + "quest_teleop_module", + "twist_teleop_module", + "visualizing_teleop_module", +] From 50320f63ad25fcc98adbf4bf154ad48465cafe6f Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 14:50:09 -0800 Subject: [PATCH 07/14] added manipulation to docker/python/ --- docker/python/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/python/Dockerfile b/docker/python/Dockerfile index 1572bbc3ba..94a2679d64 100644 --- a/docker/python/Dockerfile +++ b/docker/python/Dockerfile @@ -48,4 +48,4 @@ COPY . /app/ # Install dependencies with UV (10-100x faster than pip) RUN uv pip install --upgrade 'pip>=24' 'setuptools>=70' 'wheel' 'packaging>=24' && \ - uv pip install '.[misc,cpu,sim,drone,unitree,web,perception,visualization]' + uv pip install '.[misc,cpu,sim,drone,unitree,web,perception,visualization,manipulation]' From efc4d900f1bbe8aa6f2f3a6c161c4921b7f2b975 Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 15:02:47 -0800 Subject: [PATCH 08/14] Fix: default timeout is 30s --- dimos/e2e_tests/test_control_coordinator.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/dimos/e2e_tests/test_control_coordinator.py b/dimos/e2e_tests/test_control_coordinator.py index 40f653c50a..f6e520831d 100644 --- a/dimos/e2e_tests/test_control_coordinator.py +++ b/dimos/e2e_tests/test_control_coordinator.py @@ -46,10 +46,7 @@ def test_coordinator_starts_and_responds_to_rpc(self, lcm_spy, start_blueprint) start_blueprint("coordinator-mock") # Wait for joint state to be published (proves tick loop is running) - lcm_spy.wait_for_saved_topic( - joint_state_topic, - timeout=15.0, - ) + lcm_spy.wait_for_saved_topic(joint_state_topic) # Create RPC client and query client = RPCClient(None, ControlCoordinator) @@ -81,9 +78,7 @@ def test_coordinator_executes_trajectory(self, lcm_spy, start_blueprint) -> None start_blueprint("coordinator-mock") # Wait for it to be ready - lcm_spy.wait_for_saved_topic( - "/coordinator/joint_state#sensor_msgs.JointState", timeout=15.0 - ) + lcm_spy.wait_for_saved_topic("/coordinator/joint_state#sensor_msgs.JointState") # Create RPC client client = RPCClient(None, ControlCoordinator) @@ -138,7 +133,7 @@ def test_coordinator_joint_state_published(self, lcm_spy, start_blueprint) -> No start_blueprint("coordinator-mock") # Wait for initial message - lcm_spy.wait_for_saved_topic(joint_state_topic, timeout=10.0) + lcm_spy.wait_for_saved_topic(joint_state_topic) # Collect messages for 1 second time.sleep(1.0) @@ -165,9 +160,7 @@ def test_coordinator_cancel_trajectory(self, lcm_spy, start_blueprint) -> None: # Start coordinator start_blueprint("coordinator-mock") - lcm_spy.wait_for_saved_topic( - "/coordinator/joint_state#sensor_msgs.JointState", timeout=15.0 - ) + lcm_spy.wait_for_saved_topic("/coordinator/joint_state#sensor_msgs.JointState") client = RPCClient(None, ControlCoordinator) try: @@ -210,9 +203,7 @@ def test_dual_arm_coordinator(self, lcm_spy, start_blueprint) -> None: # Start dual-arm mock coordinator start_blueprint("coordinator-dual-mock") - lcm_spy.wait_for_saved_topic( - "/coordinator/joint_state#sensor_msgs.JointState", timeout=15.0 - ) + lcm_spy.wait_for_saved_topic("/coordinator/joint_state#sensor_msgs.JointState") client = RPCClient(None, ControlCoordinator) try: From 68656546219a5060889c3219a1fe82db727a69e0 Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 15:27:53 -0800 Subject: [PATCH 09/14] lcmspy test to filter per-topic --- dimos/utils/cli/lcmspy/test_lcmspy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dimos/utils/cli/lcmspy/test_lcmspy.py b/dimos/utils/cli/lcmspy/test_lcmspy.py index 3f7ba63b67..530f081f29 100644 --- a/dimos/utils/cli/lcmspy/test_lcmspy.py +++ b/dimos/utils/cli/lcmspy/test_lcmspy.py @@ -175,9 +175,9 @@ def test_lcmspy_global_totals() -> None: spy.msg("/odom", b"odometry data") spy.msg("/imu", b"imu data") - # The spy itself should have accumulated all messages - # (may be > 3 due to async LCM discovery packets on the subscription thread) - assert len(spy.message_history) >= 3 + # Verify each test topic received exactly one message (ignore LCM discovery packets) + for t in ("/video", "/odom", "/imu"): + assert len(spy.topic[t].message_history) == 1 # Check global statistics global_freq = spy.freq(1.0) From 30493265db36214ae71112f883656574aed491f8 Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 17:09:09 -0800 Subject: [PATCH 10/14] Fix: skip pydrake type stubs in mypy --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1b0c40acd3..0fe67fd34e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -384,6 +384,10 @@ ignore_missing_imports = true module = ["dimos.rxpy_backpressure", "dimos.rxpy_backpressure.*"] follow_imports = "skip" +[[tool.mypy.overrides]] +module = ["pydrake", "pydrake.*"] +follow_imports = "skip" + [tool.pytest.ini_options] testpaths = ["dimos"] markers = [ From c84e624a87aba5c541d2b2d574b5993371301100 Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 18:53:59 -0800 Subject: [PATCH 11/14] Fix: delete .pyi files that breaks mypy on 3.10 in CI --- docker/python/Dockerfile | 3 +++ pyproject.toml | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docker/python/Dockerfile b/docker/python/Dockerfile index 94a2679d64..aa02b0e42d 100644 --- a/docker/python/Dockerfile +++ b/docker/python/Dockerfile @@ -49,3 +49,6 @@ COPY . /app/ # Install dependencies with UV (10-100x faster than pip) RUN uv pip install --upgrade 'pip>=24' 'setuptools>=70' 'wheel' 'packaging>=24' && \ uv pip install '.[misc,cpu,sim,drone,unitree,web,perception,visualization,manipulation]' + +# Remove pydrake .pyi stubs that use Python 3.12 syntax (breaks mypy on 3.10) +RUN rm -f /usr/local/lib/python3.10/dist-packages/pydrake/*.pyi diff --git a/pyproject.toml b/pyproject.toml index 0fe67fd34e..1b0c40acd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -384,10 +384,6 @@ ignore_missing_imports = true module = ["dimos.rxpy_backpressure", "dimos.rxpy_backpressure.*"] follow_imports = "skip" -[[tool.mypy.overrides]] -module = ["pydrake", "pydrake.*"] -follow_imports = "skip" - [tool.pytest.ini_options] testpaths = ["dimos"] markers = [ From c01e5c71987d7bcafc1c82f5d4ae8da1c32b39b9 Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 20:20:59 -0800 Subject: [PATCH 12/14] Fix: find all .pyi and delete them --- docker/python/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/python/Dockerfile b/docker/python/Dockerfile index aa02b0e42d..30c9fda8eb 100644 --- a/docker/python/Dockerfile +++ b/docker/python/Dockerfile @@ -51,4 +51,4 @@ RUN uv pip install --upgrade 'pip>=24' 'setuptools>=70' 'wheel' 'packaging>=24' uv pip install '.[misc,cpu,sim,drone,unitree,web,perception,visualization,manipulation]' # Remove pydrake .pyi stubs that use Python 3.12 syntax (breaks mypy on 3.10) -RUN rm -f /usr/local/lib/python3.10/dist-packages/pydrake/*.pyi +RUN find /usr/local/lib/python3.10/dist-packages/pydrake -name '*.pyi' -delete From ad5b573fb6213b38330b24f8814c94f9a588d5ad Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 21:14:39 -0800 Subject: [PATCH 13/14] adding pydrake to the ignore_missing_imports override --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1b0c40acd3..9a0f09e8b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -362,6 +362,8 @@ module = [ "open_clip", "piper_sdk.*", "plotext", + "pydrake", + "pydrake.*", "plum.*", "pycuda", "pycuda.*", From 24d7b2ce941eaec6431ca2bcca954dcc1e3ae1eb Mon Sep 17 00:00:00 2001 From: ruthwikdasyam Date: Thu, 19 Feb 2026 23:14:47 -0800 Subject: [PATCH 14/14] follow_imports skip for pydrake --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 9a0f09e8b5..52f18fe950 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -386,6 +386,10 @@ ignore_missing_imports = true module = ["dimos.rxpy_backpressure", "dimos.rxpy_backpressure.*"] follow_imports = "skip" +[[tool.mypy.overrides]] +module = ["pydrake", "pydrake.*"] +follow_imports = "skip" + [tool.pytest.ini_options] testpaths = ["dimos"] markers = [