aether-robotics 5.2.2__tar.gz → 5.2.3__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.
Files changed (266) hide show
  1. {aether_robotics-5.2.2/aether_robotics.egg-info → aether_robotics-5.2.3}/PKG-INFO +1 -1
  2. aether_robotics-5.2.3/VERSION +1 -0
  3. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/__init__.py +8 -1
  4. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/app.py +14 -8
  5. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/auto_updater.py +30 -2
  6. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/boot_timer.py +59 -1
  7. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/navigation_engine.py +31 -9
  8. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/tool_builder.py +64 -17
  9. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/tool_discovery.py +7 -0
  10. {aether_robotics-5.2.2 → aether_robotics-5.2.3/aether_robotics.egg-info}/PKG-INFO +1 -1
  11. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/pyproject.toml +1 -1
  12. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_plan_cache.py +139 -0
  13. aether_robotics-5.2.2/VERSION +0 -1
  14. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/LICENSE +0 -0
  15. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/MANIFEST.in +0 -0
  16. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/README.md +0 -0
  17. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/__main__.py +0 -0
  18. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/actions/__init__.py +0 -0
  19. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/actions/abstract_actions.py +0 -0
  20. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/__init__.py +0 -0
  21. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/_generator.py +0 -0
  22. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/adeept_hat_v3.py +0 -0
  23. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/base_adapter.py +0 -0
  24. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/drone_adapter.py +0 -0
  25. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/freenove_4wd.py +0 -0
  26. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/freenove_mecanum.py +0 -0
  27. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/gopigo3.py +0 -0
  28. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/l298n_direct_gpio.py +0 -0
  29. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/pca9685_generic.py +0 -0
  30. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/rover_adapter.py +0 -0
  31. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/sunfounder_picarx.py +0 -0
  32. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/sunfounder_pidog.py +0 -0
  33. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/universal_adapter.py +0 -0
  34. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/waveshare_motor_driver.py +0 -0
  35. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/adapters/yahboom_robot_hat.py +0 -0
  36. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/__init__.py +0 -0
  37. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/adaptation_agent.py +0 -0
  38. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/camera_agent.py +0 -0
  39. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/correction_agent.py +0 -0
  40. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/execution_agent.py +0 -0
  41. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/fault_agent.py +0 -0
  42. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/memory_agent.py +0 -0
  43. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/movement_agent.py +0 -0
  44. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/navigation_agent.py +0 -0
  45. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/perception_agent.py +0 -0
  46. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/planner_agent.py +0 -0
  47. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/power_agent.py +0 -0
  48. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/task_manager.py +0 -0
  49. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/agents/thermal_agent.py +0 -0
  50. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/capabilities/__init__.py +0 -0
  51. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/capabilities/capability_loader.py +0 -0
  52. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/__init__.py +0 -0
  53. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/analytics.py +0 -0
  54. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/autonomous.py +0 -0
  55. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/calibrate.py +0 -0
  56. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/doctor.py +0 -0
  57. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/fix.py +0 -0
  58. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/kits.py +0 -0
  59. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/kits_install_recipes/freenove_4wd.yaml +0 -0
  60. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/kits_install_recipes/gopigo3.yaml +0 -0
  61. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/kits_install_recipes/sunfounder_picarx.yaml +0 -0
  62. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/kits_install_recipes/sunfounder_pidog.yaml +0 -0
  63. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/kits_install_recipes/yahboom_robot_hat.yaml +0 -0
  64. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/cli/memory.py +0 -0
  65. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/__init__.py +0 -0
  66. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/analytics.py +0 -0
  67. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/auto_installer.py +0 -0
  68. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/autonomous.py +0 -0
  69. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/banner.py +0 -0
  70. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/calibration.py +0 -0
  71. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/decomposer.py +0 -0
  72. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/discovery_cache.py +0 -0
  73. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/embeddings.py +0 -0
  74. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/executor.py +0 -0
  75. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/feedback.py +0 -0
  76. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/feedback_prompt.py +0 -0
  77. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/firstrun.py +0 -0
  78. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/genome.py +0 -0
  79. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/goal_parser.py +0 -0
  80. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/goals.py +0 -0
  81. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/intent.py +0 -0
  82. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/json_repair.py +0 -0
  83. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/llm_planner.py +0 -0
  84. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/mapper.py +0 -0
  85. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/mavlink_integration.py +0 -0
  86. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/memory.py +0 -0
  87. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/message_bus.py +0 -0
  88. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/metrics.py +0 -0
  89. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/net_guard.py +0 -0
  90. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/overrides.py +0 -0
  91. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/parallel_safety.py +0 -0
  92. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/patches.py +0 -0
  93. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/permissions.py +0 -0
  94. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/plan_cache.py +0 -0
  95. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/planner.py +0 -0
  96. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/providers/__init__.py +0 -0
  97. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/providers/_cache.py +0 -0
  98. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/providers/aether_cloud.py +0 -0
  99. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/providers/anthropic.py +0 -0
  100. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/providers/base.py +0 -0
  101. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/providers/gemini.py +0 -0
  102. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/providers/groq.py +0 -0
  103. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/providers/ollama.py +0 -0
  104. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/providers/openai_compat.py +0 -0
  105. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/providers/openrouter.py +0 -0
  106. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/quiet.py +0 -0
  107. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/security.py +0 -0
  108. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/task_scheduler.py +0 -0
  109. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/telemetry_config.py +0 -0
  110. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/tool_registry.py +0 -0
  111. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/ui.py +0 -0
  112. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/core/visualizer.py +0 -0
  113. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/faults/__init__.py +0 -0
  114. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/faults/fault_detector.py +0 -0
  115. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/faults/fault_injector.py +0 -0
  116. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/parallel/__init__.py +0 -0
  117. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/parallel/executor.py +0 -0
  118. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/simulation/__init__.py +0 -0
  119. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/simulation/environment.py +0 -0
  120. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/simulation/real_perception.py +0 -0
  121. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/simulation/scenarios.py +0 -0
  122. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/skills/__init__.py +0 -0
  123. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/skills/drive_until_obstacle.py +0 -0
  124. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/skills/watch_distance.py +0 -0
  125. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/tui/__init__.py +0 -0
  126. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/tui/app.py +0 -0
  127. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/tui/widgets/__init__.py +0 -0
  128. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/tui/widgets/agent.py +0 -0
  129. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/tui/widgets/common.py +0 -0
  130. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether/tui/widgets/doctor.py +0 -0
  131. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether_robotics.egg-info/SOURCES.txt +0 -0
  132. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether_robotics.egg-info/dependency_links.txt +0 -0
  133. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether_robotics.egg-info/entry_points.txt +0 -0
  134. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether_robotics.egg-info/requires.txt +0 -0
  135. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/aether_robotics.egg-info/top_level.txt +0 -0
  136. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/configs/calibrated_camera_only_20260331_121709.json +0 -0
  137. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/configs/calibrated_camera_only_20260331_122446.json +0 -0
  138. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/configs/calibrated_camera_only_20260401_094544.json +0 -0
  139. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/configs/calibrated_camera_only_20260401_094554.json +0 -0
  140. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/configs/drone_v1.json +0 -0
  141. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/configs/rover_v1.json +0 -0
  142. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/context/aether_definitions.txt +0 -0
  143. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/install.sh +0 -0
  144. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/main.py +0 -0
  145. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/requirements.txt +0 -0
  146. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/setup.cfg +0 -0
  147. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_adapter_gpio_manifests.py +0 -0
  148. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_adapter_override_wiring.py +0 -0
  149. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_adapter_preflight.py +0 -0
  150. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_adapter_resolver_v397.py +0 -0
  151. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_analytics_cli.py +0 -0
  152. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_analytics_module.py +0 -0
  153. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_analytics_wiring.py +0 -0
  154. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_atexit_adapter_shutdown.py +0 -0
  155. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_atexit_reaches_stop_all.py +0 -0
  156. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_auto_adapter.py +0 -0
  157. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_auto_updater.py +0 -0
  158. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_autonomous_cli.py +0 -0
  159. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_autonomous_loop.py +0 -0
  160. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_autonomous_loop_parallel.py +0 -0
  161. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_autonomous_manifest_parity.py +0 -0
  162. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_autonomous_no_between_step_emergency_stop.py +0 -0
  163. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_boot_resolver_flow.py +0 -0
  164. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_calibrate_wizard.py +0 -0
  165. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_calibration.py +0 -0
  166. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_calibration_unit.py +0 -0
  167. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_chaos.py +0 -0
  168. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_cli_doctor.py +0 -0
  169. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_cli_fix.py +0 -0
  170. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_cli_fix_patches.py +0 -0
  171. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_cli_fix_v3_10_6.py +0 -0
  172. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_cli_fix_v3_10_8.py +0 -0
  173. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_cli_fix_visibility.py +0 -0
  174. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_cli_kits.py +0 -0
  175. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_cli_routing.py +0 -0
  176. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_consent_flow.py +0 -0
  177. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_console_script_smoke.py +0 -0
  178. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_correction_agent.py +0 -0
  179. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_decomposer.py +0 -0
  180. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_decomposer_avoids_legacy.py +0 -0
  181. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_decomposer_reactive_routing.py +0 -0
  182. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_decomposer_routing.py +0 -0
  183. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_display_bug_fix.py +0 -0
  184. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_doctor_gpio_allocation.py +0 -0
  185. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_doctor_multi_adapter.py +0 -0
  186. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_drive_until_obstacle.py +0 -0
  187. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_embeddings.py +0 -0
  188. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_entrypoint.py +0 -0
  189. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_feedback_prompt.py +0 -0
  190. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_first_run_onboarding.py +0 -0
  191. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_fix_scope_validation.py +0 -0
  192. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_fix_sees_named_servos.py +0 -0
  193. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_genome.py +0 -0
  194. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_goal_persistence.py +0 -0
  195. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_gopigo3_adapter.py +0 -0
  196. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_gpio_conflict_detection.py +0 -0
  197. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_high_level_task_examples.py +0 -0
  198. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_install_environment.py +0 -0
  199. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_intent_category_fallback.py +0 -0
  200. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_json_repair_and_status.py +0 -0
  201. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_live_providers.py +0 -0
  202. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_map_room_high_level_task.py +0 -0
  203. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_mapper.py +0 -0
  204. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_mavlink_integration.py +0 -0
  205. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_memory_cli.py +0 -0
  206. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_memory_retrieval.py +0 -0
  207. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_migration.py +0 -0
  208. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_multi_adapter_boot_flow.py +0 -0
  209. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_multi_adapter_registration.py +0 -0
  210. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_multi_discovery.py +0 -0
  211. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_named_servo_registration.py +0 -0
  212. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_navigation_engine.py +0 -0
  213. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_oled_false_positive.py +0 -0
  214. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_overrides.py +0 -0
  215. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_overrides_multi_adapter.py +0 -0
  216. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_overrides_schema_v1_2.py +0 -0
  217. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_parallel_decomposer.py +0 -0
  218. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_parallel_executor.py +0 -0
  219. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_parallel_safety_hardware.py +0 -0
  220. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_parallel_validator.py +0 -0
  221. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_pca9685_emergency_stop.py +0 -0
  222. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_pca9685_emergency_stop_hardware.py +0 -0
  223. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_pca9685_hard_off.py +0 -0
  224. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_pca9685_pwm_primitives.py +0 -0
  225. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_pca9685_wake_sequence.py +0 -0
  226. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_persistent_memory.py +0 -0
  227. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_planner_routing.py +0 -0
  228. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_planner_stop_routing.py +0 -0
  229. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_planner_uses_retrieval.py +0 -0
  230. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_posthog_integration.py +0 -0
  231. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_primary_adapter_designation.py +0 -0
  232. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_provider_model_resolution.py +0 -0
  233. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_provider_outage_visibility.py +0 -0
  234. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_providers.py +0 -0
  235. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_quiet.py +0 -0
  236. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_retrieval_planner_contract.py +0 -0
  237. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_robot_json_v1_1.py +0 -0
  238. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_security.py +0 -0
  239. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_security_v520.py +0 -0
  240. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_set_servo_angle_duration.py +0 -0
  241. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_stop_full_off_behavior.py +0 -0
  242. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_stop_servo_sleep_fallback.py +0 -0
  243. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_sunfounder_picarx_adapter.py +0 -0
  244. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_sunfounder_pidog_adapter.py +0 -0
  245. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_synthesizer_fidelity_check.py +0 -0
  246. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_task_scheduler.py +0 -0
  247. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_tier1_motion_cleanup.py +0 -0
  248. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_tool_builder.py +0 -0
  249. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_tool_discovery.py +0 -0
  250. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_tool_discovery_gpio_cleanup.py +0 -0
  251. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_tool_namespacing.py +0 -0
  252. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_tui_app.py +0 -0
  253. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_tui_backward_compat.py +0 -0
  254. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_tui_widgets.py +0 -0
  255. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_ui_autonomous.py +0 -0
  256. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_ui_dashboard.py +0 -0
  257. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_ui_module.py +0 -0
  258. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_ui_wizard.py +0 -0
  259. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_v511_hardware_bugs.py +0 -0
  260. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_v511_ui.py +0 -0
  261. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_v512_feedback_routing.py +0 -0
  262. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_validator_new_tools.py +0 -0
  263. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_validator_rejects_legacy.py +0 -0
  264. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_watch_distance.py +0 -0
  265. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_yahboom_robot_hat_adapter.py +0 -0
  266. {aether_robotics-5.2.2 → aether_robotics-5.2.3}/tests/test_yolo_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-robotics
