agenticros 0.0.1 → 0.1.1

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 (330) hide show
  1. package/LICENSE +192 -0
  2. package/README.md +90 -4
  3. package/dist/commands/config.d.ts +20 -0
  4. package/dist/commands/config.d.ts.map +1 -0
  5. package/dist/commands/config.js +179 -0
  6. package/dist/commands/config.js.map +1 -0
  7. package/dist/commands/doctor.d.ts +33 -0
  8. package/dist/commands/doctor.d.ts.map +1 -0
  9. package/dist/commands/doctor.js +232 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/down.d.ts +15 -0
  12. package/dist/commands/down.d.ts.map +1 -0
  13. package/dist/commands/down.js +91 -0
  14. package/dist/commands/down.js.map +1 -0
  15. package/dist/commands/init.d.ts +21 -0
  16. package/dist/commands/init.d.ts.map +1 -0
  17. package/dist/commands/init.js +259 -0
  18. package/dist/commands/init.js.map +1 -0
  19. package/dist/commands/logs.d.ts +18 -0
  20. package/dist/commands/logs.d.ts.map +1 -0
  21. package/dist/commands/logs.js +67 -0
  22. package/dist/commands/logs.js.map +1 -0
  23. package/dist/commands/status.d.ts +12 -0
  24. package/dist/commands/status.d.ts.map +1 -0
  25. package/dist/commands/status.js +56 -0
  26. package/dist/commands/status.js.map +1 -0
  27. package/dist/commands/up.d.ts +20 -0
  28. package/dist/commands/up.d.ts.map +1 -0
  29. package/dist/commands/up.js +70 -0
  30. package/dist/commands/up.js.map +1 -0
  31. package/dist/index.d.ts +12 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +107 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/menu.d.ts +9 -0
  36. package/dist/menu.d.ts.map +1 -0
  37. package/dist/menu.js +96 -0
  38. package/dist/menu.js.map +1 -0
  39. package/dist/runners/real-robot.d.ts +15 -0
  40. package/dist/runners/real-robot.d.ts.map +1 -0
  41. package/dist/runners/real-robot.js +46 -0
  42. package/dist/runners/real-robot.js.map +1 -0
  43. package/dist/runners/sim.d.ts +19 -0
  44. package/dist/runners/sim.d.ts.map +1 -0
  45. package/dist/runners/sim.js +53 -0
  46. package/dist/runners/sim.js.map +1 -0
  47. package/dist/util/env.d.ts +24 -0
  48. package/dist/util/env.d.ts.map +1 -0
  49. package/dist/util/env.js +53 -0
  50. package/dist/util/env.js.map +1 -0
  51. package/dist/util/logger.d.ts +24 -0
  52. package/dist/util/logger.d.ts.map +1 -0
  53. package/dist/util/logger.js +62 -0
  54. package/dist/util/logger.js.map +1 -0
  55. package/dist/util/paths.d.ts +57 -0
  56. package/dist/util/paths.d.ts.map +1 -0
  57. package/dist/util/paths.js +132 -0
  58. package/dist/util/paths.js.map +1 -0
  59. package/dist/util/pidfile.d.ts +16 -0
  60. package/dist/util/pidfile.d.ts.map +1 -0
  61. package/dist/util/pidfile.js +63 -0
  62. package/dist/util/pidfile.js.map +1 -0
  63. package/dist/util/state.d.ts +26 -0
  64. package/dist/util/state.d.ts.map +1 -0
  65. package/dist/util/state.js +55 -0
  66. package/dist/util/state.js.map +1 -0
  67. package/package.json +60 -1
  68. package/runtime/BUNDLE.json +11 -0
  69. package/runtime/LICENSE +192 -0
  70. package/runtime/README.md +273 -0
  71. package/runtime/docs/architecture.md +366 -0
  72. package/runtime/docs/cli.md +140 -0
  73. package/runtime/docs/memory.md +292 -0
  74. package/runtime/docs/robot-setup.md +347 -0
  75. package/runtime/package.json +28 -0
  76. package/runtime/packages/agenticros/agenticros-agenticros-0.0.1.tgz +0 -0
  77. package/runtime/packages/agenticros/openclaw.plugin.json +451 -0
  78. package/runtime/packages/agenticros/package.json +41 -0
  79. package/runtime/packages/agenticros/src/camera-snapshot-cache.ts +59 -0
  80. package/runtime/packages/agenticros/src/camera-snapshot-routes.ts +44 -0
  81. package/runtime/packages/agenticros/src/commands/estop.ts +41 -0
  82. package/runtime/packages/agenticros/src/commands/transport.ts +195 -0
  83. package/runtime/packages/agenticros/src/config-file.ts +136 -0
  84. package/runtime/packages/agenticros/src/config-page.ts +498 -0
  85. package/runtime/packages/agenticros/src/context/robot-context.ts +373 -0
  86. package/runtime/packages/agenticros/src/depth.ts +313 -0
  87. package/runtime/packages/agenticros/src/describer.ts +157 -0
  88. package/runtime/packages/agenticros/src/image-binary-trim.ts +16 -0
  89. package/runtime/packages/agenticros/src/index.ts +85 -0
  90. package/runtime/packages/agenticros/src/landing-page.ts +38 -0
  91. package/runtime/packages/agenticros/src/memory.ts +44 -0
  92. package/runtime/packages/agenticros/src/plugin-api.ts +173 -0
  93. package/runtime/packages/agenticros/src/plugin-image-base64.ts +69 -0
  94. package/runtime/packages/agenticros/src/preflight.ts +110 -0
  95. package/runtime/packages/agenticros/src/routes.ts +328 -0
  96. package/runtime/packages/agenticros/src/safety/validator.ts +43 -0
  97. package/runtime/packages/agenticros/src/service.ts +359 -0
  98. package/runtime/packages/agenticros/src/skill-api.ts +65 -0
  99. package/runtime/packages/agenticros/src/skill-loader.ts +146 -0
  100. package/runtime/packages/agenticros/src/teleop/page.ts +498 -0
  101. package/runtime/packages/agenticros/src/teleop/routes.ts +650 -0
  102. package/runtime/packages/agenticros/src/tools/index.ts +26 -0
  103. package/runtime/packages/agenticros/src/tools/ros2-action.ts +50 -0
  104. package/runtime/packages/agenticros/src/tools/ros2-camera.ts +221 -0
  105. package/runtime/packages/agenticros/src/tools/ros2-depth-distance.ts +58 -0
  106. package/runtime/packages/agenticros/src/tools/ros2-introspect.ts +62 -0
  107. package/runtime/packages/agenticros/src/tools/ros2-memory.ts +158 -0
  108. package/runtime/packages/agenticros/src/tools/ros2-param.ts +87 -0
  109. package/runtime/packages/agenticros/src/tools/ros2-publish.ts +52 -0
  110. package/runtime/packages/agenticros/src/tools/ros2-service.ts +46 -0
  111. package/runtime/packages/agenticros/src/tools/ros2-subscribe.ts +71 -0
  112. package/runtime/packages/agenticros/tsconfig.json +9 -0
  113. package/runtime/packages/agenticros-claude-code/README.md +260 -0
  114. package/runtime/packages/agenticros-claude-code/config.example.json +9 -0
  115. package/runtime/packages/agenticros-claude-code/dist/config.d.ts +8 -0
  116. package/runtime/packages/agenticros-claude-code/dist/config.d.ts.map +1 -0
  117. package/runtime/packages/agenticros-claude-code/dist/config.js +93 -0
  118. package/runtime/packages/agenticros-claude-code/dist/config.js.map +1 -0
  119. package/runtime/packages/agenticros-claude-code/dist/depth.d.ts +20 -0
  120. package/runtime/packages/agenticros-claude-code/dist/depth.d.ts.map +1 -0
  121. package/runtime/packages/agenticros-claude-code/dist/depth.js +126 -0
  122. package/runtime/packages/agenticros-claude-code/dist/depth.js.map +1 -0
  123. package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.d.ts +6 -0
  124. package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.d.ts.map +1 -0
  125. package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.js +36 -0
  126. package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.js.map +1 -0
  127. package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.d.ts +33 -0
  128. package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.d.ts.map +1 -0
  129. package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.js +134 -0
  130. package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.js.map +1 -0
  131. package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.d.ts +43 -0
  132. package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.d.ts.map +1 -0
  133. package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.js +73 -0
  134. package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.js.map +1 -0
  135. package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.d.ts +58 -0
  136. package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.d.ts.map +1 -0
  137. package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.js +251 -0
  138. package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.js.map +1 -0
  139. package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.d.ts +61 -0
  140. package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.d.ts.map +1 -0
  141. package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.js +268 -0
  142. package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.js.map +1 -0
  143. package/runtime/packages/agenticros-claude-code/dist/index.d.ts +3 -0
  144. package/runtime/packages/agenticros-claude-code/dist/index.d.ts.map +1 -0
  145. package/runtime/packages/agenticros-claude-code/dist/index.js +111 -0
  146. package/runtime/packages/agenticros-claude-code/dist/index.js.map +1 -0
  147. package/runtime/packages/agenticros-claude-code/dist/memory.d.ts +17 -0
  148. package/runtime/packages/agenticros-claude-code/dist/memory.d.ts.map +1 -0
  149. package/runtime/packages/agenticros-claude-code/dist/memory.js +44 -0
  150. package/runtime/packages/agenticros-claude-code/dist/memory.js.map +1 -0
  151. package/runtime/packages/agenticros-claude-code/dist/safety.d.ts +10 -0
  152. package/runtime/packages/agenticros-claude-code/dist/safety.d.ts.map +1 -0
  153. package/runtime/packages/agenticros-claude-code/dist/safety.js +34 -0
  154. package/runtime/packages/agenticros-claude-code/dist/safety.js.map +1 -0
  155. package/runtime/packages/agenticros-claude-code/dist/tools.d.ts +36 -0
  156. package/runtime/packages/agenticros-claude-code/dist/tools.d.ts.map +1 -0
  157. package/runtime/packages/agenticros-claude-code/dist/tools.js +777 -0
  158. package/runtime/packages/agenticros-claude-code/dist/tools.js.map +1 -0
  159. package/runtime/packages/agenticros-claude-code/dist/transport.d.ts +17 -0
  160. package/runtime/packages/agenticros-claude-code/dist/transport.d.ts.map +1 -0
  161. package/runtime/packages/agenticros-claude-code/dist/transport.js +46 -0
  162. package/runtime/packages/agenticros-claude-code/dist/transport.js.map +1 -0
  163. package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.d.ts +42 -0
  164. package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.d.ts.map +1 -0
  165. package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.js +114 -0
  166. package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.js.map +1 -0
  167. package/runtime/packages/agenticros-claude-code/package.json +29 -0
  168. package/runtime/packages/agenticros-claude-code/src/config.ts +96 -0
  169. package/runtime/packages/agenticros-claude-code/src/depth.ts +173 -0
  170. package/runtime/packages/agenticros-claude-code/src/find-object/coco-classes.ts +38 -0
  171. package/runtime/packages/agenticros-claude-code/src/find-object/find-object.ts +190 -0
  172. package/runtime/packages/agenticros-claude-code/src/follow-me/controller.ts +109 -0
  173. package/runtime/packages/agenticros-claude-code/src/follow-me/depth-loop.ts +420 -0
  174. package/runtime/packages/agenticros-claude-code/src/follow-me/detector.ts +303 -0
  175. package/runtime/packages/agenticros-claude-code/src/follow-me/loop.ts +330 -0
  176. package/runtime/packages/agenticros-claude-code/src/index.ts +125 -0
  177. package/runtime/packages/agenticros-claude-code/src/memory.ts +51 -0
  178. package/runtime/packages/agenticros-claude-code/src/safety.ts +44 -0
  179. package/runtime/packages/agenticros-claude-code/src/tools.ts +891 -0
  180. package/runtime/packages/agenticros-claude-code/src/transport.ts +58 -0
  181. package/runtime/packages/agenticros-claude-code/src/zero-shot/detector.ts +169 -0
  182. package/runtime/packages/agenticros-claude-code/tsconfig.json +9 -0
  183. package/runtime/packages/agenticros-claude-code/yolo-debug.mjs +106 -0
  184. package/runtime/packages/agenticros-gemini/README.md +139 -0
  185. package/runtime/packages/agenticros-gemini/package.json +28 -0
  186. package/runtime/packages/agenticros-gemini/scripts/smoke-api.mjs +42 -0
  187. package/runtime/packages/agenticros-gemini/src/chat.ts +139 -0
  188. package/runtime/packages/agenticros-gemini/src/config.ts +92 -0
  189. package/runtime/packages/agenticros-gemini/src/depth.ts +173 -0
  190. package/runtime/packages/agenticros-gemini/src/index.ts +58 -0
  191. package/runtime/packages/agenticros-gemini/src/memory.ts +32 -0
  192. package/runtime/packages/agenticros-gemini/src/safety.ts +44 -0
  193. package/runtime/packages/agenticros-gemini/src/tools.ts +516 -0
  194. package/runtime/packages/agenticros-gemini/src/transport.ts +58 -0
  195. package/runtime/packages/agenticros-gemini/tsconfig.json +8 -0
  196. package/runtime/packages/core/package.json +47 -0
  197. package/runtime/packages/core/src/banner.ts +32 -0
  198. package/runtime/packages/core/src/cmd-vel-twist.ts +31 -0
  199. package/runtime/packages/core/src/config.ts +279 -0
  200. package/runtime/packages/core/src/index.ts +54 -0
  201. package/runtime/packages/core/src/memory/__tests__/factory.test.ts +70 -0
  202. package/runtime/packages/core/src/memory/__tests__/local-provider.test.ts +195 -0
  203. package/runtime/packages/core/src/memory/__tests__/mem0-provider.test.ts +192 -0
  204. package/runtime/packages/core/src/memory/__tests__/smart-defaults.test.ts +46 -0
  205. package/runtime/packages/core/src/memory/factory.ts +63 -0
  206. package/runtime/packages/core/src/memory/index.ts +10 -0
  207. package/runtime/packages/core/src/memory/local/provider.ts +229 -0
  208. package/runtime/packages/core/src/memory/mem0/provider.ts +379 -0
  209. package/runtime/packages/core/src/memory/types.ts +96 -0
  210. package/runtime/packages/core/src/topic-utils.ts +95 -0
  211. package/runtime/packages/core/src/transport/factory.ts +47 -0
  212. package/runtime/packages/core/src/transport/local/conversion.ts +333 -0
  213. package/runtime/packages/core/src/transport/local/entities.ts +129 -0
  214. package/runtime/packages/core/src/transport/local/transport.ts +406 -0
  215. package/runtime/packages/core/src/transport/rosbridge/actions.ts +81 -0
  216. package/runtime/packages/core/src/transport/rosbridge/adapter.ts +157 -0
  217. package/runtime/packages/core/src/transport/rosbridge/client.ts +438 -0
  218. package/runtime/packages/core/src/transport/rosbridge/services.ts +41 -0
  219. package/runtime/packages/core/src/transport/rosbridge/topics.ts +60 -0
  220. package/runtime/packages/core/src/transport/rosbridge/types.ts +118 -0
  221. package/runtime/packages/core/src/transport/transport.ts +77 -0
  222. package/runtime/packages/core/src/transport/types.ts +137 -0
  223. package/runtime/packages/core/src/transport/webrtc/signaling-client.ts +196 -0
  224. package/runtime/packages/core/src/transport/webrtc/signaling-types.ts +130 -0
  225. package/runtime/packages/core/src/transport/webrtc/transport.ts +516 -0
  226. package/runtime/packages/core/src/transport/zenoh/adapter.ts +357 -0
  227. package/runtime/packages/core/src/transport/zenoh/cdr.ts +183 -0
  228. package/runtime/packages/core/src/transport/zenoh/keys.ts +51 -0
  229. package/runtime/packages/core/tsconfig.json +9 -0
  230. package/runtime/packages/ros-camera/package.json +30 -0
  231. package/runtime/packages/ros-camera/src/index.ts +13 -0
  232. package/runtime/packages/ros-camera/src/snapshot.ts +372 -0
  233. package/runtime/packages/ros-camera/tsconfig.json +9 -0
  234. package/runtime/pnpm-lock.yaml +5260 -0
  235. package/runtime/pnpm-workspace.yaml +2 -0
  236. package/runtime/ros2_ws/src/agenticros_agent/agenticros_agent/__init__.py +0 -0
  237. package/runtime/ros2_ws/src/agenticros_agent/agenticros_agent/agent_node.py +561 -0
  238. package/runtime/ros2_ws/src/agenticros_agent/package.xml +25 -0
  239. package/runtime/ros2_ws/src/agenticros_agent/resource/agenticros_agent +0 -0
  240. package/runtime/ros2_ws/src/agenticros_agent/setup.cfg +4 -0
  241. package/runtime/ros2_ws/src/agenticros_agent/setup.py +25 -0
  242. package/runtime/ros2_ws/src/agenticros_bringup/README.md +128 -0
  243. package/runtime/ros2_ws/src/agenticros_bringup/agenticros_bringup/__init__.py +1 -0
  244. package/runtime/ros2_ws/src/agenticros_bringup/agenticros_bringup/cmd_vel_relay.py +33 -0
  245. package/runtime/ros2_ws/src/agenticros_bringup/launch/cmd_vel_bridge.launch.py +58 -0
  246. package/runtime/ros2_ws/src/agenticros_bringup/launch/gazebo_turtlebot3.launch.py +69 -0
  247. package/runtime/ros2_ws/src/agenticros_bringup/launch/mode_a_gazebo.launch.py +55 -0
  248. package/runtime/ros2_ws/src/agenticros_bringup/launch/mode_a_gazebo_rviz.launch.py +48 -0
  249. package/runtime/ros2_ws/src/agenticros_bringup/launch/realsense_rosbridge.launch.py +154 -0
  250. package/runtime/ros2_ws/src/agenticros_bringup/launch/rosbridge_gazebo.launch.py +54 -0
  251. package/runtime/ros2_ws/src/agenticros_bringup/launch/rviz.launch.py +38 -0
  252. package/runtime/ros2_ws/src/agenticros_bringup/launch/turtlebot3_gazebo_rviz.launch.py +42 -0
  253. package/runtime/ros2_ws/src/agenticros_bringup/package.xml +24 -0
  254. package/runtime/ros2_ws/src/agenticros_bringup/resource/agenticros_bringup +0 -0
  255. package/runtime/ros2_ws/src/agenticros_bringup/rviz/turtlebot3_agenticros.rviz +174 -0
  256. package/runtime/ros2_ws/src/agenticros_bringup/setup.cfg +4 -0
  257. package/runtime/ros2_ws/src/agenticros_bringup/setup.py +28 -0
  258. package/runtime/ros2_ws/src/agenticros_discovery/agenticros_discovery/__init__.py +0 -0
  259. package/runtime/ros2_ws/src/agenticros_discovery/agenticros_discovery/discovery_node.py +172 -0
  260. package/runtime/ros2_ws/src/agenticros_discovery/package.xml +22 -0
  261. package/runtime/ros2_ws/src/agenticros_discovery/resource/agenticros_discovery +0 -0
  262. package/runtime/ros2_ws/src/agenticros_discovery/setup.cfg +5 -0
  263. package/runtime/ros2_ws/src/agenticros_discovery/setup.py +25 -0
  264. package/runtime/ros2_ws/src/agenticros_follow_me/README.md +66 -0
  265. package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/__init__.py +1 -0
  266. package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/__main__.py +5 -0
  267. package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/follow_me_node.py +278 -0
  268. package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/follower_controller.py +631 -0
  269. package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/person_tracker.py +635 -0
  270. package/runtime/ros2_ws/src/agenticros_follow_me/package.xml +22 -0
  271. package/runtime/ros2_ws/src/agenticros_follow_me/resource/agenticros_follow_me +0 -0
  272. package/runtime/ros2_ws/src/agenticros_follow_me/setup.py +25 -0
  273. package/runtime/ros2_ws/src/agenticros_msgs/CMakeLists.txt +26 -0
  274. package/runtime/ros2_ws/src/agenticros_msgs/msg/CapabilityManifest.msg +9 -0
  275. package/runtime/ros2_ws/src/agenticros_msgs/package.xml +22 -0
  276. package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeGetStatus.srv +11 -0
  277. package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeSetDistance.srv +4 -0
  278. package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeSetTarget.srv +6 -0
  279. package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeStart.srv +5 -0
  280. package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeStop.srv +3 -0
  281. package/runtime/ros2_ws/src/agenticros_msgs/srv/GetCapabilities.srv +5 -0
  282. package/runtime/ros2_ws/src/agenticros_sim/CMakeLists.txt +24 -0
  283. package/runtime/ros2_ws/src/agenticros_sim/README.md +120 -0
  284. package/runtime/ros2_ws/src/agenticros_sim/config/agenticros-sim.config.json +28 -0
  285. package/runtime/ros2_ws/src/agenticros_sim/config/amr_bridge.yaml +111 -0
  286. package/runtime/ros2_ws/src/agenticros_sim/config/amr_view.rviz +172 -0
  287. package/runtime/ros2_ws/src/agenticros_sim/env-hooks/gz_resource_path.dsv.in +3 -0
  288. package/runtime/ros2_ws/src/agenticros_sim/env-hooks/gz_resource_path.sh.in +7 -0
  289. package/runtime/ros2_ws/src/agenticros_sim/launch/sim_amr.launch.py +159 -0
  290. package/runtime/ros2_ws/src/agenticros_sim/models/agenticros_amr/model.config +17 -0
  291. package/runtime/ros2_ws/src/agenticros_sim/models/agenticros_amr/model.sdf +244 -0
  292. package/runtime/ros2_ws/src/agenticros_sim/package.xml +27 -0
  293. package/runtime/ros2_ws/src/agenticros_sim/worlds/agenticros_indoor.sdf +183 -0
  294. package/runtime/scripts/activate_workspace.sh +285 -0
  295. package/runtime/scripts/agenticros-describer.policy.yaml +96 -0
  296. package/runtime/scripts/agenticros-proxy.cjs +99 -0
  297. package/runtime/scripts/agenticros-rosbridge.policy.yaml +62 -0
  298. package/runtime/scripts/check-cli-tarball-size.mjs +42 -0
  299. package/runtime/scripts/configure_agenticros.sh +200 -0
  300. package/runtime/scripts/configure_for_sim.sh +64 -0
  301. package/runtime/scripts/fix-openclaw-control-ui-path.sh +20 -0
  302. package/runtime/scripts/install_cli.sh +94 -0
  303. package/runtime/scripts/install_rosbridge_from_source.sh +67 -0
  304. package/runtime/scripts/lib/agenticros-banner.sh +28 -0
  305. package/runtime/scripts/onboard_robot.sh +75 -0
  306. package/runtime/scripts/openai.policy.yaml +77 -0
  307. package/runtime/scripts/openclaw-dashboard-url.cjs +49 -0
  308. package/runtime/scripts/pack-runtime.mjs +245 -0
  309. package/runtime/scripts/run_demo_native.sh +43 -0
  310. package/runtime/scripts/run_nemoclaw_host_stack.sh +91 -0
  311. package/runtime/scripts/run_robot_rosbridge.sh +36 -0
  312. package/runtime/scripts/sandbox_rosbridge_relay.py +137 -0
  313. package/runtime/scripts/setup-openclaw-local.cjs +75 -0
  314. package/runtime/scripts/setup_gateway_plugin.sh +329 -0
  315. package/runtime/scripts/setup_robot.sh +113 -0
  316. package/runtime/scripts/setup_workspace.sh +484 -0
  317. package/runtime/scripts/sim/run_sim.sh +146 -0
  318. package/runtime/scripts/smoke_test_nemoclaw.sh +123 -0
  319. package/runtime/scripts/start_demo.sh +55 -0
  320. package/runtime/scripts/sync-skill-tools.mjs +335 -0
  321. package/runtime/scripts/test-follow-me-sim.mjs +135 -0
  322. package/runtime/scripts/test-mcp-e2e.mjs +184 -0
  323. package/runtime/scripts/test-rclnodejs.mts +129 -0
  324. package/runtime/scripts/use-openclaw-2026.2.26.sh +19 -0
  325. package/runtime/scripts/use-openclaw-2026.3.11.sh +19 -0
  326. package/runtime/scripts/zenoh-bridge-ros2dds-robot.json5 +30 -0
  327. package/runtime/scripts/zenohd-agenticros.json5 +11 -0
  328. package/runtime/scripts/zenohd-rosclaw.json5 +11 -0
  329. package/runtime/tsconfig.base.json +19 -0
  330. package/index.js +0 -6
