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,31 @@
1
+ /**
2
+ * Many mobile bases expect the opposite angular.z sign vs standard ROS base_link
3
+ * (positive = CCW / "left"). Negate once at publish so teleop, chat, and skills agree.
4
+ */
5
+
6
+ const TWIST_TYPE_RE = /geometry_msgs\/(msg\/)?Twist/i;
7
+
8
+ function isCmdVelTopic(topic: string): boolean {
9
+ const normalized = topic.trim().replace(/\/+$/, "");
10
+ return normalized === "/cmd_vel" || /\/cmd_vel$/i.test(normalized);
11
+ }
12
+
13
+ /**
14
+ * If this is a geometry_msgs/Twist on a cmd_vel topic, return a copy with angular.z negated.
15
+ */
16
+ export function applyCmdVelTwistSignConvention(topic: string, type: string, msg: Record<string, unknown>): Record<string, unknown> {
17
+ const t = type.trim();
18
+ if (!TWIST_TYPE_RE.test(t)) return msg;
19
+ if (!isCmdVelTopic(topic)) return msg;
20
+ const angular = msg["angular"];
21
+ if (!angular || typeof angular !== "object") return msg;
22
+ const a = angular as Record<string, unknown>;
23
+ const z = Number(a["z"] ?? 0);
24
+ return {
25
+ ...msg,
26
+ angular: {
27
+ ...a,
28
+ z: -z,
29
+ },
30
+ };
31
+ }
@@ -0,0 +1,279 @@
1
+ import { z } from "zod";
2
+ import type { TransportConfig } from "./transport/types.js";
3
+
4
+ const IceServerSchema = z.object({
5
+ urls: z.union([z.string(), z.array(z.string())]),
6
+ username: z.string().optional(),
7
+ credential: z.string().optional(),
8
+ });
9
+
10
+ export const AgenticROSConfigSchema = z.object({
11
+ transport: z
12
+ .object({
13
+ /**
14
+ * Default is "local" — assumes the gateway is co-located with the robot
15
+ * (Mode A: DDS direct via rclnodejs, no router). Switch to "rosbridge",
16
+ * "zenoh", or "webrtc" when the gateway runs off-robot.
17
+ */
18
+ mode: z.enum(["rosbridge", "local", "webrtc", "zenoh"]).default("local"),
19
+ })
20
+ .default({}),
21
+
22
+ zenoh: z
23
+ .object({
24
+ /** WebSocket URL for zenoh-ts (zenoh-plugin-remote-api). Not tcp/ — use e.g. ws://localhost:10000 */
25
+ routerEndpoint: z.string().default("ws://localhost:10000"),
26
+ domainId: z.number().default(0),
27
+ /** "ros2dds" = zenoh-bridge-ros2dds key format (slashes kept). "rmw_zenoh" = rmw_zenoh key format (domain + %). */
28
+ keyFormat: z.enum(["ros2dds", "rmw_zenoh"]).default("ros2dds"),
29
+ /**
30
+ * Must match zenoh-bridge-ros2dds `plugins.ros2dds.namespace` when it is not the default "/".
31
+ * Example: bridge has `namespace: "/bot1"` → set `bridgeNamespace` to "/bot1" or "bot1".
32
+ * Omit or use "/" when the bridge uses the default (only the ROS topic path is the zenoh key).
33
+ */
34
+ bridgeNamespace: z.string().optional(),
35
+ })
36
+ .default({}),
37
+
38
+ rosbridge: z
39
+ .object({
40
+ url: z.string().default("ws://localhost:9090"),
41
+ reconnect: z.boolean().default(true),
42
+ reconnectInterval: z.number().default(3000),
43
+ })
44
+ .default({}),
45
+
46
+ local: z
47
+ .object({
48
+ domainId: z.number().default(0),
49
+ })
50
+ .default({}),
51
+
52
+ webrtc: z
53
+ .object({
54
+ signalingUrl: z.string().default(""),
55
+ apiUrl: z.string().default(""),
56
+ robotId: z.string().default(""),
57
+ robotKey: z.string().default(""),
58
+ iceServers: z
59
+ .array(IceServerSchema)
60
+ .default([{ urls: "stun:stun.l.google.com:19302" }]),
61
+ })
62
+ .default({}),
63
+
64
+ robot: z
65
+ .object({
66
+ name: z.string().default("Robot"),
67
+ namespace: z.string().default(""),
68
+ /** Camera topic for "what do you see?" (e.g. /camera/camera/color/image_raw/compressed). If set, used as default in ros2_camera_snapshot and in context. */
69
+ cameraTopic: z.string().default(""),
70
+ })
71
+ .default({}),
72
+
73
+ /** Phase 3 teleop web app: camera + twist controls. */
74
+ teleop: z
75
+ .object({
76
+ /** Default camera topic when only one source or as default selection. Falls back to robot.cameraTopic then RealSense default. */
77
+ cameraTopic: z.string().default(""),
78
+ /** Explicit list of camera sources for the selector; if empty, derived from listTopics() filtered by Image/CompressedImage. */
79
+ cameraTopics: z
80
+ .array(z.object({ topic: z.string(), label: z.string().optional() }))
81
+ .default([]),
82
+ /** cmd_vel topic override (default from robot namespace). */
83
+ cmdVelTopic: z.string().default(""),
84
+ /** Default linear speed (m/s) for teleop. */
85
+ speedDefault: z.coerce.number().min(0).max(2).default(0.3),
86
+ /** Camera poll interval in ms for the teleop page. */
87
+ cameraPollMs: z.number().min(50).max(2000).default(150),
88
+ })
89
+ .default({}),
90
+
91
+ safety: z
92
+ .object({
93
+ maxLinearVelocity: z.number().default(1.0),
94
+ maxAngularVelocity: z.number().default(1.5),
95
+ workspaceLimits: z
96
+ .object({
97
+ xMin: z.number().default(-10),
98
+ xMax: z.number().default(10),
99
+ yMin: z.number().default(-10),
100
+ yMax: z.number().default(10),
101
+ })
102
+ .default({}),
103
+ })
104
+ .default({}),
105
+
106
+ /**
107
+ * Optional in-plugin image describer.
108
+ *
109
+ * When the primary chat model is text-only (e.g. Ollama qwen2.5:7b),
110
+ * OpenClaw filters image content blocks out of tool results before the
111
+ * LLM ever sees them, so `ros2_camera_snapshot` cannot be described.
112
+ * Configuring an OpenAI-compatible chat completions endpoint here lets
113
+ * the plugin call a vision model itself and embed the description text
114
+ * directly in the snapshot tool result.
115
+ *
116
+ * Recommended Jetson setup: point at local Ollama with a VL model:
117
+ * url: "http://host.docker.internal:11434/v1/chat/completions"
118
+ * model: "qwen2.5vl:7b"
119
+ *
120
+ * If `enabled: false`, the snapshot result includes only the camera URL
121
+ * and the agent is on its own (suitable when the primary model is
122
+ * multimodal and OpenClaw does not need to filter images).
123
+ */
124
+ describer: z
125
+ .object({
126
+ enabled: z.boolean().default(false),
127
+ /** OpenAI-compat chat completions endpoint URL. */
128
+ url: z
129
+ .string()
130
+ // host.openshell.internal:11435 is the nemoclaw ollama-auth-proxy
131
+ // bound to the docker bridge — it's the ONLY way to reach the
132
+ // host's Ollama from a NemoClaw sandbox. Two reasons:
133
+ // 1. The default Ollama daemon binds 127.0.0.1:11434 on the
134
+ // host, which is not reachable from the bridge.
135
+ // 2. The built-in `local-inference` policy permits POST to
136
+ // host.openshell.internal:11435 (and 11434/8000) but the
137
+ // OPA proxy will refuse a forward to any other port.
138
+ // The auth-proxy requires `Authorization: Bearer <token>` from
139
+ // ~/.nemoclaw/ollama-proxy-token — set ``describer.apiKey``.
140
+ .default("http://host.openshell.internal:11435/v1/chat/completions"),
141
+ /** Optional Bearer token / API key for the endpoint. */
142
+ apiKey: z.string().optional(),
143
+ /** Vision model id (must accept OpenAI `image_url` content blocks). */
144
+ model: z.string().default("qwen2.5vl:7b"),
145
+ /** Prompt sent alongside the image. */
146
+ prompt: z
147
+ .string()
148
+ .default(
149
+ "Describe what is visible in this camera frame from a mobile robot. " +
150
+ "Focus on the physical scene: objects, materials, layout, colors, lighting, distances. " +
151
+ "Be concrete and specific. Do not speculate or add detail you cannot see.",
152
+ ),
153
+ /** Max tokens for the description. */
154
+ maxTokens: z.number().int().min(50).max(2048).default(400),
155
+ /** Request timeout in ms. */
156
+ timeoutMs: z.number().int().min(1000).max(180000).default(60000),
157
+ /**
158
+ * Resize images larger than this max dimension before sending to the
159
+ * VL model. Reduces token usage and latency. Set to 0 to disable.
160
+ */
161
+ maxImageDimension: z.number().int().min(0).max(4096).default(896),
162
+ })
163
+ .default({}),
164
+
165
+ /**
166
+ * Optional cross-adapter semantic memory subsystem.
167
+ *
168
+ * When `enabled: true`, OpenClaw, Claude Code, and Gemini adapters all
169
+ * register four memory tools (memory_remember / memory_recall /
170
+ * memory_forget / memory_status) backed by a shared store. Default is
171
+ * off — zero new deps when disabled.
172
+ *
173
+ * See docs/memory.md for ready-to-paste recipes.
174
+ */
175
+ memory: z
176
+ .object({
177
+ /** Master switch. When false, adapters skip registering memory tools. */
178
+ enabled: z.boolean().default(false),
179
+ /** Which backend to use. "local" has no extra deps; "mem0" needs `pnpm add mem0ai`. */
180
+ backend: z.enum(["local", "mem0"]).default("local"),
181
+ /** Override the per-record namespace. Defaults to robot.namespace at call time. */
182
+ namespace: z.string().optional(),
183
+ local: z
184
+ .object({
185
+ /** JSON-on-disk store path. Supports leading "~/". */
186
+ storePath: z.string().default("~/.agenticros/memory.json"),
187
+ })
188
+ .default({}),
189
+ mem0: z
190
+ .object({
191
+ /**
192
+ * When true, mem0 runs its LLM-driven fact extraction on `add`.
193
+ * When false (default), content is stored as-is — predictable,
194
+ * no LLM call on write, agent stays in explicit control.
195
+ */
196
+ inferOnWrite: z.boolean().default(false),
197
+ /** SQLite history db path (mem0 historyDbPath). Supports leading "~/". */
198
+ historyDbPath: z
199
+ .string()
200
+ .default("~/.agenticros/memory-history.db"),
201
+ /**
202
+ * Embedder passed verbatim to `new Memory({ embedder })`.
203
+ * When omitted, the factory auto-detects: Ollama (if reachable)
204
+ * → OpenAI (if OPENAI_API_KEY set) → error.
205
+ */
206
+ embedder: z
207
+ .object({
208
+ provider: z.string(),
209
+ config: z.record(z.string(), z.unknown()),
210
+ })
211
+ .optional(),
212
+ /** Vector store passed verbatim to `new Memory({ vectorStore })`. */
213
+ vectorStore: z
214
+ .object({
215
+ provider: z.string(),
216
+ config: z.record(z.string(), z.unknown()),
217
+ })
218
+ .optional(),
219
+ /** LLM passed verbatim to `new Memory({ llm })`. Only used when inferOnWrite is true. */
220
+ llm: z
221
+ .object({
222
+ provider: z.string(),
223
+ config: z.record(z.string(), z.unknown()),
224
+ })
225
+ .optional(),
226
+ })
227
+ .default({}),
228
+ })
229
+ .default({}),
230
+
231
+ /** Per-skill config. Keys are skill ids (e.g. followme). Each skill validates its own slice. */
232
+ skills: z.record(z.string(), z.unknown()).default({}),
233
+
234
+ /** Directories to scan for skill packages (package.json with "agenticrosSkill": true). Resolved at gateway start. */
235
+ skillPaths: z.array(z.string()).default([]),
236
+
237
+ /** Npm (or local) package names to load as skills. Resolved via require.resolve from plugin context. */
238
+ skillPackages: z.array(z.string()).default([]),
239
+ });
240
+
241
+ export type AgenticROSConfig = z.infer<typeof AgenticROSConfigSchema>;
242
+
243
+ /**
244
+ * Parse and validate raw config against the schema.
245
+ * Returns a fully-defaulted, typed config object.
246
+ * Backward compat: if raw.followMe is set, it is merged into raw.skills.followme before parsing.
247
+ */
248
+ export function parseConfig(raw: Record<string, unknown>): AgenticROSConfig {
249
+ const normalized = { ...raw };
250
+ const followMe = raw.followMe;
251
+ if (followMe !== undefined && followMe !== null && typeof followMe === "object") {
252
+ const skills = (normalized.skills as Record<string, unknown>) ?? {};
253
+ if (!(typeof skills === "object" && skills !== null && !Array.isArray(skills))) {
254
+ (normalized as Record<string, unknown>).skills = { followme: followMe };
255
+ } else if (!("followme" in skills)) {
256
+ (normalized as Record<string, unknown>).skills = { ...skills, followme: followMe };
257
+ }
258
+ }
259
+ return AgenticROSConfigSchema.parse(normalized);
260
+ }
261
+
262
+ /**
263
+ * Build TransportConfig from full config for createTransport().
264
+ */
265
+ export function getTransportConfig(config: AgenticROSConfig): TransportConfig {
266
+ const mode = config.transport?.mode ?? "local";
267
+ switch (mode) {
268
+ case "rosbridge":
269
+ return { mode: "rosbridge", rosbridge: config.rosbridge ?? { url: "ws://localhost:9090" } };
270
+ case "local":
271
+ return { mode: "local", local: config.local };
272
+ case "webrtc":
273
+ return { mode: "webrtc", webrtc: config.webrtc ?? {} };
274
+ case "zenoh":
275
+ return { mode: "zenoh", zenoh: config.zenoh ?? {} };
276
+ default:
277
+ return { mode: "local", local: config.local ?? { domainId: 0 } };
278
+ }
279
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @agenticros/core — Platform-agnostic ROS2 transport, config, and utilities.
3
+ */
4
+
5
+ export type { AgenticROSConfig } from "./config.js";
6
+ export {
7
+ AgenticROSConfigSchema,
8
+ parseConfig,
9
+ getTransportConfig,
10
+ } from "./config.js";
11
+
12
+ export { createTransport } from "./transport/factory.js";
13
+ export type { RosTransport } from "./transport/transport.js";
14
+ export type {
15
+ ConnectionStatus,
16
+ ConnectionHandler,
17
+ Subscription,
18
+ PublishOptions,
19
+ AdvertiseOptions,
20
+ SubscribeOptions,
21
+ ServiceCallOptions,
22
+ ServiceCallResult,
23
+ ActionGoalOptions,
24
+ ActionResult,
25
+ TopicInfo,
26
+ ServiceInfo,
27
+ ActionInfo,
28
+ MessageHandler,
29
+ TransportConfig,
30
+ } from "./transport/types.js";
31
+
32
+ export {
33
+ toNamespacedTopic,
34
+ toNamespacedTopicFull,
35
+ toTeleopCameraTopicShort,
36
+ resolveCameraSubscribeTopic,
37
+ } from "./topic-utils.js";
38
+ export { applyCmdVelTwistSignConvention } from "./cmd-vel-twist.js";
39
+ export { isCdrTypeSupported } from "./transport/zenoh/cdr.js";
40
+ export {
41
+ renderAgenticROSBanner,
42
+ agenticROSBannerLines,
43
+ type AgenticROSBannerOptions,
44
+ } from "./banner.js";
45
+
46
+ export { createMemory, resolveMemoryNamespace } from "./memory/index.js";
47
+ export type {
48
+ MemoryProvider,
49
+ MemoryRecord,
50
+ MemoryStatus,
51
+ RememberInput,
52
+ RecallInput,
53
+ ForgetInput,
54
+ } from "./memory/index.js";
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Unit tests for createMemory() factory and resolveMemoryNamespace().
3
+ */
4
+ import { test } from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { mkdtemp, rm } from "node:fs/promises";
7
+ import { tmpdir } from "node:os";
8
+ import path from "node:path";
9
+
10
+ import { parseConfig } from "../../config.js";
11
+ import { createMemory, resolveMemoryNamespace } from "../factory.js";
12
+
13
+ test("factory: returns null when memory.enabled is false (default)", async () => {
14
+ const config = parseConfig({});
15
+ const provider = await createMemory(config);
16
+ assert.equal(provider, null);
17
+ });
18
+
19
+ test("factory: returns null when memory block omitted entirely", async () => {
20
+ const config = parseConfig({ robot: { namespace: "robotA" } });
21
+ const provider = await createMemory(config);
22
+ assert.equal(provider, null);
23
+ });
24
+
25
+ test("factory: builds local provider when enabled with backend=local", async () => {
26
+ const dir = await mkdtemp(path.join(tmpdir(), "agenticros-fac-"));
27
+ try {
28
+ const config = parseConfig({
29
+ memory: {
30
+ enabled: true,
31
+ backend: "local",
32
+ local: { storePath: path.join(dir, "memory.json") },
33
+ },
34
+ });
35
+ const provider = await createMemory(config);
36
+ assert.ok(provider, "provider must not be null");
37
+ assert.equal(provider!.backend, "local");
38
+ } finally {
39
+ await rm(dir, { recursive: true, force: true });
40
+ }
41
+ });
42
+
43
+ test("resolveMemoryNamespace: argument wins over config", () => {
44
+ const config = parseConfig({
45
+ robot: { namespace: "robotA" },
46
+ memory: { enabled: true, namespace: "shared-ns" },
47
+ });
48
+ assert.equal(resolveMemoryNamespace(config, "explicit"), "explicit");
49
+ });
50
+
51
+ test("resolveMemoryNamespace: memory.namespace wins over robot.namespace", () => {
52
+ const config = parseConfig({
53
+ robot: { namespace: "robotA" },
54
+ memory: { enabled: true, namespace: "shared-ns" },
55
+ });
56
+ assert.equal(resolveMemoryNamespace(config), "shared-ns");
57
+ });
58
+
59
+ test("resolveMemoryNamespace: falls back to robot.namespace", () => {
60
+ const config = parseConfig({
61
+ robot: { namespace: "robotA" },
62
+ memory: { enabled: true },
63
+ });
64
+ assert.equal(resolveMemoryNamespace(config), "robotA");
65
+ });
66
+
67
+ test("resolveMemoryNamespace: empty robot namespace yields empty string", () => {
68
+ const config = parseConfig({ memory: { enabled: true } });
69
+ assert.equal(resolveMemoryNamespace(config), "");
70
+ });
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Unit tests for the local JSON-on-disk memory backend.
3
+ *
4
+ * Run via `pnpm --filter @agenticros/core test` (after build).
5
+ * Uses Node's built-in node:test runner — no extra deps.
6
+ */
7
+ import { test } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import { mkdtemp, rm, readFile } from "node:fs/promises";
10
+ import { tmpdir } from "node:os";
11
+ import path from "node:path";
12
+
13
+ import { LocalMemoryProvider } from "../local/provider.js";
14
+
15
+ async function withTempStore<T>(
16
+ fn: (provider: LocalMemoryProvider, dir: string) => Promise<T>,
17
+ ): Promise<T> {
18
+ const dir = await mkdtemp(path.join(tmpdir(), "agenticros-mem-test-"));
19
+ const storePath = path.join(dir, "memory.json");
20
+ const provider = new LocalMemoryProvider({ storePath });
21
+ try {
22
+ return await fn(provider, dir);
23
+ } finally {
24
+ await rm(dir, { recursive: true, force: true });
25
+ }
26
+ }
27
+
28
+ test("local: remember then recall returns the record", async () => {
29
+ await withTempStore(async (provider) => {
30
+ const stored = await provider.remember({
31
+ namespace: "robotA",
32
+ content: "The kitchen rug is fragile",
33
+ tags: ["preference"],
34
+ });
35
+ assert.ok(stored.id);
36
+ assert.equal(stored.namespace, "robotA");
37
+
38
+ const hits = await provider.recall({ namespace: "robotA", query: "kitchen rug" });
39
+ assert.equal(hits.length, 1);
40
+ assert.equal(hits[0].content, "The kitchen rug is fragile");
41
+ assert.ok((hits[0].score ?? 0) > 0);
42
+ });
43
+ });
44
+
45
+ test("local: namespace isolation — robotB cannot see robotA memories", async () => {
46
+ await withTempStore(async (provider) => {
47
+ await provider.remember({ namespace: "robotA", content: "kitchen rug fragile" });
48
+ await provider.remember({ namespace: "robotB", content: "garage door is loud" });
49
+
50
+ const aHits = await provider.recall({ namespace: "robotA", query: "kitchen" });
51
+ const bHits = await provider.recall({ namespace: "robotB", query: "kitchen" });
52
+ assert.equal(aHits.length, 1);
53
+ assert.equal(bHits.length, 0);
54
+ });
55
+ });
56
+
57
+ test("local: recall ranks by overlap with recency tie-break", async () => {
58
+ await withTempStore(async (provider) => {
59
+ const older = await provider.remember({
60
+ namespace: "ns",
61
+ content: "the cat is on the mat",
62
+ });
63
+ // Backdate older record by 30 days by writing the file directly.
64
+ // Easier: just stagger creation and assert order.
65
+ await new Promise((r) => setTimeout(r, 10));
66
+ const newer = await provider.remember({
67
+ namespace: "ns",
68
+ content: "the cat is on the mat",
69
+ });
70
+
71
+ const hits = await provider.recall({ namespace: "ns", query: "cat mat" });
72
+ assert.equal(hits.length, 2);
73
+ assert.equal(hits[0].id, newer.id, "newer record should rank first when scores tie");
74
+ assert.equal(hits[1].id, older.id);
75
+ });
76
+ });
77
+
78
+ test("local: forget by id deletes exactly one record", async () => {
79
+ await withTempStore(async (provider) => {
80
+ const a = await provider.remember({ namespace: "ns", content: "fact one" });
81
+ const b = await provider.remember({ namespace: "ns", content: "fact two" });
82
+
83
+ const result = await provider.forget({ id: a.id });
84
+ assert.equal(result.removed, 1);
85
+
86
+ const status = await provider.status("ns");
87
+ assert.equal(status.recordCount, 1);
88
+
89
+ const hits = await provider.recall({ namespace: "ns", query: "fact" });
90
+ assert.equal(hits.length, 1);
91
+ assert.equal(hits[0].id, b.id);
92
+ });
93
+ });
94
+
95
+ test("local: forget by query removes only matching records in namespace", async () => {
96
+ await withTempStore(async (provider) => {
97
+ await provider.remember({ namespace: "ns", content: "kitchen rug fragile" });
98
+ await provider.remember({ namespace: "ns", content: "couch is comfortable" });
99
+ await provider.remember({ namespace: "other", content: "kitchen rug fragile" });
100
+
101
+ const result = await provider.forget({ namespace: "ns", query: "kitchen" });
102
+ assert.equal(result.removed, 1);
103
+
104
+ const nsHits = await provider.recall({ namespace: "ns", query: "kitchen" });
105
+ const otherHits = await provider.recall({ namespace: "other", query: "kitchen" });
106
+ assert.equal(nsHits.length, 0);
107
+ assert.equal(otherHits.length, 1, "other-namespace record must survive");
108
+ });
109
+ });
110
+
111
+ test("local: forget by namespace removes everything in that namespace", async () => {
112
+ await withTempStore(async (provider) => {
113
+ await provider.remember({ namespace: "ns", content: "a" });
114
+ await provider.remember({ namespace: "ns", content: "b" });
115
+ await provider.remember({ namespace: "other", content: "c" });
116
+
117
+ const result = await provider.forget({ namespace: "ns" });
118
+ assert.equal(result.removed, 2);
119
+
120
+ assert.equal((await provider.status("ns")).recordCount, 0);
121
+ assert.equal((await provider.status("other")).recordCount, 1);
122
+ });
123
+ });
124
+
125
+ test("local: forget without any selector throws", async () => {
126
+ await withTempStore(async (provider) => {
127
+ await assert.rejects(() => provider.forget({}), /requires one of/);
128
+ });
129
+ });
130
+
131
+ test("local: status reports correct count and lastWriteAt", async () => {
132
+ await withTempStore(async (provider) => {
133
+ const before = Date.now();
134
+ await provider.remember({ namespace: "ns", content: "hello" });
135
+ const status = await provider.status("ns");
136
+ assert.equal(status.backend, "local");
137
+ assert.equal(status.namespace, "ns");
138
+ assert.equal(status.recordCount, 1);
139
+ assert.ok(status.lastWriteAt !== null && status.lastWriteAt >= before);
140
+ });
141
+ });
142
+
143
+ test("local: persists across instances (data on disk)", async () => {
144
+ await withTempStore(async (provider, dir) => {
145
+ const storePath = path.join(dir, "memory.json");
146
+ await provider.remember({ namespace: "ns", content: "persistent fact" });
147
+
148
+ const raw = await readFile(storePath, "utf8");
149
+ const parsed = JSON.parse(raw);
150
+ assert.equal(parsed.version, 1);
151
+ assert.equal(parsed.records.length, 1);
152
+
153
+ // New provider over the same file should see the record.
154
+ const reopened = new LocalMemoryProvider({ storePath });
155
+ const hits = await reopened.recall({ namespace: "ns", query: "persistent" });
156
+ assert.equal(hits.length, 1);
157
+ });
158
+ });
159
+
160
+ test("local: requires namespace", async () => {
161
+ await withTempStore(async (provider) => {
162
+ await assert.rejects(
163
+ () => provider.remember({ content: "no namespace" }),
164
+ /namespace is required/,
165
+ );
166
+ });
167
+ });
168
+
169
+ test("local: recent() returns most-recent first, scoped by namespace, capped at limit", async () => {
170
+ await withTempStore(async (provider) => {
171
+ // Stagger createdAt by 1ms via tiny sleeps so ordering is deterministic.
172
+ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
173
+ await provider.remember({ namespace: "ns", content: "fact 1 (oldest)" });
174
+ await sleep(2);
175
+ await provider.remember({ namespace: "ns", content: "fact 2" });
176
+ await sleep(2);
177
+ await provider.remember({ namespace: "other", content: "from other ns" });
178
+ await sleep(2);
179
+ await provider.remember({ namespace: "ns", content: "fact 3 (newest)" });
180
+
181
+ const recent = await provider.recent("ns", 5);
182
+ assert.equal(recent.length, 3, "should only count records in 'ns'");
183
+ assert.equal(recent[0].content, "fact 3 (newest)");
184
+ assert.equal(recent[1].content, "fact 2");
185
+ assert.equal(recent[2].content, "fact 1 (oldest)");
186
+
187
+ const limited = await provider.recent("ns", 2);
188
+ assert.equal(limited.length, 2);
189
+ assert.equal(limited[0].content, "fact 3 (newest)");
190
+ assert.equal(limited[1].content, "fact 2");
191
+
192
+ const emptyNs = await provider.recent("nobody", 5);
193
+ assert.equal(emptyNs.length, 0);
194
+ });
195
+ });