topicforge 0.5.6__tar.gz → 0.6.0__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 (163) hide show
  1. {topicforge-0.5.6 → topicforge-0.6.0}/CHANGELOG.md +63 -1
  2. {topicforge-0.5.6 → topicforge-0.6.0}/PKG-INFO +7 -6
  3. {topicforge-0.5.6 → topicforge-0.6.0}/README.md +5 -5
  4. {topicforge-0.5.6 → topicforge-0.6.0}/docs/TESTING.md +3 -3
  5. {topicforge-0.5.6 → topicforge-0.6.0}/docs/TROUBLESHOOTING.md +2 -2
  6. {topicforge-0.5.6 → topicforge-0.6.0}/pyproject.toml +2 -1
  7. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/__init__.py +1 -1
  8. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/base.py +3 -3
  9. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/composite.py +4 -3
  10. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/dds_cyclone/adapter.py +3 -2
  11. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/dds_dust/adapter.py +3 -3
  12. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/dds_fast/adapter.py +3 -2
  13. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/dds_opendds/adapter.py +3 -3
  14. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/ros2_live/adapter.py +193 -156
  15. topicforge-0.6.0/src/topicforge/adapters/ros2_live/echo_parser.py +165 -0
  16. topicforge-0.6.0/src/topicforge/adapters/ros2_live/echo_stream.py +195 -0
  17. topicforge-0.6.0/src/topicforge/adapters/ros2_live/process_tree.py +193 -0
  18. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/ros2_mock/adapter.py +11 -5
  19. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/ros2_mock/fixtures.py +19 -8
  20. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/constants.py +12 -0
  21. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/models/schemas.py +37 -19
  22. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/services/factory.py +3 -1
  23. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/services/inspector.py +28 -24
  24. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/tools/handlers.py +75 -35
  25. topicforge-0.6.0/tests/fixtures/ros2_echo/image_noarr.yaml +24 -0
  26. topicforge-0.6.0/tests/fixtures/ros2_echo/image_trunc4.yaml +17 -0
  27. topicforge-0.6.0/tests/fixtures/ros2_echo/scan_edge.yaml +38 -0
  28. topicforge-0.6.0/tests/fixtures/ros2_echo/scan_noarr.yaml +30 -0
  29. topicforge-0.6.0/tests/fixtures/ros2_echo/scan_trunc128.yaml +273 -0
  30. topicforge-0.6.0/tests/fixtures/ros2_echo/scan_trunc3.yaml +46 -0
  31. topicforge-0.6.0/tests/fixtures/ros2_echo/string_latched.yaml +2 -0
  32. topicforge-0.6.0/tests/fixtures/ros2_echo/twist.yaml +18 -0
  33. {topicforge-0.5.6 → topicforge-0.6.0}/tests/integration/ros2/publisher.py +10 -0
  34. topicforge-0.6.0/tests/integration/ros2/test_live_adapter.py +285 -0
  35. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_composite_adapter.py +8 -5
  36. topicforge-0.6.0/tests/test_echo_parser.py +201 -0
  37. topicforge-0.6.0/tests/test_echo_stream.py +247 -0
  38. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_live_adapter_graph.py +67 -83
  39. topicforge-0.6.0/tests/test_live_adapter_parse.py +103 -0
  40. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_live_adapter_subprocess.py +112 -50
  41. topicforge-0.6.0/tests/test_live_sample_notes.py +150 -0
  42. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_mock_adapter.py +28 -2
  43. topicforge-0.6.0/tests/test_process_tree.py +209 -0
  44. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_sample_options.py +76 -16
  45. topicforge-0.5.6/tests/fixtures/csv_echo_imu.txt +0 -12
  46. topicforge-0.5.6/tests/fixtures/csv_echo_pose_multi.txt +0 -9
  47. topicforge-0.5.6/tests/integration/ros2/test_live_adapter.py +0 -191
  48. topicforge-0.5.6/tests/test_live_adapter_parse.py +0 -276
  49. {topicforge-0.5.6 → topicforge-0.6.0}/.gitignore +0 -0
  50. {topicforge-0.5.6 → topicforge-0.6.0}/LICENSE +0 -0
  51. {topicforge-0.5.6 → topicforge-0.6.0}/docs/DDS_QUICKSTART.md +0 -0
  52. {topicforge-0.5.6 → topicforge-0.6.0}/docs/TUTORIEL.md +0 -0
  53. {topicforge-0.5.6 → topicforge-0.6.0}/docs/dds-interop-matrix.md +0 -0
  54. {topicforge-0.5.6 → topicforge-0.6.0}/examples/README.md +0 -0
  55. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/00_hello_pub_sub/README.md +0 -0
  56. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/01_who_is_on_the_bus/README.md +0 -0
  57. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/02_why_cant_they_talk/README.md +0 -0
  58. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/03_a_node_crashed/README.md +0 -0
  59. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/04_late_joiner_misses_data/README.md +0 -0
  60. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/05_reliability_in_code/README.md +0 -0
  61. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/06_durability_late_joiner_in_code/README.md +0 -0
  62. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/07_deadline_in_code/README.md +0 -0
  63. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/08_crash_seen_from_inside/README.md +0 -0
  64. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/10_lidar_silent_after_driver_swap/README.md +0 -0
  65. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/11_who_talks_to_whom/README.md +0 -0
  66. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/12_safety_monitor_dropout/README.md +0 -0
  67. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/13_deadline_not_offered/README.md +0 -0
  68. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/14_restart_loop/README.md +0 -0
  69. {topicforge-0.5.6 → topicforge-0.6.0}/examples/dds/README.md +0 -0
  70. {topicforge-0.5.6 → topicforge-0.6.0}/scripts/agent_eval/README.md +0 -0
  71. {topicforge-0.5.6 → topicforge-0.6.0}/scripts/integration/README.md +0 -0
  72. {topicforge-0.5.6 → topicforge-0.6.0}/scripts/integration/publishers/cyclone_c/README.md +0 -0
  73. {topicforge-0.5.6 → topicforge-0.6.0}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
  74. {topicforge-0.5.6 → topicforge-0.6.0}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
  75. {topicforge-0.5.6 → topicforge-0.6.0}/scripts/integration/publishers/dust_py/README.md +0 -0
  76. {topicforge-0.5.6 → topicforge-0.6.0}/scripts/integration/publishers/fast_publisher_cpp/README.md +0 -0
  77. {topicforge-0.5.6 → topicforge-0.6.0}/scripts/integration/publishers/fast_py/README.md +0 -0
  78. {topicforge-0.5.6 → topicforge-0.6.0}/scripts/integration/publishers/opensplice_publisher/README.md +0 -0
  79. {topicforge-0.5.6 → topicforge-0.6.0}/scripts/integration/publishers/rti_c/README.md +0 -0
  80. {topicforge-0.5.6 → topicforge-0.6.0}/scripts/integration/publishers/rti_cpp/README.md +0 -0
  81. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/__main__.py +0 -0
  82. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/__init__.py +0 -0
  83. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/__init__.py +0 -0
  84. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/cdr_decoder.py +0 -0
  85. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/dds_helpers.py +0 -0
  86. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/dds_introspection.py +0 -0
  87. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/discovery_tracker.py +0 -0
  88. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/endpoints.py +0 -0
  89. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/lifecycle.py +0 -0
  90. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/metrics_buffer.py +0 -0
  91. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/qos_analyzer.py +0 -0
  92. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/qos_endpoints.py +0 -0
  93. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/qos_normalize.py +0 -0
  94. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/qos_scan.py +0 -0
  95. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/topic_filter.py +0 -0
  96. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/common/xtypes.py +0 -0
  97. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
  98. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/dds_dust/__init__.py +0 -0
  99. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
  100. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/dds_opendds/__init__.py +0 -0
  101. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
  102. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/ros2_live/parsers.py +0 -0
  103. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
  104. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/config/__init__.py +0 -0
  105. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/config/settings.py +0 -0
  106. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/models/__init__.py +0 -0
  107. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/server/__init__.py +0 -0
  108. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/server/app.py +0 -0
  109. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/services/__init__.py +0 -0
  110. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/services/bag_service.py +0 -0
  111. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/services/bag_stats.py +0 -0
  112. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/services/health.py +0 -0
  113. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/services/sample_budget.py +0 -0
  114. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/telemetry/__init__.py +0 -0
  115. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/telemetry/client.py +0 -0
  116. {topicforge-0.5.6 → topicforge-0.6.0}/src/topicforge/tools/__init__.py +0 -0
  117. {topicforge-0.5.6 → topicforge-0.6.0}/tests/__init__.py +0 -0
  118. {topicforge-0.5.6 → topicforge-0.6.0}/tests/conftest.py +0 -0
  119. {topicforge-0.5.6 → topicforge-0.6.0}/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +0 -0
  120. {topicforge-0.5.6 → topicforge-0.6.0}/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +0 -0
  121. {topicforge-0.5.6 → topicforge-0.6.0}/tests/fixtures/ros2_topic_info_verbose_scan.txt +0 -0
  122. {topicforge-0.5.6 → topicforge-0.6.0}/tests/fixtures/ros2_topic_info_verbose_tf.txt +0 -0
  123. {topicforge-0.5.6 → topicforge-0.6.0}/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +0 -0
  124. {topicforge-0.5.6 → topicforge-0.6.0}/tests/integration/__init__.py +0 -0
  125. {topicforge-0.5.6 → topicforge-0.6.0}/tests/integration/ros2/Dockerfile +0 -0
  126. {topicforge-0.5.6 → topicforge-0.6.0}/tests/integration/ros2/__init__.py +0 -0
  127. {topicforge-0.5.6 → topicforge-0.6.0}/tests/integration/ros2/entrypoint.sh +0 -0
  128. {topicforge-0.5.6 → topicforge-0.6.0}/tests/integration/ros2/run_bench.py +0 -0
  129. {topicforge-0.5.6 → topicforge-0.6.0}/tests/integration/test_real_bus.py +0 -0
  130. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_analyze_bag_multi_format.py +0 -0
  131. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_bag_omnisim_humble.py +0 -0
  132. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_bag_service.py +0 -0
  133. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_bag_stats.py +0 -0
  134. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_cdr_decoder.py +0 -0
  135. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_config.py +0 -0
  136. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_cyclone_adapter.py +0 -0
  137. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_dds_cross_vendor.py +0 -0
  138. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_dds_helpers.py +0 -0
  139. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_dds_inactive_reason.py +0 -0
  140. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_dds_introspection.py +0 -0
  141. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_dds_qos_normalization.py +0 -0
  142. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_dds_schemas.py +0 -0
  143. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_discovery_tracker.py +0 -0
  144. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_dust_adapter.py +0 -0
  145. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_endpoints.py +0 -0
  146. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_example_node_spec.py +0 -0
  147. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_factory.py +0 -0
  148. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_fast_adapter.py +0 -0
  149. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_health.py +0 -0
  150. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_history_and_hints.py +0 -0
  151. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_honest_outputs.py +0 -0
  152. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_inspector.py +0 -0
  153. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_lifecycle_buffer.py +0 -0
  154. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_metrics_buffer.py +0 -0
  155. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_opendds_adapter.py +0 -0
  156. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_peek_bag_samples.py +0 -0
  157. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_qos_analyzer.py +0 -0
  158. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_qos_endpoints.py +0 -0
  159. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_qos_scan.py +0 -0
  160. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_telemetry.py +0 -0
  161. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_tools_integration.py +0 -0
  162. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_topic_metrics.py +0 -0
  163. {topicforge-0.5.6 → topicforge-0.6.0}/tests/test_xtypes.py +0 -0
