aether-robotics 4.2.0a2__tar.gz → 4.2.0a4__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 (213) hide show
  1. {aether_robotics-4.2.0a2/aether_robotics.egg-info → aether_robotics-4.2.0a4}/PKG-INFO +1 -1
  2. aether_robotics-4.2.0a4/VERSION +1 -0
  3. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/__init__.py +1 -1
  4. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/app.py +21 -1
  5. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/tui/app.py +49 -11
  6. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/tui/widgets/__init__.py +4 -3
  7. aether_robotics-4.2.0a4/aether/tui/widgets/agent.py +357 -0
  8. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/tui/widgets/common.py +24 -1
  9. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4/aether_robotics.egg-info}/PKG-INFO +1 -1
  10. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/pyproject.toml +1 -1
  11. aether_robotics-4.2.0a4/tests/test_tui_widgets.py +320 -0
  12. aether_robotics-4.2.0a2/VERSION +0 -1
  13. aether_robotics-4.2.0a2/aether/tui/widgets/agent.py +0 -132
  14. aether_robotics-4.2.0a2/tests/test_tui_widgets.py +0 -131
  15. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/LICENSE +0 -0
  16. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/MANIFEST.in +0 -0
  17. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/README.md +0 -0
  18. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/__main__.py +0 -0
  19. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/actions/__init__.py +0 -0
  20. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/actions/abstract_actions.py +0 -0
  21. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/__init__.py +0 -0
  22. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/_generator.py +0 -0
  23. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/adeept_hat_v3.py +0 -0
  24. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/base_adapter.py +0 -0
  25. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/drone_adapter.py +0 -0
  26. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/freenove_4wd.py +0 -0
  27. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/freenove_mecanum.py +0 -0
  28. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/gopigo3.py +0 -0
  29. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/l298n_direct_gpio.py +0 -0
  30. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/pca9685_generic.py +0 -0
  31. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/rover_adapter.py +0 -0
  32. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/sunfounder_picarx.py +0 -0
  33. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/sunfounder_pidog.py +0 -0
  34. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/universal_adapter.py +0 -0
  35. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/waveshare_motor_driver.py +0 -0
  36. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/adapters/yahboom_robot_hat.py +0 -0
  37. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/__init__.py +0 -0
  38. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/adaptation_agent.py +0 -0
  39. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/camera_agent.py +0 -0
  40. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/correction_agent.py +0 -0
  41. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/execution_agent.py +0 -0
  42. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/fault_agent.py +0 -0
  43. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/memory_agent.py +0 -0
  44. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/movement_agent.py +0 -0
  45. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/navigation_agent.py +0 -0
  46. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/perception_agent.py +0 -0
  47. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/planner_agent.py +0 -0
  48. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/power_agent.py +0 -0
  49. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/task_manager.py +0 -0
  50. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/agents/thermal_agent.py +0 -0
  51. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/capabilities/__init__.py +0 -0
  52. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/capabilities/capability_loader.py +0 -0
  53. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/cli/__init__.py +0 -0
  54. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/cli/analytics.py +0 -0
  55. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/cli/autonomous.py +0 -0
  56. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/cli/calibrate.py +0 -0
  57. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/cli/doctor.py +0 -0
  58. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/cli/fix.py +0 -0
  59. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/cli/kits.py +0 -0
  60. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/cli/kits_install_recipes/freenove_4wd.yaml +0 -0
  61. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/cli/kits_install_recipes/gopigo3.yaml +0 -0
  62. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/cli/kits_install_recipes/sunfounder_picarx.yaml +0 -0
  63. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/cli/kits_install_recipes/sunfounder_pidog.yaml +0 -0
  64. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/cli/kits_install_recipes/yahboom_robot_hat.yaml +0 -0
  65. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/__init__.py +0 -0
  66. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/analytics.py +0 -0
  67. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/auto_installer.py +0 -0
  68. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/auto_updater.py +0 -0
  69. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/autonomous.py +0 -0
  70. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/banner.py +0 -0
  71. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/calibration.py +0 -0
  72. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/decomposer.py +0 -0
  73. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/executor.py +0 -0
  74. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/feedback.py +0 -0
  75. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/genome.py +0 -0
  76. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/goal_parser.py +0 -0
  77. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/goals.py +0 -0
  78. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/llm_planner.py +0 -0
  79. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/mapper.py +0 -0
  80. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/mavlink_integration.py +0 -0
  81. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/memory.py +0 -0
  82. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/message_bus.py +0 -0
  83. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/metrics.py +0 -0
  84. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/navigation_engine.py +0 -0
  85. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/overrides.py +0 -0
  86. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/patches.py +0 -0
  87. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/planner.py +0 -0
  88. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/security.py +0 -0
  89. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/task_scheduler.py +0 -0
  90. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/telemetry_config.py +0 -0
  91. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/tool_builder.py +0 -0
  92. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/tool_discovery.py +0 -0
  93. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/tool_registry.py +0 -0
  94. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/ui.py +0 -0
  95. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/core/visualizer.py +0 -0
  96. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/faults/__init__.py +0 -0
  97. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/faults/fault_detector.py +0 -0
  98. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/faults/fault_injector.py +0 -0
  99. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/parallel/__init__.py +0 -0
  100. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/parallel/executor.py +0 -0
  101. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/simulation/__init__.py +0 -0
  102. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/simulation/environment.py +0 -0
  103. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/simulation/real_perception.py +0 -0
  104. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/simulation/scenarios.py +0 -0
  105. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/skills/__init__.py +0 -0
  106. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/skills/drive_until_obstacle.py +0 -0
  107. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/tui/__init__.py +0 -0
  108. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether/tui/widgets/doctor.py +0 -0
  109. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether_robotics.egg-info/SOURCES.txt +0 -0
  110. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether_robotics.egg-info/dependency_links.txt +0 -0
  111. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether_robotics.egg-info/entry_points.txt +0 -0
  112. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether_robotics.egg-info/requires.txt +0 -0
  113. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/aether_robotics.egg-info/top_level.txt +0 -0
  114. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/configs/calibrated_camera_only_20260331_121709.json +0 -0
  115. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/configs/calibrated_camera_only_20260331_122446.json +0 -0
  116. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/configs/calibrated_camera_only_20260401_094544.json +0 -0
  117. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/configs/calibrated_camera_only_20260401_094554.json +0 -0
  118. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/configs/drone_v1.json +0 -0
  119. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/configs/rover_v1.json +0 -0
  120. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/context/aether_definitions.txt +0 -0
  121. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/install.sh +0 -0
  122. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/main.py +0 -0
  123. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/requirements.txt +0 -0
  124. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/setup.cfg +0 -0
  125. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_adapter_gpio_manifests.py +0 -0
  126. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_adapter_override_wiring.py +0 -0
  127. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_adapter_preflight.py +0 -0
  128. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_adapter_resolver_v397.py +0 -0
  129. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_analytics_cli.py +0 -0
  130. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_analytics_module.py +0 -0
  131. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_analytics_wiring.py +0 -0
  132. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_atexit_adapter_shutdown.py +0 -0
  133. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_atexit_reaches_stop_all.py +0 -0
  134. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_auto_adapter.py +0 -0
  135. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_auto_updater.py +0 -0
  136. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_autonomous_cli.py +0 -0
  137. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_autonomous_loop.py +0 -0
  138. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_autonomous_no_between_step_emergency_stop.py +0 -0
  139. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_boot_resolver_flow.py +0 -0
  140. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_calibrate_wizard.py +0 -0
  141. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_calibration.py +0 -0
  142. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_calibration_unit.py +0 -0
  143. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_chaos.py +0 -0
  144. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_cli_doctor.py +0 -0
  145. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_cli_fix.py +0 -0
  146. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_cli_fix_patches.py +0 -0
  147. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_cli_fix_v3_10_6.py +0 -0
  148. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_cli_fix_v3_10_8.py +0 -0
  149. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_cli_fix_visibility.py +0 -0
  150. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_cli_kits.py +0 -0
  151. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_cli_routing.py +0 -0
  152. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_consent_flow.py +0 -0
  153. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_correction_agent.py +0 -0
  154. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_decomposer.py +0 -0
  155. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_decomposer_avoids_legacy.py +0 -0
  156. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_decomposer_routing.py +0 -0
  157. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_display_bug_fix.py +0 -0
  158. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_doctor_gpio_allocation.py +0 -0
  159. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_doctor_multi_adapter.py +0 -0
  160. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_drive_until_obstacle.py +0 -0
  161. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_entrypoint.py +0 -0
  162. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_fix_scope_validation.py +0 -0
  163. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_fix_sees_named_servos.py +0 -0
  164. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_genome.py +0 -0
  165. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_goal_persistence.py +0 -0
  166. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_gopigo3_adapter.py +0 -0
  167. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_gpio_conflict_detection.py +0 -0
  168. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_high_level_task_examples.py +0 -0
  169. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_map_room_high_level_task.py +0 -0
  170. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_mapper.py +0 -0
  171. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_mavlink_integration.py +0 -0
  172. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_multi_adapter_boot_flow.py +0 -0
  173. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_multi_adapter_registration.py +0 -0
  174. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_multi_discovery.py +0 -0
  175. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_named_servo_registration.py +0 -0
  176. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_navigation_engine.py +0 -0
  177. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_overrides.py +0 -0
  178. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_overrides_multi_adapter.py +0 -0
  179. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_overrides_schema_v1_2.py +0 -0
  180. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_parallel_executor.py +0 -0
  181. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_pca9685_emergency_stop.py +0 -0
  182. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_pca9685_emergency_stop_hardware.py +0 -0
  183. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_pca9685_hard_off.py +0 -0
  184. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_pca9685_pwm_primitives.py +0 -0
  185. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_pca9685_wake_sequence.py +0 -0
  186. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_persistent_memory.py +0 -0
  187. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_planner_routing.py +0 -0
  188. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_planner_stop_routing.py +0 -0
  189. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_posthog_integration.py +0 -0
  190. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_primary_adapter_designation.py +0 -0
  191. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_robot_json_v1_1.py +0 -0
  192. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_security.py +0 -0
  193. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_set_servo_angle_duration.py +0 -0
  194. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_stop_full_off_behavior.py +0 -0
  195. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_stop_servo_sleep_fallback.py +0 -0
  196. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_sunfounder_picarx_adapter.py +0 -0
  197. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_sunfounder_pidog_adapter.py +0 -0
  198. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_synthesizer_fidelity_check.py +0 -0
  199. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_task_scheduler.py +0 -0
  200. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_tier1_motion_cleanup.py +0 -0
  201. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_tool_builder.py +0 -0
  202. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_tool_discovery.py +0 -0
  203. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_tool_discovery_gpio_cleanup.py +0 -0
  204. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_tool_namespacing.py +0 -0
  205. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_tui_app.py +0 -0
  206. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_tui_backward_compat.py +0 -0
  207. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_ui_autonomous.py +0 -0
  208. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_ui_dashboard.py +0 -0
  209. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_ui_module.py +0 -0
  210. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_ui_wizard.py +0 -0
  211. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_validator_rejects_legacy.py +0 -0
  212. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_yahboom_robot_hat_adapter.py +0 -0
  213. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a4}/tests/test_yolo_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-robotics