@@ -0,0 +1,373 @@
1
+ import type { OpenClawPluginApi } from "../plugin-api.js";
2
+ import type { AgenticROSConfig, MemoryRecord } from "@agenticros/core";
3
+ import type { TopicInfo, ServiceInfo, ActionInfo } from "@agenticros/core";
4
+ import { resolveMemoryNamespace } from "@agenticros/core";
5
+ import { getTransport } from "../service.js";
6
+ import { getLoadedSkillIds } from "../skill-loader.js";
7
+ import { getMemory } from "../memory.js";
8
+
9
+ /** Cached discovery results with TTL. */
10
+ interface DiscoveryCache {
11
+ topics: TopicInfo[];
12
+ services: ServiceInfo[];
13
+ actions: ActionInfo[];
14
+ timestamp: number;
15
+ }
16
+
17
+ const CACHE_TTL_MS = 60_000; // 60s
18
+ let cache: DiscoveryCache | null = null;
19
+
20
+ /** Clear the discovery cache so the next agent start re-discovers capabilities. */
21
+ export function clearDiscoveryCache(): void {
22
+ cache = null;
23
+ }
24
+
25
+ /**
26
+ * Register the before_agent_start hook to inject robot capabilities
27
+ * into the AI agent's system context.
28
+ */
29
+ export function registerRobotContext(api: OpenClawPluginApi, config: AgenticROSConfig): void {
30
+ const robotName = config.robot.name;
31
+ const robotNamespace = config.robot.namespace;
32
+
33
+ // Reactive re-discovery: clear cache on transport reconnect
34
+ try {
35
+ const transport = getTransport();
36
+ transport.onConnection((status: string) => {
37
+ if (status === "connected") {
38
+ cache = null; // Force re-discovery on next agent start
39
+ api.logger.info("Transport reconnected — capability cache cleared");
40
+ }
41
+ });
42
+ } catch {
43
+ // Transport not initialized yet — will be set up by the service.
44
+ // The onConnection handler will be registered when the hook fires.
45
+ }
46
+
47
+ api.on("before_agent_start", async (_event, _ctx) => {
48
+ const capabilities = await discoverCapabilities(api, robotNamespace);
49
+ const cameraTopicHint =
50
+ (config.robot?.cameraTopic ?? "").trim() || "/camera/camera/color/image_raw/compressed";
51
+ const memorySection = await buildMemorySection(config);
52
+ const context =
53
+ buildRobotContext(config, robotName, robotNamespace, capabilities, cameraTopicHint) +
54
+ memorySection;
55
+ return { prependContext: context };
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Build a "Memory" section that tells the LLM how to use memory tools and
61
+ * what's already stored. Returns "" when memory is disabled or unavailable,
62
+ * so non-memory users see no change in their context.
63
+ */
64
+ async function buildMemorySection(config: AgenticROSConfig): Promise<string> {
65
+ if (!config.memory?.enabled) return "";
66
+ const memory = getMemory();
67
+ if (!memory) {
68
+ // Provider isn't ready yet (async init still pending). Skip silently;
69
+ // the section will appear on the next session.
70
+ return "";
71
+ }
72
+ const namespace = resolveMemoryNamespace(config);
73
+ let recent: MemoryRecord[] = [];
74
+ try {
75
+ recent = await memory.recent(namespace, 10);
76
+ } catch {
77
+ recent = [];
78
+ }
79
+ const recentBlock =
80
+ recent.length === 0
81
+ ? "_No memories saved yet for this robot._"
82
+ : recent
83
+ .map((r, i) => `${i + 1}. ${r.content.replace(/\s+/g, " ").trim()}`)
84
+ .join("\n");
85
+ return `\n\n### Memory (cross-session, ${memory.backend} backend)
86
+ You have a shared long-term memory store. It is shared across **all** AgenticROS adapters talking to this robot — facts written from Claude Desktop, Claude Code, Gemini CLI, and this OpenClaw chat all live in the same store and surface here.
87
+
88
+ **Always call \`memory_recall\` BEFORE answering** when the user asks a personal-context question, including:
89
+ - "What do I have for X?", "What's my Y?", "Where is the Z?"
90
+ - "What did I tell you about ...?", "Do you remember ...?", "What's my preference for ...?"
91
+ - Anything that depends on facts the user previously shared about themselves, their robot setup, their home, or their preferences.
92
+
93
+ **Call \`memory_remember\`** when the user explicitly says "remember that ...", "note that ...", "from now on ...", or shares a durable personal fact (preferences, names, places, routines, robot hardware). Do **not** auto-store conversation transcripts.
94
+
95
+ **Recently remembered (newest first):**
96
+ ${recentBlock}
97
+
98
+ If a question seems answerable from this list, answer directly. If you need more (e.g. semantic match, full search), call \`memory_recall\` with a focused query.`;
99
+ }
100
+
101
+ /**
102
+ * Discover live capabilities from the transport layer, with caching.
103
+ * Falls back to empty lists if discovery fails.
104
+ */
105
+ async function discoverCapabilities(
106
+ api: OpenClawPluginApi,
107
+ namespace: string,
108
+ ): Promise<DiscoveryCache> {
109
+ // Return cached results if still fresh
110
+ if (cache && Date.now() - cache.timestamp < CACHE_TTL_MS) {
111
+ return cache;
112
+ }
113
+
114
+ try {
115
+ const transport = getTransport();
116
+
117
+ const [topics, services, actions] = await Promise.all([
118
+ transport.listTopics(),
119
+ transport.listServices(),
120
+ transport.listActions(),
121
+ ]);
122
+
123
+ // Filter by namespace if configured; always include camera topics (they often live under /camera/, not robot namespace)
124
+ const filterByNs = (name: string) => {
125
+ if (!namespace) return true;
126
+ const normalized = name.replace(/^\/+/, "");
127
+ if (normalized.startsWith(namespace)) return true;
128
+ if (normalized.startsWith("camera/") || normalized.includes("/camera/")) return true;
129
+ return false;
130
+ };
131
+
132
+ cache = {
133
+ topics: topics.filter((t: TopicInfo) => filterByNs(t.name)),
134
+ services: services.filter((s: ServiceInfo) => filterByNs(s.name)),
135
+ actions: actions.filter((a: ActionInfo) => filterByNs(a.name)),
136
+ timestamp: Date.now(),
137
+ };
138
+
139
+ api.logger.info(
140
+ `Discovered ${cache.topics.length} topics, ${cache.services.length} services, ${cache.actions.length} actions`,
141
+ );
142
+
143
+ return cache;
144
+ } catch (err) {
145
+ api.logger.warn(`Capability discovery failed, using defaults: ${err}`);
146
+ return {
147
+ topics: [],
148
+ services: [],
149
+ actions: [],
150
+ timestamp: 0,
151
+ };
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Build the robot context string that gets injected into the agent's system prompt.
157
+ */
158
+ function buildRobotContext(
159
+ config: AgenticROSConfig,
160
+ name: string,
161
+ namespace: string,
162
+ capabilities: DiscoveryCache,
163
+ cameraTopicHint: string,
164
+ ): string {
165
+ const { topics, services, actions } = capabilities;
166
+
167
+ // If discovery returned results, use them
168
+ if (topics.length > 0 || services.length > 0 || actions.length > 0) {
169
+ return buildDynamicContext(config, name, namespace, topics, services, actions);
170
+ }
171
+
172
+ // Fall back to hardcoded defaults if discovery failed
173
+ return buildFallbackContext(config, name, namespace, cameraTopicHint);
174
+ }
175
+
176
+ function transportMode(config: AgenticROSConfig): "local" | "rosbridge" | "webrtc" | "zenoh" {
177
+ const m = config.transport?.mode;
178
+ if (m === "local" || m === "rosbridge" || m === "webrtc" || m === "zenoh") return m;
179
+ return "local";
180
+ }
181
+
182
+ /** How AgenticROS reaches ROS 2 — matches plugin transport.mode (injected into the model). */
183
+ function buildUserInterfaceBlurb(config: AgenticROSConfig): string {
184
+ const mode = transportMode(config);
185
+ const rosbridgeUrl = (config.rosbridge?.url ?? "ws://localhost:9090").trim() || "ws://localhost:9090";
186
+ const zenohEp = (config.zenoh?.routerEndpoint ?? "ws://localhost:10000").trim() || "ws://localhost:10000";
187
+ const domainId = config.local?.domainId ?? 0;
188
+
189
+ let connectionLine: string;
190
+ let pairingLine: string;
191
+ switch (mode) {
192
+ case "local":
193
+ connectionLine = `It connects to ROS 2 on the **same machine** as this gateway using **local DDS** (direct participant via rclnodejs, ROS_DOMAIN_ID **${domainId}** by default). No rosbridge WebSocket and no Zenoh router are used in this mode.`;
194
+ pairingLine = `If the user says "nodes status shows zero" or "how do I pair the robot": explain that the robot stack is reached through **local DDS** on this host. There is **no** OpenClaw device pairing step for ROS. Ensure ROS 2 nodes and OpenClaw share the **same domain ID** and that the AgenticROS transport is connected—\`openclaw nodes status\` counts **OpenClaw mobile/desktop nodes**, not your ROS graph.`;
195
+ break;
196
+ case "zenoh":
197
+ connectionLine = `It connects to ROS 2 through **Zenoh** (zenoh-ts), typically at **${zenohEp}** (WebSocket to zenoh-plugin-remote-api — use \`ws://\`, not raw \`tcp/\` for the plugin).`;
198
+ pairingLine = `If the user says "nodes status shows zero" or "how do I pair the robot": explain that the robot is reached via the **Zenoh router** endpoint above. No OpenClaw node pairing is required for AgenticROS. If the plugin is loaded and Zenoh/your bridge see ROS traffic, the robot is connected—\`openclaw nodes status\` is irrelevant for AgenticROS.`;
199
+ break;
200
+ case "webrtc":
201
+ connectionLine = `It connects to the robot over **WebRTC** (signaling and robot id configured in the plugin). There is typically **no** rosbridge on this path.`;
202
+ pairingLine = `If the user says "nodes status shows zero" or "how do I pair the robot": explain that **OpenClaw device pairing** is unrelated; WebRTC robot connectivity uses the plugin's signaling/robot configuration.`;
203
+ break;
204
+ default:
205
+ connectionLine = `It connects to ROS 2 via **rosbridge** (WebSocket to \`rosbridge_server\`, e.g. **${rosbridgeUrl}**).`;
206
+ pairingLine = `If the user says "nodes status shows zero" or "how do I pair the robot": explain that the robot is already connected through the AgenticROS plugin's **rosbridge** URL (e.g. **${rosbridgeUrl}**). No pairing step is required. If the plugin is loaded and rosbridge is reachable, the robot is connected—\`openclaw nodes status\` is irrelevant for AgenticROS.`;
207
+ }
208
+
209
+ return `
210
+ ### User-facing interface (tell users this when they ask)
211
+ - **There is no separate robot GUI, dashboard, or URL.** The interface is this chat.
212
+ - The user controls the robot by typing here (e.g. "move forward 1 meter", "what do you see?", "check the battery"). You execute commands with the ros2_* tools and reply in chat.
213
+ - For telemetry: use \`ros2_subscribe_once\` or \`ros2_camera_snapshot\` and describe or show the result in your reply. There is no separate feed URL—you are the feed. If they want a "controller app," this chat is it.
214
+
215
+ ### OpenClaw "nodes" — do not confuse with AgenticROS
216
+ - AgenticROS is an **OpenClaw plugin** that runs inside this gateway. ${connectionLine} There is **no separate "AgenticROS agent" or OpenClaw "node"** to pair for ROS control.
217
+ - **Never tell users** to run \`openclaw node pair\`, \`openclaw nodes status\`, QR codes, or auth tokens **for ROS / robot control**. Those apply to OpenClaw's **companion device pairing**, not to AgenticROS talking to ROS 2.
218
+ - ${pairingLine}
219
+ `.trim();
220
+ }
221
+
222
+ /** For camera / image tips — all transports AgenticROS supports. */
223
+ function imageTransportHint(config: AgenticROSConfig): string {
224
+ const mode = transportMode(config);
225
+ if (mode === "local") {
226
+ return "AgenticROS supports `sensor_msgs/msg/Image` and `sensor_msgs/msg/CompressedImage` over **local DDS** (this mode), and the same types over Zenoh or rosbridge when those transports are selected.";
227
+ }
228
+ return "AgenticROS supports `sensor_msgs/msg/Image` and `sensor_msgs/msg/CompressedImage` over **local DDS**, **Zenoh**, and **rosbridge**.";
229
+ }
230
+
231
+ function buildDynamicContext(
232
+ config: AgenticROSConfig,
233
+ name: string,
234
+ namespace: string,
235
+ topics: TopicInfo[],
236
+ services: ServiceInfo[],
237
+ actions: ActionInfo[],
238
+ ): string {
239
+ let context = `## Robot: ${name}\n\n`;
240
+ context += `You are connected to a ROS2 robot named "${name}". You can control it using the ros2_* tools.\n\n`;
241
+ context += `**Topics below** come from a **short live sample** when the session started. They are not guaranteed complete or up to date. If the user needs certainty—or says a topic is missing—call \`ros2_list_topics\` and answer from that result only.\n\n`;
242
+ if (namespace) {
243
+ context += `**Velocity commands:** Use \`ros2_publish\` with topic \`/cmd_vel\`; the plugin sends them to \`/${namespace}/cmd_vel\`.\n\n`;
244
+ }
245
+ context += `${buildUserInterfaceBlurb(config)}\n\n`;
246
+
247
+ // Cap injected lists to avoid huge context (rate limits / token burn)
248
+ const MAX_TOPICS = 25;
249
+ const MAX_SERVICES = 15;
250
+ const MAX_ACTIONS = 15;
251
+
252
+ if (topics.length > 0) {
253
+ context += "### Available Topics\n";
254
+ const showTopics = topics.slice(0, MAX_TOPICS);
255
+ for (const t of showTopics) {
256
+ context += `- \`${t.name}\` (${t.type})\n`;
257
+ }
258
+ if (topics.length > MAX_TOPICS) {
259
+ context += `- … and ${topics.length - MAX_TOPICS} more (use \`ros2_list_topics\` if needed)\n`;
260
+ }
261
+ context += "\n";
262
+ }
263
+
264
+ if (services.length > 0) {
265
+ context += "### Available Services\n";
266
+ const showServices = services.slice(0, MAX_SERVICES);
267
+ for (const s of showServices) {
268
+ context += `- \`${s.name}\` (${s.type})\n`;
269
+ }
270
+ if (services.length > MAX_SERVICES) {
271
+ context += `- … and ${services.length - MAX_SERVICES} more\n`;
272
+ }
273
+ context += "\n";
274
+ }
275
+
276
+ if (actions.length > 0) {
277
+ context += "### Available Actions\n";
278
+ const showActions = actions.slice(0, MAX_ACTIONS);
279
+ for (const a of showActions) {
280
+ context += `- \`${a.name}\` (${a.type})\n`;
281
+ }
282
+ if (actions.length > MAX_ACTIONS) {
283
+ context += `- … and ${actions.length - MAX_ACTIONS} more\n`;
284
+ }
285
+ context += "\n";
286
+ }
287
+
288
+ const skillIds = getLoadedSkillIds();
289
+ if (skillIds.length > 0) {
290
+ context += "### Available skills\n";
291
+ for (const id of skillIds) {
292
+ if (id === "followme") {
293
+ context += `- **followme**: Use the **\`follow_robot\`** tool with action \`start\`, \`stop\`, or \`status\` to control person-following. There is no separate follow-robot HTTP service or port—everything runs inside this gateway via ROS2 (Zenoh, local DDS, or rosbridge depending on transport). For \"follow me\" or \"start following\", call \`follow_robot\` with action \`start\`; to stop, use action \`stop\`. Optional: \`follow_me_see\` (what the tracker sees), \`ollama_status\` (if using Ollama).\n`;
294
+ } else {
295
+ context += `- **${id}**: Loaded; use the tools provided by this skill as documented in the skill.\n`;
296
+ }
297
+ }
298
+ context += "\n";
299
+ }
300
+
301
+ context += `### Safety Limits
302
+ - Maximum linear velocity: 1.0 m/s
303
+ - Maximum angular velocity: 1.5 rad/s
304
+ - All velocity commands are validated before execution
305
+
306
+ ### Camera / "What does the robot see?"
307
+ - When the user asks what the robot sees (or for a photo, camera view, or snapshot), **always call \`ros2_camera_snapshot\`** (or \`ros2_subscribe_once\` on a camera topic). Prefer a topic from the list above that contains **color** and **compressed** (e.g. \`/camera/camera/color/image_raw/compressed\`) for RGB. Do not assume the transport cannot decode images—${imageTransportHint(config)} If the tool returns an error, report it; otherwise show or describe the image. **Do not paste \`data:\` URLs or raw base64** in your reply—the tool returns a proper image block for the UI; describe what you see in prose.
308
+
309
+ ### Tips
310
+ - Use \`ros2_list_topics\` to discover all available topics
311
+ - Use \`ros2_subscribe_once\` to read the current value of any topic
312
+ - Use \`ros2_camera_snapshot\` to see what the robot sees
313
+ - The user can say /estop at any time to immediately stop the robot`;
314
+
315
+ return context;
316
+ }
317
+
318
+ function buildFallbackContext(
319
+ config: AgenticROSConfig,
320
+ name: string,
321
+ namespace: string,
322
+ cameraTopicHint: string,
323
+ ): string {
324
+ const skillIds = getLoadedSkillIds();
325
+ const skillsSection =
326
+ skillIds.length > 0
327
+ ? "### Available skills\n" +
328
+ skillIds
329
+ .map((id) =>
330
+ id === "followme"
331
+ ? "- **followme**: Use the **`follow_robot`** tool with action `start`, `stop`, or `status` to control person-following. There is no separate follow-robot HTTP service or port—everything runs inside this gateway via ROS2 (Zenoh, local DDS, or rosbridge depending on transport). For \"follow me\" or \"start following\", call `follow_robot` with action `start`; to stop, use action `stop`. Optional: `follow_me_see`, `ollama_status`."
332
+ : `- **${id}**: Loaded; use the tools provided by this skill as documented in the skill.`,
333
+ )
334
+ .join("\n") +
335
+ "\n\n"
336
+ : "";
337
+
338
+ const nsLine = namespace
339
+ ? `Configured **robot.namespace** is \`${namespace}\` (used for cmd_vel-style namespacing in tools — not proof those topics exist).\n\n`
340
+ : "";
341
+
342
+ return `
343
+ ## Robot: ${name}
344
+
345
+ You are connected to a ROS2 robot named "${name}". You can control it using the ros2_* tools.
346
+
347
+ ${buildUserInterfaceBlurb(config)}
348
+
349
+ ### Topic discovery (read carefully)
350
+ **No live topic list was available when this session started** (transport still connecting, Zenoh sampling saw no keys yet, or discovery failed). There is **no** "### Available Topics" section below because nothing was observed on the bus.
351
+
352
+ - **You must call \`ros2_list_topics\`** and treat its return value as the **only** authoritative list. **Do not** tell the user that topics such as \`odom\`, \`scan\`, \`battery_state\`, or \`cmd_vel\` exist unless they appear in that tool output (or you successfully subscribe and get data).
353
+ - If the user asks what topics exist, **run the tool first**, then summarize **only** what it returned. If the list is empty, say so plainly and suggest checking the robot stack, Zenoh bridge, and gateway logs.
354
+ - For camera snapshots, after listing topics pick a **CompressedImage** (or Image) topic from the tool result; if none exist, say there is no camera topic. A common **default in plugin config** (not verified live) is \`${cameraTopicHint}\` — still confirm with \`ros2_list_topics\` before relying on it.
355
+
356
+ ${nsLine}${skillsSection}### Safety Limits
357
+ - Maximum linear velocity: 1.0 m/s
358
+ - Maximum angular velocity: 1.5 rad/s
359
+ - All velocity commands are validated before execution
360
+
361
+ ### Camera / "What does the robot see?"
362
+ - When the user asks what the robot sees (or for a photo, camera view, or snapshot), **always call \`ros2_camera_snapshot\`** (or \`ros2_subscribe_once\` on a camera topic). Do not assume the transport cannot decode images—${imageTransportHint(config)} If the tool returns an error, report it; otherwise show or describe the image. **Do not paste \`data:\` URLs or raw base64** in your reply—the tool returns a proper image block for the UI; describe what you see in prose.
363
+
364
+ ### Distance / "How far am I?"
365
+ - When the user asks how far they are from the robot (or depth / distance in meters), **call \`ros2_depth_distance\`** only on a depth Image topic that **\`ros2_list_topics\`** (or prior tool output) shows exists. Report the tool result or **quote the exact error text** if it fails (do not claim a generic "decode" failure without the tool message). If the result is valid, give **distance_m** as the measured answer (nearer-surface percentile; **median_m** is also returned and often reflects background if the person only fills part of the depth patch).
366
+
367
+ ### Tips
368
+ - Use \`ros2_list_topics\` to discover all available topics
369
+ - Use \`ros2_subscribe_once\` to read the current value of any topic
370
+ - Use \`ros2_camera_snapshot\` to see what the robot sees
371
+ - The user can say /estop at any time to immediately stop the robot
372
+ `.trim();
373
+ }
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Sample distance (meters) from a ROS2 depth image topic (e.g. RealSense).
3
+ * Supports 16UC1 (depth in mm) and 32FC1 (depth in m).
4
+ */
5
+
6
+ import type { RosTransport } from "@agenticros/core";
7
+ import {
8
+ ROS_MSG_IMAGE,
9
+ coerceRosImageDataToBuffer,
10
+ normalizeDepthImageEncoding,
11
+ rosBoolField,
12
+ rosNumericField,
13
+ rosStringField,
14
+ } from "@agenticros/ros-camera";
15
+ const DEFAULT_TIMEOUT_MS = 5000;
16
+
17
+ /** Drop absurd ranges; keeps sky/glitches from skewing percentiles. */
18
+ const DEPTH_SAMPLE_MAX_M = 40;
19
+
20
+ /**
21
+ * Reported distance uses a **lower percentile** (not median): the center ROI often mixes the
22
+ * person (near) with floor/wall/sky (far). Median then tracks background; ~12th percentile
23
+ * tracks nearer surfaces better for "how far is the person in front".
24
+ */
25
+ const DEPTH_REPORT_PERCENTILE = 12;
26
+
27
+ function sanitizeDepthSamplesMeters(values: number[]): number[] {
28
+ return values.filter((v) => Number.isFinite(v) && v > 0 && v <= DEPTH_SAMPLE_MAX_M);
29
+ }
30
+
31
+ /** Ascending `sorted`; return value near the low end at percentile `p` (0–100). */
32
+ function percentileLowerSorted(sortedAsc: number[], p: number): number {
33
+ const n = sortedAsc.length;
34
+ if (n === 0) return NaN;
35
+ if (n === 1) return sortedAsc[0]!;
36
+ const pp = Math.max(0, Math.min(100, p));
37
+ const idx = Math.min(n - 1, Math.floor((pp / 100) * n));
38
+ return sortedAsc[idx]!;
39
+ }
40
+
41
+ function depthImageDataBytes(data: unknown): Uint8Array {
42
+ try {
43
+ return new Uint8Array(coerceRosImageDataToBuffer(data));
44
+ } catch (e) {
45
+ const hint = e instanceof Error ? e.message : String(e);
46
+ const kind =
47
+ data == null
48
+ ? "null"
49
+ : `${typeof data}${typeof data === "object" && data !== null ? ` (${(data as object).constructor?.name ?? "Object"})` : ""}`;
50
+ throw new Error(`Depth image bytes: ${hint} (data field: ${kind})`);
51
+ }
52
+ }
53
+
54
+ function bytesPerPixelForDepthEncoding(encoding: string): number {
55
+ return normalizeDepthImageEncoding(encoding) === "32FC1" ? 4 : 2;
56
+ }
57
+
58
+ /**
59
+ * Sample center region of a depth image and return per-pixel distances in meters.
60
+ * - 16UC1: values in mm (RealSense typical) → divide by 1000
61
+ * - 32FC1: values in m → use as-is
62
+ * Invalid/zero pixels are skipped.
63
+ */
64
+ export function sampleDepthMeters(
65
+ width: number,
66
+ height: number,
67
+ step: number,
68
+ encoding: string,
69
+ data: Uint8Array,
70
+ centerFraction = 0.3,
71
+ isBigEndian = false,
72
+ ): number[] {
73
+ const enc = normalizeDepthImageEncoding(encoding);
74
+ const values: number[] = [];
75
+ const cx = width / 2;
76
+ const cy = height / 2;
77
+ const halfW = Math.max(1, Math.floor((width * centerFraction) / 2));
78
+ const halfH = Math.max(1, Math.floor((height * centerFraction) / 2));
79
+ const x0 = Math.max(0, Math.floor(cx - halfW));
80
+ const x1 = Math.min(width, Math.floor(cx + halfW));
81
+ const y0 = Math.max(0, Math.floor(cy - halfH));
82
+ const y1 = Math.min(height, Math.floor(cy + halfH));
83
+
84
+ if (enc === "16UC1") {
85
+ // 2 bytes per pixel, row stride = step
86
+ for (let y = y0; y < y1; y++) {
87
+ for (let x = x0; x < x1; x++) {
88
+ const off = y * step + x * 2;
89
+ if (off + 2 > data.length) continue;
90
+ const lo = data[off];
91
+ const hi = data[off + 1];
92
+ const v = isBigEndian ? (lo << 8) | hi : (hi << 8) | lo;
93
+ if (v > 0) values.push(v / 1000); // mm -> m
94
+ }
95
+ }
96
+ } else if (enc === "32FC1") {
97
+ for (let y = y0; y < y1; y++) {
98
+ for (let x = x0; x < x1; x++) {
99
+ const off = y * step + x * 4;
100
+ if (off + 4 > data.length) continue;
101
+ const v = new DataView(data.buffer, data.byteOffset + off, 4).getFloat32(0, !isBigEndian);
102
+ if (Number.isFinite(v) && v > 0) values.push(v);
103
+ }
104
+ }
105
+ } else {
106
+ throw new Error(
107
+ `Unsupported depth encoding: "${encoding}" (interpreted as "${enc}"). Use 16UC1/mono16 (mm) or 32FC1 (m).`,
108
+ );
109
+ }
110
+ return values;
111
+ }
112
+
113
+ function median(sorted: number[]): number {
114
+ if (sorted.length === 0) return NaN;
115
+ const m = sorted.length >> 1;
116
+ if (sorted.length % 2 === 1) return sorted[m];
117
+ return (sorted[m - 1] + sorted[m]) / 2;
118
+ }
119
+
120
+ /** Sample a vertical band (x0..x1) of the depth image; returns distances in meters. */
121
+ function sampleBand(
122
+ width: number,
123
+ height: number,
124
+ step: number,
125
+ encoding: string,
126
+ data: Uint8Array,
127
+ x0: number,
128
+ x1: number,
129
+ heightFraction = 0.5,
130
+ isBigEndian = false,
131
+ ): number[] {
132
+ const enc = normalizeDepthImageEncoding(encoding);
133
+ const values: number[] = [];
134
+ const cy = height / 2;
135
+ const halfH = Math.max(1, Math.floor((height * heightFraction) / 2));
136
+ const y0 = Math.max(0, Math.floor(cy - halfH));
137
+ const y1 = Math.min(height, Math.floor(cy + halfH));
138
+ const ix0 = Math.max(0, Math.floor(x0));
139
+ const ix1 = Math.min(width, Math.floor(x1));
140
+
141
+ if (enc === "16UC1") {
142
+ for (let y = y0; y < y1; y++) {
143
+ for (let x = ix0; x < ix1; x++) {
144
+ const off = y * step + x * 2;
145
+ if (off + 2 > data.length) continue;
146
+ const lo = data[off];
147
+ const hi = data[off + 1];
148
+ const v = isBigEndian ? (lo << 8) | hi : (hi << 8) | lo;
149
+ if (v > 0) values.push(v / 1000);
150
+ }
151
+ }
152
+ } else if (enc === "32FC1") {
153
+ for (let y = y0; y < y1; y++) {
154
+ for (let x = ix0; x < ix1; x++) {
155
+ const off = y * step + x * 4;
156
+ if (off + 4 > data.length) continue;
157
+ const v = new DataView(data.buffer, data.byteOffset + off, 4).getFloat32(0, !isBigEndian);
158
+ if (Number.isFinite(v) && v > 0) values.push(v);
159
+ }
160
+ }
161
+ }
162
+ return values;
163
+ }
164
+
165
+ export interface DepthSectorsResult {
166
+ left_m: number;
167
+ center_m: number;
168
+ right_m: number;
169
+ valid: boolean;
170
+ topic: string;
171
+ }
172
+
173
+ /**
174
+ * Sample left, center, and right thirds of a depth image; return median distance per sector.
175
+ * Used by Follow Me to turn toward the person when not using Ollama.
176
+ */
177
+ export async function getDepthSectors(
178
+ transport: RosTransport,
179
+ topic: string,
180
+ timeoutMs = DEFAULT_TIMEOUT_MS,
181
+ ): Promise<DepthSectorsResult> {
182
+ const result = await new Promise<Record<string, unknown>>((resolve, reject) => {
183
+ const sub = transport.subscribe(
184
+ { topic, type: ROS_MSG_IMAGE },
185
+ (msg: Record<string, unknown>) => {
186
+ clearTimeout(timer);
187
+ sub.unsubscribe();
188
+ resolve(msg);
189
+ },
190
+ );
191
+ const timer = setTimeout(() => {
192
+ sub.unsubscribe();
193
+ reject(
194
+ new Error(
195
+ `Depth sectors timeout on ${topic} (${timeoutMs}ms). No sensor_msgs/Image received—check topic and that it publishes raw depth (not CompressedImage only). With Zenoh, check the gateway log for CDR decode warnings.`,
196
+ ),
197
+ );
198
+ }, timeoutMs);
199
+ });
200
+
201
+ const encoding = normalizeDepthImageEncoding(rosStringField(result.encoding, "16UC1"));
202
+ const bpp = bytesPerPixelForDepthEncoding(encoding);
203
+ const width = rosNumericField(result.width, "width");
204
+ const height = rosNumericField(result.height, "height");
205
+ const step =
206
+ result.step != null && result.step !== ""
207
+ ? rosNumericField(result.step, "step")
208
+ : width * bpp;
209
+ const isBigEndian = rosBoolField(result.is_bigendian);
210
+ const data = depthImageDataBytes(result.data);
211
+
212
+ const third = width / 3;
213
+ const leftV = sampleBand(width, height, step, encoding, data, 0, third, 0.5, isBigEndian);
214
+ const centerV = sampleBand(width, height, step, encoding, data, third, 2 * third, 0.5, isBigEndian);
215
+ const rightV = sampleBand(width, height, step, encoding, data, 2 * third, width, 0.5, isBigEndian);
216
+
217
+ const sortN = (v: number[]) => sanitizeDepthSamplesMeters(v).sort((a, b) => a - b);
218
+ const leftS = sortN(leftV);
219
+ const centerS = sortN(centerV);
220
+ const rightS = sortN(rightV);
221
+ const left_m = percentileLowerSorted(leftS, DEPTH_REPORT_PERCENTILE);
222
+ const center_m = percentileLowerSorted(centerS, DEPTH_REPORT_PERCENTILE);
223
+ const right_m = percentileLowerSorted(rightS, DEPTH_REPORT_PERCENTILE);
224
+
225
+ const round = (x: number) => (Number.isFinite(x) ? Math.round(x * 1000) / 1000 : NaN);
226
+ const valid =
227
+ leftS.length > 0 || centerS.length > 0 || rightS.length > 0;
228
+
229
+ return {
230
+ left_m: round(left_m),
231
+ center_m: round(center_m),
232
+ right_m: round(right_m),
233
+ valid,
234
+ topic,
235
+ };
236
+ }
237
+
238
+ export interface DepthSampleResult {
239
+ /** ~12th percentile depth in the center ROI — biased toward nearer surfaces (see module comment). */
240
+ distance_m: number;
241
+ /** Median depth in the same ROI (often background-dominated when a person only fills part of the patch). */
242
+ median_m: number;
243
+ valid: boolean;
244
+ topic: string;
245
+ encoding: string;
246
+ width: number;
247
+ height: number;
248
+ sample_count: number;
249
+ min_m: number;
250
+ max_m: number;
251
+ }
252
+
253
+ /**
254
+ * Subscribe to a depth topic, get one message, sample center region, return distance in meters
255
+ * (lower-tail percentile toward nearer pixels; see DEPTH_REPORT_PERCENTILE).
256
+ */
257
+ export async function getDepthDistance(
258
+ transport: RosTransport,
259
+ topic: string,
260
+ timeoutMs = DEFAULT_TIMEOUT_MS,
261
+ ): Promise<DepthSampleResult> {
262
+ const result = await new Promise<Record<string, unknown>>((resolve, reject) => {
263
+ const sub = transport.subscribe(
264
+ { topic, type: ROS_MSG_IMAGE },
265
+ (msg: Record<string, unknown>) => {
266
+ clearTimeout(timer);
267
+ sub.unsubscribe();
268
+ resolve(msg);
269
+ },
270
+ );
271
+ const timer = setTimeout(() => {
272
+ sub.unsubscribe();
273
+ reject(
274
+ new Error(
275
+ `Depth snapshot timeout on ${topic} (${timeoutMs}ms). No sensor_msgs/Image received—check topic and that it publishes raw depth (not CompressedImage only). With Zenoh, check the gateway log for CDR decode warnings.`,
276
+ ),
277
+ );
278
+ }, timeoutMs);
279
+ });
280
+
281
+ const encoding = normalizeDepthImageEncoding(rosStringField(result.encoding, "16UC1"));
282
+ const bpp = bytesPerPixelForDepthEncoding(encoding);
283
+ const width = rosNumericField(result.width, "width");
284
+ const height = rosNumericField(result.height, "height");
285
+ const step =
286
+ result.step != null && result.step !== ""
287
+ ? rosNumericField(result.step, "step")
288
+ : width * bpp;
289
+ const isBigEndian = rosBoolField(result.is_bigendian);
290
+ const data = depthImageDataBytes(result.data);
291
+
292
+ const values = sanitizeDepthSamplesMeters(
293
+ sampleDepthMeters(width, height, step, encoding, data, 0.3, isBigEndian),
294
+ );
295
+ const sorted = values.slice().sort((a, b) => a - b);
296
+ const distance_m = percentileLowerSorted(sorted, DEPTH_REPORT_PERCENTILE);
297
+ const median_m = median(sorted);
298
+ const min_m = sorted.length ? sorted[0] : NaN;
299
+ const max_m = sorted.length ? sorted[sorted.length - 1] : NaN;
300
+
301
+ return {
302
+ distance_m: Math.round(distance_m * 1000) / 1000,
303
+ median_m: Math.round(median_m * 1000) / 1000,
304
+ valid: sorted.length > 0 && Number.isFinite(distance_m),
305
+ topic,
306
+ encoding,
307
+ width,
308
+ height,
309
+ sample_count: sorted.length,
310
+ min_m: Math.round(min_m * 1000) / 1000,
311
+ max_m: Math.round(max_m * 1000) / 1000,
312
+ };
313
+ }