@@ -5,6 +5,67 @@ 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
+ ## [0.6.0] - 2026-10-05
9
+
10
+ `sample_messages` (live) rewritten after the OmniSim team's report (count
11
+ ignored, empty results on latched topics, wrong timestamps and shifted columns
12
+ on simulated time).
13
+
14
+ ### Breaking
15
+
16
+ - `sample_messages` payloads are nested named fields (`payload.header.stamp.sec`,
17
+ `payload.ranges`) instead of positional `col_0`, `col_1`, ... columns. The
18
+ verbatim row under `_raw_text` is gone: it is present only when a message could
19
+ not be parsed.
20
+ - `_truncated_after_columns` and `_truncated_columns` are replaced by
21
+ `_truncated_fields`, a list of dotted field paths (`["ranges", "intensities"]`)
22
+ for arrays, strings or bytes cut at `max_array_length`. A cut array no longer
23
+ carries the CLI's `'...'` element.
24
+ - `nan`, `inf` and `-inf` floats are returned as the strings `"nan"`, `"inf"`,
25
+ `"-inf"`.
26
+ - `MiddlewareAdapter.sample_messages` returns a `SampleResult` instead of a list
27
+ of `MessageSample` and takes `timeout_s`. `parse_csv_echo` and `parse_echo_yaml`
28
+ are removed.
29
+ - `pyyaml>=6` is a runtime dependency.
30
+ - A failing `ros2 topic echo` (non-zero exit with no message, or a CLI that cannot
31
+ be started) raises an MCP error instead of returning an empty list. `count` 0 now
32
+ validates the topic in live mode too, as it already did in the mock.
33
+
34
+ ### Added
35
+
36
+ - `sample_messages` parameter `timeout_s` (1..45, default 10): a wall deadline for
37
+ the whole call, topic lookup included. The call returns within about `timeout_s`
38
+ plus 2 s (stopping the CLI, decoding what arrived).
39
+ - `MessageSample.stamp_source` (`header` or `none`) and `received_ns` (wall clock
40
+ when TopicForge read the message).
41
+ - A short result carries a `note` (`N of M messages within T s`) that tells "no
42
+ publisher is announced" from "a publisher exists but nothing arrived in time",
43
+ hints `count` 1 for a latched topic, and gives the exit code and stderr when the
44
+ CLI exited early.
45
+ - A message over the per-message size cap (`TOPICFORGE_MAX_SAMPLE_BYTES`, 1 MiB) is
46
+ dropped while it streams and counted in the `note`, so a large `Image` no longer
47
+ costs tens of seconds of YAML decoding. Decoding uses libyaml when available and
48
+ stops at the call deadline.
49
+ - `count` above 50 is capped with a note, and the tool schema declares the
50
+ maximum.
51
+
52
+ ### Fixed
53
+
54
+ - `sample_messages` honours `count`: it streams `ros2 topic echo` and stops at
55
+ `count` messages or the deadline, then stops the process tree (SIGINT then
56
+ SIGKILL on POSIX, `taskkill /F /T` on Windows) (OmniSim D3). The echo is
57
+ started with explicit `--qos-reliability` and `--qos-durability` taken from the
58
+ publishers, so a latched (`transient_local`) topic is no longer returned empty
59
+ because the CLI picked its QoS against a cold daemon.
60
+ - Stopping the CLI kills the whole process tree even when the launcher already
61
+ exited (POSIX: always signals the process group; Windows: a Job Object), so no
62
+ orphan `ros2` process keeps running. The CLI is started with UTF-8 output.
63
+ - The mock `sample_messages` matches the live shape: headerless messages have
64
+ `timestamp_ns` 0 and `stamp_source` `none`.
65
+ - `timestamp_ns` is the top-level `header.stamp` whatever its value, so a
66
+ simulated clock (seconds since the simulation start) no longer falls back to 0
67
+ and shifts every following column by two (OmniSim D4).
68
+
8
69
  ## [0.5.6] - 2026-10-02
