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,157 @@
1
+ import type { AgenticROSConfig } from "@agenticros/core";
2
+
3
+ /**
4
+ * Optional in-plugin image describer.
5
+ *
6
+ * Why this exists:
7
+ *
8
+ * * The OpenClaw runtime filters image content blocks out of tool results
9
+ * before passing them to text-only chat models (see
10
+ * `provider-stream-EGXtbhbQ.js:309` — `model.input.includes("image") ?
11
+ * blocks : blocks.filter(b => b.type !== "image")`).
12
+ * * `agents.defaults.imageModel` configures the *explicit* `image` tool
13
+ * the agent can call, but does **not** install an automatic
14
+ * tool-result-to-caption hook. So when the primary model is text-only
15
+ * (e.g. Ollama qwen2.5:7b for tool-calling on Jetson), it never sees
16
+ * the camera frame and tends to hallucinate a generic description.
17
+ *
18
+ * The describer calls a vision-capable OpenAI-compatible endpoint
19
+ * directly from the plugin and the `ros2_camera_snapshot` tool then
20
+ * embeds the description text into its tool result. This bypasses
21
+ * OpenClaw's image filtering entirely.
22
+ *
23
+ * # Networking
24
+ *
25
+ * Inside NemoClaw the gateway runs with ``NODE_USE_ENV_PROXY=1`` and
26
+ * ``HTTP_PROXY=http://10.200.0.1:3128``. Node 22's built-in undici
27
+ * ``fetch`` reads those automatically and routes all outbound HTTP(S)
28
+ * through the OPA-managed proxy. We therefore use ``fetch`` and don't
29
+ * implement any of our own proxy logic — past attempts to manually build
30
+ * CONNECT tunnels or absolute-form URLs ended up double-proxied (the OPA
31
+ * proxy saw ``host:3128`` instead of the real upstream port) or hit
32
+ * "FORWARD denied" because Ollama on the host isn't reachable on its raw
33
+ * port from the docker bridge — only the auth proxy on
34
+ * ``host.openshell.internal:11435`` is. See the
35
+ * ``agenticros-describer.policy.yaml`` / ``local-inference`` preset for
36
+ * the corresponding policy.
37
+ *
38
+ * # Bearer token
39
+ *
40
+ * The auth proxy on port 11435 requires ``Authorization: Bearer <token>``
41
+ * (24-byte hex token written by nemoclaw to
42
+ * ``~/.nemoclaw/ollama-proxy-token`` on the host). Inside the sandbox the
43
+ * gateway does NOT receive this token in its environment by default. We
44
+ * accept it via the plugin config field ``describer.apiKey`` — set it in
45
+ * ``~/.openclaw/openclaw.json`` under ``plugins.entries.agenticros.config``.
46
+ */
47
+
48
+ export interface DescribeImageOptions {
49
+ config: AgenticROSConfig;
50
+ /** Base64-encoded image (no data: prefix). */
51
+ base64: string;
52
+ /** MIME type, e.g. "image/jpeg" or "image/png". */
53
+ mimeType: string;
54
+ /** Optional override prompt; otherwise the one from config is used. */
55
+ prompt?: string;
56
+ /** Optional logger for diagnostics. */
57
+ logger?: { info(msg: string): void; warn(msg: string): void; error(msg: string): void };
58
+ }
59
+
60
+ export interface DescribeImageResult {
61
+ description: string;
62
+ model: string;
63
+ latencyMs: number;
64
+ }
65
+
66
+ /**
67
+ * Call the configured vision model with the image and return its text caption.
68
+ *
69
+ * Throws on network/HTTP failure so callers can decide whether to surface the
70
+ * error or silently fall back to "no description".
71
+ */
72
+ export async function describeImage(opts: DescribeImageOptions): Promise<DescribeImageResult> {
73
+ const cfg = opts.config.describer;
74
+ if (!cfg.enabled) {
75
+ throw new Error("describer disabled in config");
76
+ }
77
+ const url = cfg.url;
78
+ const model = cfg.model;
79
+ const prompt = (opts.prompt ?? cfg.prompt).trim();
80
+ const maxTokens = cfg.maxTokens;
81
+ const timeoutMs = cfg.timeoutMs;
82
+ const apiKey = (cfg.apiKey ?? "").trim();
83
+
84
+ const dataUrl = `data:${opts.mimeType};base64,${opts.base64}`;
85
+
86
+ const body = JSON.stringify({
87
+ model,
88
+ stream: false,
89
+ max_tokens: maxTokens,
90
+ messages: [
91
+ {
92
+ role: "user",
93
+ content: [
94
+ { type: "text", text: prompt },
95
+ { type: "image_url", image_url: { url: dataUrl } },
96
+ ],
97
+ },
98
+ ],
99
+ });
100
+
101
+ const start = Date.now();
102
+ const controller = new AbortController();
103
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
104
+ let res: Response;
105
+ try {
106
+ res = await fetch(url, {
107
+ method: "POST",
108
+ headers: {
109
+ "Content-Type": "application/json",
110
+ "User-Agent": "AgenticROS-Describer/1.0",
111
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
112
+ },
113
+ body,
114
+ signal: controller.signal,
115
+ });
116
+ } finally {
117
+ clearTimeout(timer);
118
+ }
119
+
120
+ const text = await res.text();
121
+ if (!res.ok) {
122
+ throw new Error(`describer HTTP ${res.status} from ${url}: ${text.slice(0, 300)}`);
123
+ }
124
+ let data: { choices?: Array<{ message?: { content?: string } }>; model?: string };
125
+ try {
126
+ data = JSON.parse(text);
127
+ } catch {
128
+ throw new Error(`describer response was not JSON (HTTP ${res.status}): ${text.slice(0, 200)}`);
129
+ }
130
+ const content = data.choices?.[0]?.message?.content?.trim() ?? "";
131
+ if (!content) {
132
+ throw new Error(`describer returned empty content (HTTP ${res.status})`);
133
+ }
134
+ return {
135
+ description: content,
136
+ model: data.model ?? model,
137
+ latencyMs: Date.now() - start,
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Convenience: try to describe; on failure, log and return null.
143
+ * The caller (ros2_camera_snapshot) uses this to keep the tool result
144
+ * usable even when the describer endpoint is misconfigured or down.
145
+ */
146
+ export async function describeImageBestEffort(
147
+ opts: DescribeImageOptions,
148
+ ): Promise<DescribeImageResult | null> {
149
+ if (!opts.config.describer.enabled) return null;
150
+ try {
151
+ return await describeImage(opts);
152
+ } catch (err) {
153
+ const msg = err instanceof Error ? err.message : String(err);
154
+ opts.logger?.warn(`ros2_camera_snapshot: describer failed: ${msg}`);
155
+ return null;
156
+ }
157
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Some CompressedImage decoders leave trailing bytes after the JPEG EOI (FFD9).
3
+ * OpenClaw's image sanitizer uses getImageMetadata(); extra bytes can make metadata fail
4
+ * and drop the tool image block. Trim to the last EOI marker when the buffer looks like JPEG.
5
+ */
6
+ export function trimJpegToLastEoi(buf: Buffer): Buffer {
7
+ if (buf.length < 4 || buf[0] !== 0xff || buf[1] !== 0xd8) {
8
+ return buf;
9
+ }
10
+ for (let i = buf.length - 2; i >= 0; i--) {
11
+ if (buf[i] === 0xff && buf[i + 1] === 0xd9) {
12
+ return buf.subarray(0, i + 2);
13
+ }
14
+ }
15
+ return buf;
16
+ }
@@ -0,0 +1,85 @@
1
+ import type { OpenClawPluginApi } from "./plugin-api.js";
2
+ import { parseConfig, isCdrTypeSupported, agenticROSBannerLines } from "@agenticros/core";
3
+ import { readAgenticROSConfigFromFile } from "./config-file.js";
4
+ import { registerService } from "./service.js";
5
+ import { registerTools } from "./tools/index.js";
6
+ import { registerMemoryTools } from "./tools/ros2-memory.js";
7
+ import { initMemory } from "./memory.js";
8
+ import { loadSkills } from "./skill-loader.js";
9
+ import { registerSafetyHook } from "./safety/validator.js";
10
+ import { registerRobotContext } from "./context/robot-context.js";
11
+ import { registerEstopCommand } from "./commands/estop.js";
12
+ import { registerTransportCommand } from "./commands/transport.js";
13
+ import { registerRoutes } from "./routes.js";
14
+
15
+ /**
16
+ * AgenticROS — OpenClaw plugin for ROS2 robot control via natural language.
17
+ */
18
+ export default {
19
+ id: "agenticros",
20
+ name: "AgenticROS",
21
+
22
+ register(api: OpenClawPluginApi): void {
23
+ for (const line of agenticROSBannerLines({ tagline: true })) {
24
+ api.logger.info(line);
25
+ }
26
+ api.logger.info("AgenticROS plugin loading...");
27
+ const imageSupported = isCdrTypeSupported("sensor_msgs/msg/CompressedImage");
28
+ api.logger.info(`AgenticROS: Zenoh CDR Image/CompressedImage supported=${imageSupported}`);
29
+
30
+ let config: ReturnType<typeof parseConfig>;
31
+ try {
32
+ config = readAgenticROSConfigFromFile();
33
+ api.logger.info("AgenticROS: using config from file (transport, namespace, etc.)");
34
+ } catch (e) {
35
+ const msg = e instanceof Error ? e.message : String(e);
36
+ api.logger.warn("AgenticROS: could not read config from file: " + msg + " — using gateway pluginConfig.");
37
+ config = parseConfig(api.pluginConfig ?? {});
38
+ }
39
+ const mode = config.transport?.mode ?? "local";
40
+ const zenohEndpoint = config.zenoh?.routerEndpoint ?? "";
41
+ api.logger.info(`AgenticROS: transport mode=${mode}${mode === "zenoh" && zenohEndpoint ? ` endpoint=${zenohEndpoint}` : ""}`);
42
+
43
+ // Register HTTP routes before any await so OpenClaw gateways that don't await register() still mount them (e.g. 2026.3.11 "async registration is ignored")
44
+ if (typeof api.registerHttpRoute === "function") {
45
+ registerRoutes(api, config);
46
+ }
47
+
48
+ // Register the rosbridge WebSocket connection as a managed service
49
+ registerService(api, config);
50
+
51
+ // Register core ROS2 tools (no Follow Me — that lives in agenticros-skill-followme)
52
+ registerTools(api, config);
53
+
54
+ // Initialize optional memory subsystem (async; off-by-default; same pattern as skills below).
55
+ void initMemory(config, api.logger)
56
+ .then((memory) => {
57
+ if (memory) registerMemoryTools(api, config);
58
+ })
59
+ .catch((e) => {
60
+ const msg = e instanceof Error ? e.message : String(e);
61
+ api.logger.error("AgenticROS: memory init failed: " + msg);
62
+ });
63
+
64
+ // Load optional skills from skillPackages and skillPaths (async; OpenClaw 2026.5+ requires sync register())
65
+ void loadSkills(api, config).catch((e) => {
66
+ const msg = e instanceof Error ? e.message : String(e);
67
+ api.logger.error("AgenticROS: skill load failed: " + msg);
68
+ });
69
+
70
+ // Register safety validation hook (before_tool_call)
71
+ registerSafetyHook(api, config);
72
+
73
+ // Register robot capability injection (before_agent_start)
74
+ registerRobotContext(api, config);
75
+
76
+ // Register direct commands (bypass AI)
77
+ registerEstopCommand(api, config);
78
+ registerTransportCommand(api, config);
79
+
80
+ api.logger.info("AgenticROS plugin loaded successfully");
81
+ },
82
+ };
83
+
84
+ export type { OpenClawPluginApi } from "./plugin-api.js";
85
+ export type { SkillContext, RegisterSkill, DepthSampleResult, DepthSectorsResult } from "./skill-api.js";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Returns the AgenticROS landing page HTML (links to Config and Teleop).
3
+ *
4
+ * `basePath` is the mount prefix where this plugin is served (e.g. `/plugins/agenticros`,
5
+ * `/agenticros`, or `/api/agenticros`). Hrefs are built absolutely from it so the
6
+ * buttons work whether the user opens the landing URL with or without a trailing slash.
7
+ */
8
+ export function getLandingPageHtml(basePath = "/agenticros"): string {
9
+ const base = basePath.replace(/\/+$/, "");
10
+ return `<!DOCTYPE html>
11
+ <html lang="en">
12
+ <head>
13
+ <meta charset="UTF-8" />
14
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
15
+ <title>AgenticROS</title>
16
+ <style>
17
+ * { box-sizing: border-box; }
18
+ body { font-family: system-ui, sans-serif; margin: 12px; background: #1a1a1a; color: #e0e0e0; }
19
+ h1 { font-size: 1.5rem; margin: 0 0 8px 0; }
20
+ p { margin: 0 0 20px 0; color: #aaa; font-size: 0.95rem; }
21
+ nav { display: flex; flex-wrap: wrap; gap: 12px; }
22
+ a { display: inline-block; padding: 12px 20px; border-radius: 8px; background: #333; color: #e0e0e0; text-decoration: none; border: 1px solid #555; }
23
+ a:hover { background: #444; color: #fff; }
24
+ a.back { background: transparent; border-color: #444; color: #aaa; font-size: 0.9rem; padding: 6px 12px; margin-bottom: 12px; }
25
+ a.back:hover { background: #2a2a2a; color: #e0e0e0; }
26
+ </style>
27
+ </head>
28
+ <body>
29
+ <a class="back" href="/">← Back to chat</a>
30
+ <h1>AgenticROS</h1>
31
+ <p>ROS2 + OpenClaw — natural language control of robots.</p>
32
+ <nav>
33
+ <a href="${base}/config">Config</a>
34
+ <a href="${base}/teleop/">Teleop</a>
35
+ </nav>
36
+ </body>
37
+ </html>`;
38
+ }
@@ -0,0 +1,44 @@
1
+ import type { AgenticROSConfig, MemoryProvider } from "@agenticros/core";
2
+ import { createMemory } from "@agenticros/core";
3
+ import type { PluginLogger } from "./plugin-api.js";
4
+
5
+ let provider: MemoryProvider | null = null;
6
+ let initStarted = false;
7
+ let initError: string | null = null;
8
+
9
+ /**
10
+ * Initialize the memory provider for the OpenClaw plugin.
11
+ *
12
+ * Called once during plugin registration. The provider is held as a module
13
+ * singleton and looked up by the memory tools.
14
+ */
15
+ export async function initMemory(
16
+ config: AgenticROSConfig,
17
+ logger: PluginLogger,
18
+ ): Promise<MemoryProvider | null> {
19
+ if (initStarted) return provider;
20
+ initStarted = true;
21
+ if (!config.memory?.enabled) {
22
+ logger.info("AgenticROS: memory disabled (config.memory.enabled=false)");
23
+ return null;
24
+ }
25
+ try {
26
+ provider = await createMemory(config);
27
+ if (provider) {
28
+ logger.info(`AgenticROS: memory backend=${provider.backend} ready`);
29
+ }
30
+ } catch (err) {
31
+ initError = err instanceof Error ? err.message : String(err);
32
+ logger.error("AgenticROS: memory init failed: " + initError);
33
+ provider = null;
34
+ }
35
+ return provider;
36
+ }
37
+
38
+ export function getMemory(): MemoryProvider | null {
39
+ return provider;
40
+ }
41
+
42
+ export function getMemoryInitError(): string | null {
43
+ return initError;
44
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Type declarations matching the real OpenClaw plugin SDK.
3
+ *
4
+ * Only the subset used by the AgenticROS plugin is declared here.
5
+ * These types mirror openclaw/plugin-sdk so that the plugin compiles
6
+ * without importing the SDK at build time (it is provided at runtime).
7
+ */
8
+
9
+ import type { TSchema } from "@sinclair/typebox";
10
+
11
+ // --- Logger ---
12
+
13
+ export interface PluginLogger {
14
+ info(msg: string): void;
15
+ warn(msg: string): void;
16
+ error(msg: string): void;
17
+ }
18
+
19
+ // --- Tools ---
20
+
21
+ export interface AgentTool {
22
+ name: string;
23
+ label: string;
24
+ description: string;
25
+ parameters: TSchema;
26
+ execute(
27
+ toolCallId: string,
28
+ params: Record<string, unknown>,
29
+ signal?: AbortSignal,
30
+ ): Promise<ToolResult>;
31
+ }
32
+
33
+ export interface ToolResult {
34
+ content: ToolContent[];
35
+ details?: unknown;
36
+ }
37
+
38
+ export type ToolContent =
39
+ | { type: "text"; text: string }
40
+ | { type: "image"; data: string; mimeType: string };
41
+
42
+ // --- Services ---
43
+
44
+ export interface ServiceContext {
45
+ config: Record<string, unknown>;
46
+ stateDir: string;
47
+ logger: PluginLogger;
48
+ }
49
+
50
+ export interface PluginService {
51
+ id: string;
52
+ start(ctx: ServiceContext): Promise<void>;
53
+ stop?(ctx: ServiceContext): Promise<void>;
54
+ }
55
+
56
+ // --- Commands ---
57
+
58
+ export interface CommandContext {
59
+ senderId?: string;
60
+ channel: string;
61
+ channelId?: string;
62
+ isAuthorizedSender: boolean;
63
+ args?: string;
64
+ commandBody: string;
65
+ config: Record<string, unknown>;
66
+ from?: string;
67
+ to?: string;
68
+ accountId?: string;
69
+ messageThreadId?: number;
70
+ }
71
+
72
+ export interface PluginCommand {
73
+ name: string;
74
+ description: string;
75
+ handler(ctx: CommandContext): Promise<CommandResult> | CommandResult;
76
+ }
77
+
78
+ export interface CommandResult {
79
+ text: string;
80
+ }
81
+
82
+ // --- Hooks ---
83
+
84
+ export interface BeforeAgentStartEvent {
85
+ prompt: string;
86
+ }
87
+
88
+ export interface BeforeAgentStartResult {
89
+ prependContext?: string;
90
+ }
91
+
92
+ export interface BeforeAgentStartContext {
93
+ agentId?: string;
94
+ sessionKey?: string;
95
+ sessionId?: string;
96
+ workspaceDir?: string;
97
+ messageProvider?: string;
98
+ }
99
+
100
+ export type BeforeAgentStartHandler = (
101
+ event: BeforeAgentStartEvent,
102
+ ctx: BeforeAgentStartContext,
103
+ ) => Promise<BeforeAgentStartResult | void> | BeforeAgentStartResult | void;
104
+
105
+ export interface BeforeToolCallEvent {
106
+ toolName: string;
107
+ params: Record<string, unknown>;
108
+ }
109
+
110
+ export interface BeforeToolCallResult {
111
+ block?: boolean;
112
+ blockReason?: string;
113
+ }
114
+
115
+ export interface BeforeToolCallContext {
116
+ agentId?: string;
117
+ sessionKey?: string;
118
+ toolName: string;
119
+ }
120
+
121
+ export type BeforeToolCallHandler = (
122
+ event: BeforeToolCallEvent,
123
+ ctx: BeforeToolCallContext,
124
+ ) => Promise<BeforeToolCallResult | void> | BeforeToolCallResult | void;
125
+
126
+ // --- HTTP routes (Phase 3 teleop) ---
127
+
128
+ /** Minimal request shape for plugin HTTP route handlers (gateway provides at runtime). */
129
+ export interface HttpRouteRequest {
130
+ method: string;
131
+ url: string;
132
+ /** Read JSON body (e.g. for POST). May throw if body is invalid. */
133
+ readJsonBody?(): Promise<Record<string, unknown>>;
134
+ }
135
+
136
+ /** Minimal response shape for plugin HTTP route handlers (Node-style; gateway provides at runtime). */
137
+ export interface HttpRouteResponse {
138
+ setHeader(name: string, value: string | number): void;
139
+ /** HTTP status code (e.g. res.statusCode = 200). */
140
+ statusCode: number;
141
+ end(body?: string | Buffer): void;
142
+ }
143
+
144
+ export type HttpRouteHandler = (
145
+ req: HttpRouteRequest,
146
+ res: HttpRouteResponse,
147
+ ) => void | Promise<void>;
148
+
149
+ export interface HttpRouteOptions {
150
+ path: string;
151
+ method?: string;
152
+ handler: HttpRouteHandler;
153
+ /** When false, gateway should not require Bearer auth for this route (OpenClaw 2026.3.2+ may reject registration if missing). */
154
+ requireAuth?: boolean;
155
+ /** OpenClaw 2026.3.11+ requires explicit auth: "gateway" (use gateway token) or "plugin" (plugin-managed / no gateway auth). */
156
+ auth?: "gateway" | "plugin";
157
+ }
158
+
159
+ // --- Plugin API ---
160
+
161
+ export interface OpenClawPluginApi {
162
+ pluginConfig?: Record<string, unknown>;
163
+ logger: PluginLogger;
164
+
165
+ registerTool(tool: AgentTool, opts?: { name?: string; names?: string[]; optional?: boolean }): void;
166
+ registerService(service: PluginService): void;
167
+ registerCommand(command: PluginCommand): void;
168
+ /** Register an HTTP route (e.g. for Phase 3 teleop). Optional; gateway may not provide it. */
169
+ registerHttpRoute?(options: HttpRouteOptions): void;
170
+
171
+ on(hookName: "before_agent_start", handler: BeforeAgentStartHandler): void;
172
+ on(hookName: "before_tool_call", handler: BeforeToolCallHandler): void;
173
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Normalize base64 for OpenClaw tool image blocks.
3
+ * OpenClaw's sanitizeToolResultImages uses canonicalizeBase64(); strings must be
4
+ * strict base64 (no whitespace) with length multiple of 4, or the image is dropped
5
+ * and the UI/model may see broken data URLs.
6
+ *
7
+ * Logic aligned with openclaw/src/media/base64.ts canonicalizeBase64.
8
+ */
9
+
10
+ function isBase64DataChar(code: number): boolean {
11
+ return (
12
+ (code >= 0x41 && code <= 0x5a) ||
13
+ (code >= 0x61 && code <= 0x7a) ||
14
+ (code >= 0x30 && code <= 0x39) ||
15
+ code === 0x2b ||
16
+ code === 0x2f
17
+ );
18
+ }
19
+
20
+ function canonicalizeBase64Strict(base64: string): string | undefined {
21
+ let cleaned = "";
22
+ let padding = 0;
23
+ let sawPadding = false;
24
+ for (let i = 0; i < base64.length; i += 1) {
25
+ const code = base64.charCodeAt(i);
26
+ if (code <= 0x20) {
27
+ continue;
28
+ }
29
+ if (code === 0x3d) {
30
+ padding += 1;
31
+ if (padding > 2) {
32
+ return undefined;
33
+ }
34
+ sawPadding = true;
35
+ cleaned += "=";
36
+ continue;
37
+ }
38
+ if (sawPadding || !isBase64DataChar(code)) {
39
+ return undefined;
40
+ }
41
+ cleaned += base64[i]!;
42
+ }
43
+ if (!cleaned || cleaned.length % 4 !== 0) {
44
+ return undefined;
45
+ }
46
+ return cleaned;
47
+ }
48
+
49
+ /**
50
+ * Strip optional data-URL prefix, remove whitespace, validate, then round-trip through Buffer
51
+ * so the payload is canonical base64 OpenClaw accepts.
52
+ */
53
+ export function normalizePluginToolImageBase64(input: string): string | undefined {
54
+ let s = input.trim();
55
+ const dataUrl = /^data:[^;]+;base64,(.*)$/is.exec(s);
56
+ if (dataUrl) {
57
+ s = (dataUrl[1] ?? "").trim();
58
+ }
59
+ s = s.replace(/\s/g, "");
60
+ const canonical = canonicalizeBase64Strict(s);
61
+ if (!canonical) {
62
+ return undefined;
63
+ }
64
+ const buf = Buffer.from(canonical, "base64");
65
+ if (buf.length === 0) {
66
+ return undefined;
67
+ }
68
+ return buf.toString("base64");
69
+ }
@@ -0,0 +1,110 @@
1
+ import { createConnection, type Socket } from "node:net";
2
+
3
+ /**
4
+ * Result of a TCP-level reachability probe for a `ws://` / `wss://` endpoint.
5
+ *
6
+ * We only check that *something* accepts a TCP connection on the host:port — not
7
+ * that it speaks the WebSocket protocol. That is enough to distinguish "router
8
+ * is not running" (the common failure mode for AgenticROS users who forget to
9
+ * start zenohd) from "router is up but rejecting".
10
+ */
11
+ export interface PreflightResult {
12
+ /** True if the TCP socket connected within the timeout. */
13
+ reachable: boolean;
14
+ /** Resolved host:port (for messages). Empty when parsing failed. */
15
+ host: string;
16
+ port: number;
17
+ /** Short human-readable reason when `reachable` is false. */
18
+ reason?: string;
19
+ }
20
+
21
+ /**
22
+ * TCP-level reachability probe for a WebSocket URL.
23
+ *
24
+ * Why TCP and not an actual WebSocket handshake?
25
+ * - It's instant when the port is closed (the OS returns ECONNREFUSED, no
26
+ * timeout wait).
27
+ * - It doesn't depend on `ws://` library handshake quirks; we just need to
28
+ * know whether zenohd / rosbridge is up.
29
+ * - Calling `zenoh-ts` `Session.open()` against a closed port triggers its
30
+ * own internal retry loop that spams `WebSocket disconnected from
31
+ * remote-api-plugin: 1006` and `Restart connection (N/10)` — we want to
32
+ * avoid invoking it at all when we already know the port is dead.
33
+ */
34
+ export async function preflightWsEndpoint(
35
+ endpoint: string,
36
+ timeoutMs = 1500,
37
+ ): Promise<PreflightResult> {
38
+ const trimmed = (endpoint ?? "").trim();
39
+ if (!trimmed) {
40
+ return { reachable: false, host: "", port: 0, reason: "endpoint is empty" };
41
+ }
42
+
43
+ let url: URL;
44
+ try {
45
+ url = new URL(trimmed);
46
+ } catch {
47
+ return { reachable: false, host: "", port: 0, reason: `invalid URL "${trimmed}"` };
48
+ }
49
+
50
+ if (!/^wss?:$/i.test(url.protocol)) {
51
+ return {
52
+ reachable: false,
53
+ host: url.hostname,
54
+ port: 0,
55
+ reason: `expected ws:// or wss:// URL, got ${url.protocol}//`,
56
+ };
57
+ }
58
+
59
+ const host = url.hostname;
60
+ const defaultPort = url.protocol.toLowerCase() === "wss:" ? 443 : 80;
61
+ const port = url.port ? Number.parseInt(url.port, 10) : defaultPort;
62
+ if (!Number.isFinite(port) || port <= 0 || port > 65535) {
63
+ return { reachable: false, host, port: 0, reason: `invalid port ${url.port}` };
64
+ }
65
+
66
+ return await new Promise<PreflightResult>((resolve) => {
67
+ let settled = false;
68
+ let socket: Socket | null = null;
69
+ const finish = (result: PreflightResult): void => {
70
+ if (settled) return;
71
+ settled = true;
72
+ if (socket) {
73
+ try {
74
+ socket.destroy();
75
+ } catch {
76
+ /* ignore */
77
+ }
78
+ }
79
+ resolve(result);
80
+ };
81
+
82
+ const timer = setTimeout(() => {
83
+ finish({ reachable: false, host, port, reason: `timeout after ${timeoutMs}ms` });
84
+ }, timeoutMs);
85
+ timer.unref?.();
86
+
87
+ try {
88
+ socket = createConnection({ host, port });
89
+ } catch (err) {
90
+ clearTimeout(timer);
91
+ finish({
92
+ reachable: false,
93
+ host,
94
+ port,
95
+ reason: err instanceof Error ? err.message : String(err),
96
+ });
97
+ return;
98
+ }
99
+ socket.once("connect", () => {
100
+ clearTimeout(timer);
101
+ finish({ reachable: true, host, port });
102
+ });
103
+ socket.once("error", (err: NodeJS.ErrnoException) => {
104
+ clearTimeout(timer);
105
+ const reason =
106
+ err && typeof err.code === "string" ? `${err.code} (${err.message})` : err?.message ?? "connection error";
107
+ finish({ reachable: false, host, port, reason });
108
+ });
109
+ });
110
+ }