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.
- package/LICENSE +192 -0
- package/README.md +90 -4
- package/dist/commands/config.d.ts +20 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +179 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/doctor.d.ts +33 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +232 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/down.d.ts +15 -0
- package/dist/commands/down.d.ts.map +1 -0
- package/dist/commands/down.js +91 -0
- package/dist/commands/down.js.map +1 -0
- package/dist/commands/init.d.ts +21 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +259 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/logs.d.ts +18 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +67 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/status.d.ts +12 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +56 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/up.d.ts +20 -0
- package/dist/commands/up.d.ts.map +1 -0
- package/dist/commands/up.js +70 -0
- package/dist/commands/up.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/dist/menu.d.ts +9 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +96 -0
- package/dist/menu.js.map +1 -0
- package/dist/runners/real-robot.d.ts +15 -0
- package/dist/runners/real-robot.d.ts.map +1 -0
- package/dist/runners/real-robot.js +46 -0
- package/dist/runners/real-robot.js.map +1 -0
- package/dist/runners/sim.d.ts +19 -0
- package/dist/runners/sim.d.ts.map +1 -0
- package/dist/runners/sim.js +53 -0
- package/dist/runners/sim.js.map +1 -0
- package/dist/util/env.d.ts +24 -0
- package/dist/util/env.d.ts.map +1 -0
- package/dist/util/env.js +53 -0
- package/dist/util/env.js.map +1 -0
- package/dist/util/logger.d.ts +24 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +62 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/paths.d.ts +57 -0
- package/dist/util/paths.d.ts.map +1 -0
- package/dist/util/paths.js +132 -0
- package/dist/util/paths.js.map +1 -0
- package/dist/util/pidfile.d.ts +16 -0
- package/dist/util/pidfile.d.ts.map +1 -0
- package/dist/util/pidfile.js +63 -0
- package/dist/util/pidfile.js.map +1 -0
- package/dist/util/state.d.ts +26 -0
- package/dist/util/state.d.ts.map +1 -0
- package/dist/util/state.js +55 -0
- package/dist/util/state.js.map +1 -0
- package/package.json +60 -1
- package/runtime/BUNDLE.json +11 -0
- package/runtime/LICENSE +192 -0
- package/runtime/README.md +273 -0
- package/runtime/docs/architecture.md +366 -0
- package/runtime/docs/cli.md +140 -0
- package/runtime/docs/memory.md +292 -0
- package/runtime/docs/robot-setup.md +347 -0
- package/runtime/package.json +28 -0
- package/runtime/packages/agenticros/agenticros-agenticros-0.0.1.tgz +0 -0
- package/runtime/packages/agenticros/openclaw.plugin.json +451 -0
- package/runtime/packages/agenticros/package.json +41 -0
- package/runtime/packages/agenticros/src/camera-snapshot-cache.ts +59 -0
- package/runtime/packages/agenticros/src/camera-snapshot-routes.ts +44 -0
- package/runtime/packages/agenticros/src/commands/estop.ts +41 -0
- package/runtime/packages/agenticros/src/commands/transport.ts +195 -0
- package/runtime/packages/agenticros/src/config-file.ts +136 -0
- package/runtime/packages/agenticros/src/config-page.ts +498 -0
- package/runtime/packages/agenticros/src/context/robot-context.ts +373 -0
- package/runtime/packages/agenticros/src/depth.ts +313 -0
- package/runtime/packages/agenticros/src/describer.ts +157 -0
- package/runtime/packages/agenticros/src/image-binary-trim.ts +16 -0
- package/runtime/packages/agenticros/src/index.ts +85 -0
- package/runtime/packages/agenticros/src/landing-page.ts +38 -0
- package/runtime/packages/agenticros/src/memory.ts +44 -0
- package/runtime/packages/agenticros/src/plugin-api.ts +173 -0
- package/runtime/packages/agenticros/src/plugin-image-base64.ts +69 -0
- package/runtime/packages/agenticros/src/preflight.ts +110 -0
- package/runtime/packages/agenticros/src/routes.ts +328 -0
- package/runtime/packages/agenticros/src/safety/validator.ts +43 -0
- package/runtime/packages/agenticros/src/service.ts +359 -0
- package/runtime/packages/agenticros/src/skill-api.ts +65 -0
- package/runtime/packages/agenticros/src/skill-loader.ts +146 -0
- package/runtime/packages/agenticros/src/teleop/page.ts +498 -0
- package/runtime/packages/agenticros/src/teleop/routes.ts +650 -0
- package/runtime/packages/agenticros/src/tools/index.ts +26 -0
- package/runtime/packages/agenticros/src/tools/ros2-action.ts +50 -0
- package/runtime/packages/agenticros/src/tools/ros2-camera.ts +221 -0
- package/runtime/packages/agenticros/src/tools/ros2-depth-distance.ts +58 -0
- package/runtime/packages/agenticros/src/tools/ros2-introspect.ts +62 -0
- package/runtime/packages/agenticros/src/tools/ros2-memory.ts +158 -0
- package/runtime/packages/agenticros/src/tools/ros2-param.ts +87 -0
- package/runtime/packages/agenticros/src/tools/ros2-publish.ts +52 -0
- package/runtime/packages/agenticros/src/tools/ros2-service.ts +46 -0
- package/runtime/packages/agenticros/src/tools/ros2-subscribe.ts +71 -0
- package/runtime/packages/agenticros/tsconfig.json +9 -0
- package/runtime/packages/agenticros-claude-code/README.md +260 -0
- package/runtime/packages/agenticros-claude-code/config.example.json +9 -0
- package/runtime/packages/agenticros-claude-code/dist/config.d.ts +8 -0
- package/runtime/packages/agenticros-claude-code/dist/config.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/config.js +93 -0
- package/runtime/packages/agenticros-claude-code/dist/config.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/depth.d.ts +20 -0
- package/runtime/packages/agenticros-claude-code/dist/depth.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/depth.js +126 -0
- package/runtime/packages/agenticros-claude-code/dist/depth.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.d.ts +6 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.js +36 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.d.ts +33 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.js +134 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.d.ts +43 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.js +73 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.d.ts +58 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.js +251 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.d.ts +61 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.js +268 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/index.d.ts +3 -0
- package/runtime/packages/agenticros-claude-code/dist/index.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/index.js +111 -0
- package/runtime/packages/agenticros-claude-code/dist/index.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/memory.d.ts +17 -0
- package/runtime/packages/agenticros-claude-code/dist/memory.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/memory.js +44 -0
- package/runtime/packages/agenticros-claude-code/dist/memory.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/safety.d.ts +10 -0
- package/runtime/packages/agenticros-claude-code/dist/safety.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/safety.js +34 -0
- package/runtime/packages/agenticros-claude-code/dist/safety.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.d.ts +36 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.js +777 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/transport.d.ts +17 -0
- package/runtime/packages/agenticros-claude-code/dist/transport.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/transport.js +46 -0
- package/runtime/packages/agenticros-claude-code/dist/transport.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.d.ts +42 -0
- package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.js +114 -0
- package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/package.json +29 -0
- package/runtime/packages/agenticros-claude-code/src/config.ts +96 -0
- package/runtime/packages/agenticros-claude-code/src/depth.ts +173 -0
- package/runtime/packages/agenticros-claude-code/src/find-object/coco-classes.ts +38 -0
- package/runtime/packages/agenticros-claude-code/src/find-object/find-object.ts +190 -0
- package/runtime/packages/agenticros-claude-code/src/follow-me/controller.ts +109 -0
- package/runtime/packages/agenticros-claude-code/src/follow-me/depth-loop.ts +420 -0
- package/runtime/packages/agenticros-claude-code/src/follow-me/detector.ts +303 -0
- package/runtime/packages/agenticros-claude-code/src/follow-me/loop.ts +330 -0
- package/runtime/packages/agenticros-claude-code/src/index.ts +125 -0
- package/runtime/packages/agenticros-claude-code/src/memory.ts +51 -0
- package/runtime/packages/agenticros-claude-code/src/safety.ts +44 -0
- package/runtime/packages/agenticros-claude-code/src/tools.ts +891 -0
- package/runtime/packages/agenticros-claude-code/src/transport.ts +58 -0
- package/runtime/packages/agenticros-claude-code/src/zero-shot/detector.ts +169 -0
- package/runtime/packages/agenticros-claude-code/tsconfig.json +9 -0
- package/runtime/packages/agenticros-claude-code/yolo-debug.mjs +106 -0
- package/runtime/packages/agenticros-gemini/README.md +139 -0
- package/runtime/packages/agenticros-gemini/package.json +28 -0
- package/runtime/packages/agenticros-gemini/scripts/smoke-api.mjs +42 -0
- package/runtime/packages/agenticros-gemini/src/chat.ts +139 -0
- package/runtime/packages/agenticros-gemini/src/config.ts +92 -0
- package/runtime/packages/agenticros-gemini/src/depth.ts +173 -0
- package/runtime/packages/agenticros-gemini/src/index.ts +58 -0
- package/runtime/packages/agenticros-gemini/src/memory.ts +32 -0
- package/runtime/packages/agenticros-gemini/src/safety.ts +44 -0
- package/runtime/packages/agenticros-gemini/src/tools.ts +516 -0
- package/runtime/packages/agenticros-gemini/src/transport.ts +58 -0
- package/runtime/packages/agenticros-gemini/tsconfig.json +8 -0
- package/runtime/packages/core/package.json +47 -0
- package/runtime/packages/core/src/banner.ts +32 -0
- package/runtime/packages/core/src/cmd-vel-twist.ts +31 -0
- package/runtime/packages/core/src/config.ts +279 -0
- package/runtime/packages/core/src/index.ts +54 -0
- package/runtime/packages/core/src/memory/__tests__/factory.test.ts +70 -0
- package/runtime/packages/core/src/memory/__tests__/local-provider.test.ts +195 -0
- package/runtime/packages/core/src/memory/__tests__/mem0-provider.test.ts +192 -0
- package/runtime/packages/core/src/memory/__tests__/smart-defaults.test.ts +46 -0
- package/runtime/packages/core/src/memory/factory.ts +63 -0
- package/runtime/packages/core/src/memory/index.ts +10 -0
- package/runtime/packages/core/src/memory/local/provider.ts +229 -0
- package/runtime/packages/core/src/memory/mem0/provider.ts +379 -0
- package/runtime/packages/core/src/memory/types.ts +96 -0
- package/runtime/packages/core/src/topic-utils.ts +95 -0
- package/runtime/packages/core/src/transport/factory.ts +47 -0
- package/runtime/packages/core/src/transport/local/conversion.ts +333 -0
- package/runtime/packages/core/src/transport/local/entities.ts +129 -0
- package/runtime/packages/core/src/transport/local/transport.ts +406 -0
- package/runtime/packages/core/src/transport/rosbridge/actions.ts +81 -0
- package/runtime/packages/core/src/transport/rosbridge/adapter.ts +157 -0
- package/runtime/packages/core/src/transport/rosbridge/client.ts +438 -0
- package/runtime/packages/core/src/transport/rosbridge/services.ts +41 -0
- package/runtime/packages/core/src/transport/rosbridge/topics.ts +60 -0
- package/runtime/packages/core/src/transport/rosbridge/types.ts +118 -0
- package/runtime/packages/core/src/transport/transport.ts +77 -0
- package/runtime/packages/core/src/transport/types.ts +137 -0
- package/runtime/packages/core/src/transport/webrtc/signaling-client.ts +196 -0
- package/runtime/packages/core/src/transport/webrtc/signaling-types.ts +130 -0
- package/runtime/packages/core/src/transport/webrtc/transport.ts +516 -0
- package/runtime/packages/core/src/transport/zenoh/adapter.ts +357 -0
- package/runtime/packages/core/src/transport/zenoh/cdr.ts +183 -0
- package/runtime/packages/core/src/transport/zenoh/keys.ts +51 -0
- package/runtime/packages/core/tsconfig.json +9 -0
- package/runtime/packages/ros-camera/package.json +30 -0
- package/runtime/packages/ros-camera/src/index.ts +13 -0
- package/runtime/packages/ros-camera/src/snapshot.ts +372 -0
- package/runtime/packages/ros-camera/tsconfig.json +9 -0
- package/runtime/pnpm-lock.yaml +5260 -0
- package/runtime/pnpm-workspace.yaml +2 -0
- package/runtime/ros2_ws/src/agenticros_agent/agenticros_agent/__init__.py +0 -0
- package/runtime/ros2_ws/src/agenticros_agent/agenticros_agent/agent_node.py +561 -0
- package/runtime/ros2_ws/src/agenticros_agent/package.xml +25 -0
- package/runtime/ros2_ws/src/agenticros_agent/resource/agenticros_agent +0 -0
- package/runtime/ros2_ws/src/agenticros_agent/setup.cfg +4 -0
- package/runtime/ros2_ws/src/agenticros_agent/setup.py +25 -0
- package/runtime/ros2_ws/src/agenticros_bringup/README.md +128 -0
- package/runtime/ros2_ws/src/agenticros_bringup/agenticros_bringup/__init__.py +1 -0
- package/runtime/ros2_ws/src/agenticros_bringup/agenticros_bringup/cmd_vel_relay.py +33 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/cmd_vel_bridge.launch.py +58 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/gazebo_turtlebot3.launch.py +69 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/mode_a_gazebo.launch.py +55 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/mode_a_gazebo_rviz.launch.py +48 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/realsense_rosbridge.launch.py +154 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/rosbridge_gazebo.launch.py +54 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/rviz.launch.py +38 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/turtlebot3_gazebo_rviz.launch.py +42 -0
- package/runtime/ros2_ws/src/agenticros_bringup/package.xml +24 -0
- package/runtime/ros2_ws/src/agenticros_bringup/resource/agenticros_bringup +0 -0
- package/runtime/ros2_ws/src/agenticros_bringup/rviz/turtlebot3_agenticros.rviz +174 -0
- package/runtime/ros2_ws/src/agenticros_bringup/setup.cfg +4 -0
- package/runtime/ros2_ws/src/agenticros_bringup/setup.py +28 -0
- package/runtime/ros2_ws/src/agenticros_discovery/agenticros_discovery/__init__.py +0 -0
- package/runtime/ros2_ws/src/agenticros_discovery/agenticros_discovery/discovery_node.py +172 -0
- package/runtime/ros2_ws/src/agenticros_discovery/package.xml +22 -0
- package/runtime/ros2_ws/src/agenticros_discovery/resource/agenticros_discovery +0 -0
- package/runtime/ros2_ws/src/agenticros_discovery/setup.cfg +5 -0
- package/runtime/ros2_ws/src/agenticros_discovery/setup.py +25 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/README.md +66 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/__init__.py +1 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/__main__.py +5 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/follow_me_node.py +278 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/follower_controller.py +631 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/person_tracker.py +635 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/package.xml +22 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/resource/agenticros_follow_me +0 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/setup.py +25 -0
- package/runtime/ros2_ws/src/agenticros_msgs/CMakeLists.txt +26 -0
- package/runtime/ros2_ws/src/agenticros_msgs/msg/CapabilityManifest.msg +9 -0
- package/runtime/ros2_ws/src/agenticros_msgs/package.xml +22 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeGetStatus.srv +11 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeSetDistance.srv +4 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeSetTarget.srv +6 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeStart.srv +5 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeStop.srv +3 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/GetCapabilities.srv +5 -0
- package/runtime/ros2_ws/src/agenticros_sim/CMakeLists.txt +24 -0
- package/runtime/ros2_ws/src/agenticros_sim/README.md +120 -0
- package/runtime/ros2_ws/src/agenticros_sim/config/agenticros-sim.config.json +28 -0
- package/runtime/ros2_ws/src/agenticros_sim/config/amr_bridge.yaml +111 -0
- package/runtime/ros2_ws/src/agenticros_sim/config/amr_view.rviz +172 -0
- package/runtime/ros2_ws/src/agenticros_sim/env-hooks/gz_resource_path.dsv.in +3 -0
- package/runtime/ros2_ws/src/agenticros_sim/env-hooks/gz_resource_path.sh.in +7 -0
- package/runtime/ros2_ws/src/agenticros_sim/launch/sim_amr.launch.py +159 -0
- package/runtime/ros2_ws/src/agenticros_sim/models/agenticros_amr/model.config +17 -0
- package/runtime/ros2_ws/src/agenticros_sim/models/agenticros_amr/model.sdf +244 -0
- package/runtime/ros2_ws/src/agenticros_sim/package.xml +27 -0
- package/runtime/ros2_ws/src/agenticros_sim/worlds/agenticros_indoor.sdf +183 -0
- package/runtime/scripts/activate_workspace.sh +285 -0
- package/runtime/scripts/agenticros-describer.policy.yaml +96 -0
- package/runtime/scripts/agenticros-proxy.cjs +99 -0
- package/runtime/scripts/agenticros-rosbridge.policy.yaml +62 -0
- package/runtime/scripts/check-cli-tarball-size.mjs +42 -0
- package/runtime/scripts/configure_agenticros.sh +200 -0
- package/runtime/scripts/configure_for_sim.sh +64 -0
- package/runtime/scripts/fix-openclaw-control-ui-path.sh +20 -0
- package/runtime/scripts/install_cli.sh +94 -0
- package/runtime/scripts/install_rosbridge_from_source.sh +67 -0
- package/runtime/scripts/lib/agenticros-banner.sh +28 -0
- package/runtime/scripts/onboard_robot.sh +75 -0
- package/runtime/scripts/openai.policy.yaml +77 -0
- package/runtime/scripts/openclaw-dashboard-url.cjs +49 -0
- package/runtime/scripts/pack-runtime.mjs +245 -0
- package/runtime/scripts/run_demo_native.sh +43 -0
- package/runtime/scripts/run_nemoclaw_host_stack.sh +91 -0
- package/runtime/scripts/run_robot_rosbridge.sh +36 -0
- package/runtime/scripts/sandbox_rosbridge_relay.py +137 -0
- package/runtime/scripts/setup-openclaw-local.cjs +75 -0
- package/runtime/scripts/setup_gateway_plugin.sh +329 -0
- package/runtime/scripts/setup_robot.sh +113 -0
- package/runtime/scripts/setup_workspace.sh +484 -0
- package/runtime/scripts/sim/run_sim.sh +146 -0
- package/runtime/scripts/smoke_test_nemoclaw.sh +123 -0
- package/runtime/scripts/start_demo.sh +55 -0
- package/runtime/scripts/sync-skill-tools.mjs +335 -0
- package/runtime/scripts/test-follow-me-sim.mjs +135 -0
- package/runtime/scripts/test-mcp-e2e.mjs +184 -0
- package/runtime/scripts/test-rclnodejs.mts +129 -0
- package/runtime/scripts/use-openclaw-2026.2.26.sh +19 -0
- package/runtime/scripts/use-openclaw-2026.3.11.sh +19 -0
- package/runtime/scripts/zenoh-bridge-ros2dds-robot.json5 +30 -0
- package/runtime/scripts/zenohd-agenticros.json5 +11 -0
- package/runtime/scripts/zenohd-rosclaw.json5 +11 -0
- package/runtime/tsconfig.base.json +19 -0
- package/index.js +0 -6
|
File without changes
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgenticROS Agent Node — robot-side bridge for Mode C (Cloud/Remote) deployments.
|
|
3
|
+
|
|
4
|
+
This ROS2 node runs on the robot and connects outbound to the signaling server,
|
|
5
|
+
establishing a WebRTC data channel with the cloud-side AgenticROS plugin. All ROS2
|
|
6
|
+
commands and responses flow over this encrypted peer-to-peer channel.
|
|
7
|
+
|
|
8
|
+
Connection flow:
|
|
9
|
+
1. Connect to signaling server via WebSocket
|
|
10
|
+
2. Send robot_connect with robot_token
|
|
11
|
+
3. Wait for session_invitation, validate robot_key
|
|
12
|
+
4. Send session_accepted (server adds robot to room automatically)
|
|
13
|
+
5. Wait for peer_joined (frontend), then create and send SDP offer
|
|
14
|
+
6. Exchange ICE candidates
|
|
15
|
+
7. Data channel opens — rosbridge JSON over WebRTC
|
|
16
|
+
|
|
17
|
+
Message flow:
|
|
18
|
+
- Receive rosbridge JSON on the data channel (publish, subscribe,
|
|
19
|
+
call_service, send_action_goal, etc.)
|
|
20
|
+
- Execute against the local ROS2 DDS bus via rclpy
|
|
21
|
+
- Send responses back over the data channel
|
|
22
|
+
|
|
23
|
+
Configuration (ROS2 parameters with env var fallback):
|
|
24
|
+
signaling_url / AGENTICROS_SIGNALING_URL — WebSocket URL of the signaling server
|
|
25
|
+
robot_token / AGENTICROS_ROBOT_TOKEN — Authentication token for the signaling server
|
|
26
|
+
robot_key / AGENTICROS_ROBOT_KEY — Secret key validated by this node
|
|
27
|
+
robot_id / AGENTICROS_ROBOT_ID — This robot's ID on the signaling server
|
|
28
|
+
|
|
29
|
+
Pass via ROS2 parameters:
|
|
30
|
+
ros2 run agenticros_agent agent_node --ros-args -p signaling_url:=wss://example.com
|
|
31
|
+
Or via environment variables:
|
|
32
|
+
AGENTICROS_SIGNALING_URL=wss://example.com ros2 run agenticros_agent agent_node
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import asyncio
|
|
38
|
+
import json
|
|
39
|
+
import logging
|
|
40
|
+
import os
|
|
41
|
+
import uuid
|
|
42
|
+
from typing import Any
|
|
43
|
+
|
|
44
|
+
import rclpy
|
|
45
|
+
from rclpy.node import Node
|
|
46
|
+
from rclpy.executors import SingleThreadedExecutor
|
|
47
|
+
|
|
48
|
+
from aiortc import RTCPeerConnection, RTCSessionDescription, RTCConfiguration, RTCIceServer
|
|
49
|
+
from aiortc.contrib.signaling import object_to_string
|
|
50
|
+
|
|
51
|
+
import websockets
|
|
52
|
+
from websockets.asyncio.client import connect as ws_connect
|
|
53
|
+
|
|
54
|
+
from rosbridge_library.internal.message_conversion import (
|
|
55
|
+
extract_values as msg_to_dict,
|
|
56
|
+
populate_instance as dict_to_msg,
|
|
57
|
+
)
|
|
58
|
+
from rosbridge_library.internal.ros_loader import (
|
|
59
|
+
get_message_class,
|
|
60
|
+
get_service_class,
|
|
61
|
+
get_action_class,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
logger = logging.getLogger("agenticros_agent")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class AgenticROSAgentNode(Node):
|
|
68
|
+
"""ROS2 node that bridges WebRTC data channels to local DDS."""
|
|
69
|
+
|
|
70
|
+
def __init__(self) -> None:
|
|
71
|
+
super().__init__("agenticros_agent")
|
|
72
|
+
|
|
73
|
+
# Parameters: --ros-args -p key:=value > env var > hardcoded default
|
|
74
|
+
self.declare_parameter("signaling_url", os.environ.get("AGENTICROS_SIGNALING_URL", "ws://localhost:8000"))
|
|
75
|
+
self.declare_parameter("robot_token", os.environ.get("AGENTICROS_ROBOT_TOKEN", "your-secret-token-1"))
|
|
76
|
+
self.declare_parameter("robot_key", os.environ.get("AGENTICROS_ROBOT_KEY", "my-secret-key"))
|
|
77
|
+
self.declare_parameter("robot_id", os.environ.get("AGENTICROS_ROBOT_ID", "robot_1"))
|
|
78
|
+
|
|
79
|
+
self.signaling_url: str = self.get_parameter("signaling_url").value
|
|
80
|
+
self.robot_token: str = self.get_parameter("robot_token").value
|
|
81
|
+
self.robot_key: str = self.get_parameter("robot_key").value
|
|
82
|
+
self.robot_id: str = self.get_parameter("robot_id").value
|
|
83
|
+
|
|
84
|
+
# WebRTC state
|
|
85
|
+
self.pc: RTCPeerConnection | None = None
|
|
86
|
+
self.data_channel: Any = None
|
|
87
|
+
self.ws: Any = None
|
|
88
|
+
self.current_room_id: str = ""
|
|
89
|
+
self.frontend_peer_id: str = ""
|
|
90
|
+
|
|
91
|
+
# ROS2 bridge state — tracks active publishers, subscribers, service clients
|
|
92
|
+
# Prefixed with _ to avoid shadowing Node's read-only properties
|
|
93
|
+
self._pubs: dict[str, Any] = {}
|
|
94
|
+
self._subs: dict[str, Any] = {}
|
|
95
|
+
self._srv_clients: dict[str, Any] = {}
|
|
96
|
+
|
|
97
|
+
self.get_logger().info(
|
|
98
|
+
f"AgenticROS agent initialized: robot_id={self.robot_id}, "
|
|
99
|
+
f"signaling={self.signaling_url}"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# --- Signaling ---
|
|
103
|
+
|
|
104
|
+
async def run_signaling(self) -> None:
|
|
105
|
+
"""Main signaling loop — connect, authenticate, wait for sessions."""
|
|
106
|
+
while rclpy.ok():
|
|
107
|
+
try:
|
|
108
|
+
await self._signaling_session()
|
|
109
|
+
except Exception as e:
|
|
110
|
+
self.get_logger().error(f"Signaling session error: {e}")
|
|
111
|
+
await asyncio.sleep(5)
|
|
112
|
+
|
|
113
|
+
async def _signaling_session(self) -> None:
|
|
114
|
+
"""Single signaling session: connect → authenticate → wait for invitation."""
|
|
115
|
+
ws_url = f"{self.signaling_url}/ws"
|
|
116
|
+
self.get_logger().info(f"Connecting to signaling server: {ws_url}")
|
|
117
|
+
|
|
118
|
+
async with ws_connect(ws_url) as ws:
|
|
119
|
+
self.ws = ws
|
|
120
|
+
|
|
121
|
+
# Step 1: ROBOT_CONNECT
|
|
122
|
+
await ws.send(json.dumps({
|
|
123
|
+
"type": "robot_connect",
|
|
124
|
+
"robot_token": self.robot_token,
|
|
125
|
+
"robot_id": self.robot_id,
|
|
126
|
+
"capabilities": self._get_capabilities(),
|
|
127
|
+
}))
|
|
128
|
+
self.get_logger().info("Sent ROBOT_CONNECT, waiting for session invitation...")
|
|
129
|
+
|
|
130
|
+
# Step 2: Message loop
|
|
131
|
+
async for raw in ws:
|
|
132
|
+
msg = json.loads(raw)
|
|
133
|
+
msg_type = msg.get("type", "")
|
|
134
|
+
|
|
135
|
+
if msg_type == "session_invitation":
|
|
136
|
+
await self._handle_session_invitation(ws, msg)
|
|
137
|
+
|
|
138
|
+
elif msg_type == "peer_joined":
|
|
139
|
+
self.get_logger().info(
|
|
140
|
+
f"Peer joined: {msg.get('peer_id')} ({msg.get('peer_type')})"
|
|
141
|
+
)
|
|
142
|
+
if msg.get("peer_type") == "frontend":
|
|
143
|
+
self.frontend_peer_id = msg["peer_id"]
|
|
144
|
+
await self._create_offer(ws)
|
|
145
|
+
|
|
146
|
+
elif msg_type == "answer":
|
|
147
|
+
await self._handle_answer(msg)
|
|
148
|
+
|
|
149
|
+
elif msg_type == "ice_candidate":
|
|
150
|
+
await self._handle_ice_candidate(msg)
|
|
151
|
+
|
|
152
|
+
elif msg_type == "heartbeat_request":
|
|
153
|
+
await ws.send(json.dumps({
|
|
154
|
+
"type": "heartbeat",
|
|
155
|
+
"timestamp": msg.get("timestamp", 0),
|
|
156
|
+
}))
|
|
157
|
+
|
|
158
|
+
elif msg_type == "session_ended":
|
|
159
|
+
self.get_logger().info(
|
|
160
|
+
f"Session ended: {msg.get('reason', 'unknown')}"
|
|
161
|
+
)
|
|
162
|
+
await self._cleanup_webrtc()
|
|
163
|
+
|
|
164
|
+
elif msg_type == "error":
|
|
165
|
+
self.get_logger().error(f"Signaling error: {msg}")
|
|
166
|
+
|
|
167
|
+
async def _handle_session_invitation(self, ws: Any, msg: dict) -> None:
|
|
168
|
+
"""Validate robot_key and accept session. Server adds robot to room automatically."""
|
|
169
|
+
session_id = msg["session_id"]
|
|
170
|
+
room_id = msg["room_id"]
|
|
171
|
+
received_key = msg.get("robot_key", "")
|
|
172
|
+
|
|
173
|
+
if self.robot_key and received_key != self.robot_key:
|
|
174
|
+
self.get_logger().warning(f"Rejecting session {session_id}: invalid robot_key")
|
|
175
|
+
await ws.send(json.dumps({
|
|
176
|
+
"type": "session_rejected",
|
|
177
|
+
"session_id": session_id,
|
|
178
|
+
"robot_id": self.robot_id,
|
|
179
|
+
"reason": "invalid_robot_key",
|
|
180
|
+
}))
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
self.get_logger().info(f"Accepting session {session_id}")
|
|
184
|
+
|
|
185
|
+
# Accept session
|
|
186
|
+
await ws.send(json.dumps({
|
|
187
|
+
"type": "session_accepted",
|
|
188
|
+
"session_id": session_id,
|
|
189
|
+
"robot_id": self.robot_id,
|
|
190
|
+
}))
|
|
191
|
+
|
|
192
|
+
# Server adds robot to room automatically on session_accepted.
|
|
193
|
+
# Offer is deferred until frontend peer joins (peer_joined event).
|
|
194
|
+
self.current_room_id = room_id
|
|
195
|
+
|
|
196
|
+
async def _create_offer(self, ws: Any) -> None:
|
|
197
|
+
"""Create RTCPeerConnection with data channels and send SDP offer."""
|
|
198
|
+
config = RTCConfiguration(
|
|
199
|
+
iceServers=[RTCIceServer(urls=["stun:stun.l.google.com:19302"])]
|
|
200
|
+
)
|
|
201
|
+
self.pc = RTCPeerConnection(configuration=config)
|
|
202
|
+
|
|
203
|
+
# Create data channel for rosbridge commands
|
|
204
|
+
self.data_channel = self.pc.createDataChannel("commands")
|
|
205
|
+
|
|
206
|
+
@self.data_channel.on("open")
|
|
207
|
+
def on_open() -> None:
|
|
208
|
+
self.get_logger().info("Data channel 'commands' opened")
|
|
209
|
+
|
|
210
|
+
@self.data_channel.on("message")
|
|
211
|
+
def on_message(message: str) -> None:
|
|
212
|
+
asyncio.ensure_future(self._handle_data_channel_message(message))
|
|
213
|
+
|
|
214
|
+
@self.data_channel.on("close")
|
|
215
|
+
def on_close() -> None:
|
|
216
|
+
self.get_logger().info("Data channel 'commands' closed")
|
|
217
|
+
self._cleanup_ros_bridge()
|
|
218
|
+
|
|
219
|
+
# ICE candidate handler
|
|
220
|
+
@self.pc.on("icecandidate")
|
|
221
|
+
async def on_ice_candidate(candidate: Any) -> None:
|
|
222
|
+
if candidate:
|
|
223
|
+
await ws.send(json.dumps({
|
|
224
|
+
"type": "ice_candidate",
|
|
225
|
+
"data": {
|
|
226
|
+
"candidate": candidate.candidate,
|
|
227
|
+
"sdpMid": candidate.sdpMid,
|
|
228
|
+
"sdpMLineIndex": candidate.sdpMLineIndex,
|
|
229
|
+
},
|
|
230
|
+
"target_peer_id": self.frontend_peer_id,
|
|
231
|
+
}))
|
|
232
|
+
|
|
233
|
+
# Create and send offer
|
|
234
|
+
offer = await self.pc.createOffer()
|
|
235
|
+
await self.pc.setLocalDescription(offer)
|
|
236
|
+
|
|
237
|
+
await ws.send(json.dumps({
|
|
238
|
+
"type": "offer",
|
|
239
|
+
"room_id": self.current_room_id,
|
|
240
|
+
"peer_id": self.robot_id,
|
|
241
|
+
"target_peer_id": self.frontend_peer_id,
|
|
242
|
+
"data": {
|
|
243
|
+
"type": self.pc.localDescription.type,
|
|
244
|
+
"sdp": self.pc.localDescription.sdp,
|
|
245
|
+
},
|
|
246
|
+
}))
|
|
247
|
+
self.get_logger().info(f"Sent SDP offer to {self.frontend_peer_id}")
|
|
248
|
+
|
|
249
|
+
async def _handle_answer(self, msg: dict) -> None:
|
|
250
|
+
"""Set remote SDP answer."""
|
|
251
|
+
if not self.pc:
|
|
252
|
+
return
|
|
253
|
+
answer_data = msg.get("data", {})
|
|
254
|
+
if not answer_data.get("sdp"):
|
|
255
|
+
self.get_logger().warning("Received answer with no SDP data")
|
|
256
|
+
return
|
|
257
|
+
answer = RTCSessionDescription(sdp=answer_data["sdp"], type=answer_data.get("type", "answer"))
|
|
258
|
+
await self.pc.setRemoteDescription(answer)
|
|
259
|
+
self.get_logger().info(f"Set remote SDP answer from {msg.get('from_peer_id')}")
|
|
260
|
+
|
|
261
|
+
async def _handle_ice_candidate(self, msg: dict) -> None:
|
|
262
|
+
"""Add remote ICE candidate."""
|
|
263
|
+
if not self.pc:
|
|
264
|
+
return
|
|
265
|
+
from aiortc import RTCIceCandidate
|
|
266
|
+
# ICE candidate fields are wrapped in a data object
|
|
267
|
+
data = msg.get("data", {})
|
|
268
|
+
candidate_str = data.get("candidate", "")
|
|
269
|
+
if candidate_str:
|
|
270
|
+
# aiortc addIceCandidate expects an RTCIceCandidate, but we can
|
|
271
|
+
# pass the raw info. For simplicity, just log — ICE gathering
|
|
272
|
+
# typically completes with STUN.
|
|
273
|
+
self.get_logger().debug(f"Received ICE candidate: {candidate_str[:60]}...")
|
|
274
|
+
|
|
275
|
+
# --- ROS2 Bridge ---
|
|
276
|
+
|
|
277
|
+
async def _handle_data_channel_message(self, raw: str) -> None:
|
|
278
|
+
"""Parse rosbridge JSON and execute against local ROS2."""
|
|
279
|
+
try:
|
|
280
|
+
msg = json.loads(raw)
|
|
281
|
+
except json.JSONDecodeError:
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
op = msg.get("op", "")
|
|
285
|
+
msg_id = msg.get("id")
|
|
286
|
+
|
|
287
|
+
try:
|
|
288
|
+
if op == "publish":
|
|
289
|
+
self._handle_publish(msg)
|
|
290
|
+
elif op == "subscribe":
|
|
291
|
+
self._handle_subscribe(msg)
|
|
292
|
+
elif op == "unsubscribe":
|
|
293
|
+
self._handle_unsubscribe(msg)
|
|
294
|
+
elif op == "call_service":
|
|
295
|
+
await self._handle_call_service(msg, msg_id)
|
|
296
|
+
elif op == "send_action_goal":
|
|
297
|
+
await self._handle_send_action_goal(msg, msg_id)
|
|
298
|
+
elif op == "cancel_action_goal":
|
|
299
|
+
self._handle_cancel_action_goal(msg)
|
|
300
|
+
else:
|
|
301
|
+
self.get_logger().warning(f"Unknown op: {op}")
|
|
302
|
+
except Exception as e:
|
|
303
|
+
self.get_logger().error(f"Error handling op={op}: {e}")
|
|
304
|
+
if msg_id:
|
|
305
|
+
self._send_response({
|
|
306
|
+
"op": "service_response" if op == "call_service" else "action_result",
|
|
307
|
+
"id": msg_id,
|
|
308
|
+
"result": False,
|
|
309
|
+
"values": {"error": str(e)},
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
def _handle_publish(self, msg: dict) -> None:
|
|
313
|
+
"""Publish a message to a local ROS2 topic."""
|
|
314
|
+
topic = msg["topic"]
|
|
315
|
+
msg_type_str = msg.get("type", "")
|
|
316
|
+
payload = msg.get("msg", {})
|
|
317
|
+
|
|
318
|
+
if topic not in self._pubs:
|
|
319
|
+
msg_class = get_message_class(msg_type_str)
|
|
320
|
+
self._pubs[topic] = self.create_publisher(msg_class, topic, 10)
|
|
321
|
+
|
|
322
|
+
pub = self._pubs[topic]
|
|
323
|
+
ros_msg = dict_to_msg(payload, pub.msg_type())
|
|
324
|
+
pub.publish(ros_msg)
|
|
325
|
+
|
|
326
|
+
def _handle_subscribe(self, msg: dict) -> None:
|
|
327
|
+
"""Subscribe to a local ROS2 topic and forward messages over data channel."""
|
|
328
|
+
topic = msg["topic"]
|
|
329
|
+
msg_type_str = msg.get("type", "")
|
|
330
|
+
|
|
331
|
+
if topic in self._subs:
|
|
332
|
+
return # Already subscribed
|
|
333
|
+
|
|
334
|
+
msg_class = get_message_class(msg_type_str)
|
|
335
|
+
|
|
336
|
+
def callback(ros_msg: Any) -> None:
|
|
337
|
+
payload = msg_to_dict(ros_msg)
|
|
338
|
+
self._send_response({
|
|
339
|
+
"op": "publish",
|
|
340
|
+
"topic": topic,
|
|
341
|
+
"msg": payload,
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
sub = self.create_subscription(msg_class, topic, callback, 10)
|
|
345
|
+
self._subs[topic] = sub
|
|
346
|
+
|
|
347
|
+
def _handle_unsubscribe(self, msg: dict) -> None:
|
|
348
|
+
"""Unsubscribe from a local ROS2 topic."""
|
|
349
|
+
topic = msg["topic"]
|
|
350
|
+
if topic in self._subs:
|
|
351
|
+
self.destroy_subscription(self._subs.pop(topic))
|
|
352
|
+
|
|
353
|
+
async def _handle_call_service(self, msg: dict, msg_id: str | None) -> None:
|
|
354
|
+
"""Call a local ROS2 service and send the response."""
|
|
355
|
+
service = msg["service"]
|
|
356
|
+
srv_type_str = msg.get("type", "")
|
|
357
|
+
args = msg.get("args", {})
|
|
358
|
+
|
|
359
|
+
# Intercept rosapi introspection calls — no rosapi node in Mode C
|
|
360
|
+
if service == "/rosapi/topics":
|
|
361
|
+
topics, types = [], []
|
|
362
|
+
for name, type_list in self.get_topic_names_and_types():
|
|
363
|
+
topics.append(name)
|
|
364
|
+
types.append(type_list[0] if type_list else "")
|
|
365
|
+
self._send_response({
|
|
366
|
+
"op": "service_response",
|
|
367
|
+
"id": msg_id,
|
|
368
|
+
"service": service,
|
|
369
|
+
"result": True,
|
|
370
|
+
"values": {"topics": topics, "types": types},
|
|
371
|
+
})
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
if service == "/rosapi/services":
|
|
375
|
+
services, types = [], []
|
|
376
|
+
for name, type_list in self.get_service_names_and_types():
|
|
377
|
+
services.append(name)
|
|
378
|
+
types.append(type_list[0] if type_list else "")
|
|
379
|
+
self._send_response({
|
|
380
|
+
"op": "service_response",
|
|
381
|
+
"id": msg_id,
|
|
382
|
+
"service": service,
|
|
383
|
+
"result": True,
|
|
384
|
+
"values": {"services": services, "types": types},
|
|
385
|
+
})
|
|
386
|
+
return
|
|
387
|
+
|
|
388
|
+
srv_class = get_service_class(srv_type_str)
|
|
389
|
+
|
|
390
|
+
if service not in self._srv_clients:
|
|
391
|
+
self._srv_clients[service] = self.create_client(srv_class, service)
|
|
392
|
+
|
|
393
|
+
client = self._srv_clients[service]
|
|
394
|
+
|
|
395
|
+
available = await asyncio.to_thread(client.wait_for_service, timeout_sec=5.0)
|
|
396
|
+
if not available:
|
|
397
|
+
self._send_response({
|
|
398
|
+
"op": "service_response",
|
|
399
|
+
"id": msg_id,
|
|
400
|
+
"service": service,
|
|
401
|
+
"result": False,
|
|
402
|
+
"values": {"error": f"Service {service} not available"},
|
|
403
|
+
})
|
|
404
|
+
return
|
|
405
|
+
|
|
406
|
+
request = dict_to_msg(args, srv_class.Request())
|
|
407
|
+
future = client.call_async(request)
|
|
408
|
+
|
|
409
|
+
event = asyncio.Event()
|
|
410
|
+
future.add_done_callback(lambda _: event.set())
|
|
411
|
+
await event.wait()
|
|
412
|
+
|
|
413
|
+
result = future.result()
|
|
414
|
+
self._send_response({
|
|
415
|
+
"op": "service_response",
|
|
416
|
+
"id": msg_id,
|
|
417
|
+
"service": service,
|
|
418
|
+
"result": True,
|
|
419
|
+
"values": msg_to_dict(result),
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
async def _handle_send_action_goal(self, msg: dict, msg_id: str | None) -> None:
|
|
423
|
+
"""Send a goal to a local ROS2 action server."""
|
|
424
|
+
action_name = msg["action"]
|
|
425
|
+
action_type_str = msg.get("action_type", "")
|
|
426
|
+
args = msg.get("args", {})
|
|
427
|
+
|
|
428
|
+
action_class = get_action_class(action_type_str)
|
|
429
|
+
from rclpy.action import ActionClient as RclpyActionClient
|
|
430
|
+
|
|
431
|
+
action_client = RclpyActionClient(self, action_class, action_name)
|
|
432
|
+
|
|
433
|
+
available = await asyncio.to_thread(action_client.wait_for_server, timeout_sec=5.0)
|
|
434
|
+
if not available:
|
|
435
|
+
self._send_response({
|
|
436
|
+
"op": "action_result",
|
|
437
|
+
"id": msg_id,
|
|
438
|
+
"action": action_name,
|
|
439
|
+
"result": False,
|
|
440
|
+
"values": {"error": f"Action server {action_name} not available"},
|
|
441
|
+
})
|
|
442
|
+
action_client.destroy()
|
|
443
|
+
return
|
|
444
|
+
|
|
445
|
+
goal_msg = dict_to_msg(args, action_class.Goal())
|
|
446
|
+
send_goal_future = action_client.send_goal_async(
|
|
447
|
+
goal_msg,
|
|
448
|
+
feedback_callback=lambda feedback: self._send_response({
|
|
449
|
+
"op": "action_feedback",
|
|
450
|
+
"id": msg_id,
|
|
451
|
+
"action": action_name,
|
|
452
|
+
"values": msg_to_dict(feedback.feedback),
|
|
453
|
+
}),
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
goal_event = asyncio.Event()
|
|
457
|
+
send_goal_future.add_done_callback(lambda _: goal_event.set())
|
|
458
|
+
await goal_event.wait()
|
|
459
|
+
|
|
460
|
+
goal_handle = send_goal_future.result()
|
|
461
|
+
if not goal_handle.accepted:
|
|
462
|
+
self._send_response({
|
|
463
|
+
"op": "action_result",
|
|
464
|
+
"id": msg_id,
|
|
465
|
+
"action": action_name,
|
|
466
|
+
"result": False,
|
|
467
|
+
"values": {"error": "Goal rejected"},
|
|
468
|
+
})
|
|
469
|
+
action_client.destroy()
|
|
470
|
+
return
|
|
471
|
+
|
|
472
|
+
result_future = goal_handle.get_result_async()
|
|
473
|
+
result_event = asyncio.Event()
|
|
474
|
+
result_future.add_done_callback(lambda _: result_event.set())
|
|
475
|
+
await result_event.wait()
|
|
476
|
+
|
|
477
|
+
result = result_future.result()
|
|
478
|
+
self._send_response({
|
|
479
|
+
"op": "action_result",
|
|
480
|
+
"id": msg_id,
|
|
481
|
+
"action": action_name,
|
|
482
|
+
"result": True,
|
|
483
|
+
"values": msg_to_dict(result.result),
|
|
484
|
+
})
|
|
485
|
+
action_client.destroy()
|
|
486
|
+
|
|
487
|
+
def _handle_cancel_action_goal(self, msg: dict) -> None:
|
|
488
|
+
"""Cancel an in-progress action goal."""
|
|
489
|
+
self.get_logger().info(f"Cancel action goal: {msg.get('action')}")
|
|
490
|
+
# Action cancellation would require tracking goal handles — left as
|
|
491
|
+
# a future enhancement since it needs per-goal handle storage.
|
|
492
|
+
|
|
493
|
+
def _send_response(self, msg: dict) -> None:
|
|
494
|
+
"""Send a JSON message over the data channel."""
|
|
495
|
+
if self.data_channel and self.data_channel.readyState == "open":
|
|
496
|
+
self.data_channel.send(json.dumps(msg))
|
|
497
|
+
|
|
498
|
+
# --- Helpers ---
|
|
499
|
+
|
|
500
|
+
def _get_capabilities(self) -> list[str]:
|
|
501
|
+
"""Return a list of capability strings for the signaling server."""
|
|
502
|
+
capabilities = []
|
|
503
|
+
for name, types in self.get_topic_names_and_types():
|
|
504
|
+
capabilities.append(f"topic:{name}:{types[0] if types else ''}")
|
|
505
|
+
for name, types in self.get_service_names_and_types():
|
|
506
|
+
capabilities.append(f"service:{name}:{types[0] if types else ''}")
|
|
507
|
+
return capabilities
|
|
508
|
+
|
|
509
|
+
async def _cleanup_webrtc(self) -> None:
|
|
510
|
+
"""Close WebRTC resources."""
|
|
511
|
+
self._cleanup_ros_bridge()
|
|
512
|
+
if self.pc:
|
|
513
|
+
await self.pc.close()
|
|
514
|
+
self.pc = None
|
|
515
|
+
self.data_channel = None
|
|
516
|
+
|
|
517
|
+
def _cleanup_ros_bridge(self) -> None:
|
|
518
|
+
"""Clean up all ROS2 subscriptions and publishers created for the session."""
|
|
519
|
+
for sub in self._subs.values():
|
|
520
|
+
self.destroy_subscription(sub)
|
|
521
|
+
self._subs.clear()
|
|
522
|
+
|
|
523
|
+
for pub in self._pubs.values():
|
|
524
|
+
self.destroy_publisher(pub)
|
|
525
|
+
self._pubs.clear()
|
|
526
|
+
|
|
527
|
+
for client in self._srv_clients.values():
|
|
528
|
+
self.destroy_client(client)
|
|
529
|
+
self._srv_clients.clear()
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
async def async_main() -> None:
|
|
533
|
+
"""Async entry point: run ROS2 spinning + signaling concurrently."""
|
|
534
|
+
rclpy.init()
|
|
535
|
+
node = AgenticROSAgentNode()
|
|
536
|
+
executor = SingleThreadedExecutor()
|
|
537
|
+
executor.add_node(node)
|
|
538
|
+
|
|
539
|
+
# Run ROS2 spin in a background thread
|
|
540
|
+
loop = asyncio.get_event_loop()
|
|
541
|
+
spin_task = loop.run_in_executor(None, executor.spin)
|
|
542
|
+
|
|
543
|
+
try:
|
|
544
|
+
await node.run_signaling()
|
|
545
|
+
except KeyboardInterrupt:
|
|
546
|
+
pass
|
|
547
|
+
finally:
|
|
548
|
+
node.get_logger().info("Shutting down...")
|
|
549
|
+
executor.shutdown()
|
|
550
|
+
node.destroy_node()
|
|
551
|
+
rclpy.shutdown()
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def main() -> None:
|
|
555
|
+
"""Entry point for the agent node."""
|
|
556
|
+
logging.basicConfig(level=logging.INFO)
|
|
557
|
+
asyncio.run(async_main())
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
if __name__ == "__main__":
|
|
561
|
+
main()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
|
3
|
+
<package format="3">
|
|
4
|
+
<name>agenticros_agent</name>
|
|
5
|
+
<version>0.0.1</version>
|
|
6
|
+
<description>ROS2 agent node that bridges WebRTC data channels to local DDS for cloud/remote deployments (Mode C)</description>
|
|
7
|
+
<maintainer email="team@plaipin.com">PlaiPin</maintainer>
|
|
8
|
+
<license>Apache-2.0</license>
|
|
9
|
+
|
|
10
|
+
<depend>rclpy</depend>
|
|
11
|
+
<depend>std_msgs</depend>
|
|
12
|
+
<depend>rosbridge_library</depend>
|
|
13
|
+
|
|
14
|
+
<exec_depend>python3-aiortc</exec_depend>
|
|
15
|
+
<exec_depend>python3-websockets</exec_depend>
|
|
16
|
+
|
|
17
|
+
<test_depend>ament_copyright</test_depend>
|
|
18
|
+
<test_depend>ament_flake8</test_depend>
|
|
19
|
+
<test_depend>ament_pep257</test_depend>
|
|
20
|
+
<test_depend>python3-pytest</test_depend>
|
|
21
|
+
|
|
22
|
+
<export>
|
|
23
|
+
<build_type>ament_python</build_type>
|
|
24
|
+
</export>
|
|
25
|
+
</package>
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import find_packages, setup
|
|
2
|
+
|
|
3
|
+
package_name = "agenticros_agent"
|
|
4
|
+
|
|
5
|
+
setup(
|
|
6
|
+
name=package_name,
|
|
7
|
+
version="0.0.1",
|
|
8
|
+
packages=find_packages(exclude=["test"]),
|
|
9
|
+
data_files=[
|
|
10
|
+
("share/ament_index/resource_index/packages", ["resource/" + package_name]),
|
|
11
|
+
("share/" + package_name, ["package.xml"]),
|
|
12
|
+
],
|
|
13
|
+
install_requires=["setuptools", "aiortc", "websockets"],
|
|
14
|
+
zip_safe=True,
|
|
15
|
+
maintainer="PlaiPin",
|
|
16
|
+
maintainer_email="team@plaipin.com",
|
|
17
|
+
description="ROS2 agent node for cloud/remote WebRTC bridge (Mode C)",
|
|
18
|
+
license="Apache-2.0",
|
|
19
|
+
tests_require=["pytest"],
|
|
20
|
+
entry_points={
|
|
21
|
+
"console_scripts": [
|
|
22
|
+
"agent_node = agenticros_agent.agent_node:main",
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
)
|