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,650 @@
1
+ import type { OpenClawPluginApi, HttpRouteRequest, HttpRouteResponse } from "../plugin-api.js";
2
+ import type { AgenticROSConfig, ConnectionStatus } from "@agenticros/core";
3
+ import {
4
+ toNamespacedTopicFull,
5
+ toTeleopCameraTopicShort,
6
+ resolveCameraSubscribeTopic,
7
+ applyCmdVelTwistSignConvention,
8
+ } from "@agenticros/core";
9
+ import { getTransport, getTransportOrNull, getTransportMode, tryReconnectFromFile } from "../service.js";
10
+ import { readAgenticROSConfigFromFile } from "../config-file.js";
11
+ import { getTeleopPageHtml } from "./page.js";
12
+ import { ROS_MSG_COMPRESSED_IMAGE, bufferAndMimeFromCompressedImageMessage } from "@agenticros/ros-camera";
13
+
14
+ const TWIST_TYPE = "geometry_msgs/msg/Twist";
15
+
16
+ /** Image/CompressedImage type names (for filtering topics). */
17
+ const IMAGE_TYPE_PATTERN = /Image|CompressedImage/i;
18
+ /** When type is unknown (e.g. Zenoh), treat topic as camera if name looks like one. */
19
+ const CAMERA_TOPIC_NAME_PATTERN = /camera|image|compressed/i;
20
+
21
+ /**
22
+ * Preset topics when Zenoh discovery returns nothing (remote-api). Values are full ROS names like `ros2 topic list`.
23
+ */
24
+ const COMMON_CAMERA_TOPIC_PRESETS: ReadonlyArray<string> = [
25
+ "/camera/camera/color/image_raw/compressed",
26
+ "/camera/camera/color/image_raw/compressed/zstd",
27
+ "/camera/image_raw/compressed",
28
+ "/image_raw/compressed",
29
+ "/camera/camera/infra1/image_rect_raw/compressed",
30
+ "/camera/camera/infra2/image_rect_raw/compressed",
31
+ "/zed/zed_node/rgb/image_rect_color/compressed",
32
+ "/usb_cam/image_raw/compressed",
33
+ ];
34
+
35
+ function parseUrl(req: HttpRouteRequest): { pathname: string; searchParams: URLSearchParams } {
36
+ const base = "http://localhost";
37
+ const url = new URL(req.url ?? "", base);
38
+ return { pathname: url.pathname, searchParams: url.searchParams };
39
+ }
40
+
41
+ /** Machine-readable reason for teleop/camera JSON errors (client UI / automation). */
42
+ export type TeleopCameraErrorCode =
43
+ | "unsupported_image_type"
44
+ | "transport_unready"
45
+ | "transport_not_connected"
46
+ | "worker"
47
+ | "timeout"
48
+ | "unknown";
49
+
50
+ function teleopCameraErrorBody(code: TeleopCameraErrorCode, error: string): string {
51
+ return JSON.stringify({ code, error });
52
+ }
53
+
54
+ function readRequestBody(stream: NodeJS.ReadableStream): Promise<string> {
55
+ return new Promise((resolve, reject) => {
56
+ const chunks: Buffer[] = [];
57
+ stream.on("data", (chunk: Buffer | string) => {
58
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
59
+ });
60
+ stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
61
+ stream.on("error", reject);
62
+ });
63
+ }
64
+
65
+ function getDefaultCameraTopic(config: AgenticROSConfig): string {
66
+ const t = (config.teleop?.cameraTopic ?? "").trim();
67
+ if (t) return toTeleopCameraTopicShort(config, t);
68
+ const r = (config.robot?.cameraTopic ?? "").trim();
69
+ if (r) return toTeleopCameraTopicShort(config, r);
70
+ return "/camera/camera/color/image_raw/compressed";
71
+ }
72
+
73
+ /** Resolve cmd_vel topic from config (teleop override or robot namespace). Used by estop and teleop. */
74
+ export function getCmdVelTopic(config: AgenticROSConfig): string {
75
+ const t = (config.teleop?.cmdVelTopic ?? "").trim();
76
+ if (t) return t;
77
+ return toNamespacedTopicFull(config, "/cmd_vel");
78
+ }
79
+
80
+ function clampTwist(
81
+ config: AgenticROSConfig,
82
+ linearX: number,
83
+ linearY: number,
84
+ linearZ: number,
85
+ angularX: number,
86
+ angularY: number,
87
+ angularZ: number,
88
+ ): { linear: { x: number; y: number; z: number }; angular: { x: number; y: number; z: number } } {
89
+ const maxLin = config.safety?.maxLinearVelocity ?? 1.0;
90
+ const maxAng = config.safety?.maxAngularVelocity ?? 1.5;
91
+
92
+ const linMag = Math.sqrt(linearX * linearX + linearY * linearY + linearZ * linearZ);
93
+ const scaleLin = linMag > maxLin && linMag > 0 ? maxLin / linMag : 1;
94
+ const angMag = Math.abs(angularZ);
95
+ const scaleAng = angMag > maxAng && angMag > 0 ? maxAng / angMag : 1;
96
+
97
+ return {
98
+ linear: {
99
+ x: linearX * scaleLin,
100
+ y: linearY * scaleLin,
101
+ z: linearZ * scaleLin,
102
+ },
103
+ angular: {
104
+ x: angularX * scaleAng,
105
+ y: angularY * scaleAng,
106
+ z: Math.max(-maxAng, Math.min(maxAng, angularZ)),
107
+ },
108
+ };
109
+ }
110
+
111
+ /** Shared latest-frame cache: one subscription per topic, serve from cache so polling doesn't timeout. */
112
+ type CameraCacheState = {
113
+ topic: string;
114
+ sub: { unsubscribe(): void } | null;
115
+ cache: Buffer | null;
116
+ mime: string;
117
+ firstFrameResolve: ((r: { buf: Buffer; mime: string }) => void) | null;
118
+ firstFrameReject: ((e: Error) => void) | null;
119
+ firstFrameTimeout: ReturnType<typeof setTimeout> | null;
120
+ };
121
+ let cameraCacheState: CameraCacheState | null = null;
122
+ /** Throttle camera timeout logs to avoid flooding (log at most once per 15s per topic). */
123
+ let lastCameraTimeoutLog: { topic: string; at: number } = { topic: "", at: 0 };
124
+ const CAMERA_TIMEOUT_LOG_INTERVAL_MS = 15_000;
125
+
126
+ /**
127
+ * Register Phase 3 teleop HTTP routes (sources, camera, twist, index page).
128
+ * Only registers when api.registerHttpRoute is available.
129
+ */
130
+ export function registerTeleopRoutes(api: OpenClawPluginApi, config: AgenticROSConfig): void {
131
+ const register = api.registerHttpRoute;
132
+ if (typeof register !== "function") {
133
+ api.logger.info("AgenticROS teleop: registerHttpRoute not available, skipping routes");
134
+ return;
135
+ }
136
+ const route = (opts: { path: string; method?: string; handler: (req: HttpRouteRequest, res: HttpRouteResponse) => void | Promise<void> }) =>
137
+ register({ ...opts, requireAuth: false, auth: "plugin" });
138
+
139
+ /** Use config from file so namespace/camera/topics apply without gateway restart; fallback to initial config. */
140
+ function getCurrentConfig(): AgenticROSConfig {
141
+ try {
142
+ return readAgenticROSConfigFromFile();
143
+ } catch {
144
+ return config;
145
+ }
146
+ }
147
+
148
+ const pingHandler = (_req: HttpRouteRequest, res: HttpRouteResponse) => {
149
+ res.setHeader("Content-Type", "application/json");
150
+ res.statusCode = 200;
151
+ res.end(JSON.stringify({ ok: true, agenticros: "teleop" }));
152
+ };
153
+
154
+ const statusHandler = (_req: HttpRouteRequest, res: HttpRouteResponse) => {
155
+ const t = getTransportOrNull();
156
+ const mode = getTransportMode();
157
+ const connected = t ? t.getStatus() === "connected" : false;
158
+ res.setHeader("Content-Type", "application/json");
159
+ res.statusCode = 200;
160
+ res.end(JSON.stringify({ mode: mode ?? "none", connected }));
161
+ };
162
+
163
+ const reconnectHandler = async (_req: HttpRouteRequest, res: HttpRouteResponse) => {
164
+ try {
165
+ const result = await tryReconnectFromFile(api);
166
+ res.setHeader("Content-Type", "application/json");
167
+ res.statusCode = 200;
168
+ res.end(JSON.stringify(result));
169
+ } catch (e) {
170
+ res.setHeader("Content-Type", "application/json");
171
+ res.statusCode = 500;
172
+ res.end(JSON.stringify({ ok: false, error: e instanceof Error ? e.message : String(e) }));
173
+ }
174
+ };
175
+
176
+ for (const base of ["/agenticros", "/api/agenticros", "/plugins/agenticros"]) {
177
+ route({ path: `${base}/teleop/ping`, method: "GET", handler: pingHandler });
178
+ route({ path: `${base}/teleop/status`, method: "GET", handler: statusHandler });
179
+ route({ path: `${base}/teleop/reconnect`, method: "GET", handler: reconnectHandler });
180
+ route({ path: `${base}/teleop/reconnect`, method: "POST", handler: reconnectHandler });
181
+ }
182
+
183
+ /** Config default + preset paths + Zenoh discovery (deduped). Dropdown uses short `/camera/...` paths; server namespaces on subscribe. */
184
+ function buildTeleopCameraSources(
185
+ cfg: AgenticROSConfig,
186
+ defaultTopic: string,
187
+ discovered: Array<{ topic: string; label?: string }>,
188
+ ): Array<{ topic: string; label?: string }> {
189
+ const seen = new Set<string>();
190
+ const out: Array<{ topic: string; label?: string }> = [];
191
+ const add = (topic: string) => {
192
+ const short = toTeleopCameraTopicShort(cfg, topic);
193
+ if (!short || short === "/" || seen.has(short)) return;
194
+ seen.add(short);
195
+ out.push({ topic: short, label: short });
196
+ };
197
+ add(defaultTopic);
198
+ for (const preset of COMMON_CAMERA_TOPIC_PRESETS) add(preset);
199
+ for (const d of discovered) add(d.topic);
200
+ return out;
201
+ }
202
+
203
+ // GET .../teleop/sources — JSON list of camera topics. Always returns at least one option (default camera) when discovery is empty or transport fails.
204
+ const sourcesHandler = async (_req: HttpRouteRequest, res: HttpRouteResponse) => {
205
+ try {
206
+ const currentConfig = getCurrentConfig();
207
+ const defaultTopic = getDefaultCameraTopic(currentConfig);
208
+ let list: Array<{ topic: string; label?: string }>;
209
+ const explicit = currentConfig.teleop?.cameraTopics ?? [];
210
+ if (explicit.length > 0) {
211
+ list = explicit.map((o) => {
212
+ const topic = toTeleopCameraTopicShort(currentConfig, o.topic.trim());
213
+ const label = (o.label ?? "").trim() || topic;
214
+ return { topic, label };
215
+ });
216
+ } else {
217
+ try {
218
+ const transport = getTransport();
219
+ const topics = await transport.listTopics();
220
+ const imageTopics = topics.filter((t) => {
221
+ if (t.type && IMAGE_TYPE_PATTERN.test(t.type)) return true;
222
+ if (!t.type || t.type === "unknown") {
223
+ return CAMERA_TOPIC_NAME_PATTERN.test(t.name);
224
+ }
225
+ return false;
226
+ });
227
+ const compressedOnly = imageTopics.filter((t) => !/\/zstd\/?$/i.test(t.name));
228
+ const toList = compressedOnly.length > 0 ? compressedOnly : imageTopics;
229
+ toList.sort((a, b) => {
230
+ const aCompressed = /compressed/i.test(a.name) ? 1 : 0;
231
+ const bCompressed = /compressed/i.test(b.name) ? 1 : 0;
232
+ return bCompressed - aCompressed;
233
+ });
234
+ const discovered = toList.map((t) => {
235
+ const name = t.name.startsWith("/") ? t.name : `/${t.name}`;
236
+ return { topic: name, label: name };
237
+ });
238
+ list = buildTeleopCameraSources(currentConfig, defaultTopic, discovered);
239
+ } catch (e) {
240
+ api.logger.warn("Teleop sources (discovery failed, using presets): " + (e instanceof Error ? e.message : String(e)));
241
+ list = buildTeleopCameraSources(currentConfig, defaultTopic, []);
242
+ }
243
+ }
244
+ res.setHeader("Content-Type", "application/json");
245
+ res.statusCode = 200;
246
+ res.end(JSON.stringify(list));
247
+ } catch (e) {
248
+ api.logger.warn("Teleop sources error: " + (e instanceof Error ? e.message : String(e)));
249
+ res.setHeader("Content-Type", "application/json");
250
+ res.statusCode = 200;
251
+ try {
252
+ const cfg = getCurrentConfig();
253
+ const dt = getDefaultCameraTopic(cfg);
254
+ res.end(JSON.stringify(buildTeleopCameraSources(cfg, dt, [])));
255
+ } catch {
256
+ res.end(JSON.stringify(buildTeleopCameraSources(config, "/camera/camera/color/image_raw/compressed", [])));
257
+ }
258
+ }
259
+ };
260
+
261
+ for (const base of ["/agenticros", "/api/agenticros", "/plugins/agenticros"]) {
262
+ route({ path: `${base}/teleop/sources`, method: "GET", handler: sourcesHandler });
263
+ }
264
+
265
+ // GET .../teleop/camera?topic=...&type=compressed|image
266
+ const cameraHandler = async (req: HttpRouteRequest, res: HttpRouteResponse) => {
267
+ const currentConfig = getCurrentConfig();
268
+ const { searchParams } = parseUrl(req);
269
+ let topic = searchParams.get("topic")?.trim();
270
+ if (topic) topic = topic.replace(/\?.*$/, "").replace(/#.*$/, "").trim();
271
+ if (!topic) topic = getDefaultCameraTopic(currentConfig);
272
+ if (topic && /\/zstd\/?$/i.test(topic)) {
273
+ topic = topic.replace(/\/zstd\/?$/i, "/compressed");
274
+ }
275
+ if (topic && /image_raw$/i.test(topic) && !/compressed|zstd/i.test(topic)) {
276
+ topic = topic.replace(/\/?$/, "") + "/compressed";
277
+ }
278
+ const typeParam = (searchParams.get("type") ?? "compressed").toLowerCase();
279
+ const useImage = typeParam === "image";
280
+ // Camera topics are often absolute `/camera/...` on the DDS graph while cmd_vel is `/<robot.namespace>/cmd_vel`.
281
+ // Full namespacing every path breaks those feeds (Zenoh key mismatch → timeout → 503).
282
+ const resolvedTopic = resolveCameraSubscribeTopic(currentConfig, topic);
283
+ if (cameraCacheState?.topic !== resolvedTopic) {
284
+ api.logger.info(`Teleop camera: topic=${topic} resolvedTopic=${resolvedTopic} namespace=${(currentConfig.robot?.namespace ?? "").trim() || "(none)"}`);
285
+ }
286
+
287
+ if (useImage) {
288
+ res.setHeader("Content-Type", "application/json");
289
+ res.setHeader("Cache-Control", "no-store");
290
+ res.setHeader("Pragma", "no-cache");
291
+ res.statusCode = 501;
292
+ res.end(
293
+ teleopCameraErrorBody(
294
+ "unsupported_image_type",
295
+ "Raw Image topics not supported; use a CompressedImage topic (e.g. .../image_raw/compressed)",
296
+ ),
297
+ );
298
+ return;
299
+ }
300
+
301
+ try {
302
+ if (cameraCacheState?.topic !== resolvedTopic) {
303
+ if (cameraCacheState?.sub) {
304
+ cameraCacheState.sub.unsubscribe();
305
+ if (cameraCacheState.firstFrameTimeout) clearTimeout(cameraCacheState.firstFrameTimeout);
306
+ }
307
+ cameraCacheState = {
308
+ topic: resolvedTopic,
309
+ sub: null,
310
+ cache: null,
311
+ mime: "image/jpeg",
312
+ firstFrameResolve: null,
313
+ firstFrameReject: null,
314
+ firstFrameTimeout: null,
315
+ };
316
+ }
317
+ const state = cameraCacheState;
318
+
319
+ if (state.cache && state.cache.length > 0) {
320
+ res.setHeader("Content-Type", state.mime);
321
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
322
+ res.setHeader("Pragma", "no-cache");
323
+ res.statusCode = 200;
324
+ res.end(state.cache);
325
+ return;
326
+ }
327
+
328
+ if (!state.sub) {
329
+ const transport = getTransportOrNull();
330
+ if (!transport) {
331
+ res.setHeader("Content-Type", "application/json");
332
+ res.setHeader("Cache-Control", "no-store");
333
+ res.setHeader("Pragma", "no-cache");
334
+ res.statusCode = 503;
335
+ res.end(
336
+ teleopCameraErrorBody(
337
+ "transport_unready",
338
+ "ROS2 transport not ready (no session on this process). Start Zenoh/rosbridge, then Reconnect — or use a single gateway worker so status and camera hit the same process.",
339
+ ),
340
+ );
341
+ return;
342
+ }
343
+ const camConn: ConnectionStatus = transport.getStatus();
344
+ if (camConn !== "connected") {
345
+ res.setHeader("Content-Type", "application/json");
346
+ res.setHeader("Cache-Control", "no-store");
347
+ res.setHeader("Pragma", "no-cache");
348
+ res.statusCode = 503;
349
+ res.end(
350
+ teleopCameraErrorBody(
351
+ "transport_not_connected",
352
+ `ROS2 transport not connected (status: ${camConn}). Wait for the session or fix Zenoh/rosbridge; see gateway logs.`,
353
+ ),
354
+ );
355
+ return;
356
+ }
357
+ const firstFramePromise = new Promise<{ buf: Buffer; mime: string }>((resolve, reject) => {
358
+ state.firstFrameResolve = resolve;
359
+ state.firstFrameReject = reject;
360
+ state.firstFrameTimeout = setTimeout(() => {
361
+ if (state.firstFrameReject) {
362
+ state.firstFrameReject(new Error("timeout"));
363
+ state.firstFrameResolve = null;
364
+ state.firstFrameReject = null;
365
+ state.firstFrameTimeout = null;
366
+ }
367
+ }, 8000);
368
+ });
369
+ const handler = (msg: Record<string, unknown>) => {
370
+ const out = bufferAndMimeFromCompressedImageMessage(msg);
371
+ if (!out || out.buf.length === 0) return;
372
+ state.mime = out.mime;
373
+ state.cache = out.buf;
374
+ if (state.firstFrameTimeout) {
375
+ clearTimeout(state.firstFrameTimeout);
376
+ state.firstFrameTimeout = null;
377
+ }
378
+ if (state.firstFrameResolve) {
379
+ state.firstFrameResolve({ buf: out.buf, mime: state.mime });
380
+ state.firstFrameResolve = null;
381
+ state.firstFrameReject = null;
382
+ }
383
+ };
384
+ if (typeof (transport as { subscribeAsync?: unknown }).subscribeAsync === "function") {
385
+ state.sub = await (transport as { subscribeAsync(opts: { topic: string; type: string }, h: (msg: Record<string, unknown>) => void): Promise<{ unsubscribe(): void }> }).subscribeAsync(
386
+ { topic: resolvedTopic, type: ROS_MSG_COMPRESSED_IMAGE },
387
+ handler,
388
+ );
389
+ } else {
390
+ state.sub = transport.subscribe(
391
+ { topic: resolvedTopic, type: ROS_MSG_COMPRESSED_IMAGE },
392
+ handler,
393
+ );
394
+ }
395
+ const { buf, mime } = await firstFramePromise;
396
+ res.setHeader("Content-Type", mime);
397
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
398
+ res.setHeader("Pragma", "no-cache");
399
+ res.statusCode = 200;
400
+ res.end(buf);
401
+ return;
402
+ }
403
+
404
+ const waitForFirst = new Promise<{ buf: Buffer; mime: string }>((resolve, reject) => {
405
+ const deadline = Date.now() + 5000;
406
+ const check = () => {
407
+ if (state.cache && state.cache.length > 0) {
408
+ resolve({ buf: state.cache, mime: state.mime });
409
+ return;
410
+ }
411
+ if (Date.now() >= deadline) {
412
+ reject(new Error("timeout"));
413
+ return;
414
+ }
415
+ setTimeout(check, 30);
416
+ };
417
+ check();
418
+ });
419
+ const { buf, mime } = await waitForFirst;
420
+ res.setHeader("Content-Type", mime);
421
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
422
+ res.setHeader("Pragma", "no-cache");
423
+ res.statusCode = 200;
424
+ res.end(buf);
425
+ } catch (e) {
426
+ const raw = e instanceof Error ? e.message : String(e);
427
+ const isTimeout = /timeout/i.test(raw);
428
+ const now = Date.now();
429
+ if (!isTimeout || resolvedTopic !== lastCameraTimeoutLog.topic || now - lastCameraTimeoutLog.at >= CAMERA_TIMEOUT_LOG_INTERVAL_MS) {
430
+ api.logger.warn("Teleop camera error: " + raw);
431
+ if (isTimeout) lastCameraTimeoutLog = { topic: resolvedTopic, at: now };
432
+ }
433
+ res.setHeader("Content-Type", "application/json");
434
+ res.setHeader("Cache-Control", "no-store");
435
+ res.setHeader("Pragma", "no-cache");
436
+ res.statusCode = 503;
437
+ const { code, userMsg }: { code: TeleopCameraErrorCode; userMsg: string } = (() => {
438
+ if (/session|transport|not initialized|undefined/i.test(raw)) {
439
+ return {
440
+ code: "worker" as const,
441
+ userMsg:
442
+ "Camera unavailable. Transport may be on another gateway worker — try opening teleop on the gateway directly or run the gateway with a single worker.",
443
+ };
444
+ }
445
+ if (isTimeout) {
446
+ return {
447
+ code: "timeout" as const,
448
+ userMsg: `No CompressedImage frames on "${resolvedTopic}" before timeout. Confirm the topic exists and publishes sensor_msgs/msg/CompressedImage (e.g. ros2 topic echo); match robot namespace; check Zenoh/rosbridge and gateway logs.`,
449
+ };
450
+ }
451
+ return { code: "unknown" as const, userMsg: raw };
452
+ })();
453
+ res.end(teleopCameraErrorBody(code, userMsg));
454
+ }
455
+ };
456
+
457
+ for (const base of ["/agenticros", "/api/agenticros", "/plugins/agenticros"]) {
458
+ route({ path: `${base}/teleop/camera`, method: "GET", handler: cameraHandler });
459
+ }
460
+
461
+ // Shared: publish twist and send JSON response
462
+ async function publishTwistAndRespond(
463
+ res: HttpRouteResponse,
464
+ lx: number,
465
+ ly: number,
466
+ lz: number,
467
+ ax: number,
468
+ ay: number,
469
+ az: number,
470
+ ): Promise<void> {
471
+ const currentConfig = getCurrentConfig();
472
+ const clamped = clampTwist(currentConfig, lx, ly, lz, ax, ay, az);
473
+ const topic = getCmdVelTopic(currentConfig);
474
+ const twistMsg = applyCmdVelTwistSignConvention(topic, TWIST_TYPE, {
475
+ linear: clamped.linear,
476
+ angular: clamped.angular,
477
+ });
478
+ const lin = twistMsg["linear"] as Record<string, unknown> | undefined;
479
+ const ang = twistMsg["angular"] as Record<string, unknown> | undefined;
480
+ api.logger.info(`Teleop twist: publishing linear.x=${lin?.["x"]} angular.z=${ang?.["z"]} to topic=${topic}`);
481
+ try {
482
+ const transport = getTransport();
483
+ const status = transport.getStatus?.();
484
+ if (status && status !== "connected") {
485
+ res.setHeader("Content-Type", "application/json");
486
+ res.statusCode = 503;
487
+ res.end(JSON.stringify({ error: `ROS2 transport not connected (status: ${status}). Check Zenoh router and gateway logs.` }));
488
+ return;
489
+ }
490
+ const publishResult = transport.publish({
491
+ topic,
492
+ type: TWIST_TYPE,
493
+ msg: twistMsg,
494
+ });
495
+ await Promise.resolve(publishResult);
496
+ res.setHeader("Content-Type", "application/json");
497
+ res.statusCode = 200;
498
+ res.end(JSON.stringify({ ok: true, topic }));
499
+ } catch (e) {
500
+ const msg = e instanceof Error ? e.message : String(e);
501
+ api.logger.warn("Teleop twist error: " + msg);
502
+ res.setHeader("Content-Type", "application/json");
503
+ const isUnavailable = /not initialized|not connected/i.test(msg);
504
+ res.statusCode = isUnavailable ? 503 : 500;
505
+ res.end(JSON.stringify({ error: isUnavailable ? "ROS2 transport not ready. Start the gateway service and ensure Zenoh/rosbridge is connected." : "Failed to publish twist" }));
506
+ }
507
+ }
508
+
509
+ const twistPostHandler = async (req: HttpRouteRequest, res: HttpRouteResponse) => {
510
+ let body: Record<string, unknown> = {};
511
+ try {
512
+ if (typeof req.readJsonBody === "function") {
513
+ body = (await req.readJsonBody()) ?? {};
514
+ }
515
+ if (Object.keys(body).length === 0) {
516
+ const raw = (req as { body?: unknown }).body;
517
+ if (typeof raw === "object" && raw !== null) {
518
+ body = raw as Record<string, unknown>;
519
+ } else if (raw && typeof (raw as Promise<unknown>).then === "function") {
520
+ const parsed = await (raw as Promise<Record<string, unknown>>);
521
+ if (parsed && typeof parsed === "object") body = parsed;
522
+ }
523
+ }
524
+ if (Object.keys(body).length === 0 && typeof (req as { on?: (e: string, cb: (c?: Buffer) => void) => void }).on === "function") {
525
+ const rawBody = await readRequestBody(req as unknown as NodeJS.ReadableStream);
526
+ if (rawBody.trim()) {
527
+ try {
528
+ body = JSON.parse(rawBody) as Record<string, unknown>;
529
+ } catch {
530
+ try {
531
+ for (const part of rawBody.split("&")) {
532
+ const eq = part.indexOf("=");
533
+ const k = eq >= 0 ? decodeURIComponent(part.slice(0, eq).replace(/\+/g, " ")) : decodeURIComponent(part.replace(/\+/g, " "));
534
+ const v = eq >= 0 ? decodeURIComponent(part.slice(eq + 1).replace(/\+/g, " ")) : "";
535
+ if (k) body[k] = v;
536
+ }
537
+ } catch {
538
+ // leave body empty
539
+ }
540
+ }
541
+ }
542
+ }
543
+ } catch {
544
+ res.setHeader("Content-Type", "application/json");
545
+ res.statusCode = 400;
546
+ res.end(JSON.stringify({ error: "Invalid JSON body" }));
547
+ return;
548
+ }
549
+ let lx = Number(body.linear_x ?? (body as Record<string, unknown>).linearX ?? 0);
550
+ let ly = Number(body.linear_y ?? (body as Record<string, unknown>).linearY ?? 0);
551
+ let lz = Number(body.linear_z ?? (body as Record<string, unknown>).linearZ ?? 0);
552
+ let ax = Number(body.angular_x ?? (body as Record<string, unknown>).angularX ?? 0);
553
+ let ay = Number(body.angular_y ?? (body as Record<string, unknown>).angularY ?? 0);
554
+ let az = Number(body.angular_z ?? (body as Record<string, unknown>).angularZ ?? 0);
555
+ const bodyAllZero = lx === 0 && ly === 0 && lz === 0 && ax === 0 && ay === 0 && az === 0;
556
+ if (bodyAllZero) {
557
+ const fromQuery = getTwistParamsFromQuery(req);
558
+ if (fromQuery.source !== "none") {
559
+ lx = fromQuery.lx;
560
+ ly = fromQuery.ly;
561
+ lz = fromQuery.lz;
562
+ ax = fromQuery.ax;
563
+ ay = fromQuery.ay;
564
+ az = fromQuery.az;
565
+ api.logger.info(`Teleop twist POST: linear_x=${lx} linear_y=${ly} angular_z=${az} (from ${fromQuery.source}, body was empty/zeros)`);
566
+ } else {
567
+ api.logger.info(`Teleop twist POST: linear_x=0 linear_y=0 angular_z=0 (body empty/zeros, no query — open via proxy so query is forwarded)`);
568
+ }
569
+ } else {
570
+ api.logger.info(`Teleop twist POST: linear_x=${lx} linear_y=${ly} angular_z=${az}`);
571
+ }
572
+ await publishTwistAndRespond(res, lx, ly, lz, ax, ay, az);
573
+ };
574
+
575
+ /** Get a header value (case-insensitive). */
576
+ function getHeader(req: HttpRouteRequest, name: string): string | undefined {
577
+ const h = (req as { headers?: Record<string, string | string[] | undefined> }).headers;
578
+ if (!h) return undefined;
579
+ const lower = name.toLowerCase();
580
+ for (const [k, v] of Object.entries(h)) {
581
+ if (k.toLowerCase() === lower) {
582
+ return typeof v === "string" ? v : Array.isArray(v) ? (v[0] as string) : undefined;
583
+ }
584
+ }
585
+ return undefined;
586
+ }
587
+
588
+ /** Parse twist params from URL query string or X-AgenticROS-Query (used by GET and as POST fallback when body is zeros). */
589
+ function getTwistParamsFromQuery(req: HttpRouteRequest): { lx: number; ly: number; lz: number; ax: number; ay: number; az: number; source: string } {
590
+ const rawUrl = (req as { url?: string; originalUrl?: string }).url ?? (req as { originalUrl?: string }).originalUrl ?? "";
591
+ const forwardedQuery = getHeader(req, "x-agenticros-query");
592
+ const { searchParams } = parseUrl(req);
593
+ const q = (req as { query?: Record<string, string | string[] | undefined> }).query;
594
+ const getParam = (name: string, alt: string): string | undefined => {
595
+ const fromUrl = searchParams.get(name) ?? searchParams.get(alt);
596
+ if (fromUrl != null) return fromUrl;
597
+ if (q) {
598
+ const v = q[name] ?? q[alt];
599
+ return Array.isArray(v) ? v[0] : v;
600
+ }
601
+ const queryString = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?") + 1) : forwardedQuery;
602
+ if (queryString) {
603
+ try {
604
+ const params = new URLSearchParams(queryString);
605
+ return params.get(name) ?? params.get(alt) ?? undefined;
606
+ } catch {
607
+ // ignore
608
+ }
609
+ }
610
+ return undefined;
611
+ };
612
+ const lx = Number(getParam("linear_x", "linearX") ?? 0);
613
+ const ly = Number(getParam("linear_y", "linearY") ?? 0);
614
+ const lz = Number(getParam("linear_z", "linearZ") ?? 0);
615
+ const ax = Number(getParam("angular_x", "angularX") ?? 0);
616
+ const ay = Number(getParam("angular_y", "angularY") ?? 0);
617
+ const az = Number(getParam("angular_z", "angularZ") ?? 0);
618
+ const source = rawUrl.includes("?") ? "url" : forwardedQuery ? "header" : "none";
619
+ return { lx, ly, lz, ax, ay, az, source };
620
+ }
621
+
622
+ const twistGetHandler = async (req: HttpRouteRequest, res: HttpRouteResponse) => {
623
+ const { lx, ly, lz, ax, ay, az, source } = getTwistParamsFromQuery(req);
624
+ const allZero = lx === 0 && ly === 0 && lz === 0 && ax === 0 && ay === 0 && az === 0;
625
+ api.logger.info(`Teleop twist GET: linear_x=${lx} linear_y=${ly} angular_z=${az} (source: ${source})${allZero && source === "none" ? " — params missing: open teleop via proxy (http://127.0.0.1:18790/plugins/agenticros/) so twist query is forwarded" : ""}`);
626
+ await publishTwistAndRespond(res, lx, ly, lz, ax, ay, az);
627
+ };
628
+
629
+ for (const base of ["/agenticros", "/api/agenticros", "/plugins/agenticros"]) {
630
+ route({ path: `${base}/teleop/twist`, method: "POST", handler: twistPostHandler });
631
+ route({ path: `${base}/teleop/twist`, method: "GET", handler: twistGetHandler });
632
+ }
633
+
634
+ // GET .../teleop/ and .../teleop/index.html
635
+ const servePage = (_req: HttpRouteRequest, res: HttpRouteResponse) => {
636
+ const html = getTeleopPageHtml(getCurrentConfig());
637
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
638
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
639
+ res.setHeader("Pragma", "no-cache");
640
+ res.statusCode = 200;
641
+ res.end(html);
642
+ };
643
+
644
+ for (const base of ["/agenticros", "/api/agenticros", "/plugins/agenticros"]) {
645
+ route({ path: `${base}/teleop/`, method: "GET", handler: servePage });
646
+ route({ path: `${base}/teleop/index.html`, method: "GET", handler: servePage });
647
+ }
648
+
649
+ api.logger.info("AgenticROS teleop routes registered (GET /agenticros/teleop/, /ping, /sources, /camera; GET/POST /twist)");
650
+ }
@@ -0,0 +1,26 @@
1
+ import type { OpenClawPluginApi } from "../plugin-api.js";
2
+ import type { AgenticROSConfig } from "@agenticros/core";
3
+ import { registerPublishTool } from "./ros2-publish.js";
4
+ import { registerSubscribeTool } from "./ros2-subscribe.js";
5
+ import { registerServiceTool } from "./ros2-service.js";
6
+ import { registerActionTool } from "./ros2-action.js";
7
+ import { registerParamTools } from "./ros2-param.js";
8
+ import { registerIntrospectTool } from "./ros2-introspect.js";
9
+ import { registerCameraTool } from "./ros2-camera.js";
10
+ import { registerDepthDistanceTool } from "./ros2-depth-distance.js";
11
+
12
+ /**
13
+ * Register core ROS2 tools with the OpenClaw AI agent.
14
+ * Optional skills (e.g. Follow Me) register their own tools via the skill loader.
15
+ * Memory tools register asynchronously from index.ts after initMemory resolves.
16
+ */
17
+ export function registerTools(api: OpenClawPluginApi, config: AgenticROSConfig): void {
18
+ registerPublishTool(api, config);
19
+ registerSubscribeTool(api, config);
20
+ registerServiceTool(api, config);
21
+ registerActionTool(api, config);
22
+ registerParamTools(api, config);
23
+ registerIntrospectTool(api);
24
+ registerCameraTool(api, config);
25
+ registerDepthDistanceTool(api, config);
26
+ }