9
70
 
10
71
  Fixes from a live run of 0.5.3 and 0.5.5 against OmniSim's simulated
@@ -1202,7 +1263,8 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
1202
1263
  - The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
1203
1264
  - `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
1204
1265
 
1205
- [Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.5.6...HEAD
1266
+ [Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.6.0...HEAD
1267
+ [0.6.0]: https://github.com/yaniswav/TopicForge/compare/v0.5.6...v0.6.0
1206
1268
  [0.5.6]: https://github.com/yaniswav/TopicForge/compare/v0.5.5...v0.5.6
1207
1269
  [0.5.5]: https://github.com/yaniswav/TopicForge/compare/v0.5.4...v0.5.5
1208
1270
  [0.5.4]: https://github.com/yaniswav/TopicForge/compare/v0.5.3...v0.5.4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: topicforge
3
- Version: 0.5.6
3
+ Version: 0.6.0
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
@@ -42,6 +42,7 @@ Classifier: Programming Language :: Python :: 3.13
42
42
  Requires-Python: >=3.10
43
43
  Requires-Dist: mcp<2,>=1.0.0
44
44
  Requires-Dist: pydantic>=2.6
45
+ Requires-Dist: pyyaml>=6
45
46
  Provides-Extra: all
46
47
  Requires-Dist: cyclonedds>=0.10; extra == 'all'
47
48
  Provides-Extra: bags
@@ -108,8 +109,8 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
108
109
  | `health_check` | Environment and mode introspection. Always succeeds; reports `mode` next to `requested_mode` |
109
110
  | `list_topics` | Discover the ROS2 graph |
110
111
  | `get_topic_info` | Message type, publisher/subscriber counts and QoS for one topic |
111
- | `sample_messages` | Peek recent messages on a ROS2 topic (count clamped to 50) |
112
- | `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
112
+ | `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
113
+ | `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
113
114
  | `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
114
115
  | `detect_qos_mismatches` | Incompatible QoS pairs between DDS readers and writers |
115
116
  | `peek_dds_samples` | Raw DDS samples; structured on the three builtin discovery topics, presence-only on user topics |
@@ -159,12 +160,12 @@ Samples with comments are in [`.env.example`](.env.example). Any invalid value s
159
160
 
160
161
  - DDS validation is partial. The Cyclone adapter has run against a real bus, with Cyclone and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`). The Fast DDS adapter has never run against a bus, and no RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.