3
- Version: 5.2.2
3
+ Version: 5.2.3
4
4
  Summary: Autonomous multi-agent robotics system with DRL-First Hybrid FDIR
5
5
  Author: Chahel Paatur
6
6
  License: MIT
@@ -0,0 +1 @@
1
+ 5.2.3
@@ -1,4 +1,11 @@
1
- __version__ = "5.2.2"
1
+ import time as _time
2
+
3
+ #: Wall clock at the very first moment AETHER code runs. boot_timer measures
4
+ #: from here so "python_imports" is attributed rather than showing up as an
5
+ #: unexplained gap between the phase total and wall clock (v5.2.3).
6
+ _IMPORT_START = _time.time()
7
+
8
+ __version__ = "5.2.3"
2
9
  __author__ = "Chahel Paatur"
3
10
 
4
11
  #: Where users should actually be sent. v5.1.2: the GitHub repository is
@@ -907,6 +907,7 @@ def _extract_key_facts(output) -> str:
907
907
 
908
908
  def run_agent(args) -> None:
909
909
  """Real agent mode: prompt-driven loop with live tool execution."""
910
+ from aether.core import boot_timer
910
911
  from aether.core.analytics import check_consent_or_prompt
911
912
  check_consent_or_prompt() # v4.1.0.2: consent gate on every entrypoint
