devagent-physical-engine 0.11.4__tar.gz → 0.11.5__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 (136) hide show
  1. {devagent_physical_engine-0.11.4/src/devagent_physical_engine.egg-info → devagent_physical_engine-0.11.5}/PKG-INFO +30 -4
  2. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/README.md +29 -3
  3. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/pyproject.toml +1 -1
  4. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/__init__.py +1 -1
  5. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ai_cli.py +157 -0
  6. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5/src/devagent_physical_engine.egg-info}/PKG-INFO +30 -4
  7. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_ai_cli.py +110 -0
  8. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/NOTICE +0 -0
  9. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/setup.cfg +0 -0
  10. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/__init__.py +0 -0
  11. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/compiler.py +0 -0
  12. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/contracts.py +0 -0
  13. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/coordinator.py +0 -0
  14. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/critic.py +0 -0
  15. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/evidence.py +0 -0
  16. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/interpreter.py +0 -0
  17. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/planner.py +0 -0
  18. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/recovery.py +0 -0
  19. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/routing.py +0 -0
  20. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/runtime.py +0 -0
  21. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/semantic.py +0 -0
  22. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/agent/structured.py +0 -0
  23. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/cli.py +0 -0
  24. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/continuous_clearance.py +0 -0
  25. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/doctor.py +0 -0
  26. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/engineering_agent.py +0 -0
  27. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/engineering_request.py +0 -0
  28. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/execution.py +0 -0
  29. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/models.py +0 -0
  30. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/operating_envelope.py +0 -0
  31. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/optimization/__init__.py +0 -0
  32. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/optimization/benchmark.py +0 -0
  33. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/optimization/candidates.py +0 -0
  34. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/optimization/contracts.py +0 -0
  35. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/optimization/evaluator.py +0 -0
  36. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/optimization/evidence.py +0 -0
  37. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/optimization/experience.py +0 -0
  38. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/optimization/measured.py +0 -0
  39. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/optimization/optimizer.py +0 -0
  40. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/optimization/orchestrator.py +0 -0
  41. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/physical_campaign.py +0 -0
  42. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/physical_evidence.py +0 -0
  43. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/physical_motion.py +0 -0
  44. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/planning.py +0 -0
  45. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/preexecution_contract.py +0 -0
  46. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/provider_adapters/__init__.py +0 -0
  47. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/provider_adapters/anthropic.py +0 -0
  48. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/provider_adapters/common.py +0 -0
  49. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/provider_adapters/factory.py +0 -0
  50. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/provider_adapters/gemini.py +0 -0
  51. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/provider_adapters/openai.py +0 -0
  52. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/provider_qualification.py +0 -0
  53. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/providers.py +0 -0
  54. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/qualification.py +0 -0
  55. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/qualification_cli.py +0 -0
  56. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/qualification_harness.py +0 -0
  57. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/robot_platform.py +0 -0
  58. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/robots.py +0 -0
  59. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/__init__.py +0 -0
  60. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/acceptance.py +0 -0
  61. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/commands.py +0 -0
  62. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/continuous_clearance_probe.py +0 -0
  63. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/doctor.py +0 -0
  64. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/fk_probe.py +0 -0
  65. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/frame_alignment.py +0 -0
  66. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/gazebo_world.py +0 -0
  67. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/joint_state_recorder.py +0 -0
  68. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/measured_motion.py +0 -0
  69. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/moveit_scene.py +0 -0
  70. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/preexecution.py +0 -0
  71. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/qualification.py +0 -0
  72. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/qualification_v10.py +0 -0
  73. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/qualification_v11.py +0 -0
  74. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/scene_probe.py +0 -0
  75. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/state_validity_probe.py +0 -0
  76. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/tf_probe.py +0 -0
  77. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/tool_scene.py +0 -0
  78. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/tool_scene_probe.py +0 -0
  79. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/trajectory.py +0 -0
  80. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/ur5e.py +0 -0
  81. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/ur5e_adapter.py +0 -0
  82. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/ur5e_v10_adapter.py +0 -0
  83. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/workpiece_scene.py +0 -0
  84. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/ros2/workpiece_scene_probe.py +0 -0
  85. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/setup_profile.py +0 -0
  86. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/sim_real_qualification.py +0 -0
  87. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/simulation.py +0 -0
  88. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/simulation_platform.py +0 -0
  89. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/tool_collision.py +0 -0
  90. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/trajectory_qualification.py +0 -0
  91. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/twin.py +0 -0
  92. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/twin_builder.py +0 -0
  93. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/twin_materialization.py +0 -0
  94. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/verification.py +0 -0
  95. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine/workpiece_lifecycle.py +0 -0
  96. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine.egg-info/SOURCES.txt +0 -0
  97. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine.egg-info/dependency_links.txt +0 -0
  98. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine.egg-info/entry_points.txt +0 -0
  99. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine.egg-info/requires.txt +0 -0
  100. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/src/devagent_physical_engine.egg-info/top_level.txt +0 -0
  101. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_agent_core.py +0 -0
  102. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_agent_semantic_hardening.py +0 -0
  103. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_ai_cli_v07.py +0 -0
  104. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_continuous_clearance_v011.py +0 -0
  105. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_core.py +0 -0
  106. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_engineering_request_v07.py +0 -0
  107. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_interpreter_profiles_v08.py +0 -0
  108. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_live_provider_qualification.py +0 -0
  109. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_measured_motion_v10.py +0 -0
  110. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_natural_language_agent_v07.py +0 -0
  111. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_openai_strict_schema_v0113.py +0 -0
  112. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_optimization_v05.py +0 -0
  113. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_physical_campaign_v09.py +0 -0
  114. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_physical_evidence_v09.py +0 -0
  115. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_physical_motion_v09.py +0 -0
  116. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_preexecution_contract_v10.py +0 -0
  117. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_provider_and_qualification.py +0 -0
  118. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_qualification_cli_lifecycle_v06.py +0 -0
  119. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_qualification_harness_v06.py +0 -0
  120. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_real_provider_adapters.py +0 -0
  121. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_robot_platform_v08.py +0 -0
  122. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_ros2_production_receipts_v011.py +0 -0
  123. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_ros2_simulation.py +0 -0
  124. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_ros_trajectory_v09.py +0 -0
  125. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_setup_profile.py +0 -0
  126. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_sim_real_qualification_v011.py +0 -0
  127. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_simulation_platform_v08.py +0 -0
  128. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_tool_collision_v011.py +0 -0
  129. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_trajectory_qualification_v09.py +0 -0
  130. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_twin_materialization_v10.py +0 -0
  131. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_twin_v08.py +0 -0
  132. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_ur5e_adapter_scopes_v09.py +0 -0
  133. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_ur5e_v10_adapter_scopes.py +0 -0
  134. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_v052_stabilization.py +0 -0
  135. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_v08_catalog_mapping_regression.py +0 -0
  136. {devagent_physical_engine-0.11.4 → devagent_physical_engine-0.11.5}/tests/test_workpiece_lifecycle_v011.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devagent-physical-engine