161
162
  - User-topic payloads are not decoded. `peek_dds_samples` on a user topic returns count 0 and a note that the topic is announced on the bus; no traffic is read. `topic_metrics` therefore has data only for the builtin discovery topics and says so in its `status`. It is a discovery-layer probe, not a publish-rate monitor.
162
- - Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned for 0.5.6. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
163
+ - Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
163
164
  - Cyclone vendor ids: participants that do not follow the RTPS vendor-id convention in their GUID prefix (Dust DDS, and RTI by default) are reported with vendor `unknown`.
164
165
  - Single domain: the server observes the domain it joined at startup; changing it needs a restart.
165
166
  - DDS Security is not handled. A participant without credentials sees an empty secure bus. `detect_qos_mismatches` checks Partition, type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership (kind), DestinationOrder and DataRepresentation (History as a risk); Presentation, XTypes assignability and runtime behavior are not checked, and the result lists them in `policies_unchecked`. It returns a `MismatchScan` envelope: read `reports` for the mismatches.
166
167
  - Fast DDS serves no `list_endpoints`.
167
- - `sample_messages` (live) runs `ros2 topic echo --csv --once` with a short timeout, so it returns at most one message, and a topic with no current publisher returns an empty sample. `timestamp_ns` is the message `header.stamp` for `Header`-stamped types and `0` for headerless ones. Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_after_columns`.
168
+ - `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..45, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation) or 0 for headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
168
169
  - `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, and is served only by the ROS2 CLI adapter or the mock; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output.
169
170
  - Synchronous handlers: the tools run on the MCP event loop; on Windows a hung `ros2` launcher can block the server.
170
171
  - No streaming or push subscriptions: tools are strictly request/response.
@@ -188,7 +189,7 @@ When on, each tool call emits one event with exactly six fields:
188
189
  | `tool_name` | `"list_topics"` | One of the twelve tools, never argument values |
189
190
  | `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