912
913
  from aether.core.tool_registry import ToolRegistry, ToolResult, register_built_tools
@@ -942,14 +943,15 @@ def run_agent(args) -> None:
942
943
  # ── Step 2: Banner already printed in main() ──────────────────────
943
944
 
944
945
  # ── Step 3: AutoUpdater ───────────────────────────────────────────
945
- updater = AutoUpdater()
946
+ with boot_timer.phase("autoupdater"):
947
+ updater = AutoUpdater()
946
948
  AutoUpdater.ensure_version_file(_PROJECT_ROOT)
947
949
  updater.run(auto_update=auto_update, no_update=no_update)
948
950
 
949
951
  # ── Step 4: Capability Discovery ──────────────────────────────────
950
- from aether.core import boot_timer
951
952
  discovery = ToolDiscovery()
952
- discovery.discover()
953
+ with boot_timer.phase("discovery"):
954
+ discovery.discover()
953
955
  # v5.1.1 UI 1: the ~70-line inventory is a debugging surface, not a boot
954
956
  # screen. It stays one `capabilities` command (or AETHER_VERBOSE=1) away,
955
957
  # and `aether doctor` is untouched.
@@ -957,8 +959,10 @@ def run_agent(args) -> None:
957
959
  discovery.print_summary()
958
960
 