3
- Version: 0.11.4
3
+ Version: 0.11.5
4
4
  Summary: Verification-first agentic planning runtime for robotic and industrial automation.
5
5
  Author: Tom Ha
6
6
  License: All Rights Reserved
@@ -49,11 +49,11 @@ DevAgent Smart Physical Engine adds AI-assisted engineering to existing automati
49
49
 
50
50
  ## Current version
51
51
 
52
- **v0.11.4 — Beta software / evidence-gated physical runtime**
52
+ **v0.11.5 — Beta software / evidence-gated physical runtime**
53
53
 
54
54
  The Python software stack is regression-tested on Python 3.11, 3.12, and 3.13 with separate static/coverage, package-install, and runtime dependency security gates. The UR5e Gazebo/MoveIt path now contains production-candidate continuous-clearance, tool-collision, and workpiece-lifecycle capabilities, but those physical-simulation scopes remain **EXPERIMENTAL until the executable target-workstation v0.11 campaign passes**.
55
55
 
56
- v0.11.3 normalized provider-neutral agent schemas at the OpenAI transport boundary so strict Structured Outputs can represent DevAgent optional fields without changing Anthropic/Gemini or deterministic core semantics. v0.11.4 adds live terminal progress tracing for AI request, engineering, and provider-qualification workflows. Progress is written to `stderr`; final machine-readable JSON remains isolated on `stdout`.
56
+ v0.11.3 normalized provider-neutral agent schemas at the OpenAI transport boundary so strict Structured Outputs can represent DevAgent optional fields without changing Anthropic/Gemini or deterministic core semantics. v0.11.4 added live terminal progress tracing for AI request, engineering, and provider-qualification workflows. v0.11.5 adds explicit visualization modes: `--visualize` requests exact task-specific visualization and fails closed until an exact physical Twin plus compiled `PhysicalMotionPlan` exist; `--visual-demo` launches the UR5e Gazebo/MoveIt motion demo without misrepresenting that generic motion as the requested BOX/source/destination task.
57
57
 