190
191
  | `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
191
- | `version` | `"0.5.6"` | TopicForge server version |
192
+ | `version` | `"0.6.0"` | TopicForge server version |
192
193
  | `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
193
194
  | `success` | `true` | Whether the handler returned or raised |
194
195
 
@@ -49,8 +49,8 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
49
49
  | `health_check` | Environment and mode introspection. Always succeeds; reports `mode` next to `requested_mode` |
50
50
  | `list_topics` | Discover the ROS2 graph |
51
51
  | `get_topic_info` | Message type, publisher/subscriber counts and QoS for one topic |
52
- | `sample_messages` | Peek recent messages on a ROS2 topic (count clamped to 50) |
53
- | `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
52
+ | `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
53
+ | `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
54
54
  | `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
55
55
  | `detect_qos_mismatches` | Incompatible QoS pairs between DDS readers and writers |
56
56
  | `peek_dds_samples` | Raw DDS samples; structured on the three builtin discovery topics, presence-only on user topics |
@@ -100,12 +100,12 @@ Samples with comments are in [`.env.example`](.env.example). Any invalid value s
100
100
 
101
101
  - DDS validation is partial. The Cyclone adapter has run against a real bus, with Cyclone and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`). The Fast DDS adapter has never run against a bus, and no RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.
102
102
  - User-topic payloads are not decoded. `peek_dds_samples` on a user topic returns count 0 and a note that the topic is announced on the bus; no traffic is read. `topic_metrics` therefore has data only for the builtin discovery topics and says so in its `status`. It is a discovery-layer probe, not a publish-rate monitor.
103
- - Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned for 0.5.6. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
103
+ - Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
104
104
  - Cyclone vendor ids: participants that do not follow the RTPS vendor-id convention in their GUID prefix (Dust DDS, and RTI by default) are reported with vendor `unknown`.
105
105
  - Single domain: the server observes the domain it joined at startup; changing it needs a restart.
106
106
  - DDS Security is not handled. A participant without credentials sees an empty secure bus. `detect_qos_mismatches` checks Partition, type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership (kind), DestinationOrder and DataRepresentation (History as a risk); Presentation, XTypes assignability and runtime behavior are not checked, and the result lists them in `policies_unchecked`. It returns a `MismatchScan` envelope: read `reports` for the mismatches.
107
107
  - Fast DDS serves no `list_endpoints`.
108
- - `sample_messages` (live) runs `ros2 topic echo --csv --once` with a short timeout, so it returns at most one message, and a topic with no current publisher returns an empty sample. `timestamp_ns` is the message `header.stamp` for `Header`-stamped types and `0` for headerless ones. Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_after_columns`.
108
+ - `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..45, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation) or 0 for headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
109
109
  - `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, and is served only by the ROS2 CLI adapter or the mock; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output.
110
110
  - Synchronous handlers: the tools run on the MCP event loop; on Windows a hung `ros2` launcher can block the server.
111
111
  - No streaming or push subscriptions: tools are strictly request/response.
@@ -129,7 +129,7 @@ When on, each tool call emits one event with exactly six fields:
129
129
  | `tool_name` | `"list_topics"` | One of the twelve tools, never argument values |