959
961
  # ── Step 5: AutoInstaller ─────────────────────────────────────────
960
- installer = AutoInstaller(discovery.manifest)
961
- packages_installed = installer.run(auto_install=auto_install, no_install=no_install)
962
+ with boot_timer.phase("autoinstaller"):
963
+ installer = AutoInstaller(discovery.manifest)
964
+ packages_installed = installer.run(auto_install=auto_install,
965
+ no_install=no_install)
962
966
 
963
967
  # ── Step 6: Re-run discovery if anything was installed ────────────
964
968
  if packages_installed:
@@ -1080,8 +1084,9 @@ def run_agent(args) -> None:
1080
1084
  planner = Planner()
1081
1085
 
1082
1086
  # ── Step 7: Build Tools + Navigation Engine ───────────────────────
1083
- builder = ToolBuilder(discovery.manifest)
1084
- built_tools = builder.build_all()
1087
+ with boot_timer.phase("tool_build"):
1088
+ builder = ToolBuilder(discovery.manifest)
1089
+ built_tools = builder.build_all()
1085
1090
  _print_activity("TOOLS", builder.build_summary())
1086
1091
 
1087
1092
  # OLED startup animation (only on Pi with real hardware attached)
@@ -1093,7 +1098,8 @@ def run_agent(args) -> None:
1093
1098
  except Exception as e:
1094
1099
  _print_activity("OLED", f"startup animation failed: {e}")
1095
1100
 
1096
- nav_engine = NavigationEngine(discovery.manifest, tools=built_tools)
1101
+ with boot_timer.phase("nav_engine"):
1102
+ nav_engine = NavigationEngine(discovery.manifest, tools=built_tools)
1097
1103
  _register_gpio_cleanup(nav_engine) # drain PWMs + GPIO.cleanup on exit
1098
1104
  nav_actions = nav_engine.available_actions()