58
58
  Real robot execution remains locked. Functional-safety certification and site qualification are not claimed.
59
59
 
@@ -66,6 +66,8 @@ Real robot execution remains locked. Functional-safety certification and site qu
66
66
  | Planner / Critic / Recovery | Implemented |
67
67
  | Natural-language engineering request front door | Implemented |
68
68
  | Live terminal stage tracing (`stderr`) | Implemented; `--quiet` supported |
69
+ | Explicit AI visual demo (`--visual-demo`) | Implemented for UR5e workstation visual motion; not task-specific |
70
+ | Exact task visualization (`--visualize`) | Evidence-gated; blocked until exact Twin + compiled physical motion are available |
69
71
  | Deterministic compiler / semantic / verification gates | Implemented |
70
72
  | Multi-vendor robot profile registry | Implemented |
71
73
  | UR5e / FANUC CRX / KUKA KR / ABB IRB abstraction | Implemented; physical qualification is vendor-specific |
@@ -211,7 +213,7 @@ devagent-physical-ai engineer \
211
213
  --provider openai --model <model-id>
212
214
  ```
213
215
 
214
- Live commands now show stage-by-stage progress while they are running, for example:
216
+ Live commands show stage-by-stage progress while they are running, for example:
215
217
 
216
218
  ```text
217
219
  [DevAgent + 0.00s] START provider=openai model=<model-id>
@@ -230,6 +232,30 @@ Live commands now show stage-by-stage progress while they are running, for examp
230
232
 
231
233
  Progress logs are emitted to `stderr`, so piping `stdout` still produces clean JSON. For CI or scripts that do not want terminal progress, add `--quiet`.
232
234
 
235
+ ### Visualization semantics
236
+
237
+ `--visualize` means **visualize the exact engineering task**, not merely make a robot move. Therefore it fails closed until the request has enough physical evidence to build the exact scene and a compiled physical trajectory:
238
+
239
+ ```bash
240
+ devagent-physical-ai engineer \
241
+ "Use a UR5e to load BOX_101 from conveyor_a to cnc_04. Simulate and verify the plan." \
242
+ --provider openai --model <model-id> \
243
+ --visualize
244
+ ```
245
+
246
+ Until the task-specific physical bridge is complete, the result reports `task_visualization_requires_physical_twin_motion_plan` and lists required evidence such as robot/source/destination poses, collision geometry, workpiece/tool geometry, and a compiled `PhysicalMotionPlan`. DevAgent intentionally does not substitute a canned motion and claim it is the requested BOX task.
247
+
248
+ To verify that the workstation can actually launch Gazebo/MoveIt and visibly move a UR5e now, use the explicit demo mode:
249
+
250
+ ```bash
251
+ devagent-physical-ai engineer \
252
+ "Use a UR5e to load BOX_101 from conveyor_a to cnc_04. Simulate and verify the plan." \
253
+ --provider openai --model <model-id> \
254
+ --visual-demo
255
+ ```
256
+
257
+ The visual demo uses the packaged UR simulation motion smoke, records an acceptance report under `~/.devagent/ai-visual`, and leaves Gazebo/MoveIt open after success unless `--close-after` is supplied. Its JSON explicitly reports `task_specific=false` and `motion_source=ur_robot_driver/example_move.py`.
258
+
233
259
  Equivalent routing supports Anthropic and Gemini. Live AI qualification does not unlock real robot execution.
234
260
 
235
261
  ## Software production gates
@@ -8,11 +8,11 @@ DevAgent Smart Physical Engine adds AI-assisted engineering to existing automati
8
8
 
9
9
  ## Current version
10
10
 
11
- **v0.11.4 — Beta software / evidence-gated physical runtime**
11
+ **v0.11.5 — Beta software / evidence-gated physical runtime**
12
12
 
13
13
  The Python software stack is regression-tested on Python 3.11, 3.12, and 3.13 with separate static/coverage, package-install, and runtime dependency security gates. The UR5e Gazebo/MoveIt path now contains production-candidate continuous-clearance, tool-collision, and workpiece-lifecycle capabilities, but those physical-simulation scopes remain **EXPERIMENTAL until the executable target-workstation v0.11 campaign passes**.
14
14
 