130
130
  | `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
131
131
  | `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
132
- | `version` | `"0.5.6"` | TopicForge server version |
132
+ | `version` | `"0.6.0"` | TopicForge server version |
133
133
  | `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
134
134
  | `success` | `true` | Whether the handler returned or raised |
135
135
 
@@ -15,7 +15,7 @@ How to get a working ROS2 environment to point TopicForge at, and how to wire it
15
15
  ```bash
16
16
  python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1
17
17
  pip install topicforge
18
- python -m topicforge --version # -> topicforge 0.5.6
18
+ python -m topicforge --version # -> topicforge 0.6.0
19
19
  TOPICFORGE_MODE=mock python -m topicforge # blocks on stdio; MCP clients spawn it
20
20
  ```
21
21
 
@@ -41,7 +41,7 @@ python3 -m venv ~/topicforge-venv && source ~/topicforge-venv/bin/activate
41
41
  pip install topicforge # Ubuntu 22.04 ships Python 3.10, which is enough
42
42
  ```
43
43
 
44
- Then run live mode with three terminals. In the first, `ros2 run demo_nodes_cpp talker` publishes `/chatter` at about 1 Hz. In the second, `ros2 topic list` should show `/chatter`. In the third, source ROS2, activate the venv and run `TOPICFORGE_MODE=live python -m topicforge`. The startup line reads `topicforge 0.5.6 ready (mode=live, requested_mode=live, adapter=ros2_cli, telemetry=off)`; `mode=mock` means `ros2` was not found and the server fell back to fixtures.
44
+ Then run live mode with three terminals. In the first, `ros2 run demo_nodes_cpp talker` publishes `/chatter` at about 1 Hz. In the second, `ros2 topic list` should show `/chatter`. In the third, source ROS2, activate the venv and run `TOPICFORGE_MODE=live python -m topicforge`. The startup line reads `topicforge 0.6.0 ready (mode=live, requested_mode=live, adapter=ros2_cli, telemetry=off)`; `mode=mock` means `ros2` was not found and the server fell back to fixtures.
45
45
 
46
46
  ## Linux native
47
47
 