1099
1105
  _print_activity("NAV", f"Level {nav_engine.level} ({nav_engine.level_name}) — "
@@ -87,6 +87,26 @@ class AutoUpdater:
87
87
  pass
88
88
  return CURRENT_VERSION
89
89
 
90
+ #: v5.2.3: the update check is CACHED for a day. It used to hit PyPI on
91
+ #: every boot — network I/O on the critical path, which is exactly the
92
+ #: user>>real gap that made a 50s boot only 21s of CPU. A version check is
93
+ #: not worth a single millisecond of a robot's startup.
94
+ _CHECK_TTL_S = 24 * 3600
95
+
96
+ def _cached_check(self):
97
+ try:
98
+ from aether.core.providers._cache import cache_get
99
+ return cache_get("update:latest", self._CHECK_TTL_S)
100
+ except Exception:
101
+ return None
102
+
103
+ def _remember_check(self, payload) -> None:
104
+ try:
105
+ from aether.core.providers._cache import cache_set
106
+ cache_set("update:latest", payload)
107
+ except Exception:
108
+ pass
109
+
90
110
  def _fetch_latest_release(self) -> Optional[Dict]:
91
111
  """Latest published version, from PyPI.
92
112
 
@@ -98,6 +118,11 @@ class AutoUpdater:
98
118
 
99
119
  Returns the same {"tag_name", "body"} shape the callers already expect.
100
120
  """
121
+ cached = self._cached_check()
122
+ if isinstance(cached, dict) and cached.get("tag_name"):
123
+ return cached
124
+ if os.environ.get("AETHER_NO_UPDATE_CHECK"):
125
+ return None
101
126
  try:
102
127
  import urllib.request
103
128
  with urllib.request.urlopen(_PYPI_JSON_URL,
@@ -108,8 +133,11 @@ class AutoUpdater:
108
133
  version = str(info.get("version") or "").strip()
109
134
  if not version:
110
135
  return None
111
- return {"tag_name": f"v{version}",
112
- "body": f"See {info.get('project_url') or 'PyPI'} for details."}
136
+ payload = {"tag_name": f"v{version}",
137
+ "body": f"See {info.get('project_url') or 'PyPI'} "
138
+ f"for details."}
139
+ self._remember_check(payload)
140
+ return payload
113
141
 
114
142
  def check_for_update(self) -> Optional[str]:
115
143
  """Check GitHub releases for a newer version.
@@ -75,6 +75,56 @@ def summary_line(limit: int = 6) -> str:
75
75
  return " · ".join(parts) + f" · total {total_ms():.0f}ms"
76
76
 
77
77
 
78
+ #: Wall clock at interpreter start, so the report covers process start to
79
+ #: prompt rather than only the phases someone remembered to wrap.
80
+ try:
81
+ from aether import _IMPORT_START as _PROCESS_START
82
+ except Exception:
83
+ _PROCESS_START = time.time()
84
+
85
+ #: The measured phases must account for at least this fraction of wall clock.
86
+ #: v5.2.2 shipped a timer that wrapped ONE function and then printed
87
+ #: "total 25ms" for a 50,845ms boot — a claim far worse than no instrumentation,
88
+ #: because it said the problem was solved. If coverage is below this, the
89
+ #: report says so instead of quoting a total it cannot justify.
90
+ COVERAGE_FLOOR = 0.95
91
+
92
+
93
+ def wall_ms() -> float:
94
+ return (time.time() - _PROCESS_START) * 1000.0
95
+
96
+
97
+ def coverage() -> float:
98
+ """Fraction of wall clock the measured phases actually account for."""
99
+ wall = wall_ms()
100
+ return (total_ms() / wall) if wall > 0 else 0.0
101
+
102
+
103
+ def coverage_note() -> str:
104
+ """'(wall 50845ms ✓)' or a loud warning that the numbers do not sum."""
105
+ wall = wall_ms()
106
+ if coverage() >= COVERAGE_FLOOR:
107
+ return f"(wall {wall:.0f}ms ✓)"
108
+ unmeasured = wall - total_ms()
109
+ return (f"(wall {wall:.0f}ms — ⚠ {unmeasured:.0f}ms UNMEASURED, "
110
+ f"phases cover only {coverage() * 100:.0f}%)")
111
+
112
+
113
+ def record_import_phase() -> None:
114
+ """Attribute everything before the first timed phase to python_imports.
115
+
116
+ Without this the phases never sum to wall clock and the coverage check
117
+ warns forever, which would train everyone to ignore it — the same way the
118
+ v5.2.2 "total 25ms" line trained us to believe boot was fast.
119
+ """
120
+ if not _enabled or any(p["phase"] == "python_imports" for p in _phases):
121
+ return
122
+ already = total_ms()
123
+ before = wall_ms() - already
124
+ if before > 0:
125
+ _phases.insert(0, {"phase": "python_imports", "ms": round(before, 1)})
126
+
127
+
78
128
  def report() -> None:
79
129
  """Print the summary under AETHER_VERBOSE=1 and always emit analytics.
80
130
 
@@ -84,9 +134,13 @@ def report() -> None:
84
134
  """
85
135
  if not _phases:
86
136
  return
137
+ record_import_phase()
87
138
  try:
88
139
  if os.environ.get("AETHER_VERBOSE"):
89
- print(f" [BOOT] {summary_line()}")
140
+ for entry in sorted(_phases, key=lambda p: -p["ms"]):
141
+ print(f" [BOOT] {entry['phase']:<24} {entry['ms']:>8.0f}ms")
142
+ print(f" [BOOT] {'TOTAL':<24} {total_ms():>8.0f}ms "
143
+ f"{coverage_note()}")
90
144
  except Exception:
91
145
  pass
92
146
  try:
@@ -94,5 +148,9 @@ def report() -> None:
94
148
  for entry in _phases:
95
149
  track("boot_latency", {"phase": entry["phase"], "ms": entry["ms"]})
96
150
  track("boot_latency", {"phase": "total", "ms": total_ms()})
151
+ # Coverage travels with the numbers: a total that accounts for 3% of
152
+ # wall clock must not be read as a fast boot.
153
+ track("boot_latency", {"phase": "wall", "ms": round(wall_ms(), 1),
154
+ "coverage": round(coverage(), 3)})
97
155
  except Exception:
98
156
  pass
@@ -149,22 +149,44 @@ class _CameraNav:
149
149
  cv2 is not installed.
150
150
  """
151
151
 
152
+ @property
153
+ def _backend_name(self):
154
+ self._resolve_nav_backend()
155
+ return self._backend_name_cached
156
+
157
+ @property
158
+ def _backend_mod(self):
159
+ self._resolve_nav_backend()
160
+ return self._backend_mod_cached
161
+
162
+ def _resolve_nav_backend(self):
163
+ if self._nav_backend_resolved:
164
+ return
165
+ self._nav_backend_resolved = True
166
+ (self._backend_name_cached,
167
+ self._backend_mod_cached) = _detect_nav_camera_backend()
168
+ # Logged here, not in __init__: reading the backend just to name it
169
+ # was what imported cv2 on every boot.
170
+ if self._backend_name_cached == "cv2":
171
+ self._cv2 = self._backend_mod_cached
172
+ _nav_cam_log.info("Using cv2 backend")
173
+ elif self._backend_name_cached == "picamera2":
174
+ _nav_cam_log.info("Using picamera2 backend")
175
+ else:
176
+ _nav_cam_log.warning("No camera backend available")
177
+
152
178
  def __init__(self):
153
- self._backend_name, self._backend_mod = _detect_nav_camera_backend()
179
+ # v5.2.3: deferred — this imported cv2 on every boot for a camera the
180
+ # session may never use.
181
+ self._backend_name_cached = None
182
+ self._backend_mod_cached = None
183
+ self._nav_backend_resolved = False
154
184
  self._cv2 = None
155
185
  self._cap = None # cv2 VideoCapture
156
186
  self._picam = None # picamera2 instance
157
187
  self._picam_started = False
158
188
  self._prev_gray = None
159
189
 
160
- if self._backend_name == "cv2":
161
- self._cv2 = self._backend_mod
162
- _nav_cam_log.info("Using cv2 backend")
163
- elif self._backend_name == "picamera2":
164
- _nav_cam_log.info("Using picamera2 backend")
165
- else:
166
- _nav_cam_log.warning("No camera backend available")
167
-
168
190
  atexit.register(self.close)
169
191
 
170
192
  def _ensure_picamera(self) -> Optional[str]:
@@ -187,22 +187,47 @@ class CameraTool:
187
187
  Camera is initialized once (singleton) and released on shutdown.
188
188
  """
189
189
 
190
+ @property
191
+ def _backend_name(self):
192
+ """Resolved on first access (v5.2.3 lazy import)."""
193
+ self._resolve_backend()
194
+ return self._backend_name_cached
195
+
196
+ @property
197
+ def _backend(self):
198
+ self._resolve_backend()
199
+ return self._backend_cached
200
+
201
+ def _resolve_backend(self):
202
+ if self._backend_resolved:
203
+ return
204
+ self._backend_resolved = True
205
+ (self._backend_name_cached,
206
+ self._backend_cached) = _detect_camera_backend()
207
+ # Logged here rather than in __init__: reading the backend just to say
208
+ # which one it is was what imported cv2 on every boot.
209
+ if self._backend_name_cached == "cv2":
210
+ self._cv2 = self._backend_cached
211
+ _cam_log.info("Using cv2 backend")
212
+ elif self._backend_name_cached == "picamera2":
213
+ _cam_log.info("Using picamera2 backend")
214
+ else:
215
+ _cam_log.warning(
216
+ "No camera backend available (neither cv2 nor picamera2)")
217
+
190
218
  def __init__(self):
191
- self._backend_name, self._backend = _detect_camera_backend()
219
+ # v5.2.3: backend detection imports cv2 (or picamera2), which is
220
+ # ~0.1s on a Mac and far worse on a Pi. Deferred to first camera use —
221
+ # a session that never touches the camera never pays for it.
222
+ self._backend_name_cached = None
223
+ self._backend_cached = None
224
+ self._backend_resolved = False
192
225
  self._cv2 = None
193
226
  self._cap = None # cv2 VideoCapture
194
227
  self._picam = None # picamera2 instance
195
228
  self._picam_started = False
196
229
  self._prev_frame = None # for motion detection (numpy array)
197
230
 
198
- if self._backend_name == "cv2":
199
- self._cv2 = self._backend
200
- _cam_log.info("Using cv2 backend")
201
- elif self._backend_name == "picamera2":
202
- _cam_log.info("Using picamera2 backend")
203
- else:
204
- _cam_log.warning("No camera backend available (neither cv2 nor picamera2)")
205
-
206
231
  atexit.register(self.close)
207
232
 
208
233
  # ── picamera2 singleton management ────────────────────────────────
@@ -484,15 +509,24 @@ class YOLOTool:
484
509
 
485
510
  def __init__(self, camera_tool: Optional["CameraTool"] = None):
486
511
  self._model = None
512
+ # v5.2.3: ultralytics is NOT imported here. It pulls torch, so
513
+ # constructing this tool at boot cost the full torch import for a
514
+ # detector most sessions never invoke.
487
515
  self._ultralytics = None
488
516
  self._camera = camera_tool
489
- self._has_ultralytics = False
490
- try:
491
- import ultralytics
492
- self._ultralytics = ultralytics
493
- self._has_ultralytics = True
494
- except ImportError:
495
- pass
517
+ self._ultra_resolved = False
518
+
519
+ @property
520
+ def _has_ultralytics(self) -> bool:
521
+ """Resolved on first access (v5.2.3 lazy import)."""
522
+ if not self._ultra_resolved:
523
+ self._ultra_resolved = True
524
+ try:
525
+ import ultralytics
526
+ self._ultralytics = ultralytics
527
+ except ImportError:
528
+ self._ultralytics = None
529
+ return self._ultralytics is not None
496
530
 
497
531
  def _ensure_model(self):
498
532
  """Lazy-load the YOLOv8n model on first use."""
@@ -907,19 +941,32 @@ class GPUTool:
907
941
  """GPU operations via PyTorch. Built when torch + gpu available."""
908
942
 
909
943
  def __init__(self):
944
+ # v5.2.3: torch is NOT imported here. Constructing this tool at boot
945
+ # used to pull the whole of torch — ~0.6s on a dev Mac and 10-20s on a
946
+ # Pi 4 — before the prompt appeared, for a GPU tool most sessions
947
+ # never call. Resolution is deferred to first use.
910
948
  self._torch = None
911
949
  self._device = None
950
+ self._resolved = False
951
+
952
+ def _ensure_torch(self):
953
+ """Import torch on first actual use. Idempotent."""
954
+ if self._resolved:
955
+ return self._torch
956
+ self._resolved = True
912
957
  try:
913
958
  import torch
914
959
  self._torch = torch
915
960
  if torch.cuda.is_available():
916
961
  self._device = torch.device("cuda")
917
- elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
962
+ elif hasattr(torch.backends, "mps") \
963
+ and torch.backends.mps.is_available():
918
964
  self._device = torch.device("mps")
919
965
  else:
920
966
  self._device = torch.device("cpu")
921
967
  except ImportError:
922
968
  pass
969
+ return self._torch
923
970
 
924
971
  def get_gpu_memory(self) -> Dict:
925
972
  """Report GPU memory usage."""
@@ -1071,6 +1071,13 @@ class ToolDiscovery:
1071
1071
  print(" [DISCOVERY] Using cached hardware profile "
1072
1072
  "(fingerprint match)")
1073
1073
  self._manifest = cached
1074
+ # v5.2.3: restore the score too. v5.2.2 returned the manifest
1075
+ # without it, so a cached boot displayed "Capability 0/100" while
1076
+ # a cold boot on the same robot showed 79/100.
1077
+ try:
1078
+ self._score = int(cached.get("capability_score", 0) or 0)
1079
+ except Exception:
1080
+ self._score = 0
1074
1081
  return self._manifest
1075
1082
 
1076
1083
  with boot_timer.phase("platform"):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-robotics
3
- Version: 5.2.2
3
+ Version: 5.2.3
4
4
  Summary: Autonomous multi-agent robotics system with DRL-First Hybrid FDIR
5
5
  Author: Chahel Paatur
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aether-robotics"
7
- version = "5.2.2"
7
+ version = "5.2.3"
8
8
  description = "Autonomous multi-agent robotics system with DRL-First Hybrid FDIR"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -502,3 +502,142 @@ def test_one_shot_flag_exists_and_skips_the_repl():
502
502
  assert "enable_readline" in src[idx_repl - 400:idx_repl] or True
503
503
  # and it must not block on the feedback prompt
504
504
  assert "session_ended()" in src[idx_oneshot:idx_repl]
505
+
506
+
507
+ # ── v5.2.3: instrumentation must not lie ───────────────────────────────
508
+
509
+ def test_coverage_is_reported_when_phases_do_not_sum():
510
+ """v5.2.2 wrapped ONE function and printed "total 25ms" for a 50,845ms
511
+ boot. A total that accounts for 3% of wall clock is worse than no
512
+ instrumentation, because it says the problem is solved."""
513
+ from aether.core import boot_timer
514
+
515
+ boot_timer.reset(); boot_timer.enable()
516
+ boot_timer.record("fingerprint", 25.0)
517
+ boot_timer._PROCESS_START = time.time() - 50.845 # 50,845ms of wall
518
+
519
+ assert boot_timer.coverage() < 0.05
520
+ note = boot_timer.coverage_note()
521
+ assert "UNMEASURED" in note and "⚠" in note
522
+ assert "3%" in note or "0%" in note
523
+ boot_timer.reset()
524
+
525
+
526
+ def test_healthy_coverage_reports_a_tick():
527
+ from aether.core import boot_timer
528
+ boot_timer.reset(); boot_timer.enable()
529
+ boot_timer._PROCESS_START = time.time() - 1.0
530
+ boot_timer.record("everything", 1000.0)
531
+ assert boot_timer.coverage() >= boot_timer.COVERAGE_FLOOR
532
+ assert "✓" in boot_timer.coverage_note()
533
+ boot_timer.reset()
534
+
535
+
536
+ def test_import_time_is_attributed_not_left_as_a_gap():
537
+ from aether.core import boot_timer
538
+ boot_timer.reset(); boot_timer.enable()
539
+ boot_timer._PROCESS_START = time.time() - 2.0
540
+ boot_timer.record("tool_build", 500.0)
541
+ boot_timer.record_import_phase()
542
+
543
+ names = [p["phase"] for p in boot_timer.phases()]
544
+ assert names[0] == "python_imports", "import cost leads the report"
545
+ assert boot_timer.coverage() >= boot_timer.COVERAGE_FLOOR
546
+ boot_timer.reset()
547
+
548
+
549
+ def test_import_phase_is_recorded_once():
550
+ from aether.core import boot_timer
551
+ boot_timer.reset(); boot_timer.enable()
552
+ boot_timer._PROCESS_START = time.time() - 1.0
553
+ boot_timer.record("x", 100.0)
554
+ boot_timer.record_import_phase()
555
+ boot_timer.record_import_phase()
556
+ assert sum(p["phase"] == "python_imports"
557
+ for p in boot_timer.phases()) == 1
558
+ boot_timer.reset()
559
+
560
+
561
+ # ── v5.2.3: heavy imports must not happen at boot ──────────────────────
562
+
563
+ def test_gpu_tool_does_not_import_torch_at_construction():
564
+ """torch is ~0.6s on a dev Mac and 10-20s on a Pi 4. Constructing a tool
565
+ most sessions never call must not pay for it."""
566
+ import sys
567
+ from aether.core.tool_builder import GPUTool
568
+
569
+ had_torch = "torch" in sys.modules
570
+ tool = GPUTool()
571
+ if not had_torch:
572
+ assert "torch" not in sys.modules, "GPUTool imported torch at boot"
573
+ assert tool._resolved is False
574
+
575
+
576
+ def test_camera_tool_does_not_detect_backend_at_construction():
577
+ """Backend detection imports cv2 / picamera2."""
578
+ from aether.core.tool_builder import CameraTool
579
+ tool = CameraTool()
580
+ assert tool._backend_resolved is False, \
581
+ "CameraTool resolved its backend (and imported cv2) at boot"
582
+
583
+
584
+ def test_camera_backend_still_resolves_on_use():
585
+ from aether.core.tool_builder import CameraTool
586
+ tool = CameraTool()
587
+ _ = tool._backend_name # property triggers resolution
588
+ assert tool._backend_resolved is True
589
+
590
+
591
+ def test_no_heavy_import_reaches_module_scope():
592
+ """The hard rule: nothing heavyweight before the prompt."""
593
+ import ast
594
+ import os
595
+
596
+ heavy = {"torch", "cv2", "picamera2", "ultralytics",
597
+ "sentence_transformers"}
598
+ offenders = []
599
+ for root, _dirs, files in os.walk("aether"):
600
+ if "__pycache__" in root:
601
+ continue
602
+ for name in files:
603
+ if not name.endswith(".py"):
604
+ continue
605
+ path = os.path.join(root, name)
606
+ with open(path, encoding="utf-8", errors="replace") as handle:
607
+ try:
608
+ tree = ast.parse(handle.read())
609
+ except SyntaxError:
610
+ continue
611
+ for node in tree.body: # MODULE SCOPE only
612
+ if isinstance(node, ast.Import):
613
+ names = [a.name.split(".")[0] for a in node.names]
614
+ elif isinstance(node, ast.ImportFrom):
615
+ names = [(node.module or "").split(".")[0]]
616
+ else:
617
+ continue
618
+ if heavy.intersection(names):
619
+ offenders.append(f"{path}: {names}")
620
+ assert not offenders, f"heavy imports at module scope: {offenders}"
621
+
622
+
623
+ def test_update_check_is_cached_and_can_be_disabled(monkeypatch):
624
+ """Network I/O on the boot path is the user<<real gap. Never block."""
625
+ from aether.core.auto_updater import AutoUpdater
626
+
627
+ monkeypatch.setenv("AETHER_NO_UPDATE_CHECK", "1")
628
+ monkeypatch.setattr(AutoUpdater, "_cached_check", lambda self: None)
629
+ updater = AutoUpdater(current_version="1.0")
630
+ assert updater._fetch_latest_release() is None
631
+
632
+
633
+ def test_a_cached_update_check_skips_the_network(monkeypatch):
634
+ from aether.core.auto_updater import AutoUpdater
635
+
636
+ monkeypatch.setattr(AutoUpdater, "_cached_check",
637
+ lambda self: {"tag_name": "v9.9.9", "body": ""})
638
+ def explode(*a, **k):
639
+ raise AssertionError("network was contacted despite a cache hit")
640
+ monkeypatch.setattr("urllib.request.urlopen", explode)
641
+
642
+ updater = AutoUpdater(current_version="1.0")
643
+ assert updater._fetch_latest_release()["tag_name"] == "v9.9.9"
@@ -1 +0,0 @@
1
- 5.2.2
File without changes