3
- Version: 4.2.0a2
3
+ Version: 4.2.0a4
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
+ 4.2.0a4
@@ -1,4 +1,4 @@
1
- __version__ = "4.2.0a2"
1
+ __version__ = "4.2.0a4"
2
2
  __author__ = "Chahel Paatur"
3
3
 
4
4
  # v3.9.6: one-time scrub of any secrets that leaked into experience memory in
@@ -1719,7 +1719,8 @@ def _summarize_output(output) -> str:
1719
1719
 
1720
1720
  def _execute_objective(objective: str, registry, llm_planner, corrector,
1721
1721
  parser_obj, manifest, recent_memory,
1722
- available, fix_mode: bool = False) -> Dict:
1722
+ available, fix_mode: bool = False,
1723
+ on_step=None) -> Dict:
1723
1724
  """Execute a single objective and return structured result.
1724
1725
 
1725
1726
  Used by agent mode (interactive), server mode (HTTP), and `aether --fix`
@@ -1755,6 +1756,20 @@ def _execute_objective(objective: str, registry, llm_planner, corrector,
1755
1756
  if fix_mode:
1756
1757
  print(f" [agent] planning: {objective}")
1757
1758
 
1759
+ # v4.2.0a3: optional live-progress hook (TUI). Phases: "plan" (full plan
1760
+ # list, once), "start" (before each step), "done" (after each step).
1761
+ # Presentation-only — a hook failure must never affect execution.
1762
+ def _report(phase: str, data: Dict) -> None:
1763
+ if on_step is None:
1764
+ return
1765
+ try:
1766
+ on_step(phase, data)
1767
+ except Exception:
1768
+ pass
1769
+
1770
+ _report("plan", {"plan": plan, "source": plan_source,
1771
+ "objective": objective})
1772
+
1758
1773
  # Execute plan steps
1759
1774
  step_faults = 0
1760
1775
  prev_output = None
@@ -1767,6 +1782,8 @@ def _execute_objective(objective: str, registry, llm_planner, corrector,
1767
1782
  for i, action in enumerate(plan, 1):
1768
1783
  tool_name = action["tool"]
1769
1784
  params = dict(action.get("params", {}))
1785
+ _report("start", {"index": i, "total": len(plan),
1786
+ "tool": tool_name, "params": dict(params)})
1770
1787
 
1771
1788
  # Pipe previous output
1772
1789
  pipe_key = action.get("pipe_input")
@@ -1854,6 +1871,7 @@ def _execute_objective(objective: str, registry, llm_planner, corrector,
1854
1871
 
1855
1872
  action_log.append(entry)
1856
1873
  step_outputs.append(str(prev_output) if prev_output else None)
1874
+ _report("done", {"index": i, "total": len(plan), "entry": dict(entry)})
1857
1875
 
1858
1876
  if fix_mode:
1859
1877
  from aether.core import ui
@@ -1976,6 +1994,8 @@ def bootstrap_agent_state_for_fix() -> dict:
1976
1994
  "parser": parser,
1977
1995
  "manifest": manifest,
1978
1996
  "available": registry.available_tools(),
1997
+ # v4.2.0a3: adapter identity for the TUI status bar / boot detail.
1998
+ "adapter_id": _adapter_id,
1979
1999
  }
1980
2000
 
1981
2001
 
@@ -60,8 +60,8 @@ AETHER_THEME = Theme(
60
60
  )
61
61
 
62
62
  from aether import __version__
63
- from aether.tui.widgets import (AgentWidget, DoctorWidget, PlaceholderWidget,
64
- WelcomeWidget)
63
+ from aether.tui.widgets import (AgentWidget, DoctorWidget, MascotWidget,
64
+ PlaceholderWidget, WelcomeWidget)
65
65
 
66
66
  # (mode id, sidebar label, phase-1 availability)
67
67
  MODES = [
@@ -140,9 +140,11 @@ class AetherApp(App):
140
140
  #header-bar { dock: top; height: 1; background: ansi_default;
141
141
  padding: 0 1; }
142
142
  #body { height: 1fr; background: ansi_default; }
143
- #sidebar { width: 14; background: ansi_default; border: none;
144
- padding: 0 1; }
143
+ #sidebar-col { width: 17; background: ansi_default; }
144
+ #sidebar { width: 100%; height: 1fr; background: ansi_default;
145
+ border: none; padding: 0 1; }
145
146
  #sidebar ListItem { background: ansi_default; padding: 0 1; }
147
+ #mascot { dock: bottom; }
146
148
  #main-switcher { width: 1fr; border: none; background: ansi_default;
147
149
  padding: 0 1; }
148
150
  #status-bar { height: 1; background: ansi_default;
@@ -165,15 +167,27 @@ class AetherApp(App):
165
167
  super().__init__(**kwargs)
166
168
  self._session_start = time.monotonic()
167
169
  self._telemetry_enabled: Optional[bool] = None
170
+ self._captured_prints: list = [] # tail of swallowed print() output
171
+ self._busy_spin = 0
172
+
173
+ def on_print(self, event) -> None:
174
+ """Swallow print() output from the classic pipeline (see on_mount);
175
+ keep the last 200 lines for debugging."""
176
+ text = event.text.rstrip()
177
+ if text:
178
+ self._captured_prints.append(text)
179
+ del self._captured_prints[:-200]
168
180
 
169
181
  # ── layout ─────────────────────────────────────────────────────────
170
182
  def compose(self) -> ComposeResult:
171
183
  yield Static(id="header-bar")
172
184
  with Horizontal(id="body"):
173
- yield ListView(
174
- *[ListItem(Label(label), id=f"nav-{mode_id}")
175
- for mode_id, label, _ in MODES],
176
- id="sidebar")
185
+ with Vertical(id="sidebar-col"):
186
+ yield ListView(
187
+ *[ListItem(Label(label), id=f"nav-{mode_id}")
188
+ for mode_id, label, _ in MODES],
189
+ id="sidebar")
190
+ yield MascotWidget(id="mascot")
177
191
  with ContentSwitcher(initial="welcome", id="main-switcher"):
178
192
  yield WelcomeWidget(id="welcome")
179
193
  yield AgentWidget(id="agent")
@@ -187,6 +201,11 @@ class AetherApp(App):
187
201
  def on_mount(self) -> None:
188
202
  self.register_theme(AETHER_THEME)
189
203
  self.theme = "aether"
204
+ # v4.2.0a4: the classic pipeline (discovery, adapters, planner) does
205
+ # raw print()s; uncaptured they hit the terminal under the TUI and
206
+ # flash/corrupt the screen until the next repaint. Capture them
207
+ # app-wide; keep the tail for debugging.
208
+ self.begin_capture_print(self)
190
209
  try:
191
210
  from aether.core.analytics import load_telemetry_config
192
211
  self._telemetry_enabled = bool(
@@ -196,6 +215,7 @@ class AetherApp(App):
196
215
  self._refresh_header()
197
216
  self._refresh_status()
198
217
  self.set_interval(1.0, self._tick_session)
218
+ self.set_interval(0.12, self._tick_busy)
199
219
  self.query_one("#sidebar", ListView).focus()
200
220
 
201
221
  # ── reactive watchers ──────────────────────────────────────────────
@@ -220,6 +240,13 @@ class AetherApp(App):
220
240
  def _tick_session(self) -> None:
221
241
  self.session_seconds = int(time.monotonic() - self._session_start)
222
242
 
243
+ def _tick_busy(self) -> None:
244
+ """Animate the status-bar spinner while something is in flight."""
245
+ if self.busy_text:
246
+ from aether.tui.widgets.agent import SPINNER
247
+ self._busy_spin = (self._busy_spin + 1) % len(SPINNER)
248
+ self._refresh_status()
249
+
223
250
  def _refresh_header(self) -> None:
224
251
  adapters = (f"{self.adapter_count} adapter"
225
252
  f"{'s' if self.adapter_count != 1 else ''}")
@@ -234,8 +261,13 @@ class AetherApp(App):
234
261
  m, s = divmod(self.session_seconds, 60)
235
262
  telemetry = {True: "enabled", False: "disabled",
236
263
  None: "unknown"}[self._telemetry_enabled]
237
- busy = f" • [yellow]⣾ {self.busy_text}[/yellow]" if self.busy_text \
238
- else ""
264
+ if self.busy_text:
265
+ from aether.tui.widgets.agent import SPINNER
266
+ frame = SPINNER[self._busy_spin]
267
+ busy = (f" • [{AETHER_ORANGE}]{frame} "
268
+ f"{self.busy_text}[/{AETHER_ORANGE}]")
269
+ else:
270
+ busy = ""
239
271
  try:
240
272
  self.query_one("#status-bar", Static).update(
241
273
  f"● {self.adapters_line} • Telemetry: {telemetry} • "
@@ -266,8 +298,14 @@ class AetherApp(App):
266
298
  return
267
299
  except Exception:
268
300
  pass
301
+ # Single-adapter path (load_tier1 doesn't fill the active list).
302
+ adapter_id = state.get("adapter_id")
269
303
  n = len(state.get("available", []))
270
- self.adapters_line = f"registry ready ({n} tools)"
304
+ if adapter_id:
305
+ self.adapters_line = f"{adapter_id} (primary) · {n} tools"
306
+ self.adapter_count = 1
307
+ else:
308
+ self.adapters_line = f"registry ready ({n} tools)"
271
309
 
272
310
  # ── mode switching ─────────────────────────────────────────────────
273
311
  def activate_mode(self, mode_id: str) -> None:
@@ -5,7 +5,8 @@ render a PlaceholderWidget until phase 2/3.
5
5
  """
6
6
  from aether.tui.widgets.agent import AgentWidget
7
7
  from aether.tui.widgets.doctor import DoctorWidget
8
- from aether.tui.widgets.common import PlaceholderWidget, WelcomeWidget
8
+ from aether.tui.widgets.common import (MascotWidget, PlaceholderWidget,
9
+ WelcomeWidget)
9
10
 
10
- __all__ = ["AgentWidget", "DoctorWidget", "PlaceholderWidget",
11
- "WelcomeWidget"]
11
+ __all__ = ["AgentWidget", "DoctorWidget", "MascotWidget",
12
+ "PlaceholderWidget", "WelcomeWidget"]
@@ -0,0 +1,357 @@
1
+ """Agent mode widget: objective input + in-flow live plan blocks (v4.2.0a4).
2
+
3
+ Dispatches through the exact same pipeline as the classic REPL:
4
+ bootstrap_agent_state_for_fix() builds the registry/planner once (in a
5
+ thread worker, since it touches hardware), then each objective runs through
6
+ aether.app._execute_objective and its on_step hook.
7
+
8
+ Layout model: the log is a scroll of blocks. Each objective mounts ONE
9
+ RunBlock directly under it that live-updates in place — thinking spinner →
10
+ concise plan ("thought") line → step list with ○/spinner/●/seconds — and
11
+ simply stays there when the run finishes. Planning is always right below
12
+ the objective, never docked at the bottom. Only presentation; execution is
13
+ untouched (DO NOT CHANGE contract).
14
+ """
15
+ from typing import Callable, List, Optional
16
+
17
+ from aether.tui import AETHER_ORANGE
18
+
19
+ from textual import work
20
+ from textual.app import ComposeResult
21
+ from textual.containers import Vertical, VerticalScroll
22
+ from textual.widgets import Input, Static
23
+
24
+ _SLASH_PREFIX = "/"
25
+ SPINNER = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
26
+
27
+
28
+ def _params_str(params: dict) -> str:
29
+ return ", ".join(f"{k}={v!r}" for k, v in (params or {}).items())
30
+
31
+
32
+ class RunBlock(Static):
33
+ """One objective's live block: thinking → plan → steps → verdict.
34
+
35
+ All mutation happens on the UI thread; `tick()` advances the spinner
36
+ while anything is in flight.
37
+ """
38
+
39
+ def __init__(self, objective: str, **kwargs) -> None:
40
+ super().__init__(**kwargs)
41
+ self.objective = objective
42
+ self.phase = "thinking" # thinking | running | finished
43
+ self.plan_source = ""
44
+ self.steps: List[dict] = []
45
+ self.verdict = ""
46
+ self._spin = 0
47
+ self._render_now()
48
+
49
+ # -- events from the worker (already marshalled to the UI thread) --
50
+ def on_step(self, phase: str, data: dict) -> None:
51
+ if phase == "plan":
52
+ self.phase = "running"
53
+ self.plan_source = data.get("source", "?")
54
+ self.steps = [
55
+ {"tool": a.get("tool", "?"),
56
+ "params": dict(a.get("params", {})),
57
+ "status": "pending", "secs": None, "error": ""}
58
+ for a in data.get("plan", [])]
59
+ elif phase == "start" and self.steps:
60
+ i = data.get("index", 1) - 1
61
+ if 0 <= i < len(self.steps):
62
+ self.steps[i]["status"] = "running"
63
+ elif phase == "done" and self.steps:
64
+ i = data.get("index", 1) - 1
65
+ entry = data.get("entry", {})
66
+ if 0 <= i < len(self.steps):
67
+ step = self.steps[i]
68
+ step["status"] = "done" if entry.get("success") else "failed"
69
+ step["secs"] = entry.get("duration_ms", 0) / 1000.0
70
+ step["error"] = entry.get("error", "")
71
+ self._render_now()
72
+
73
+ def finish(self, result: dict) -> None:
74
+ self.phase = "finished"
75
+ status = result.get("status", "?")
76
+ color = "green" if status in ("ok", "success", "SUCCESS",
77
+ "completed") else "yellow"
78
+ if self.steps:
79
+ done = sum(1 for s in self.steps if s["status"] == "done")
80
+ total_s = sum(s["secs"] or 0.0 for s in self.steps)
81
+ detail = f"{done}/{len(self.steps)} steps · {total_s:.1f}s total"
82
+ else:
83
+ # Legacy path (execute_fn without on_step): backfill from result.
84
+ self.plan_source = result.get("plan_source", "?")
85
+ self.steps = [
86
+ {"tool": e.get("tool", "?"), "params": {},
87
+ "status": "done" if e.get("success") else "failed",
88
+ "secs": e.get("duration_ms", 0) / 1000.0,
89
+ "error": e.get("error", "")}
90
+ for e in result.get("actions", [])]
91
+ detail = f"{len(self.steps)} step(s)"
92
+ self.verdict = (f"[{color}]status: {status}[/{color}] "
93
+ f"[dim]· {detail}[/dim]")
94
+ self._render_now()
95
+
96
+ def fail(self, message: str) -> None:
97
+ self.phase = "finished"
98
+ self.verdict = f"[red]\\[FAIL][/red] {message}"
99
+ self._render_now()
100
+
101
+ def tick(self) -> bool:
102
+ """Advance the spinner. Returns True while still animating."""
103
+ if self.phase == "finished":
104
+ return False
105
+ self._spin = (self._spin + 1) % len(SPINNER)
106
+ self._render_now()
107
+ return True
108
+
109
+ # -- rendering --
110
+ def _thought_line(self) -> str:
111
+ chain = " → ".join(s["tool"] for s in self.steps)
112
+ return (f" [{AETHER_ORANGE}]\\[PLAN][/{AETHER_ORANGE}] "
113
+ f"{self.plan_source} · {len(self.steps)} step(s)"
114
+ f" [dim]{chain}[/dim]")
115
+
116
+ def _step_line(self, step: dict) -> str:
117
+ call = f"{step['tool']}({_params_str(step['params'])})"
118
+ status = step["status"]
119
+ if status == "pending":
120
+ return f" [dim]○ {call}[/dim]"
121
+ if status == "running":
122
+ frame = SPINNER[self._spin]
123
+ return (f" [{AETHER_ORANGE}]{frame}[/{AETHER_ORANGE}] {call} "
124
+ f"[dim]running...[/dim]")
125
+ secs = (f" [dim]({step['secs']:.1f}s)[/dim]"
126
+ if step["secs"] is not None else "")
127
+ if status == "done":
128
+ return f" [green]●[/green] {call}{secs}"
129
+ return (f" [red]●[/red] {call}{secs} "
130
+ f"[red]{step.get('error', '')}[/red]")
131
+
132
+ def _render_now(self) -> None:
133
+ lines = [f"[bold {AETHER_ORANGE}]objective>[/bold {AETHER_ORANGE}] "
134
+ f"{self.objective}", ""]
135
+ if self.phase == "thinking":
136
+ frame = SPINNER[self._spin]
137
+ lines.append(f" [{AETHER_ORANGE}]{frame}[/{AETHER_ORANGE}] "
138
+ f"[dim]thinking...[/dim]")
139
+ else:
140
+ lines.append(self._thought_line())
141
+ if self.steps:
142
+ lines.append("")
143
+ lines.extend(self._step_line(s) for s in self.steps)
144
+ if self.verdict:
145
+ lines.extend(["", f" {self.verdict}"])
146
+ self.update("\n".join(lines))
147
+
148
+
149
+ class BootBlock(Static):
150
+ """Animated one-shot block for the hardware bootstrap."""
151
+
152
+ def __init__(self, **kwargs) -> None:
153
+ super().__init__(**kwargs)
154
+ self.done = False
155
+ self._spin = 0
156
+ self._render_now()
157
+
158
+ def tick(self) -> bool:
159
+ if self.done:
160
+ return False
161
+ self._spin = (self._spin + 1) % len(SPINNER)
162
+ self._render_now()
163
+ return True
164
+
165
+ def _render_now(self) -> None:
166
+ frame = SPINNER[self._spin]
167
+ self.update(f" [{AETHER_ORANGE}]{frame}[/{AETHER_ORANGE}] "
168
+ f"[dim]discovering hardware + adapters...[/dim]")
169
+
170
+ def finish(self, lines: List[str]) -> None:
171
+ self.done = True
172
+ self.update("\n".join(lines))
173
+
174
+
175
+ class AgentWidget(Vertical):
176
+ """Objective REPL rendered as a TUI pane."""
177
+
178
+ DEFAULT_CSS = """
179
+ AgentWidget { background: ansi_default; }
180
+ AgentWidget #agent-log { height: 1fr; border: none;
181
+ background: ansi_default; padding: 0 1; }
182
+ AgentWidget #agent-log > Static { margin-bottom: 1;
183
+ background: ansi_default; }
184
+ AgentWidget #agent-input { border: round $primary 50%;
185
+ background: ansi_default; padding: 0 1;
186
+ margin-top: 1; }
187
+ AgentWidget #agent-input:focus { border: round $primary; }
188
+ """
189
+
190
+ def __init__(self, bootstrap_fn: Optional[Callable] = None,
191
+ execute_fn: Optional[Callable] = None, **kwargs) -> None:
192
+ """bootstrap_fn/execute_fn are injectable for tests; the defaults are
193
+ the production agent pipeline. An injected execute_fn may accept
194
+ (objective, state) or (objective, state, on_step)."""
195
+ super().__init__(**kwargs)
196
+ self._bootstrap_fn = bootstrap_fn
197
+ self._execute_fn = execute_fn
198
+ self._agent_state: Optional[dict] = None
199
+ self._active_blocks: List = [] # blocks still animating
200
+
201
+ # -- layout --
202
+ def compose(self) -> ComposeResult:
203
+ yield VerticalScroll(id="agent-log")
204
+ yield Input(placeholder="objective> (or / for commands)",
205
+ id="agent-input")
206
+
207
+ def on_mount(self) -> None:
208
+ self._log("[bold]AETHER Agent[/bold]\n"
209
+ "[dim]Type an objective in plain English. "
210
+ "First objective boots the hardware registry.[/dim]")
211
+ self.set_interval(0.12, self._animate)
212
+
213
+ def focus_input(self) -> None:
214
+ self.query_one("#agent-input", Input).focus()
215
+
216
+ # -- log helpers --
217
+ def _scroll(self) -> VerticalScroll:
218
+ return self.query_one("#agent-log", VerticalScroll)
219
+
220
+ def _log(self, markup: str) -> None:
221
+ log = self._scroll()
222
+ log.mount(Static(markup))
223
+ log.scroll_end(animate=False)
224
+
225
+ def _mount_block(self, block) -> None:
226
+ log = self._scroll()
227
+ log.mount(block)
228
+ self._active_blocks.append(block)
229
+ log.scroll_end(animate=False)
230
+
231
+ def _animate(self) -> None:
232
+ if not self._active_blocks:
233
+ return
234
+ self._active_blocks = [b for b in self._active_blocks if b.tick()]
235
+ self._scroll().scroll_end(animate=False)
236
+
237
+ def clear_log(self) -> None:
238
+ self._scroll().remove_children()
239
+ self._active_blocks = []
240
+
241
+ def log_text(self) -> str:
242
+ """Plain text of every block (used by /help and tests)."""
243
+ return "\n".join(str(w.render()) for w in self._scroll().children
244
+ if isinstance(w, Static))
245
+
246
+ # -- input handling --
247
+ def on_input_submitted(self, event: Input.Submitted) -> None:
248
+ text = event.value.strip()
249
+ event.input.value = ""
250
+ if not text:
251
+ return
252
+ if text.startswith(_SLASH_PREFIX):
253
+ self.app.run_slash_command(text)
254
+ return
255
+ block = RunBlock(text)
256
+ self._mount_block(block)
257
+ self.run_objective(text, block)
258
+
259
+ # -- objective pipeline (thread worker: hardware + LLM latency) --
260
+ @work(thread=True, exclusive=True, group="agent-objective")
261
+ def run_objective(self, objective: str, block: RunBlock) -> None:
262
+ app = self.app
263
+ try:
264
+ state = self._ensure_agent_state()
265
+ app.call_from_thread(app.set_busy,
266
+ "planning objective with Claude...")
267
+
268
+ def on_step(phase: str, data: dict) -> None:
269
+ app.call_from_thread(block.on_step, phase, data)
270
+
271
+ execute = self._execute_fn or self._default_execute
272
+ try:
273
+ result = execute(objective, state, on_step)
274
+ except TypeError:
275
+ result = execute(objective, state) # legacy 2-arg fake
276
+ app.call_from_thread(block.finish, result)
277
+ app.call_from_thread(app.count_action)
278
+ except Exception as e: # noqa: BLE001 — surface, never crash the TUI
279
+ app.call_from_thread(block.fail, f"{type(e).__name__}: {e}")
280
+ finally:
281
+ app.call_from_thread(app.set_busy, "")
282
+
283
+ def _ensure_agent_state(self) -> dict:
284
+ if self._agent_state is None:
285
+ app = self.app
286
+ app.call_from_thread(app.set_busy, "booting hardware registry...")
287
+ boot = BootBlock()
288
+ app.call_from_thread(self._mount_block, boot)
289
+ try:
290
+ bootstrap = self._bootstrap_fn or self._default_bootstrap
291
+ self._agent_state = bootstrap()
292
+ except Exception:
293
+ app.call_from_thread(
294
+ boot.finish, [" [red]hardware bootstrap failed[/red]"])
295
+ raise
296
+ app.call_from_thread(app.on_agent_state_ready, self._agent_state)
297
+ app.call_from_thread(
298
+ boot.finish, _adapter_detail_lines(self._agent_state))
299
+ return self._agent_state
300
+
301
+ @staticmethod
302
+ def _default_bootstrap() -> dict:
303
+ from aether.app import bootstrap_agent_state_for_fix
304
+ return bootstrap_agent_state_for_fix()
305
+
306
+ @staticmethod
307
+ def _default_execute(objective: str, state: dict, on_step=None) -> dict:
308
+ from aether.app import _execute_objective
309
+ return _execute_objective(
310
+ objective, state["registry"], state["planner"],
311
+ state["corrector"], state["parser"], state["manifest"],
312
+ recent_memory=[], available=state["available"], on_step=on_step)
313
+
314
+
315
+ def _adapter_detail_lines(state: dict) -> List[str]:
316
+ """Boot-time adapter detail: name, role, tool count, address, GPIO.
317
+ Best effort — never raises."""
318
+ lines: List[str] = []
319
+ try:
320
+ from aether.adapters import (get_active_adapters, get_adapter_gpio_pins,
321
+ get_last_detected, get_primary_adapter,
322
+ get_tool_origin)
323
+ active = get_active_adapters()
324
+ addr_of = {d["adapter"]: d.get("address") for d in get_last_detected()}
325
+ if active:
326
+ primary = get_primary_adapter()
327
+ counts: dict = {}
328
+ for _tool, adp in get_tool_origin().items():
329
+ counts[adp] = counts.get(adp, 0) + 1
330
+ for name in active:
331
+ lines.append(_adapter_line(
332
+ name, name == primary, counts.get(name, 0),
333
+ addr_of.get(name), get_adapter_gpio_pins(name)))
334
+ return lines
335
+ adapter_id = state.get("adapter_id")
336
+ if adapter_id:
337
+ tools = len(state.get("manifest", {}).get("adapter_tools", []))
338
+ lines.append(_adapter_line(
339
+ adapter_id, True, tools, addr_of.get(adapter_id),
340
+ get_adapter_gpio_pins(adapter_id)))
341
+ return lines
342
+ except Exception:
343
+ pass
344
+ if not lines:
345
+ lines.append(f" [dim]● registry ready "
346
+ f"({len(state.get('available', []))} tools)[/dim]")
347
+ return lines
348
+
349
+
350
+ def _adapter_line(name: str, primary: bool, tools: int, address,
351
+ gpio_pins) -> str:
352
+ role = ("[green]primary[/green]" if primary
353
+ else f"[{AETHER_ORANGE}]namespaced[/{AETHER_ORANGE}]")
354
+ gpio = ", ".join(str(p) for p in (gpio_pins or [])) or "none (I2C only)"
355
+ addr = address or "vendor lib"
356
+ return (f" [green]●[/green] [bold]{name}[/bold] — {role} · "
357
+ f"{tools} tools · addr {addr} · [dim]GPIO {gpio}[/dim]")
@@ -1,9 +1,32 @@
1
- """Shared TUI widgets: first-run welcome screen and phase-2 placeholders."""
1
+ """Shared TUI widgets: mascot mark, first-run welcome, phase-2 placeholders."""
2
2
  from textual.app import ComposeResult