@@ -77,7 +77,7 @@ TopicForge resolves the `ros2` launcher with `shutil.which` (normally `ros2.exe`
77
77
 
78
78
  Discover the graph. Ask "What topics are currently being published, and what message types do they carry?" TopicForge calls `list_topics`. Live mode shows `/chatter` plus `/rosout` and `/parameter_events`; mock mode shows `/cmd_vel`, `/odom`, `/scan`, `/tf` and `/camera/image_raw`.
79
79
 
80
- Inspect and sample. Ask "Show me the latest message on /chatter." TopicForge calls `get_topic_info` then `sample_messages`. The payload exposes fields as positional CSV columns (`col_0`, `col_1`, ...) plus `_raw_text`, the verbatim row from `ros2 topic echo --csv --once`. `timestamp_ns` is `0` for headerless types such as `std_msgs/String`.
80
+ Inspect and sample. Ask "Show me the latest message on /chatter." TopicForge calls `get_topic_info` then `sample_messages`. The payload has the message fields as nested named values. `timestamp_ns` is the `header.stamp` and `0` for headerless types such as `std_msgs/String` (`stamp_source` says which).
81
81
 
82
82
  Record and analyze a bag.
83
83
 
@@ -63,12 +63,12 @@ The adapter was written against the 2.6.x binding and has never run against a bu
63
63
  - `lookback_seconds must be in 1..86400` (`participant_events`): default 300. The lifecycle buffer keeps at most 200 events, newest first, regardless of window.
64
64
  - `window_seconds must be in 1..3600` (`topic_metrics`): default 60. The buffer holds 1000 samples per topic, drop-oldest, so frequency is computed from buffered samples, not a true rolling window.
65
65
  - `DDS topic name is malformed`: DDS names match `^[A-Za-z_/][A-Za-z0-9_/:]*$` (builtin names like `DCPSParticipant` and `::` separators are allowed; whitespace and dashes are not). ROS2 names start with `/` and use `_`, so `my-topic` becomes `/my_topic`.
66
- - `sample_messages` and `peek_*` silently clamp `count` to 50; the `count` field of the result is what was actually returned.
66
+ - `sample_messages` caps `count` at 50 and says so in `note`; `peek_*` clamp silently. The `count` field of the result is what was actually returned.
67
67
 
68
68
  ## Other
69
69
 
70
70
  - `topicforge: command not found`: the entry point is not on PATH. Re-activate the venv, or use `python -m topicforge`.
71
- - `sample_messages` returns no samples: live mode runs `ros2 topic echo --once` with a 3 second timeout; a topic with no active publisher returns nothing. Check `ros2 topic info -v <topic>`.
71
+ - `sample_messages` returns fewer samples than asked: live mode waits `timeout_s` seconds (default 10, max 60, counted from the start of the `ros2` CLI, which can take a few seconds on a slow machine) and `note` says `N of M messages` and whether a publisher exists. Raise `timeout_s` for a topic slower than 1 Hz; check `ros2 topic info -v <topic>`.
72
72
  - `analyze_bag` says the path does not exist: the path is resolved in the shell where TopicForge runs. Under WSL use `/mnt/c/demos/run.mcap`, not `C:\demos\run.mcap`. In mock mode only `.mcap`, `.db3`, `.bag` or extensionless paths are accepted.
73
73
  - Claude Desktop shows no tools: check Help -> View Logs -> MCP, confirm `topicforge --version` runs from the same environment (or use the absolute path of the binary in the config), and validate the config with `python -m json.tool claude_desktop_config.json`, since a JSON error silently drops the whole file.
74
74
  - First live call is slow: `ros2 topic list -t` initializes the DDS middleware, a 1-2 second warm-up.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "topicforge"
7
- version = "0.5.6"
7
+ version = "0.6.0"
8
8
  description = "ROS Topic Inspector & Bag Analyzer MCP server for AI agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -25,6 +25,7 @@ classifiers = [
25
25
  dependencies = [
26
26
  "mcp>=1.0.0,<2",
27
27
  "pydantic>=2.6",
28
+ "pyyaml>=6",
28
29
  ]
29
30
 
30
31
  [project.optional-dependencies]
@@ -1,5 +1,5 @@
1
1
  """TopicForge: ROS Topic Inspector & Bag Analyzer MCP server."""
2
2
 
3
- __version__ = "0.5.6"
3
+ __version__ = "0.6.0"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -9,11 +9,10 @@ from __future__ import annotations
9
9
 
10
10
  from typing import Literal, Protocol, runtime_checkable
11
11
 
12
- from topicforge.constants import DEFAULT_MAX_ARRAY_LENGTH
12
+ from topicforge.constants import DEFAULT_MAX_ARRAY_LENGTH, DEFAULT_SAMPLE_TIMEOUT_S
13
13
  from topicforge.models import (
14
14
  BagAnalysis,
15
15
  EndpointListing,
16
- MessageSample,
17
16
  MismatchScan,
18
17
  ParticipantEvent,
19
18
  ParticipantInfo,
@@ -96,7 +95,8 @@ class MiddlewareAdapter(Protocol):
96
95
  *,
97
96
  max_array_length: int | None = DEFAULT_MAX_ARRAY_LENGTH,
98
97
  arrays_summary_only: bool = False,
99
- ) -> list[MessageSample]: ...
98
+ timeout_s: float = DEFAULT_SAMPLE_TIMEOUT_S,
99
+ ) -> SampleResult: ...
100
100
 
101
101
  def analyze_bag(self, path: str) -> BagAnalysis: ...
102
102
 
@@ -15,11 +15,10 @@ from __future__ import annotations
15
15
  from typing import Any
16
16
 
17
17
  from topicforge.adapters.base import AdapterName, EffectiveMode, MiddlewareAdapter
18
- from topicforge.constants import DEFAULT_MAX_ARRAY_LENGTH
18
+ from topicforge.constants import DEFAULT_MAX_ARRAY_LENGTH, DEFAULT_SAMPLE_TIMEOUT_S
19
19
  from topicforge.models import (
20
20
  BagAnalysis,
21
21
  EndpointListing,
22
- MessageSample,
23
22
  MismatchScan,
24
23
  ParticipantEvent,
25
24
  ParticipantInfo,
@@ -69,12 +68,14 @@ class CompositeAdapter:
69
68
  *,
70
69
  max_array_length: int | None = DEFAULT_MAX_ARRAY_LENGTH,
71
70
  arrays_summary_only: bool = False,
72
- ) -> list[MessageSample]:
71
+ timeout_s: float = DEFAULT_SAMPLE_TIMEOUT_S,
72
+ ) -> SampleResult:
73
73
  return self._ros.sample_messages(
74
74
  topic,
75
75
  count,
76
76
  max_array_length=max_array_length,
77
77
  arrays_summary_only=arrays_summary_only,
78
+ timeout_s=timeout_s,
78
79
  )
79
80
 
80
81
  def analyze_bag(self, path: str) -> BagAnalysis:
