aether-robotics 4.2.0a2__tar.gz → 4.2.0a3__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.
- {aether_robotics-4.2.0a2/aether_robotics.egg-info → aether_robotics-4.2.0a3}/PKG-INFO +1 -1
- aether_robotics-4.2.0a3/VERSION +1 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/__init__.py +1 -1
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/app.py +21 -1
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/tui/app.py +19 -9
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/tui/widgets/__init__.py +4 -3
- aether_robotics-4.2.0a3/aether/tui/widgets/agent.py +297 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/tui/widgets/common.py +24 -1
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3/aether_robotics.egg-info}/PKG-INFO +1 -1
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/pyproject.toml +1 -1
- aether_robotics-4.2.0a3/tests/test_tui_widgets.py +277 -0
- aether_robotics-4.2.0a2/VERSION +0 -1
- aether_robotics-4.2.0a2/aether/tui/widgets/agent.py +0 -132
- aether_robotics-4.2.0a2/tests/test_tui_widgets.py +0 -131
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/LICENSE +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/MANIFEST.in +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/README.md +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/__main__.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/actions/__init__.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/actions/abstract_actions.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/__init__.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/_generator.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/adeept_hat_v3.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/base_adapter.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/drone_adapter.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/freenove_4wd.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/freenove_mecanum.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/gopigo3.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/l298n_direct_gpio.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/pca9685_generic.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/rover_adapter.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/sunfounder_picarx.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/sunfounder_pidog.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/universal_adapter.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/waveshare_motor_driver.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/yahboom_robot_hat.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/__init__.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/adaptation_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/camera_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/correction_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/execution_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/fault_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/memory_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/movement_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/navigation_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/perception_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/planner_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/power_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/task_manager.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/thermal_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/capabilities/__init__.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/capabilities/capability_loader.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/__init__.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/analytics.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/autonomous.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/calibrate.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/doctor.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/fix.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/kits.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/kits_install_recipes/freenove_4wd.yaml +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/kits_install_recipes/gopigo3.yaml +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/kits_install_recipes/sunfounder_picarx.yaml +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/kits_install_recipes/sunfounder_pidog.yaml +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/kits_install_recipes/yahboom_robot_hat.yaml +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/__init__.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/analytics.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/auto_installer.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/auto_updater.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/autonomous.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/banner.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/calibration.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/decomposer.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/executor.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/feedback.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/genome.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/goal_parser.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/goals.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/llm_planner.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/mapper.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/mavlink_integration.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/memory.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/message_bus.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/metrics.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/navigation_engine.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/overrides.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/patches.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/planner.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/security.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/task_scheduler.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/telemetry_config.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/tool_builder.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/tool_discovery.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/tool_registry.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/ui.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/visualizer.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/faults/__init__.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/faults/fault_detector.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/faults/fault_injector.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/parallel/__init__.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/parallel/executor.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/simulation/__init__.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/simulation/environment.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/simulation/real_perception.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/simulation/scenarios.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/skills/__init__.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/skills/drive_until_obstacle.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/tui/__init__.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/tui/widgets/doctor.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether_robotics.egg-info/SOURCES.txt +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether_robotics.egg-info/dependency_links.txt +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether_robotics.egg-info/entry_points.txt +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether_robotics.egg-info/requires.txt +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether_robotics.egg-info/top_level.txt +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/configs/calibrated_camera_only_20260331_121709.json +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/configs/calibrated_camera_only_20260331_122446.json +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/configs/calibrated_camera_only_20260401_094544.json +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/configs/calibrated_camera_only_20260401_094554.json +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/configs/drone_v1.json +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/configs/rover_v1.json +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/context/aether_definitions.txt +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/install.sh +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/main.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/requirements.txt +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/setup.cfg +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_adapter_gpio_manifests.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_adapter_override_wiring.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_adapter_preflight.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_adapter_resolver_v397.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_analytics_cli.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_analytics_module.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_analytics_wiring.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_atexit_adapter_shutdown.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_atexit_reaches_stop_all.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_auto_adapter.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_auto_updater.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_autonomous_cli.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_autonomous_loop.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_autonomous_no_between_step_emergency_stop.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_boot_resolver_flow.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_calibrate_wizard.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_calibration.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_calibration_unit.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_chaos.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_doctor.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_fix.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_fix_patches.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_fix_v3_10_6.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_fix_v3_10_8.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_fix_visibility.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_kits.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_routing.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_consent_flow.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_correction_agent.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_decomposer.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_decomposer_avoids_legacy.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_decomposer_routing.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_display_bug_fix.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_doctor_gpio_allocation.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_doctor_multi_adapter.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_drive_until_obstacle.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_entrypoint.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_fix_scope_validation.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_fix_sees_named_servos.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_genome.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_goal_persistence.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_gopigo3_adapter.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_gpio_conflict_detection.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_high_level_task_examples.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_map_room_high_level_task.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_mapper.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_mavlink_integration.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_multi_adapter_boot_flow.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_multi_adapter_registration.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_multi_discovery.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_named_servo_registration.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_navigation_engine.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_overrides.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_overrides_multi_adapter.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_overrides_schema_v1_2.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_parallel_executor.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_pca9685_emergency_stop.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_pca9685_emergency_stop_hardware.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_pca9685_hard_off.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_pca9685_pwm_primitives.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_pca9685_wake_sequence.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_persistent_memory.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_planner_routing.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_planner_stop_routing.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_posthog_integration.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_primary_adapter_designation.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_robot_json_v1_1.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_security.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_set_servo_angle_duration.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_stop_full_off_behavior.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_stop_servo_sleep_fallback.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_sunfounder_picarx_adapter.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_sunfounder_pidog_adapter.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_synthesizer_fidelity_check.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_task_scheduler.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tier1_motion_cleanup.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tool_builder.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tool_discovery.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tool_discovery_gpio_cleanup.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tool_namespacing.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tui_app.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tui_backward_compat.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_ui_autonomous.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_ui_dashboard.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_ui_module.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_ui_wizard.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_validator_rejects_legacy.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_yahboom_robot_hat_adapter.py +0 -0
- {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_yolo_integration.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
4.2.0a3
|
|
@@ -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
|
|
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,
|
|
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:
|
|
144
|
-
|
|
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;
|
|
@@ -170,10 +172,12 @@ class AetherApp(App):
|
|
|
170
172
|
def compose(self) -> ComposeResult:
|
|
171
173
|
yield Static(id="header-bar")
|
|
172
174
|
with Horizontal(id="body"):
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
with Vertical(id="sidebar-col"):
|
|
176
|
+
yield ListView(
|
|
177
|
+
*[ListItem(Label(label), id=f"nav-{mode_id}")
|
|
178
|
+
for mode_id, label, _ in MODES],
|
|
179
|
+
id="sidebar")
|
|
180
|
+
yield MascotWidget(id="mascot")
|
|
177
181
|
with ContentSwitcher(initial="welcome", id="main-switcher"):
|
|
178
182
|
yield WelcomeWidget(id="welcome")
|
|
179
183
|
yield AgentWidget(id="agent")
|
|
@@ -266,8 +270,14 @@ class AetherApp(App):
|
|
|
266
270
|
return
|
|
267
271
|
except Exception:
|
|
268
272
|
pass
|
|
273
|
+
# Single-adapter path (load_tier1 doesn't fill the active list).
|
|
274
|
+
adapter_id = state.get("adapter_id")
|
|
269
275
|
n = len(state.get("available", []))
|
|
270
|
-
|
|
276
|
+
if adapter_id:
|
|
277
|
+
self.adapters_line = f"{adapter_id} (primary) · {n} tools"
|
|
278
|
+
self.adapter_count = 1
|
|
279
|
+
else:
|
|
280
|
+
self.adapters_line = f"registry ready ({n} tools)"
|
|
271
281
|
|
|
272
282
|
# ── mode switching ─────────────────────────────────────────────────
|
|
273
283
|
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,
|
|
8
|
+
from aether.tui.widgets.common import (MascotWidget, PlaceholderWidget,
|
|
9
|
+
WelcomeWidget)
|
|
9
10
|
|
|
10
|
-
__all__ = ["AgentWidget", "DoctorWidget", "
|
|
11
|
-
"WelcomeWidget"]
|
|
11
|
+
__all__ = ["AgentWidget", "DoctorWidget", "MascotWidget",
|
|
12
|
+
"PlaceholderWidget", "WelcomeWidget"]
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Agent mode widget: objective input + live plan progress (v4.2.0a3).
|
|
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 — the function agent/server/--fix already
|
|
7
|
+
share. The optional on_step hook it now exposes drives a Claude Code-style
|
|
8
|
+
live step list: ○ pending, spinner while running, ● green/red with the
|
|
9
|
+
step's wall-clock seconds when finished. Only presentation; execution is
|
|
10
|
+
untouched (DO NOT CHANGE contract).
|
|
11
|
+
"""
|
|
12
|
+
from typing import Callable, List, Optional
|
|
13
|
+
|
|
14
|
+
from aether.tui import AETHER_ORANGE
|
|
15
|
+
|
|
16
|
+
from textual import work
|
|
17
|
+
from textual.app import ComposeResult
|
|
18
|
+
from textual.containers import Vertical
|
|
19
|
+
from textual.widgets import Input, RichLog, Static
|
|
20
|
+
|
|
21
|
+
_SLASH_PREFIX = "/"
|
|
22
|
+
_SPINNER = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _params_str(params: dict) -> str:
|
|
26
|
+
return ", ".join(f"{k}={v!r}" for k, v in (params or {}).items())
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AgentWidget(Vertical):
|
|
30
|
+
"""Objective REPL rendered as a TUI pane."""
|
|
31
|
+
|
|
32
|
+
DEFAULT_CSS = """
|
|
33
|
+
AgentWidget { background: ansi_default; }
|
|
34
|
+
AgentWidget #agent-log { height: 1fr; border: none;
|
|
35
|
+
background: ansi_default; padding: 0 1; }
|
|
36
|
+
AgentWidget #agent-live { height: auto; background: ansi_default;
|
|
37
|
+
padding: 0 2; display: none; }
|
|
38
|
+
AgentWidget #agent-input { border: round $primary 50%;
|
|
39
|
+
background: ansi_default; padding: 0 1; }
|
|
40
|
+
AgentWidget #agent-input:focus { border: round $primary; }
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, bootstrap_fn: Optional[Callable] = None,
|
|
44
|
+
execute_fn: Optional[Callable] = None, **kwargs) -> None:
|
|
45
|
+
"""bootstrap_fn/execute_fn are injectable for tests; the defaults are
|
|
46
|
+
the production agent pipeline. An injected execute_fn may accept
|
|
47
|
+
(objective, state) or (objective, state, on_step)."""
|
|
48
|
+
super().__init__(**kwargs)
|
|
49
|
+
self._bootstrap_fn = bootstrap_fn
|
|
50
|
+
self._execute_fn = execute_fn
|
|
51
|
+
self._agent_state: Optional[dict] = None
|
|
52
|
+
# Live-run state (UI thread only)
|
|
53
|
+
self._steps: List[dict] = []
|
|
54
|
+
self._plan_source = ""
|
|
55
|
+
self._spin = 0
|
|
56
|
+
|
|
57
|
+
# -- layout --
|
|
58
|
+
def compose(self) -> ComposeResult:
|
|
59
|
+
yield RichLog(id="agent-log", markup=True, wrap=True)
|
|
60
|
+
yield Static(id="agent-live")
|
|
61
|
+
yield Input(placeholder="objective> (or / for commands)",
|
|
62
|
+
id="agent-input")
|
|
63
|
+
|
|
64
|
+
def on_mount(self) -> None:
|
|
65
|
+
log = self.query_one("#agent-log", RichLog)
|
|
66
|
+
log.write("[bold]AETHER Agent[/bold]")
|
|
67
|
+
log.write("[dim]Type an objective in plain English. "
|
|
68
|
+
"First objective boots the hardware registry.[/dim]")
|
|
69
|
+
self.set_interval(0.12, self._animate)
|
|
70
|
+
|
|
71
|
+
def focus_input(self) -> None:
|
|
72
|
+
self.query_one("#agent-input", Input).focus()
|
|
73
|
+
|
|
74
|
+
# -- input handling --
|
|
75
|
+
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
76
|
+
text = event.value.strip()
|
|
77
|
+
event.input.value = ""
|
|
78
|
+
if not text:
|
|
79
|
+
return
|
|
80
|
+
if text.startswith(_SLASH_PREFIX):
|
|
81
|
+
self.app.run_slash_command(text)
|
|
82
|
+
return
|
|
83
|
+
self._log(f"\n[bold {AETHER_ORANGE}]objective>"
|
|
84
|
+
f"[/bold {AETHER_ORANGE}] {text}")
|
|
85
|
+
self.run_objective(text)
|
|
86
|
+
|
|
87
|
+
# -- objective pipeline (thread worker: hardware + LLM latency) --
|
|
88
|
+
@work(thread=True, exclusive=True, group="agent-objective")
|
|
89
|
+
def run_objective(self, objective: str) -> None:
|
|
90
|
+
app = self.app
|
|
91
|
+
try:
|
|
92
|
+
state = self._ensure_agent_state()
|
|
93
|
+
app.call_from_thread(app.set_busy,
|
|
94
|
+
"planning objective with Claude...")
|
|
95
|
+
app.call_from_thread(self._live_planning)
|
|
96
|
+
|
|
97
|
+
def on_step(phase: str, data: dict) -> None:
|
|
98
|
+
app.call_from_thread(self._on_step_ui, phase, data)
|
|
99
|
+
|
|
100
|
+
execute = self._execute_fn or self._default_execute
|
|
101
|
+
try:
|
|
102
|
+
result = execute(objective, state, on_step)
|
|
103
|
+
except TypeError:
|
|
104
|
+
result = execute(objective, state) # legacy 2-arg fake
|
|
105
|
+
app.call_from_thread(self._render_result, result)
|
|
106
|
+
app.call_from_thread(app.count_action)
|
|
107
|
+
except Exception as e: # noqa: BLE001 — surface, never crash the TUI
|
|
108
|
+
app.call_from_thread(
|
|
109
|
+
self._log, f" [red]\\[FAIL][/red] {type(e).__name__}: {e}")
|
|
110
|
+
app.call_from_thread(self._live_clear)
|
|
111
|
+
finally:
|
|
112
|
+
app.call_from_thread(app.set_busy, "")
|
|
113
|
+
|
|
114
|
+
def _ensure_agent_state(self) -> dict:
|
|
115
|
+
if self._agent_state is None:
|
|
116
|
+
app = self.app
|
|
117
|
+
app.call_from_thread(app.set_busy, "booting hardware registry...")
|
|
118
|
+
app.call_from_thread(
|
|
119
|
+
self._log, "[dim]Discovering hardware + adapters...[/dim]")
|
|
120
|
+
bootstrap = self._bootstrap_fn or self._default_bootstrap
|
|
121
|
+
self._agent_state = bootstrap()
|
|
122
|
+
app.call_from_thread(app.on_agent_state_ready, self._agent_state)
|
|
123
|
+
for line in _adapter_detail_lines(self._agent_state):
|
|
124
|
+
app.call_from_thread(self._log, line)
|
|
125
|
+
return self._agent_state
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def _default_bootstrap() -> dict:
|
|
129
|
+
from aether.app import bootstrap_agent_state_for_fix
|
|
130
|
+
return bootstrap_agent_state_for_fix()
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def _default_execute(objective: str, state: dict, on_step=None) -> dict:
|
|
134
|
+
from aether.app import _execute_objective
|
|
135
|
+
return _execute_objective(
|
|
136
|
+
objective, state["registry"], state["planner"],
|
|
137
|
+
state["corrector"], state["parser"], state["manifest"],
|
|
138
|
+
recent_memory=[], available=state["available"], on_step=on_step)
|
|
139
|
+
|
|
140
|
+
# -- live plan panel (all methods below run on the UI thread) --
|
|
141
|
+
def _live(self) -> Static:
|
|
142
|
+
return self.query_one("#agent-live", Static)
|
|
143
|
+
|
|
144
|
+
def _live_planning(self) -> None:
|
|
145
|
+
self._steps = []
|
|
146
|
+
self._plan_source = ""
|
|
147
|
+
live = self._live()
|
|
148
|
+
live.styles.display = "block"
|
|
149
|
+
self._render_live()
|
|
150
|
+
|
|
151
|
+
def _live_clear(self) -> None:
|
|
152
|
+
self._steps = []
|
|
153
|
+
live = self._live()
|
|
154
|
+
live.update("")
|
|
155
|
+
live.styles.display = "none"
|
|
156
|
+
|
|
157
|
+
def _on_step_ui(self, phase: str, data: dict) -> None:
|
|
158
|
+
if phase == "plan":
|
|
159
|
+
self._plan_source = data.get("source", "?")
|
|
160
|
+
self._steps = [
|
|
161
|
+
{"tool": a.get("tool", "?"),
|
|
162
|
+
"params": dict(a.get("params", {})),
|
|
163
|
+
"status": "pending", "secs": None, "error": ""}
|
|
164
|
+
for a in data.get("plan", [])]
|
|
165
|
+
elif phase == "start" and self._steps:
|
|
166
|
+
i = data.get("index", 1) - 1
|
|
167
|
+
if 0 <= i < len(self._steps):
|
|
168
|
+
self._steps[i]["status"] = "running"
|
|
169
|
+
elif phase == "done" and self._steps:
|
|
170
|
+
i = data.get("index", 1) - 1
|
|
171
|
+
entry = data.get("entry", {})
|
|
172
|
+
if 0 <= i < len(self._steps):
|
|
173
|
+
step = self._steps[i]
|
|
174
|
+
step["status"] = "done" if entry.get("success") else "failed"
|
|
175
|
+
step["secs"] = entry.get("duration_ms", 0) / 1000.0
|
|
176
|
+
step["error"] = entry.get("error", "")
|
|
177
|
+
self._render_live()
|
|
178
|
+
|
|
179
|
+
def _animate(self) -> None:
|
|
180
|
+
if any(s["status"] == "running" for s in self._steps) or (
|
|
181
|
+
self._live().styles.display == "block" and not self._steps):
|
|
182
|
+
self._spin = (self._spin + 1) % len(_SPINNER)
|
|
183
|
+
self._render_live()
|
|
184
|
+
|
|
185
|
+
def _step_line(self, step: dict) -> str:
|
|
186
|
+
call = f"{step['tool']}({_params_str(step['params'])})"
|
|
187
|
+
status = step["status"]
|
|
188
|
+
if status == "pending":
|
|
189
|
+
return f" [dim]○ {call}[/dim]"
|
|
190
|
+
if status == "running":
|
|
191
|
+
frame = _SPINNER[self._spin]
|
|
192
|
+
return (f" [{AETHER_ORANGE}]{frame}[/{AETHER_ORANGE}] {call} "
|
|
193
|
+
f"[dim]running...[/dim]")
|
|
194
|
+
secs = f" [dim]({step['secs']:.1f}s)[/dim]" if step["secs"] is not None \
|
|
195
|
+
else ""
|
|
196
|
+
if status == "done":
|
|
197
|
+
return f" [green]●[/green] {call}{secs}"
|
|
198
|
+
return (f" [red]●[/red] {call}{secs} "
|
|
199
|
+
f"[red]{step.get('error', '')}[/red]")
|
|
200
|
+
|
|
201
|
+
def _render_live(self) -> None:
|
|
202
|
+
if not self._steps:
|
|
203
|
+
frame = _SPINNER[self._spin]
|
|
204
|
+
self._live().update(
|
|
205
|
+
f"[{AETHER_ORANGE}]{frame}[/{AETHER_ORANGE}] "
|
|
206
|
+
f"[dim]planning objective with Claude...[/dim]")
|
|
207
|
+
return
|
|
208
|
+
done = sum(1 for s in self._steps if s["status"] in ("done", "failed"))
|
|
209
|
+
header = (f"[{AETHER_ORANGE}]\\[PLAN][/{AETHER_ORANGE}] "
|
|
210
|
+
f"{self._plan_source} · {len(self._steps)} step(s) · "
|
|
211
|
+
f"[dim]{done}/{len(self._steps)} complete[/dim]")
|
|
212
|
+
self._live().update(
|
|
213
|
+
"\n".join([header] + [self._step_line(s) for s in self._steps]))
|
|
214
|
+
|
|
215
|
+
# -- final render into scrollback --
|
|
216
|
+
def _log(self, line: str) -> None:
|
|
217
|
+
self.query_one("#agent-log", RichLog).write(line)
|
|
218
|
+
|
|
219
|
+
def _render_result(self, result: dict) -> None:
|
|
220
|
+
if self._steps:
|
|
221
|
+
# Rich path: persist the live step list (with dots + seconds).
|
|
222
|
+
done = sum(1 for s in self._steps if s["status"] == "done")
|
|
223
|
+
self._log(f" [{AETHER_ORANGE}]\\[PLAN][/{AETHER_ORANGE}] "
|
|
224
|
+
f"{self._plan_source} · {len(self._steps)} step(s)")
|
|
225
|
+
total_s = 0.0
|
|
226
|
+
for step in self._steps:
|
|
227
|
+
self._log(self._step_line(step))
|
|
228
|
+
total_s += step["secs"] or 0.0
|
|
229
|
+
summary = f"{done}/{len(self._steps)} steps · {total_s:.1f}s total"
|
|
230
|
+
else:
|
|
231
|
+
# Legacy path (no on_step data): render from the result dict.
|
|
232
|
+
actions = result.get("actions", [])
|
|
233
|
+
self._log(f" [{AETHER_ORANGE}]\\[PLAN][/{AETHER_ORANGE}] "
|
|
234
|
+
f"{result.get('plan_source', '?')} plan · "
|
|
235
|
+
f"{len(actions)} step(s)")
|
|
236
|
+
for entry in actions:
|
|
237
|
+
self._log(f" [{AETHER_ORANGE}]\\[EXEC][/{AETHER_ORANGE}] "
|
|
238
|
+
f"{entry.get('tool', '?')}")
|
|
239
|
+
if entry.get("success"):
|
|
240
|
+
self._log(f" [green]\\[OK][/green] success "
|
|
241
|
+
f"({entry.get('duration_ms', 0):.0f}ms)")
|
|
242
|
+
else:
|
|
243
|
+
self._log(f" [red]\\[FAIL][/red] {entry.get('error', '')}")
|
|
244
|
+
summary = f"{len(actions)} step(s)"
|
|
245
|
+
status = result.get("status", "?")
|
|
246
|
+
color = "green" if status in ("ok", "success", "SUCCESS",
|
|
247
|
+
"completed") else "yellow"
|
|
248
|
+
self._log(f" [{color}]status: {status}[/{color}] [dim]· {summary}[/dim]")
|
|
249
|
+
self._live_clear()
|
|
250
|
+
|
|
251
|
+
def clear_log(self) -> None:
|
|
252
|
+
self.query_one("#agent-log", RichLog).clear()
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _adapter_detail_lines(state: dict) -> List[str]:
|
|
256
|
+
"""Boot-time adapter detail for the log: name, role, tool count,
|
|
257
|
+
address, GPIO pins. Best effort — never raises."""
|
|
258
|
+
lines: List[str] = []
|
|
259
|
+
try:
|
|
260
|
+
from aether.adapters import (get_active_adapters, get_adapter_gpio_pins,
|
|
261
|
+
get_last_detected, get_primary_adapter,
|
|
262
|
+
get_tool_origin)
|
|
263
|
+
active = get_active_adapters()
|
|
264
|
+
addr_of = {d["adapter"]: d.get("address") for d in get_last_detected()}
|
|
265
|
+
if active:
|
|
266
|
+
primary = get_primary_adapter()
|
|
267
|
+
counts: dict = {}
|
|
268
|
+
for _tool, adp in get_tool_origin().items():
|
|
269
|
+
counts[adp] = counts.get(adp, 0) + 1
|
|
270
|
+
for name in active:
|
|
271
|
+
lines.append(_adapter_line(
|
|
272
|
+
name, name == primary, counts.get(name, 0),
|
|
273
|
+
addr_of.get(name), get_adapter_gpio_pins(name)))
|
|
274
|
+
return lines
|
|
275
|
+
adapter_id = state.get("adapter_id")
|
|
276
|
+
if adapter_id:
|
|
277
|
+
tools = len(state.get("manifest", {}).get("adapter_tools", []))
|
|
278
|
+
lines.append(_adapter_line(
|
|
279
|
+
adapter_id, True, tools, addr_of.get(adapter_id),
|
|
280
|
+
get_adapter_gpio_pins(adapter_id)))
|
|
281
|
+
return lines
|
|
282
|
+
except Exception:
|
|
283
|
+
pass
|
|
284
|
+
if not lines:
|
|
285
|
+
lines.append(f"[dim]● registry ready "
|
|
286
|
+
f"({len(state.get('available', []))} tools)[/dim]")
|
|
287
|
+
return lines
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _adapter_line(name: str, primary: bool, tools: int, address,
|
|
291
|
+
gpio_pins) -> str:
|
|
292
|
+
role = ("[green]primary[/green]" if primary
|
|
293
|
+
else f"[{AETHER_ORANGE}]namespaced[/{AETHER_ORANGE}]")
|
|
294
|
+
gpio = ", ".join(str(p) for p in (gpio_pins or [])) or "none (I2C only)"
|
|
295
|
+
addr = address or "vendor lib"
|
|
296
|
+
return (f"[green]●[/green] [bold]{name}[/bold] — {role} · "
|
|
297
|
+
f"{tools} tools · addr {addr} · [dim]GPIO {gpio}[/dim]")
|
|
@@ -1,9 +1,32 @@
|
|
|
1
|
-
"""Shared TUI widgets: first-run welcome
|
|
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):
|