15
- v0.11.3 normalized provider-neutral agent schemas at the OpenAI transport boundary so strict Structured Outputs can represent DevAgent optional fields without changing Anthropic/Gemini or deterministic core semantics. v0.11.4 adds live terminal progress tracing for AI request, engineering, and provider-qualification workflows. Progress is written to `stderr`; final machine-readable JSON remains isolated on `stdout`.
15
+ v0.11.3 normalized provider-neutral agent schemas at the OpenAI transport boundary so strict Structured Outputs can represent DevAgent optional fields without changing Anthropic/Gemini or deterministic core semantics. v0.11.4 added live terminal progress tracing for AI request, engineering, and provider-qualification workflows. v0.11.5 adds explicit visualization modes: `--visualize` requests exact task-specific visualization and fails closed until an exact physical Twin plus compiled `PhysicalMotionPlan` exist; `--visual-demo` launches the UR5e Gazebo/MoveIt motion demo without misrepresenting that generic motion as the requested BOX/source/destination task.
16
16
 
17
17
  Real robot execution remains locked. Functional-safety certification and site qualification are not claimed.
18
18
 
@@ -25,6 +25,8 @@ Real robot execution remains locked. Functional-safety certification and site qu
25
25
  | Planner / Critic / Recovery | Implemented |
26
26
  | Natural-language engineering request front door | Implemented |
27
27
  | Live terminal stage tracing (`stderr`) | Implemented; `--quiet` supported |
28
+ | Explicit AI visual demo (`--visual-demo`) | Implemented for UR5e workstation visual motion; not task-specific |
29
+ | Exact task visualization (`--visualize`) | Evidence-gated; blocked until exact Twin + compiled physical motion are available |
28
30
  | Deterministic compiler / semantic / verification gates | Implemented |
29
31
  | Multi-vendor robot profile registry | Implemented |
30
32
  | UR5e / FANUC CRX / KUKA KR / ABB IRB abstraction | Implemented; physical qualification is vendor-specific |
@@ -170,7 +172,7 @@ devagent-physical-ai engineer \
170
172
  --provider openai --model <model-id>
171
173
  ```
172
174
 
173
- Live commands now show stage-by-stage progress while they are running, for example:
175
+ Live commands show stage-by-stage progress while they are running, for example:
174
176
 
175
177
  ```text
176
178
  [DevAgent + 0.00s] START provider=openai model=<model-id>
@@ -189,6 +191,30 @@ Live commands now show stage-by-stage progress while they are running, for examp
189
191
 
190
192
  Progress logs are emitted to `stderr`, so piping `stdout` still produces clean JSON. For CI or scripts that do not want terminal progress, add `--quiet`.
191
193
 
194
+ ### Visualization semantics
195
+
196
+ `--visualize` means **visualize the exact engineering task**, not merely make a robot move. Therefore it fails closed until the request has enough physical evidence to build the exact scene and a compiled physical trajectory:
197
+
198
+ ```bash
199
+ devagent-physical-ai engineer \
200
+ "Use a UR5e to load BOX_101 from conveyor_a to cnc_04. Simulate and verify the plan." \
201
+ --provider openai --model <model-id> \
202
+ --visualize
203
+ ```
204
+
205
+ Until the task-specific physical bridge is complete, the result reports `task_visualization_requires_physical_twin_motion_plan` and lists required evidence such as robot/source/destination poses, collision geometry, workpiece/tool geometry, and a compiled `PhysicalMotionPlan`. DevAgent intentionally does not substitute a canned motion and claim it is the requested BOX task.
206
+
207
+ To verify that the workstation can actually launch Gazebo/MoveIt and visibly move a UR5e now, use the explicit demo mode:
208
+
209
+ ```bash
210
+ devagent-physical-ai engineer \
211
+ "Use a UR5e to load BOX_101 from conveyor_a to cnc_04. Simulate and verify the plan." \
212
+ --provider openai --model <model-id> \
213
+ --visual-demo
214
+ ```
215
+
216
+ The visual demo uses the packaged UR simulation motion smoke, records an acceptance report under `~/.devagent/ai-visual`, and leaves Gazebo/MoveIt open after success unless `--close-after` is supplied. Its JSON explicitly reports `task_specific=false` and `motion_source=ur_robot_driver/example_move.py`.
217
+
192
218
  Equivalent routing supports Anthropic and Gemini. Live AI qualification does not unlock real robot execution.
193
219
 
194
220
  ## Software production gates
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devagent-physical-engine"
7
- version = "0.11.4"
7
+ version = "0.11.5"
8
8
  description = "Verification-first agentic planning runtime for robotic and industrial automation."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -41,4 +41,4 @@ __all__ = [
41
41
  "TwinValidator",
42
42
  ]
43
43
 