@@ -63,7 +63,7 @@ from topicforge.adapters.common import (
63
63
  from topicforge.adapters.common import (
64
64
  cyclone_extract_topic_name as _extract_topic_name,
65
65
  )
66
- from topicforge.constants import DEFAULT_MAX_ARRAY_LENGTH
66
+ from topicforge.constants import DEFAULT_MAX_ARRAY_LENGTH, DEFAULT_SAMPLE_TIMEOUT_S
67
67
  from topicforge.models import (
68
68
  BagAnalysis,
69
69
  EndpointListing,
@@ -334,7 +334,8 @@ class CycloneDdsAdapter:
334
334
  *,
335
335
  max_array_length: int | None = DEFAULT_MAX_ARRAY_LENGTH,
336
336
  arrays_summary_only: bool = False,
337
- ) -> list[MessageSample]:
337
+ timeout_s: float = DEFAULT_SAMPLE_TIMEOUT_S,
338
+ ) -> SampleResult:
338
339
  raise AdapterError(DDS_ONLY_ERROR_MSG)
339
340
 
340
341
  def analyze_bag(self, path: str) -> BagAnalysis:
@@ -12,11 +12,10 @@ import logging
12
12
 
13
13
  from topicforge.adapters.base import AdapterError, AdapterName, EffectiveMode
14
14
  from topicforge.adapters.common import validate_domain_id
15
- from topicforge.constants import DEFAULT_MAX_ARRAY_LENGTH
15
+ from topicforge.constants import DEFAULT_MAX_ARRAY_LENGTH, DEFAULT_SAMPLE_TIMEOUT_S
16
16
  from topicforge.models import (
17
17
  BagAnalysis,
18
18
  EndpointListing,
19
- MessageSample,
20
19
  MismatchScan,
21
20
  ParticipantEvent,
22
21
  ParticipantInfo,
@@ -66,7 +65,8 @@ class DustDdsAdapter:
66
65
  *,
67
66
  max_array_length: int | None = DEFAULT_MAX_ARRAY_LENGTH,
68
67
  arrays_summary_only: bool = False,
69
- ) -> list[MessageSample]:
68
+ timeout_s: float = DEFAULT_SAMPLE_TIMEOUT_S,
69
+ ) -> SampleResult:
70
70
  raise AdapterError(_DUST_ROADMAP_MSG)
71
71
 
72
72
  def analyze_bag(self, path: str) -> BagAnalysis:
@@ -52,7 +52,7 @@ from topicforge.adapters.common import (
52
52
  from topicforge.adapters.common import (
53
53
  is_removal as _is_removal,
54
54
  )
55
- from topicforge.constants import DEFAULT_MAX_ARRAY_LENGTH
55
+ from topicforge.constants import DEFAULT_MAX_ARRAY_LENGTH, DEFAULT_SAMPLE_TIMEOUT_S
56
56
  from topicforge.models import (
57
57
  BagAnalysis,
58
58
  EndpointListing,
@@ -241,7 +241,8 @@ class FastDdsAdapter:
241
241
  *,
242
242
  max_array_length: int | None = DEFAULT_MAX_ARRAY_LENGTH,
243
243
  arrays_summary_only: bool = False,
244
- ) -> list[MessageSample]:
244
+ timeout_s: float = DEFAULT_SAMPLE_TIMEOUT_S,
245
+ ) -> SampleResult:
245
246
  raise AdapterError(DDS_ONLY_ERROR_MSG)
246
247
 
247
248
  def analyze_bag(self, path: str) -> BagAnalysis:
@@ -16,11 +16,10 @@ import logging
16
16
 
17
17
  from topicforge.adapters.base import AdapterError, AdapterName, EffectiveMode
18
18
  from topicforge.adapters.common import validate_domain_id
19
- from topicforge.constants import DEFAULT_MAX_ARRAY_LENGTH
19
+ from topicforge.constants import DEFAULT_MAX_ARRAY_LENGTH, DEFAULT_SAMPLE_TIMEOUT_S
20
20
  from topicforge.models import (
21
21
  BagAnalysis,
22
22
  EndpointListing,
23
- MessageSample,
24
23
  MismatchScan,
25
24
  ParticipantEvent,
26
25
  ParticipantInfo,
@@ -72,7 +71,8 @@ class OpenDdsAdapter:
72
71
  *,
73
72
  max_array_length: int | None = DEFAULT_MAX_ARRAY_LENGTH,
74
73
  arrays_summary_only: bool = False,
75
- ) -> list[MessageSample]:
74
+ timeout_s: float = DEFAULT_SAMPLE_TIMEOUT_S,
75
+ ) -> SampleResult:
76
76
  raise AdapterError(_OPENDDS_ROADMAP_MSG)
77
77
 
78
78
  def analyze_bag(self, path: str) -> BagAnalysis: