topicforge 0.7.2__tar.gz → 0.8.1__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.
- {topicforge-0.7.2 → topicforge-0.8.1}/CHANGELOG.md +192 -2
- {topicforge-0.7.2 → topicforge-0.8.1}/PKG-INFO +10 -9
- {topicforge-0.7.2 → topicforge-0.8.1}/README.md +9 -8
- {topicforge-0.7.2 → topicforge-0.8.1}/docs/CLIENTS.md +70 -28
- {topicforge-0.7.2 → topicforge-0.8.1}/docs/CONTRACT.md +468 -9
- {topicforge-0.7.2 → topicforge-0.8.1}/docs/DDS_QUICKSTART.md +1 -1
- topicforge-0.8.1/docs/EVAL.md +320 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/docs/TESTING.md +4 -4
- {topicforge-0.7.2 → topicforge-0.8.1}/docs/TOOLS.md +300 -67
- {topicforge-0.7.2 → topicforge-0.8.1}/docs/TROUBLESHOOTING.md +1 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/docs/TUTORIEL.md +1 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/README.md +1 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/plugin/README.md +4 -3
- {topicforge-0.7.2 → topicforge-0.8.1}/plugin/pyproject.toml +2 -2
- {topicforge-0.7.2 → topicforge-0.8.1}/pyproject.toml +1 -1
- topicforge-0.8.1/scripts/agent_eval/README.md +71 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/scripts/ground_truth/README.md +22 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/__init__.py +1 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/__main__.py +41 -8
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/base.py +13 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/__init__.py +2 -0
- topicforge-0.8.1/src/topicforge/adapters/common/bag_range.py +59 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/discovery_tracker.py +3 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/endpoints.py +58 -10
- topicforge-0.8.1/src/topicforge/adapters/common/log_filter.py +184 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/nodes.py +53 -10
- topicforge-0.8.1/src/topicforge/adapters/common/param_diff.py +284 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_scan.py +48 -8
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/ros_discovery.py +37 -4
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/tf_tree.py +98 -12
- topicforge-0.8.1/src/topicforge/adapters/common/urdf.py +131 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/composite.py +31 -2
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/dds_cyclone/adapter.py +17 -2
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/dds_fast/adapter.py +13 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/adapter.py +166 -7
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/parsers.py +18 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/sample_extras.py +7 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/adapter.py +76 -5
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/fixtures.py +163 -7
- topicforge-0.8.1/src/topicforge/adapters/ros2_mock/graph_fixtures.py +36 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/node_fixtures.py +24 -2
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/models/__init__.py +6 -0
- topicforge-0.8.1/src/topicforge/models/graph_diff.py +73 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/models/nodes.py +48 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/models/schemas.py +75 -15
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/models/summaries.py +17 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/models/tf.py +12 -1
- topicforge-0.8.1/src/topicforge/server/http.py +162 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/server/prompts.py +47 -4
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/bag_service.py +107 -5
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/dds_views.py +45 -6
- topicforge-0.8.1/src/topicforge/services/graph_diff.py +324 -0
- topicforge-0.8.1/src/topicforge/services/graph_snapshot.py +160 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/health.py +48 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/inspector.py +117 -5
- topicforge-0.8.1/src/topicforge/services/params_file.py +59 -0
- topicforge-0.8.1/src/topicforge/services/summaries/clock.py +103 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/summaries/rate.py +9 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/tools/guard.py +2 -2
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/tools/handlers.py +116 -27
- topicforge-0.8.1/src/topicforge/tools/schema_compact.py +127 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/_prompts.json +18 -2
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/analyze_bag.json +61 -93
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/detect_qos_mismatches.json +106 -153
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/get_node_info.json +103 -64
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/get_topic_info.json +16 -33
- topicforge-0.8.1/tests/contract/graph_diff.json +243 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/health_check.json +118 -168
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/inspect_tf.json +99 -116
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/list_endpoints.json +188 -304
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/list_nodes.json +29 -12
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/list_participants.json +61 -104
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/list_topics.json +6 -12
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/participant_events.json +31 -63
- topicforge-0.8.1/tests/contract/peek_bag_samples.json +362 -0
- topicforge-0.8.1/tests/contract/peek_dds_samples.json +299 -0
- topicforge-0.8.1/tests/contract/sample_messages.json +357 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/contract/topic_metrics.json +34 -68
- topicforge-0.8.1/tests/fixtures/ros2_node/service_list_types.txt +7 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/integration/ros2/Dockerfile +3 -0
- topicforge-0.8.1/tests/integration/ros2/bench_params.yaml +8 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/integration/ros2/entrypoint.sh +14 -3
- topicforge-0.8.1/tests/integration/ros2/fault_nodes.py +93 -0
- topicforge-0.8.1/tests/integration/ros2/faults.py +157 -0
- topicforge-0.8.1/tests/integration/ros2/graph_change.py +50 -0
- topicforge-0.8.1/tests/integration/ros2/ground_truth.py +562 -0
- topicforge-0.8.1/tests/integration/ros2/lifecycle_node.py +34 -0
- topicforge-0.8.1/tests/integration/ros2/publisher.py +269 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/integration/ros2/run_bench.py +14 -4
- topicforge-0.8.1/tests/integration/ros2/sros2_check.py +159 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/integration/ros2/test_live_adapter.py +134 -5
- topicforge-0.8.1/tests/test_agent_eval_tools.py +397 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_analyze_bag_multi_format.py +1 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_bag_service.py +1 -1
- topicforge-0.8.1/tests/test_clock_basis.py +104 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_composite_adapter.py +13 -3
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_contract_shape.py +2 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_contract_v2.py +1 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_cyclone_ros_info.py +41 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_dds_schemas.py +80 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_endpoints.py +105 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_factory.py +3 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_friction_fixes.py +23 -1
- topicforge-0.8.1/tests/test_graph_diff.py +486 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_ground_truth_compare.py +736 -14
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_health.py +65 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_honest_outputs.py +1 -1
- topicforge-0.8.1/tests/test_http_transport.py +184 -0
- topicforge-0.8.1/tests/test_log_filter.py +482 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_mock_adapter.py +1 -1
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_node_logic.py +25 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_node_parsers.py +13 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_node_tools.py +191 -2
- topicforge-0.8.1/tests/test_output_schema_validation.py +115 -0
- topicforge-0.8.1/tests/test_param_diff.py +213 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_prompts.py +61 -3
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_qos_scan.py +21 -2
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_ros_discovery.py +86 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_sample_options.py +5 -1
- topicforge-0.8.1/tests/test_summary_schema_compat.py +126 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_tf_tools.py +209 -3
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_tf_tree.py +34 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_tool_annotations.py +2 -2
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_tool_descriptions.py +2 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_tools_integration.py +5 -2
- topicforge-0.8.1/tests/test_urdf.py +108 -0
- topicforge-0.7.2/docs/EVAL.md +0 -158
- topicforge-0.7.2/scripts/agent_eval/README.md +0 -41
- topicforge-0.7.2/src/topicforge/server/http.py +0 -75
- topicforge-0.7.2/src/topicforge/tools/schema_compact.py +0 -60
- topicforge-0.7.2/tests/contract/peek_bag_samples.json +0 -1083
- topicforge-0.7.2/tests/contract/peek_dds_samples.json +0 -1077
- topicforge-0.7.2/tests/contract/sample_messages.json +0 -1104
- topicforge-0.7.2/tests/integration/ros2/ground_truth.py +0 -270
- topicforge-0.7.2/tests/integration/ros2/publisher.py +0 -156
- topicforge-0.7.2/tests/test_http_transport.py +0 -112
- {topicforge-0.7.2 → topicforge-0.8.1}/.gitignore +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/LICENSE +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/docs/MIGRATION-0.7.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/docs/VALIDATION.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/docs/dds-interop-matrix.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/00_hello_pub_sub/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/01_who_is_on_the_bus/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/02_why_cant_they_talk/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/03_a_node_crashed/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/04_late_joiner_misses_data/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/05_reliability_in_code/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/06_durability_late_joiner_in_code/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/07_deadline_in_code/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/08_crash_seen_from_inside/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/10_lidar_silent_after_driver_swap/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/11_who_talks_to_whom/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/12_safety_monitor_dropout/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/13_deadline_not_offered/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/14_restart_loop/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/examples/dds/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/plugin/LICENSE +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/scripts/integration/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/scripts/integration/publishers/cyclone_c/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/scripts/integration/publishers/dust_py/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/scripts/integration/publishers/fast_publisher_cpp/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/scripts/integration/publishers/fast_py/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/scripts/integration/publishers/opensplice_publisher/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/scripts/integration/publishers/rti_c/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/scripts/integration/publishers/rti_cpp/README.md +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/bag_kind.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/cdr_decoder.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/dds_helpers.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/dds_introspection.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/lifecycle.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/metrics_buffer.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_analyzer.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_endpoints.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_fixes.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_normalize.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/ros_names.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/spawned_pids.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/stamps.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/topic_filter.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/common/xtypes.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/dds_cyclone/ros_info.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/echo_parser.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/echo_stream.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/node_parsers.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/process_runner.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/process_tree.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/tf_capture.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/tf_parsers.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/tf_fixtures.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/budget.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/config/settings.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/constants.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/server/app.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/bag_stats.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/factory.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/sample_budget.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/summaries/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/summaries/_common.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/summaries/laser_scan.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/summaries/motion.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/services/summaries/sensor_data.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/telemetry/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/telemetry/client.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/src/topicforge/tools/annotations.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/conftest.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_064_results.json +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_064_truth.json +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_071_morning_results.json +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_071_morning_truth.json +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_071_rerun_results.json +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_071_rerun_truth.json +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_echo/image_noarr.yaml +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_echo/image_trunc4.yaml +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_echo/scan_edge.yaml +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_echo/scan_noarr.yaml +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_echo/scan_trunc128.yaml +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_echo/scan_trunc3.yaml +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_echo/string_latched.yaml +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_echo/twist.yaml +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_info_humble.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_info_no_param_services.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_info_not_found.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_list_duplicates.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_list_empty.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_list_plain.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_list_warning_stdout.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_node/param_dump_humble.yaml +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_node/param_dump_secrets.yaml +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_clock.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_scan.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_tf.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros_discovery/humble_two_nodes.hex +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/fixtures/ros_discovery/jazzy_two_nodes.hex +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/integration/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/integration/ros2/__init__.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/integration/ros2/blocked_node.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/integration/test_real_bus.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_bag_omnisim_humble.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_bag_stats.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_bag_summaries.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_cdr_decoder.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_composite_ghosts.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_config.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_contract_snapshots.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_cyclone_adapter.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_dds_cross_vendor.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_dds_helpers.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_dds_inactive_reason.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_dds_introspection.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_dds_qos_normalization.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_discovery_tracker.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_echo_parser.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_echo_stream.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_example_node_spec.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_fast_adapter.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_field_names.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_history_and_hints.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_inspector.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_lifecycle_buffer.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_live_adapter_graph.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_live_adapter_parse.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_live_adapter_subprocess.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_live_sample_notes.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_metrics_buffer.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_peek_bag_samples.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_process_runner.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_process_tree.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_qos_analyzer.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_qos_endpoints.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_qos_fixes.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_rate_verdicts.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_restarts_by_node.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_sample_summary_flow.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_server_guard.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_server_info_version.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_server_lanes.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_stamps.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_summaries.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_telemetry.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_topic_metrics.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_version_consistency.py +0 -0
- {topicforge-0.7.2 → topicforge-0.8.1}/tests/test_xtypes.py +0 -0
|
@@ -5,7 +5,195 @@ All notable changes to TopicForge are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.8.1] - 2026-10-26
|
|
9
|
+
|
|
10
|
+
The 0.8.0 evaluation findings, a URDF read from the robot_state_publisher parameter, an optional
|
|
11
|
+
HTTP bearer token, a Docker image and a SROS2 bench cell. Additive only (`docs/CONTRACT.md`
|
|
12
|
+
section 9.18). `tools/list` is 97,315 bytes (+467).
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- HTTP transport: `TOPICFORGE_HTTP_TOKEN` (at least 32 printable characters, environment only)
|
|
17
|
+
makes every request carry `Authorization: Bearer <token>`, compared in constant time, else
|
|
18
|
+
`401`. `--host` may bind an address other than loopback only with a token; such a bind lifts
|
|
19
|
+
the DNS-rebinding allow-list. Plain HTTP: SECURITY.md says what the token does not cover.
|
|
20
|
+
- `inspect_tf` reads the URDF from the `robot_description` parameter of the node named
|
|
21
|
+
`robot_state_publisher` when no node publishes `/robot_description`; `urdf_note` names the node.
|
|
22
|
+
- `reader_node` and `writer_node` on `detect_qos_mismatches` reports, `not_matched` and
|
|
23
|
+
`matched` pairs.
|
|
24
|
+
- A root `Dockerfile`: a stdio image on `ros:jazzy-ros-core` with the `ros2` CLI and Cyclone DDS,
|
|
25
|
+
non-root. Live graphs need Linux `--network host`; docs/CLIENTS.md has the Docker Desktop
|
|
26
|
+
limits.
|
|
27
|
+
- Bench: a `sros2` job (`run_bench.py --sros2`) with one node under SROS2 Enforce. Run on jazzy
|
|
28
|
+
with Cyclone and Fast DDS, 4 of 4 checks in each.
|
|
29
|
+
- Eval harness: `restarts_at_first_call` in the `restart_loop` summary, counted at the agent's
|
|
30
|
+
first call rather than at `READY`.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- `inspect_tf`: an edge with a steady rate silent for over ten of its periods (at least 1 s) is
|
|
35
|
+
stale even under `stale_after_s`. With the defaults, the evaluation's `tf_stale` read as healthy.
|
|
36
|
+
- `restarts_by_name` leaves out the name `/` that every ROS 2 node shares on Fast DDS; the note
|
|
37
|
+
points to `restarts_by_node`.
|
|
38
|
+
- `min_log_level` accepts `WARN`, `Error` and `warning`.
|
|
39
|
+
- `detect_qos_mismatches` without `topic` leaves `ros_discovery_info` out, and folds orphan
|
|
40
|
+
topics into one hint per side instead of one hint each.
|
|
41
|
+
- `param_diff_summary` names the `same` and `live_only` parameters.
|
|
42
|
+
- A filtered `/rosout` read that received nothing says so, instead of mixing the no-match advice
|
|
43
|
+
with the latched-topic advice.
|
|
44
|
+
|
|
45
|
+
## [0.8.0] - 2026-10-21
|
|
46
|
+
|
|
47
|
+
Graph diff, declared-against-real checks, DDS visibility and a Nav2 prompt. It also carries the
|
|
48
|
+
0.7.3 work (schema compaction, clock fields, `/rosout` filters, bag ranges, lifecycle nodes),
|
|
49
|
+
which was never published on its own: see "Folded in from 0.7.3" below. Additive only
|
|
50
|
+
(`docs/CONTRACT.md` section 9). Validated on the Docker ROS 2 bench (Humble and Jazzy,
|
|
51
|
+
rmw_fastrtps and rmw_cyclonedds) and by a blind evaluation of 14 runs (`docs/EVAL.md`).
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- `graph_diff`, the sixteenth tool: "what changed since it last worked". Each call snapshots the
|
|
56
|
+
ROS 2 graph (nodes, topics, types, publisher and subscriber counts, writer QoS when a DDS backend
|
|
57
|
+
is up) or a bag (`bag_path`: topics, types, rates, latching), keeps it in an in-memory ring of 16
|
|
58
|
+
(`snap-N` ids, emptied on restart, nothing written to disk) and compares it with `baseline`:
|
|
59
|
+
`previous`, a snapshot id or a bag path, in one string. The result is a sorted list of node,
|
|
60
|
+
topic, type, qos, rate and count differences with what could not be compared named in
|
|
61
|
+
`not_compared` (nodes and counts only live against live, rates only bag against bag, writer QoS
|
|
62
|
+
only with a DDS backend). Endpoint-count noise on `/rosout` and `/parameter_events` is tallied in
|
|
63
|
+
`builtin_changes_count`, never reported. ROS lane, read-only, open world. `docs/CONTRACT.md`
|
|
64
|
+
section 9.14.
|
|
65
|
+
- The mock robot ships a `snap-0` baseline derived from its fixtures minus a node, a topic and a
|
|
66
|
+
QoS: the first mock call reports exactly three changes, the second none, and the mock bag is
|
|
67
|
+
accepted as a baseline.
|
|
68
|
+
- Bench: `drive.py` gains `{"spawn": PATH}` (the
|
|
69
|
+
process is stopped when the session ends), `tests/integration/ros2/graph_change.py` adds the
|
|
70
|
+
node `/bench_v2`, the topic `/scan_v2` and a second `/cmd_vel_out` writer, and
|
|
71
|
+
`scripts/ground_truth/calls_graph_diff.json` runs the first, steady, v2 and bag calls. The
|
|
72
|
+
comparator gets a `graph_diff` section: the steady graph must give zero changes, the v2 graph
|
|
73
|
+
exactly three, a bag baseline no removal and no type or QoS change.
|
|
74
|
+
- The `diagnose-nav2` prompt (the third, with optional `symptom` and `namespace`) and the plugin
|
|
75
|
+
skill of the same name: a read-only call order for a Nav2 stack (node list and lifecycle flags,
|
|
76
|
+
`lifecycle_manager` parameters, `/bond` heartbeats, the hidden latched `_action/status` topics
|
|
77
|
+
read with `sample_messages`, `inspect_tf` map to base_link, filtered `/rosout`, the controller's
|
|
78
|
+
velocity output) and a list of what TopicForge cannot see (lifecycle state, costmaps, the
|
|
79
|
+
behavior tree). No tool added, 0 bytes in `tools/list`. `docs/CONTRACT.md` section 9.17. Nav2
|
|
80
|
+
itself was not run: the prompt is verified on its primitives, not on a real stack.
|
|
81
|
+
- Bench for those primitives: the image adds
|
|
82
|
+
`example_interfaces` and `bond`, `publisher.py` serves a never-called action `/bench_fib` and
|
|
83
|
+
publishes `/bond` (`bond/msg/Status`, id `bench_robot`, 1 Hz), `calls_nav_primitives.json` runs
|
|
84
|
+
`40_action_status` and `41_bond`, and the comparator gets a `nav_primitives` section (one latched
|
|
85
|
+
sample with an empty `status_list`, the action in `get_node_info.action_servers`, the status
|
|
86
|
+
topic absent from `list_topics`, the bond id on every `/bond` sample).
|
|
87
|
+
- DDS visibility (lot C), without any verdict on security. `health_check` reports
|
|
88
|
+
`ros_security_enabled`, `ros_security_strategy`, `ros_security_keystore_set` (never the path)
|
|
89
|
+
and, when enabled, `ros_security_note` (a secured node's participant stays visible through SPDP,
|
|
90
|
+
its endpoints and data do not), all `null` without the `ros2` CLI. `ParticipantInfo.endpoint_count`
|
|
91
|
+
counts the endpoints seen per participant, and `list_participants` notes the ones with none for
|
|
92
|
+
more than 10 s, naming both causes (endpoint-less, or hidden by DDS Security). `EndpointInfo.node_name`
|
|
93
|
+
names the ROS 2 node of each endpoint, read from the `ros_discovery_info` gids (bytes 0 to 15 of
|
|
94
|
+
the 24-byte or 16-byte gid are the endpoint GUID), and `TopicSummary.writer_nodes` and
|
|
95
|
+
`reader_nodes` roll it up: the answer to "who writes `/scan`" when every Fast DDS participant is
|
|
96
|
+
called `/`. `tools/list` grows by 1,078 bytes. `docs/CONTRACT.md` section 9.16. Bench:
|
|
97
|
+
a `visibility` section checks the `/scan` writer is `/bench_robot`, its
|
|
98
|
+
participant has at least 12 endpoints and the security fields are `false` and `null`. No SROS2
|
|
99
|
+
cell yet.
|
|
100
|
+
- Declared against real (lot B), `docs/CONTRACT.md` section 9.15. `inspect_tf` reads the latched
|
|
101
|
+
`/robot_description` after the capture and compares the URDF links with the TF frames:
|
|
102
|
+
`urdf_source`, `urdf_link_count`, `declared_frames_missing`, `frames_not_in_urdf`, `urdf_note`
|
|
103
|
+
and a `declared_not_published` finding (a link nothing publishes, with a reminder that a
|
|
104
|
+
non-fixed joint needs `/joint_states`). It needs `robot_state_publisher` (a topic; a URDF held
|
|
105
|
+
only as a parameter is not read) and a missing or refused document is a note, never an error.
|
|
106
|
+
`get_node_info` takes `params_file`, a server-side ROS 2 parameter YAML (`.yaml`/`.yml`, 1 MiB,
|
|
107
|
+
`yaml.safe_load`, never sent to the CLI or the node, nothing written) and returns `param_diff`
|
|
108
|
+
(`different`, `declared_only`, `masked`), `param_diff_summary` and `param_diff_note`; secrets
|
|
109
|
+
are never echoed from the file. The comparison runs in the service, so the `MiddlewareAdapter`
|
|
110
|
+
protocol is unchanged, and it works in mock mode. The mock robot publishes `/robot_description`
|
|
111
|
+
with a `gripper_link` nothing publishes (`list_topics` gains one topic). `tools/list` grows by
|
|
112
|
+
1,377 bytes. Bench: the publisher
|
|
113
|
+
latches a URDF with an unpublished `arm_link` and declares three parameters,
|
|
114
|
+
`bench_params.yaml` disagrees on `max_range` and adds `extra_gain`, `calls_declared.json` runs
|
|
115
|
+
`03p_node_info_params`, and the comparator gets a `declared` section.
|
|
116
|
+
- Evaluation tooling for the 0.8.0 campaign (lot E; results in `docs/EVAL.md`, "0.8.0
|
|
117
|
+
campaign (v2)"). `scripts/agent_eval/tf.py list --brief` prints one line per tool (name, inputs
|
|
118
|
+
with `*` on required ones, first sentence): 2,309 bytes against 35,514 for the full listing on
|
|
119
|
+
the mock server; `list --tool NAME` prints one tool in full and `--host` reaches a broker on
|
|
120
|
+
another machine. `broker.py` gains `--server-cmd` (any command that starts TopicForge, such as
|
|
121
|
+
`/opt/tfenv/bin/topicforge` in the bench image), `--mode`, `--dds-backend`, repeatable `--env`
|
|
122
|
+
and `--calls-log` (one JSON line per request, so the runner counts an agent's tool calls itself);
|
|
123
|
+
it still listens on 127.0.0.1 unless `--host 0.0.0.0` is given. `publisher.py --fault NAME`
|
|
124
|
+
turns the bench robot into a clean robot with one fault: `healthy`, `blocked_executor`,
|
|
125
|
+
`tf_stale`, `qos_mismatch` (a best_effort `/scan_edge` writer against a reliable
|
|
126
|
+
`/edge_viewer`), `restart_loop` (a node that dies every 6 s and is started again),
|
|
127
|
+
`declared_drift` and `graph_v1_v2`; the faults live in the bench, TopicForge stays read-only, and
|
|
128
|
+
`publisher.py` without the flag is unchanged. `scripts/agent_eval/ros2_scenarios.py` starts one
|
|
129
|
+
scenario (broker, robot, companions) and writes the agent's workspace and prompt, with a
|
|
130
|
+
`--variant prompt` that adds the text of `inspect-ros2-robot` or `diagnose-nav2`; the expected
|
|
131
|
+
diagnoses are in `scripts/agent_eval/TRUTH_ROS2.md`. The image copies `scripts/agent_eval`.
|
|
132
|
+
|
|
133
|
+
### Changed
|
|
134
|
+
|
|
135
|
+
- `tools/list` is 96,848 bytes with sixteen tools (118,884 with fifteen in 0.7.2), under the
|
|
136
|
+
100,000-byte test ceiling. `graph_diff` itself costs nothing net: two levers pay for it (`docs/CONTRACT.md`
|
|
137
|
+
section 9.13, wire-compatible): the `**Duration**` lane note is one sentence, and
|
|
138
|
+
`anyOf: [X, null]` is served as `type: [X, "null"]` in output schemas only. A new test validates
|
|
139
|
+
every mock result against the output schema the server serves.
|
|
140
|
+
- The ROS lane now serves eight tools (`graph_diff` joins the seven `ros2` CLI tools). The
|
|
141
|
+
`MiddlewareAdapter` protocol is unchanged.
|
|
142
|
+
- Bench: `test_a_short_latched_result_hints_count_one` waits 6 s instead of 3 (it got no sample once
|
|
143
|
+
on a loaded Jazzy and Fast DDS runner).
|
|
144
|
+
|
|
145
|
+
### Folded in from 0.7.3 (never published separately)
|
|
146
|
+
|
|
147
|
+
#### Added
|
|
148
|
+
|
|
149
|
+
- `list_nodes` flags lifecycle (managed) nodes: `NodeListItem.lifecycle`, plus
|
|
150
|
+
`NodeListing.lifecycle_count`, `lifecycle_note` (set when the service list failed, never a
|
|
151
|
+
silent false) and `by_namespace` (distinct node names per namespace). `NodeInfo.lifecycle`
|
|
152
|
+
likewise. A node is lifecycle when it serves `<full_name>/get_state` of type
|
|
153
|
+
`lifecycle_msgs/srv/GetState`; the state is never read. Costs one extra
|
|
154
|
+
`ros2 service list -t` (`docs/CONTRACT.md` section 9.10). The mock `camera_driver` is a
|
|
155
|
+
lifecycle node, which adds the topic `/camera_driver/transition_event` to the mock graph.
|
|
156
|
+
The bench gains `/robot1/bench_lifecycle` and the comparator rows for both fields.
|
|
157
|
+
- `TopicRate` clock fields on `sample_messages` and `peek_bag_samples` (`docs/CONTRACT.md`
|
|
158
|
+
section 9.11): `clock_basis` (`wall`, `sim`, `none`, `mixed`), `stamp_age_median_s` and
|
|
159
|
+
`stamp_age_p95_s` (arrival minus stamp, wall-stamped topics only) and `stamp_regression_count`.
|
|
160
|
+
Additive, all with defaults. The bench gains `/imu/data`, `/pose_mixed` and `/stamps_regress`.
|
|
161
|
+
- `sample_messages` filters `/rosout` (`rcl_interfaces/msg/Log`) with `min_log_level`
|
|
162
|
+
(`debug` .. `fatal`) and `node` (`/ns/name`, `name` or `ns.name`; exact or child logger, never a
|
|
163
|
+
prefix). A Log topic is read with a depth-2000 reader to receive the 10 s transient_local
|
|
164
|
+
buffer; with a filter the call streams until `timeout_s`, filters after decoding and returns the
|
|
165
|
+
NEWEST `count` matches in stamp order, with a `M of N matched` note. A filter on any other topic
|
|
166
|
+
is an error. `docs/CONTRACT.md` section 9.12.
|
|
167
|
+
- `peek_bag_samples` gains `start_s` / `end_s` (bag-relative seconds on the record time,
|
|
168
|
+
`[start, end)`) and the same log filter (the FIRST `count` matches in range, bag order), and
|
|
169
|
+
`analyze_bag` returns `start_ns`, the record time of the bag's first message. The mock gains a
|
|
170
|
+
`/rosout` topic and bag topic. The bench logs WARN/ERROR/INFO, records `/rosout`, and the
|
|
171
|
+
comparator gets a `logs` section.
|
|
172
|
+
|
|
173
|
+
#### Changed
|
|
174
|
+
|
|
175
|
+
- `MiddlewareAdapter.sample_messages` gains keyword arguments `min_log_level` and `node`, and
|
|
176
|
+
`peek_bag_samples` gains `start_s`, `end_s`, `min_log_level` and `node` (all default `None`),
|
|
177
|
+
like `capture_tf` in 0.7.2. Custom adapters must accept them.
|
|
178
|
+
- `tools/list` shrinks from 118,884 to about 95 KB (94,957 bytes, `docs/CONTRACT.md` section 9.9),
|
|
179
|
+
about 5 KB under the new 100,000-byte test ceiling, which is the headroom left before
|
|
180
|
+
`graph_diff`. Additive: `MessageSample.summary` is
|
|
181
|
+
advertised as an open object keyed by `summary_type` (the five strict shapes are still built
|
|
182
|
+
and validated by the server, and documented in `docs/TOOLS.md`), and `default` keywords are
|
|
183
|
+
dropped from output schemas only. Payloads are unchanged.
|
|
184
|
+
- The ground-truth comparator checks the `tools/list` size (< 100,000 bytes).
|
|
185
|
+
- The mock graph gained `/camera_driver/transition_event` and `/rosout`, so mock `list_topics`
|
|
186
|
+
totals change (two more topics).
|
|
187
|
+
- A filtered bag read (`peek_bag_samples` with a log filter or a range) stops after 20 s or when
|
|
188
|
+
the call budget runs out and says so in `note`. A bag that starts at 0 (sim time) reports
|
|
189
|
+
`start_ns` 0, not `null`. The live `/rosout` depth-2000 reader is used only when a log filter
|
|
190
|
+
is set; an unfiltered read behaves as in 0.7.2.
|
|
191
|
+
|
|
192
|
+
#### Fixed
|
|
193
|
+
|
|
194
|
+
- `inspect_tf`: the `no_path` finding for unknown frames named every requested frame, even one
|
|
195
|
+
that exists ("No frame named camera_optical_frame or base_laser was seen"). Its text and
|
|
196
|
+
`frames` now list only the missing ones, like `path_note`.
|
|
9
197
|
|
|
10
198
|
## [0.7.2] - 2026-10-18
|
|
11
199
|
|
|
@@ -1713,7 +1901,9 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
|
|
|
1713
1901
|
- The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
|
|
1714
1902
|
- `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
|
|
1715
1903
|
|
|
1716
|
-
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.
|
|
1904
|
+
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.8.1...HEAD
|
|
1905
|
+
[0.8.1]: https://github.com/yaniswav/TopicForge/compare/v0.8.0...v0.8.1
|
|
1906
|
+
[0.8.0]: https://github.com/yaniswav/TopicForge/compare/v0.7.2...v0.8.0
|
|
1717
1907
|
[0.7.2]: https://github.com/yaniswav/TopicForge/compare/v0.7.1...v0.7.2
|
|
1718
1908
|
[0.7.1]: https://github.com/yaniswav/TopicForge/compare/v0.7.0...v0.7.1
|
|
1719
1909
|
[0.7.0]: https://github.com/yaniswav/TopicForge/compare/v0.6.4...v0.7.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: topicforge
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: ROS Topic Inspector & Bag Analyzer MCP server for AI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/yaniswav/TopicForge
|
|
6
6
|
Project-URL: Repository, https://github.com/yaniswav/TopicForge
|
|
@@ -73,7 +73,7 @@ A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a
|
|
|
73
73
|
|
|
74
74
|
Every tool also declares itself read-only in the MCP protocol (`readOnlyHint`, `destructiveHint=false`, `idempotentHint`, with an honest `openWorldHint`), and a test fails if any tool lacks it. See [`SECURITY.md`](SECURITY.md#read-only-guarantee-architecture-declaration-proof) for the guarantee and its limits.
|
|
75
75
|
|
|
76
|
-
It gives the agent
|
|
76
|
+
It gives the agent sixteen typed tools that return frozen Pydantic schemas (output contract 2: one object per tool, one `note`, units in the field names; see [`docs/CONTRACT.md`](docs/CONTRACT.md)), identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why `nav_planner` gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see [`examples/02-debug-qos-mismatch.md`](examples/02-debug-qos-mismatch.md)). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
|
|
77
77
|
|
|
78
78
|
For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Cyclone DDS, Fast DDS and Dust DDS participants have been observed on live buses: the Fast DDS ones on every CI run of the ROS 2 test bench (Humble and Jazzy with `rmw_fastrtps_cpp`) and in the OmniSim validation. RTI Connext, OpenDDS and CoreDX announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
|
|
79
79
|
|
|
@@ -101,9 +101,9 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
|
|
|
101
101
|
}
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), `topicforge --transport streamable-http --port 8765` serves `http://127.0.0.1:8765/mcp` on loopback
|
|
104
|
+
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), `topicforge --transport streamable-http --port 8765` serves `http://127.0.0.1:8765/mcp` on loopback (another `--host` needs a bearer token in `TOPICFORGE_HTTP_TOKEN`); see [`docs/CLIENTS.md`](docs/CLIENTS.md#local-http-transport).
|
|
105
105
|
|
|
106
|
-
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=
|
|
106
|
+
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMSIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.8.1%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
|
|
107
107
|
|
|
108
108
|
Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in [`docs/TESTING.md`](docs/TESTING.md); recurring monitoring prompts and the privacy contract are in [`docs/TUTORIEL.md`](docs/TUTORIEL.md).
|
|
109
109
|
|
|
@@ -119,6 +119,7 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
|
|
|
119
119
|
| `list_nodes` | ROS 2 nodes on the graph: names, namespaces, duplicate names (one `ros2 node list` call) |
|
|
120
120
|
| `get_node_info` | One node's publishers, subscribers, services, actions, parameters (secrets masked) and `use_sim_time`; a node that does not answer is reported, not an error |
|
|
121
121
|
| `inspect_tf` | The TF tree from `/tf_static` and `/tf`: roots, stale and two-parent frames, disconnected trees, sim-time-safe ages and rates, optional path between two frames |
|
|
122
|
+
| `graph_diff` | What changed since it last worked: snapshots the ROS 2 graph (or a bag) and diffs it with `previous`, a `snap-N` id or a bag: nodes, topics, types, writer QoS, counts, bag rates |
|
|
122
123
|
| `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
|
|
123
124
|
| `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
|
|
124
125
|
| `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
|
|
@@ -129,7 +130,7 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
|
|
|
129
130
|
| `peek_bag_samples` | Decoded samples from a recorded bag, including ROS 1 `.bag` (`rosbags` ships with the package) |
|
|
130
131
|
| `list_endpoints` | DDS writers and readers with structured QoS, per-topic roll-up that flags orphans (writer with no reader, reader with no writer) |
|
|
131
132
|
|
|
132
|
-
The server also offers
|
|
133
|
+
The server also offers three MCP prompts, `diagnose-dds-bus`, `inspect-ros2-robot` and `diagnose-nav2`, that give a client the read-only call order, and short server `instructions` for clients that do not show prompts (see [`docs/CLIENTS.md`](docs/CLIENTS.md#prompts-and-instructions)).
|
|
133
134
|
|
|
134
135
|
Walkthroughs against the mock, each with the exact tool calls and payloads, are in [`examples/`](examples/README.md). To run the DDS tools against a real bus with several programs and vendors, see [`examples/dds/README.md`](examples/dds/README.md) (`python examples/dds/run_all.py`).
|
|
135
136
|
|
|
@@ -151,7 +152,7 @@ pip install "topicforge[dds]" # alias, kept so older commands
|
|
|
151
152
|
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
|
|
152
153
|
```
|
|
153
154
|
|
|
154
|
-
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the
|
|
155
|
+
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the eight `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `inspect_tf`, `graph_diff`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` reads bags without the CLI.
|
|
155
156
|
|
|
156
157
|
A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra. Any other value (`rti`, `opensplice`, `coredx`, `intercom`, `opendds`, `dust`) is rejected at startup with an error that lists the valid ones; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
|
|
157
158
|
|
|
@@ -167,7 +168,7 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
|
|
|
167
168
|
| `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
|
|
168
169
|
| `TOPICFORGE_MAX_SAMPLE_BYTES` | `1048576` | Size cap for one sampled message (1 KiB..64 MiB); a call returns at most 4 times that. Over-cap messages are dropped with a note |
|
|
169
170
|
|
|
170
|
-
Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`)
|
|
171
|
+
Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`), `--port N` (default 8765) and `--host ADDR` (default `127.0.0.1`), the last two only with `streamable-http`. The HTTP transport binds `127.0.0.1` with DNS-rebinding protection; `TOPICFORGE_HTTP_TOKEN` (at least 32 characters) makes every request carry `Authorization: Bearer <token>` and is required for any other `--host` (plain HTTP, so prefer an SSH tunnel); see [docs/CLIENTS.md](docs/CLIENTS.md#local-http-transport).
|
|
171
172
|
|
|
172
173
|
Samples with comments are in [`.env.example`](.env.example). Any invalid value stops the server with `topicforge: configuration error: ...` and exit code 2, so a typo cannot silently change behaviour.
|
|
173
174
|
|
|
@@ -203,10 +204,10 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
203
204
|
|
|
204
205
|
| Field | Example | Notes |
|
|
205
206
|
| ------------ | --------------- | ----------------------------------------------------------- |
|
|
206
|
-
| `tool_name` | `"list_topics"` | One of the
|
|
207
|
+
| `tool_name` | `"list_topics"` | One of the sixteen tools, never argument values |
|
|
207
208
|
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
208
209
|
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
209
|
-
| `version` | `"0.
|
|
210
|
+
| `version` | `"0.8.1"` | TopicForge server version |
|
|
210
211
|
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
211
212
|
| `success` | `true` | Whether the handler returned or raised |
|
|
212
213
|
|
|
@@ -12,7 +12,7 @@ A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a
|
|
|
12
12
|
|
|
13
13
|
Every tool also declares itself read-only in the MCP protocol (`readOnlyHint`, `destructiveHint=false`, `idempotentHint`, with an honest `openWorldHint`), and a test fails if any tool lacks it. See [`SECURITY.md`](SECURITY.md#read-only-guarantee-architecture-declaration-proof) for the guarantee and its limits.
|
|
14
14
|
|
|
15
|
-
It gives the agent
|
|
15
|
+
It gives the agent sixteen typed tools that return frozen Pydantic schemas (output contract 2: one object per tool, one `note`, units in the field names; see [`docs/CONTRACT.md`](docs/CONTRACT.md)), identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why `nav_planner` gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see [`examples/02-debug-qos-mismatch.md`](examples/02-debug-qos-mismatch.md)). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
|
|
16
16
|
|
|
17
17
|
For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Cyclone DDS, Fast DDS and Dust DDS participants have been observed on live buses: the Fast DDS ones on every CI run of the ROS 2 test bench (Humble and Jazzy with `rmw_fastrtps_cpp`) and in the OmniSim validation. RTI Connext, OpenDDS and CoreDX announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
|
|
18
18
|
|
|
@@ -40,9 +40,9 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
|
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), `topicforge --transport streamable-http --port 8765` serves `http://127.0.0.1:8765/mcp` on loopback
|
|
43
|
+
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), `topicforge --transport streamable-http --port 8765` serves `http://127.0.0.1:8765/mcp` on loopback (another `--host` needs a bearer token in `TOPICFORGE_HTTP_TOKEN`); see [`docs/CLIENTS.md`](docs/CLIENTS.md#local-http-transport).
|
|
44
44
|
|
|
45
|
-
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=
|
|
45
|
+
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMSIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.8.1%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
|
|
46
46
|
|
|
47
47
|
Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in [`docs/TESTING.md`](docs/TESTING.md); recurring monitoring prompts and the privacy contract are in [`docs/TUTORIEL.md`](docs/TUTORIEL.md).
|
|
48
48
|
|
|
@@ -58,6 +58,7 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
|
|
|
58
58
|
| `list_nodes` | ROS 2 nodes on the graph: names, namespaces, duplicate names (one `ros2 node list` call) |
|
|
59
59
|
| `get_node_info` | One node's publishers, subscribers, services, actions, parameters (secrets masked) and `use_sim_time`; a node that does not answer is reported, not an error |
|
|
60
60
|
| `inspect_tf` | The TF tree from `/tf_static` and `/tf`: roots, stale and two-parent frames, disconnected trees, sim-time-safe ages and rates, optional path between two frames |
|
|
61
|
+
| `graph_diff` | What changed since it last worked: snapshots the ROS 2 graph (or a bag) and diffs it with `previous`, a `snap-N` id or a bag: nodes, topics, types, writer QoS, counts, bag rates |
|
|
61
62
|
| `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
|
|
62
63
|
| `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
|
|
63
64
|
| `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
|
|
@@ -68,7 +69,7 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
|
|
|
68
69
|
| `peek_bag_samples` | Decoded samples from a recorded bag, including ROS 1 `.bag` (`rosbags` ships with the package) |
|
|
69
70
|
| `list_endpoints` | DDS writers and readers with structured QoS, per-topic roll-up that flags orphans (writer with no reader, reader with no writer) |
|
|
70
71
|
|
|
71
|
-
The server also offers
|
|
72
|
+
The server also offers three MCP prompts, `diagnose-dds-bus`, `inspect-ros2-robot` and `diagnose-nav2`, that give a client the read-only call order, and short server `instructions` for clients that do not show prompts (see [`docs/CLIENTS.md`](docs/CLIENTS.md#prompts-and-instructions)).
|
|
72
73
|
|
|
73
74
|
Walkthroughs against the mock, each with the exact tool calls and payloads, are in [`examples/`](examples/README.md). To run the DDS tools against a real bus with several programs and vendors, see [`examples/dds/README.md`](examples/dds/README.md) (`python examples/dds/run_all.py`).
|
|
74
75
|
|
|
@@ -90,7 +91,7 @@ pip install "topicforge[dds]" # alias, kept so older commands
|
|
|
90
91
|
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
|
|
91
92
|
```
|
|
92
93
|
|
|
93
|
-
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the
|
|
94
|
+
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the eight `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `inspect_tf`, `graph_diff`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` reads bags without the CLI.
|
|
94
95
|
|
|
95
96
|
A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra. Any other value (`rti`, `opensplice`, `coredx`, `intercom`, `opendds`, `dust`) is rejected at startup with an error that lists the valid ones; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
|
|
96
97
|
|
|
@@ -106,7 +107,7 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
|
|
|
106
107
|
| `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
|
|
107
108
|
| `TOPICFORGE_MAX_SAMPLE_BYTES` | `1048576` | Size cap for one sampled message (1 KiB..64 MiB); a call returns at most 4 times that. Over-cap messages are dropped with a note |
|
|
108
109
|
|
|
109
|
-
Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`)
|
|
110
|
+
Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`), `--port N` (default 8765) and `--host ADDR` (default `127.0.0.1`), the last two only with `streamable-http`. The HTTP transport binds `127.0.0.1` with DNS-rebinding protection; `TOPICFORGE_HTTP_TOKEN` (at least 32 characters) makes every request carry `Authorization: Bearer <token>` and is required for any other `--host` (plain HTTP, so prefer an SSH tunnel); see [docs/CLIENTS.md](docs/CLIENTS.md#local-http-transport).
|
|
110
111
|
|
|
111
112
|
Samples with comments are in [`.env.example`](.env.example). Any invalid value stops the server with `topicforge: configuration error: ...` and exit code 2, so a typo cannot silently change behaviour.
|
|
112
113
|
|
|
@@ -142,10 +143,10 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
142
143
|
|
|
143
144
|
| Field | Example | Notes |
|
|
144
145
|
| ------------ | --------------- | ----------------------------------------------------------- |
|
|
145
|
-
| `tool_name` | `"list_topics"` | One of the
|
|
146
|
+
| `tool_name` | `"list_topics"` | One of the sixteen tools, never argument values |
|
|
146
147
|
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
147
148
|
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
148
|
-
| `version` | `"0.
|
|
149
|
+
| `version` | `"0.8.1"` | TopicForge server version |
|
|
149
150
|
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
150
151
|
| `success` | `true` | Whether the handler returned or raised |
|
|
151
152
|
|
|
@@ -6,7 +6,7 @@ TopicForge (ROS 2 / DDS) is a local, read-only MCP server that speaks stdio.
|
|
|
6
6
|
Every client below launches it as a child process with the same command:
|
|
7
7
|
|
|
8
8
|
```
|
|
9
|
-
uvx --from "topicforge==0.
|
|
9
|
+
uvx --from "topicforge==0.8.1" topicforge
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
and the same three environment variables:
|
|
@@ -21,7 +21,7 @@ The registry name is `io.github.yaniswav/topicforge`. Where a client asks for a
|
|
|
21
21
|
display name, use **TopicForge (ROS 2 / DDS)**: an unrelated SEO product is also
|
|
22
22
|
called TopicForge in the MCP registries.
|
|
23
23
|
|
|
24
|
-
The version pin (`==0.
|
|
24
|
+
The version pin (`==0.8.1`) is moved on every release together with the other
|
|
25
25
|
version strings; a test keeps them in sync.
|
|
26
26
|
|
|
27
27
|
## Before you start
|
|
@@ -44,7 +44,7 @@ version strings; a test keeps them in sync.
|
|
|
44
44
|
`"--from"` in the args. To build the binding yourself, install the Cyclone C library and
|
|
45
45
|
run `pip install "topicforge[dds]"`. The `[dds]` and `[dds-cyclone]` extras still work
|
|
46
46
|
as aliases, so older commands keep installing.
|
|
47
|
-
- **pip alternative.** `pip install "topicforge==0.
|
|
47
|
+
- **pip alternative.** `pip install "topicforge==0.8.1"`, then use
|
|
48
48
|
`"command": "topicforge"` (or `"command": "python", "args": ["-m", "topicforge"]`)
|
|
49
49
|
with no `args` for uvx. Use the absolute path of the binary if the client does
|
|
50
50
|
not see your venv.
|
|
@@ -72,7 +72,7 @@ say where the file lives and what differs.
|
|
|
72
72
|
"mcpServers": {
|
|
73
73
|
"topicforge": {
|
|
74
74
|
"command": "uvx",
|
|
75
|
-
"args": ["--from", "topicforge==0.
|
|
75
|
+
"args": ["--from", "topicforge==0.8.1", "topicforge"],
|
|
76
76
|
"env": {
|
|
77
77
|
"TOPICFORGE_MODE": "auto",
|
|
78
78
|
"TOPICFORGE_DDS_BACKEND": "cyclone",
|
|
@@ -85,7 +85,7 @@ say where the file lives and what differs.
|
|
|
85
85
|
|
|
86
86
|
## Claude Code
|
|
87
87
|
|
|
88
|
-
Plugin (server plus
|
|
88
|
+
Plugin (server plus three skills, asks for backend and domain at install):
|
|
89
89
|
|
|
90
90
|
```
|
|
91
91
|
claude plugin marketplace add yaniswav/TopicForge
|
|
@@ -95,7 +95,7 @@ claude plugin install topicforge@topicforge
|
|
|
95
95
|
Or just the server:
|
|
96
96
|
|
|
97
97
|
```
|
|
98
|
-
claude mcp add topicforge --env TOPICFORGE_MODE=auto --env TOPICFORGE_DDS_BACKEND=cyclone --env TOPICFORGE_DDS_DOMAIN_ID=0 -- uvx --from "topicforge==0.
|
|
98
|
+
claude mcp add topicforge --env TOPICFORGE_MODE=auto --env TOPICFORGE_DDS_BACKEND=cyclone --env TOPICFORGE_DDS_DOMAIN_ID=0 -- uvx --from "topicforge==0.8.1" topicforge
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
## Claude Desktop
|
|
@@ -112,22 +112,22 @@ the common JSON shape above and restart the app completely. On Windows, if
|
|
|
112
112
|
|
|
113
113
|
The one-click links in this page and in the README are https redirects, because GitHub and PyPI strip `cursor://` and `vscode:` links. Only the raw schemes are documented by Cursor and VS Code; the https forms (`cursor.com/install-mcp`, `vscode.dev/redirect/mcp/install`) were observed to redirect correctly on 2026-10-06 but are not in the vendors' docs (unverified, last checked 2026-10-06). The raw links are given as a fallback.
|
|
114
114
|
|
|
115
|
-
One click: [Add TopicForge to Cursor](https://cursor.com/install-mcp?name=topicforge&config=
|
|
115
|
+
One click: [Add TopicForge to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMSIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D)
|
|
116
116
|
|
|
117
|
-
If that page does not open Cursor, use the raw deeplink: `cursor://anysphere.cursor-deeplink/mcp/install?name=topicforge&config=
|
|
117
|
+
If that page does not open Cursor, use the raw deeplink: `cursor://anysphere.cursor-deeplink/mcp/install?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMSIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D`
|
|
118
118
|
|
|
119
119
|
Or paste the common JSON shape into `~/.cursor/mcp.json` (all projects) or
|
|
120
120
|
`.cursor/mcp.json` (one project).
|
|
121
121
|
|
|
122
122
|
## Agent Plugins (Cursor and other compatible clients)
|
|
123
123
|
|
|
124
|
-
The `plugin/` folder of this repository is also an [Agent Plugins](https://agent-plugins.org) 1.0.0 package: `plugin.json` (manifest), `mcp.json` (the same pinned `uvx` launch) and `skills/` (
|
|
124
|
+
The `plugin/` folder of this repository is also an [Agent Plugins](https://agent-plugins.org) 1.0.0 package: `plugin.json` (manifest), `mcp.json` (the same pinned `uvx` launch) and `skills/` (three skills). Clients that load that format, such as Cursor, can install it as a plugin and get the server and the skills in one step; the plain JSON config above remains the simplest route for everything else. The format has no user-configuration prompt, so the backend is fixed to `cyclone` and the domain to `0`; edit the `env` block of `mcp.json` to change them (see `plugin/README.md`). Cursor Directory (cursor.directory/plugins/new) and the Cursor marketplace take a repository URL; a repo-root `.cursor-plugin/marketplace.json` points Cursor at the `plugin/` subfolder. Whether each directory scanner accepts a subfolder is unverified.
|
|
125
125
|
|
|
126
126
|
## VS Code / GitHub Copilot
|
|
127
127
|
|
|
128
|
-
One click: [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.
|
|
128
|
+
One click: [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.8.1%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D)
|
|
129
129
|
|
|
130
|
-
Fallback, the raw URL handler: `vscode:mcp/install?%7B%22name%22%3A%22topicforge%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.
|
|
130
|
+
Fallback, the raw URL handler: `vscode:mcp/install?%7B%22name%22%3A%22topicforge%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.8.1%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D`
|
|
131
131
|
|
|
132
132
|
Or put this in `.vscode/mcp.json` (the key is `servers`, not `mcpServers`), or run
|
|
133
133
|
"MCP: Add Server" from the Command Palette:
|
|
@@ -138,7 +138,7 @@ Or put this in `.vscode/mcp.json` (the key is `servers`, not `mcpServers`), or r
|
|
|
138
138
|
"topicforge": {
|
|
139
139
|
"type": "stdio",
|
|
140
140
|
"command": "uvx",
|
|
141
|
-
"args": ["--from", "topicforge==0.
|
|
141
|
+
"args": ["--from", "topicforge==0.8.1", "topicforge"],
|
|
142
142
|
"env": {
|
|
143
143
|
"TOPICFORGE_MODE": "auto",
|
|
144
144
|
"TOPICFORGE_DDS_BACKEND": "cyclone",
|
|
@@ -167,7 +167,7 @@ Servers", and paste the common JSON shape (the file is
|
|
|
167
167
|
Project file `.roo/mcp.json` (takes precedence) or the global `mcp_settings.json`
|
|
168
168
|
opened from the MCP view. Paste the common JSON shape. Roo's docs say Windows
|
|
169
169
|
needs a `cmd /c` wrapper for `npx`; for `uvx`, try it directly first and fall back
|
|
170
|
-
to `"command": "cmd", "args": ["/c", "uvx", "--from", "topicforge==0.
|
|
170
|
+
to `"command": "cmd", "args": ["/c", "uvx", "--from", "topicforge==0.8.1",
|
|
171
171
|
"topicforge"]` if it fails to start.
|
|
172
172
|
|
|
173
173
|
## Continue
|
|
@@ -184,7 +184,7 @@ mcpServers:
|
|
|
184
184
|
command: uvx
|
|
185
185
|
args:
|
|
186
186
|
- "--from"
|
|
187
|
-
- "topicforge==0.
|
|
187
|
+
- "topicforge==0.8.1"
|
|
188
188
|
- "topicforge"
|
|
189
189
|
env:
|
|
190
190
|
TOPICFORGE_MODE: auto
|
|
@@ -201,7 +201,7 @@ In `settings.json`, under `context_servers`:
|
|
|
201
201
|
"context_servers": {
|
|
202
202
|
"topicforge": {
|
|
203
203
|
"command": "uvx",
|
|
204
|
-
"args": ["--from", "topicforge==0.
|
|
204
|
+
"args": ["--from", "topicforge==0.8.1", "topicforge"],
|
|
205
205
|
"env": {
|
|
206
206
|
"TOPICFORGE_MODE": "auto",
|
|
207
207
|
"TOPICFORGE_DDS_BACKEND": "cyclone",
|
|
@@ -227,7 +227,7 @@ run with the dds extra can exceed, so set it to 60 s:
|
|
|
227
227
|
```toml
|
|
228
228
|
[mcp_servers.topicforge]
|
|
229
229
|
command = "uvx"
|
|
230
|
-
args = ["--from", "topicforge==0.
|
|
230
|
+
args = ["--from", "topicforge==0.8.1", "topicforge"]
|
|
231
231
|
startup_timeout_sec = 60
|
|
232
232
|
|
|
233
233
|
[mcp_servers.topicforge.env]
|
|
@@ -258,7 +258,7 @@ extensions:
|
|
|
258
258
|
name: TopicForge (ROS 2 / DDS)
|
|
259
259
|
type: stdio
|
|
260
260
|
cmd: uvx
|
|
261
|
-
args: [--from, "topicforge==0.
|
|
261
|
+
args: [--from, "topicforge==0.8.1", topicforge]
|
|
262
262
|
enabled: true
|
|
263
263
|
envs: { "TOPICFORGE_MODE": "auto", "TOPICFORGE_DDS_BACKEND": "cyclone", "TOPICFORGE_DDS_DOMAIN_ID": "0" }
|
|
264
264
|
timeout: 300
|
|
@@ -269,7 +269,7 @@ extensions:
|
|
|
269
269
|
Program tab > Install > Edit `mcp.json` (same notation as Cursor), then paste the
|
|
270
270
|
common JSON shape. LM Studio's docs only show remote examples; local command
|
|
271
271
|
servers follow the Cursor notation but are not documented explicitly. Small
|
|
272
|
-
local models may struggle with
|
|
272
|
+
local models may struggle with sixteen tools.
|
|
273
273
|
|
|
274
274
|
## Amazon Q Developer / Kiro
|
|
275
275
|
|
|
@@ -297,19 +297,61 @@ The endpoint is `http://127.0.0.1:8765/mcp`. Two cases it is meant for:
|
|
|
297
297
|
- A robot: run it on the robot, then `ssh -L 8765:127.0.0.1:8765 robot` and use the same URL
|
|
298
298
|
on your laptop.
|
|
299
299
|
|
|
300
|
-
Threat model: the server has no authentication, so anything that can reach the
|
|
301
|
-
your robot graph. It therefore binds `127.0.0.1`
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
300
|
+
Threat model: without a token the server has no authentication, so anything that can reach the
|
|
301
|
+
port can read your robot graph. It therefore binds `127.0.0.1` by default and rejects any
|
|
302
|
+
request whose `Host` or `Origin` is not a loopback name with the served port, which stops a web
|
|
303
|
+
page in your browser from reaching it through DNS rebinding. Do not forward the port to a
|
|
304
|
+
network interface; use an SSH tunnel instead.
|
|
305
|
+
|
|
306
|
+
Bearer token (0.8.1). Set `TOPICFORGE_HTTP_TOKEN` to a secret of at least 32 printable
|
|
307
|
+
characters (for example `openssl rand -hex 32`) and every request must then carry
|
|
308
|
+
`Authorization: Bearer <token>`; a request without it gets `401`. The token is read from the
|
|
309
|
+
environment only, never from a flag. Only with a token may `--host` name another address:
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
TOPICFORGE_HTTP_TOKEN=... topicforge --transport streamable-http --host 0.0.0.0 --port 8765
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
This is plain HTTP: the token and every answer cross the network in clear, so keep it to a
|
|
316
|
+
network you trust, or put a TLS reverse proxy in front. The SSH tunnel above remains the safer
|
|
317
|
+
way to reach a robot. Clients that support HTTP headers in their MCP config (Claude Code's
|
|
318
|
+
`--header`, VS Code's `headers`, Cursor's `headers`) pass the token there.
|
|
319
|
+
|
|
320
|
+
## Docker
|
|
321
|
+
|
|
322
|
+
The repository's `Dockerfile` builds a stdio image on ROS 2 Jazzy (`ros:jazzy-ros-core`, the
|
|
323
|
+
`ros2` CLI and Cyclone DDS included, a non-root user):
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
docker build -t topicforge .
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Point a client at `docker` instead of `uvx`:
|
|
330
|
+
|
|
331
|
+
```json
|
|
332
|
+
{ "command": "docker", "args": ["run", "-i", "--rm", "topicforge"] }
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
What the container can see depends on its network:
|
|
336
|
+
|
|
337
|
+
- Linux with `--network host` (and the robot's `ROS_DOMAIN_ID`, e.g. `-e ROS_DOMAIN_ID=0`):
|
|
338
|
+
the live graph, as a local install would.
|
|
339
|
+
- Docker Desktop on Windows or macOS: the container does not see the host's DDS traffic. The
|
|
340
|
+
`ros2` CLI is in the image, so `auto` still picks live mode and finds an empty graph (only
|
|
341
|
+
`/rosout` and `/parameter_events`). Use it for bags, mounted with
|
|
342
|
+
`-v /path/to/bags:/bags:ro` (then ask about `/bags/run.mcap`), or for the demo robot with
|
|
343
|
+
`-e TOPICFORGE_MODE=mock`.
|
|
344
|
+
|
|
345
|
+
The image is about 1 GB (ROS 2 Jazzy core plus Cyclone DDS).
|
|
305
346
|
|
|
306
347
|
## Prompts and instructions
|
|
307
348
|
|
|
308
|
-
The server offers
|
|
309
|
-
a node crashed)
|
|
349
|
+
The server offers three MCP prompts, `diagnose-dds-bus` (nodes do not talk, a topic gets no data,
|
|
350
|
+
a node crashed), `inspect-ros2-robot` (what topics exist, what a bag contains) and `diagnose-nav2`
|
|
351
|
+
(a Nav2 stack that does not start, aborts goals or does not move the robot), plus short
|
|
310
352
|
server `instructions` sent in the `initialize` result (the read-only guarantee, which tool to
|
|
311
|
-
call first, `contract_version`).
|
|
312
|
-
`bag_path`) that only focus the text; they run no tool by themselves.
|
|
353
|
+
call first, `contract_version`). The prompts take optional arguments (`topic`, `symptom`,
|
|
354
|
+
`bag_path` or `namespace`) that only focus the text; they run no tool by themselves.
|
|
313
355
|
|
|
314
356
|
| Client | Prompts | Instructions |
|
|
315
357
|
| --- | --- | --- |
|
|
@@ -325,7 +367,7 @@ when the question matches.
|
|
|
325
367
|
|
|
326
368
|
## Troubleshooting
|
|
327
369
|
|
|
328
|
-
- Run the command by hand first: `uvx --from "topicforge==0.
|
|
370
|
+
- Run the command by hand first: `uvx --from "topicforge==0.8.1" topicforge --version`.
|
|
329
371
|
- Server starts but sees no DDS participants: check the domain id, and that the
|
|
330
372
|
client machine is on the same network as the robot (DDS discovery uses
|
|
331
373
|
multicast UDP). Ask the assistant to call `health_check`.
|