44
- __version__ = "0.11.4"
44
+ __version__ = "0.11.5"
@@ -4,6 +4,7 @@ import argparse
4
4
  import importlib.util
5
5
  import json
6
6
  import os
7
+ from pathlib import Path
7
8
  import sys
8
9
  import time
9
10
  from collections.abc import Callable
@@ -45,6 +46,36 @@ def _add_live_request_args(parser: argparse.ArgumentParser) -> None:
45
46
  )
46
47
 
47
48
 
49
+ def _add_visualization_args(parser: argparse.ArgumentParser) -> None:
50
+ visual = parser.add_mutually_exclusive_group()
51
+ visual.add_argument(
52
+ "--visualize",
53
+ action="store_true",
54
+ help=(
55
+ "Request task-specific Gazebo/MoveIt visualization. This fails closed until "
56
+ "an exact physical Twin and compiled motion plan are available."
57
+ ),
58
+ )
59
+ visual.add_argument(
60
+ "--visual-demo",
61
+ action="store_true",
62
+ help=(
63
+ "Run the qualified UR5e Gazebo/MoveIt motion demo after engineering verification. "
64
+ "The demo is visual but is not the BOX/source/destination task-specific motion."
65
+ ),
66
+ )
67
+ parser.add_argument("--ros-setup", default="/opt/ros/jazzy/setup.bash")
68
+ parser.add_argument("--workspace-setup", default=None)
69
+ parser.add_argument("--visual-log-dir", default="~/.devagent/ai-visual")
70
+ parser.add_argument("--startup-timeout", type=float, default=120.0)
71
+ parser.add_argument("--motion-timeout", type=float, default=90.0)
72
+ parser.add_argument(
73
+ "--close-after",
74
+ action="store_true",
75
+ help="Close Gazebo after a successful visual demo instead of leaving it open",
76
+ )
77
+
78
+
48
79
  def _parser() -> argparse.ArgumentParser:
49
80
  parser = argparse.ArgumentParser(prog="devagent-physical-ai")
50
81
  sub = parser.add_subparsers(dest="command", required=True)
@@ -81,6 +112,7 @@ def _parser() -> argparse.ArgumentParser:
81
112
  help="Natural language -> validated request -> AI plan/critic -> deterministic model smoke",
82
113
  )
83
114
  _add_live_request_args(engineer)
115
+ _add_visualization_args(engineer)
84
116
  return parser
85
117
 
86
118
 
@@ -203,6 +235,104 @@ def _request(args: argparse.Namespace, progress: ProgressCallback | None = None)
203
235
  return 3
204
236
 
205
237
 
238
+ def _task_visualization_block() -> dict:
239
+ return {
240
+ "requested": True,
241
+ "mode": "task_specific",
242
+ "launched": False,
243
+ "passed": False,
244
+ "task_specific": True,
245
+ "failure_code": "task_visualization_requires_physical_twin_motion_plan",
246
+ "required_evidence": [
247
+ "robot_base_pose",
248
+ "source_pose_and_collision_geometry",
249
+ "destination_pose_and_collision_geometry",
250
+ "workpiece_geometry",
251
+ "tool_tcp_and_collision_geometry",
252
+ "compiled_physical_motion_plan",
253
+ ],
254
+ "detail": (
255
+ "The verified high-level pick/move/place graph cannot be represented as an exact "
256
+ "Gazebo/MoveIt motion without measured or user-declared physical scene geometry and "
257
+ "a compiled PhysicalMotionPlan. DevAgent will not substitute a generic robot motion "
258
+ "and label it as the requested task."
259
+ ),
260
+ "physical_qualification": False,
261
+ "real_execution_allowed": False,
262
+ }
263
+
264
+
265
+ def _run_visual_demo(
266
+ args: argparse.Namespace,
267
+ *,
268
+ robot: str,
269
+ progress: ProgressCallback | None,
270
+ ) -> dict:
271
+ if robot != "ur5e":
272
+ return {
273
+ "requested": True,
274
+ "mode": "robot_motion_demo",
275
+ "launched": False,
276
+ "passed": False,
277
+ "task_specific": False,
278
+ "robot": robot,
279
+ "failure_code": "visual_demo_robot_not_supported",
280
+ "detail": "The packaged visual demo currently supports UR5e only.",
281
+ "physical_qualification": False,
282
+ "real_execution_allowed": False,
283
+ }
284
+ if args.startup_timeout <= 0 or args.motion_timeout <= 0:
285
+ raise ValueError("visual_timeouts_must_be_positive")
286
+
287
+ from .ros2 import SubprocessRosRunner, UR5eLaptopAcceptance
288
+
289
+ runner = SubprocessRosRunner(
290
+ ros_setup=args.ros_setup,
291
+ workspace_setup=args.workspace_setup,
292
+ )
293
+ root = Path(args.visual_log_dir).expanduser().resolve()
294
+ report_path = root / "latest-ai-visual-demo.json"
295
+ _emit(progress, "VISUAL", "checking display and ROS/Gazebo/MoveIt readiness")
296
+ report = UR5eLaptopAcceptance(runner, ros_setup=args.ros_setup).run(
297
+ log_dir=root,
298
+ startup_timeout_s=args.startup_timeout,
299
+ motion_timeout_s=args.motion_timeout,
300
+ keep_open=not args.close_after,
301
+ )
302
+ report.write_json(report_path)
303
+ if report.passed:
304
+ _emit(
305
+ progress,
306
+ "VISUAL",
307
+ (
308
+ "UR5e motion demo passed; simulator closed"
309
+ if args.close_after
310
+ else "UR5e motion demo passed; Gazebo/MoveIt left open for inspection"
311
+ ),
312
+ )
313
+ else:
314
+ failed = next((stage.name for stage in report.stages if not stage.passed), "unknown")
315
+ _emit(progress, "VISUAL", f"UR5e motion demo blocked at stage={failed}")
316
+
317
+ return {
318
+ "requested": True,
319
+ "mode": "robot_motion_demo",
320
+ "launched": any(stage.name == "simulation_launch" and stage.passed for stage in report.stages),
321
+ "passed": report.passed,
322
+ "task_specific": False,
323
+ "robot": "ur5e",
324
+ "motion_source": "ur_robot_driver/example_move.py",
325
+ "warning": (
326
+ "This validates the workstation visual motion stack only. It is not the exact "
327
+ "BOX/source/destination motion generated from the engineering request."
328
+ ),
329
+ "report_path": str(report_path),
330
+ "report": report.to_dict(),
331
+ "physical_qualification": False,
332
+ "real_execution_allowed": False,
333
+ }
334
+
335
+
206
336
  def _engineer(args: argparse.Namespace, progress: ProgressCallback | None = None) -> int:
207
337
  _emit(progress, "START", f"provider={args.provider} model={args.model}")
208
338
  _, runtime, policy = _live_runtime(
@@ -227,6 +357,33 @@ def _engineer(args: argparse.Namespace, progress: ProgressCallback | None = None
227
357
  payload = result.to_dict()
228
358
  payload["provider_mode"] = "simulation_only_ephemeral_route"
229
359
  payload["real_execution_allowed"] = False
360
+
361
+ if result.ready_for_twin_simulation and args.visualize:
362
+ _emit(
363
+ progress,
364
+ "VISUAL",
365
+ "task-specific visualization requested; checking physical motion evidence",
366
+ )
367
+ payload["visualization"] = _task_visualization_block()
368
+ _emit(
369
+ progress,
370
+ "VISUAL",
371
+ "blocked: exact Twin geometry and compiled PhysicalMotionPlan are required",
372
+ )
373
+ print(json.dumps(payload, indent=2))
374
+ return 6
375
+
376
+ if result.ready_for_twin_simulation and args.visual_demo:
377
+ request = result.interpretation.validated
378
+ robot = "" if request is None else request.robot
379
+ payload["visualization"] = _run_visual_demo(
380
+ args,
381
+ robot=robot,
382
+ progress=progress,
383
+ )
384
+ print(json.dumps(payload, indent=2))
385
+ return 0 if payload["visualization"]["passed"] else 6
386
+
230
387
  print(json.dumps(payload, indent=2))
231
388
  if result.ready_for_twin_simulation:
232
389
  return 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devagent-physical-engine
3
- Version: 0.11.4
3
+ Version: 0.11.5
4
4
  Summary: Verification-first agentic planning runtime for robotic and industrial automation.
5
5
  Author: Tom Ha
6
6
  License: All Rights Reserved
@@ -49,11 +49,11 @@ DevAgent Smart Physical Engine adds AI-assisted engineering to existing automati
49
49
 
50
50
  ## Current version
51
51
 
52
- **v0.11.4 — Beta software / evidence-gated physical runtime**
52
+ **v0.11.5 — Beta software / evidence-gated physical runtime**
53
53
 
54
54
  The Python software stack is regression-tested on Python 3.11, 3.12, and 3.13 with separate static/coverage, package-install, and runtime dependency security gates. The UR5e Gazebo/MoveIt path now contains production-candidate continuous-clearance, tool-collision, and workpiece-lifecycle capabilities, but those physical-simulation scopes remain **EXPERIMENTAL until the executable target-workstation v0.11 campaign passes**.
55
55
 
56
- v0.11.3 normalized provider-neutral agent schemas at the OpenAI transport boundary so strict Structured Outputs can represent DevAgent optional fields without changing Anthropic/Gemini or deterministic core semantics. v0.11.4 adds live terminal progress tracing for AI request, engineering, and provider-qualification workflows. Progress is written to `stderr`; final machine-readable JSON remains isolated on `stdout`.
56
+ v0.11.3 normalized provider-neutral agent schemas at the OpenAI transport boundary so strict Structured Outputs can represent DevAgent optional fields without changing Anthropic/Gemini or deterministic core semantics. v0.11.4 added live terminal progress tracing for AI request, engineering, and provider-qualification workflows. v0.11.5 adds explicit visualization modes: `--visualize` requests exact task-specific visualization and fails closed until an exact physical Twin plus compiled `PhysicalMotionPlan` exist; `--visual-demo` launches the UR5e Gazebo/MoveIt motion demo without misrepresenting that generic motion as the requested BOX/source/destination task.
57
57
 
58
58
  Real robot execution remains locked. Functional-safety certification and site qualification are not claimed.
59
59
 
@@ -66,6 +66,8 @@ Real robot execution remains locked. Functional-safety certification and site qu
66
66
  | Planner / Critic / Recovery | Implemented |
67
67
  | Natural-language engineering request front door | Implemented |
68
68
  | Live terminal stage tracing (`stderr`) | Implemented; `--quiet` supported |
69
+ | Explicit AI visual demo (`--visual-demo`) | Implemented for UR5e workstation visual motion; not task-specific |
70
+ | Exact task visualization (`--visualize`) | Evidence-gated; blocked until exact Twin + compiled physical motion are available |
69
71
  | Deterministic compiler / semantic / verification gates | Implemented |
70
72
  | Multi-vendor robot profile registry | Implemented |
71
73
  | UR5e / FANUC CRX / KUKA KR / ABB IRB abstraction | Implemented; physical qualification is vendor-specific |
@@ -211,7 +213,7 @@ devagent-physical-ai engineer \
211
213
  --provider openai --model <model-id>
212
214
  ```
213
215
 
214
- Live commands now show stage-by-stage progress while they are running, for example:
216
+ Live commands show stage-by-stage progress while they are running, for example:
215
217
 
216
218
  ```text
217
219
  [DevAgent + 0.00s] START provider=openai model=<model-id>
@@ -230,6 +232,30 @@ Live commands now show stage-by-stage progress while they are running, for examp
230
232
 
231
233
  Progress logs are emitted to `stderr`, so piping `stdout` still produces clean JSON. For CI or scripts that do not want terminal progress, add `--quiet`.
232
234
 
235
+ ### Visualization semantics
236
+
237
+ `--visualize` means **visualize the exact engineering task**, not merely make a robot move. Therefore it fails closed until the request has enough physical evidence to build the exact scene and a compiled physical trajectory:
238
+
239
+ ```bash
240
+ devagent-physical-ai engineer \
241
+ "Use a UR5e to load BOX_101 from conveyor_a to cnc_04. Simulate and verify the plan." \
242
+ --provider openai --model <model-id> \
243
+ --visualize
244
+ ```
245
+
246
+ Until the task-specific physical bridge is complete, the result reports `task_visualization_requires_physical_twin_motion_plan` and lists required evidence such as robot/source/destination poses, collision geometry, workpiece/tool geometry, and a compiled `PhysicalMotionPlan`. DevAgent intentionally does not substitute a canned motion and claim it is the requested BOX task.
247
+
248
+ To verify that the workstation can actually launch Gazebo/MoveIt and visibly move a UR5e now, use the explicit demo mode:
249
+
250
+ ```bash
251
+ devagent-physical-ai engineer \
252
+ "Use a UR5e to load BOX_101 from conveyor_a to cnc_04. Simulate and verify the plan." \
253
+ --provider openai --model <model-id> \
254
+ --visual-demo
255
+ ```
256
+
257
+ The visual demo uses the packaged UR simulation motion smoke, records an acceptance report under `~/.devagent/ai-visual`, and leaves Gazebo/MoveIt open after success unless `--close-after` is supplied. Its JSON explicitly reports `task_specific=false` and `motion_source=ur_robot_driver/example_move.py`.
258
+
233
259
  Equivalent routing supports Anthropic and Gemini. Live AI qualification does not unlock real robot execution.
234
260
 
235
261
  ## Software production gates
@@ -95,6 +95,116 @@ class AiCliTests(unittest.TestCase):
95
95
  self.assertEqual(json.loads(output.getvalue()), {"passed": True})
96
96
  self.assertIsNone(qualify.call_args.kwargs["progress"])
97
97
 
98
+ def test_engineer_parser_accepts_task_visualization(self):
99
+ args = ai_cli._parser().parse_args(
100
+ [
101
+ "engineer",
102
+ "load box",
103
+ "--provider",
104
+ "openai",
105
+ "--model",
106
+ "model-id",
107
+ "--visualize",
108
+ ]
109
+ )
110
+ self.assertTrue(args.visualize)
111
+ self.assertFalse(args.visual_demo)
112
+
113
+ def test_task_visualization_fails_closed_without_physical_motion_plan(self):
114
+ fake_result = SimpleNamespace(
115
+ ready_for_twin_simulation=True,
116
+ failure_code=None,
117
+ interpretation=SimpleNamespace(
118
+ validated=SimpleNamespace(robot="ur5e"),
119
+ rejection_code=None,
120
+ ),
121
+ to_dict=lambda: {
122
+ "ready_for_twin_simulation": True,
123
+ "physical_qualification": False,
124
+ },
125
+ )
126
+ output = io.StringIO()
127
+ with patch.object(
128
+ ai_cli,
129
+ "_live_runtime",
130
+ return_value=(object(), object(), object()),
131
+ ), patch.object(ai_cli, "SmartEngineeringAgent") as smart, patch.object(
132
+ ai_cli, "_run_visual_demo"
133
+ ) as demo, contextlib.redirect_stdout(output):
134
+ smart.return_value.run.return_value = fake_result
135
+ code = ai_cli.main(
136
+ [
137
+ "engineer",
138
+ "load box",
139
+ "--provider",
140
+ "openai",
141
+ "--model",
142
+ "model-id",
143
+ "--visualize",
144
+ "--quiet",
145
+ ]
146
+ )
147
+ payload = json.loads(output.getvalue())
148
+ self.assertEqual(code, 6)
149
+ self.assertFalse(payload["visualization"]["launched"])
150
+ self.assertTrue(payload["visualization"]["task_specific"])
151
+ self.assertEqual(
152
+ payload["visualization"]["failure_code"],
153
+ "task_visualization_requires_physical_twin_motion_plan",
154
+ )
155
+ demo.assert_not_called()
156
+
157
+ def test_visual_demo_is_explicitly_not_task_specific(self):
158
+ fake_result = SimpleNamespace(
159
+ ready_for_twin_simulation=True,
160
+ failure_code=None,
161
+ interpretation=SimpleNamespace(
162
+ validated=SimpleNamespace(robot="ur5e"),
163
+ rejection_code=None,
164
+ ),
165
+ to_dict=lambda: {
166
+ "ready_for_twin_simulation": True,
167
+ "physical_qualification": False,
168
+ },
169
+ )
170
+ visual = {
171
+ "requested": True,
172
+ "mode": "robot_motion_demo",
173
+ "launched": True,
174
+ "passed": True,
175
+ "task_specific": False,
176
+ "physical_qualification": False,
177
+ "real_execution_allowed": False,
178
+ }
179
+ output = io.StringIO()
180
+ with patch.object(
181
+ ai_cli,
182
+ "_live_runtime",
183
+ return_value=(object(), object(), object()),
184
+ ), patch.object(ai_cli, "SmartEngineeringAgent") as smart, patch.object(
185
+ ai_cli,
186
+ "_run_visual_demo",
187
+ return_value=visual,
188
+ ) as demo, contextlib.redirect_stdout(output):
189
+ smart.return_value.run.return_value = fake_result
190
+ code = ai_cli.main(
191
+ [
192
+ "engineer",
193
+ "load box",
194
+ "--provider",
195
+ "openai",
196
+ "--model",
197
+ "model-id",
198
+ "--visual-demo",
199
+ "--quiet",
200
+ ]
201
+ )
202
+ payload = json.loads(output.getvalue())
203
+ self.assertEqual(code, 0)
204
+ self.assertTrue(payload["visualization"]["launched"])
205
+ self.assertFalse(payload["visualization"]["task_specific"])
206
+ demo.assert_called_once()
207
+
98
208
 
99
209
  if __name__ == "__main__":
100
210
  unittest.main()