3
3
  from textual.containers import Horizontal, Vertical
4
4
  from textual.widgets import Button, Static
5
5
 
6
6
  from aether import __version__
7
+ from aether.tui import AETHER_ORANGE
8
+
9
+ # Compact rendition of the AETHER banner mascot (the robot face) for the
10
+ # bottom-left corner of the TUI.
11
+ _MASCOT_ART = (
12
+ f"[{AETHER_ORANGE}]▄████▄ ▄████▄[/{AETHER_ORANGE}]\n"
13
+ f"[{AETHER_ORANGE}]█████████████[/{AETHER_ORANGE}]\n"
14
+ f"[dim]█▀▀[/dim][{AETHER_ORANGE}]███████[/{AETHER_ORANGE}][dim]▀▀█[/dim]\n"
15
+ f"[{AETHER_ORANGE}]▀████▀ ▀████▀[/{AETHER_ORANGE}]\n"
16
+ f"[dim] ━━ AETHER ━━[/dim]"
17
+ )
18
+
19
+
20
+ class MascotWidget(Static):
21
+ """The AETHER robot face, parked bottom-left under the sidebar."""
22
+
23
+ DEFAULT_CSS = """
24
+ MascotWidget { height: 5; background: ansi_default; padding: 0 1;
25
+ color: $primary; }
26
+ """
27
+
28
+ def __init__(self, **kwargs) -> None:
29
+ super().__init__(_MASCOT_ART, **kwargs)
7
30
 
8
31
 
9
32
  class WelcomeWidget(Vertical):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-robotics
3
- Version: 4.2.0a2
3
+ Version: 4.2.0a4
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 = "4.2.0a2"
7
+ version = "4.2.0a4"
8
8
  description = "Autonomous multi-agent robotics system with DRL-First Hybrid FDIR"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}