agenticros 0.3.4 → 0.4.0

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 (79) hide show
  1. package/README.md +18 -3
  2. package/dist/commands/doctor.d.ts.map +1 -1
  3. package/dist/commands/doctor.js +34 -1
  4. package/dist/commands/doctor.js.map +1 -1
  5. package/dist/commands/skills.d.ts +3 -5
  6. package/dist/commands/skills.d.ts.map +1 -1
  7. package/dist/commands/skills.js +21 -7
  8. package/dist/commands/skills.js.map +1 -1
  9. package/dist/util/skill-manifest.d.ts.map +1 -1
  10. package/dist/util/skill-manifest.js +22 -1
  11. package/dist/util/skill-manifest.js.map +1 -1
  12. package/dist/util/skills.d.ts +6 -0
  13. package/dist/util/skills.d.ts.map +1 -1
  14. package/dist/util/skills.js +69 -1
  15. package/dist/util/skills.js.map +1 -1
  16. package/package.json +2 -1
  17. package/runtime/BUNDLE.json +1 -1
  18. package/runtime/README.md +31 -6
  19. package/runtime/docs/cli.md +1 -1
  20. package/runtime/docs/robot-setup.md +1 -1
  21. package/runtime/packages/agenticros/openclaw.plugin.json +147 -1
  22. package/runtime/packages/agenticros/src/__tests__/capabilities-plugin.test.ts +2 -0
  23. package/runtime/packages/agenticros/src/index.ts +8 -1
  24. package/runtime/packages/agenticros/src/routes.ts +4 -3
  25. package/runtime/packages/agenticros/src/tools/index.ts +6 -5
  26. package/runtime/packages/agenticros/src/tools/mission-pause.ts +49 -0
  27. package/runtime/packages/agenticros/src/tools/mission-resume.ts +41 -0
  28. package/runtime/packages/agenticros/src/tools/ros2-capabilities.ts +9 -12
  29. package/runtime/packages/agenticros/src/tools/ros2-mission.ts +33 -84
  30. package/runtime/packages/agenticros-claude-code/README.md +2 -0
  31. package/runtime/packages/agenticros-claude-code/dist/config.d.ts +5 -3
  32. package/runtime/packages/agenticros-claude-code/dist/config.d.ts.map +1 -1
  33. package/runtime/packages/agenticros-claude-code/dist/config.js +32 -10
  34. package/runtime/packages/agenticros-claude-code/dist/config.js.map +1 -1
  35. package/runtime/packages/agenticros-claude-code/dist/index.js +3 -3
  36. package/runtime/packages/agenticros-claude-code/dist/index.js.map +1 -1
  37. package/runtime/packages/agenticros-claude-code/dist/tools.d.ts.map +1 -1
  38. package/runtime/packages/agenticros-claude-code/dist/tools.js +137 -118
  39. package/runtime/packages/agenticros-claude-code/dist/tools.js.map +1 -1
  40. package/runtime/packages/agenticros-claude-code/src/config.ts +33 -10
  41. package/runtime/packages/agenticros-claude-code/src/index.ts +3 -3
  42. package/runtime/packages/agenticros-claude-code/src/tools.ts +151 -107
  43. package/runtime/packages/agenticros-gemini/package.json +2 -0
  44. package/runtime/packages/agenticros-gemini/src/config.ts +27 -18
  45. package/runtime/packages/agenticros-gemini/src/find-object/coco-classes.ts +38 -0
  46. package/runtime/packages/agenticros-gemini/src/find-object/find-object.ts +191 -0
  47. package/runtime/packages/agenticros-gemini/src/follow-me/controller.ts +109 -0
  48. package/runtime/packages/agenticros-gemini/src/follow-me/depth-loop.ts +452 -0
  49. package/runtime/packages/agenticros-gemini/src/follow-me/detector.ts +303 -0
  50. package/runtime/packages/agenticros-gemini/src/follow-me/loop.ts +359 -0
  51. package/runtime/packages/agenticros-gemini/src/index.ts +2 -2
  52. package/runtime/packages/agenticros-gemini/src/tools.ts +356 -92
  53. package/runtime/packages/core/README.md +1 -1
  54. package/runtime/packages/core/package.json +1 -1
  55. package/runtime/packages/core/src/__tests__/config-persistence.test.ts +26 -0
  56. package/runtime/packages/core/src/__tests__/external-capability.test.ts +72 -0
  57. package/runtime/packages/core/src/__tests__/heartbeat-fleet.test.ts +152 -0
  58. package/runtime/packages/core/src/__tests__/mission-bindings.test.ts +101 -0
  59. package/runtime/packages/core/src/__tests__/mission-pause.test.ts +104 -0
  60. package/runtime/packages/core/src/__tests__/skill-refs.test.ts +77 -0
  61. package/runtime/packages/core/src/capabilities.ts +38 -3
  62. package/runtime/packages/core/src/capability-schema.ts +58 -0
  63. package/runtime/packages/core/src/config.ts +28 -0
  64. package/runtime/packages/core/src/discoverable-capabilities.ts +178 -0
  65. package/runtime/packages/core/src/discovery.ts +21 -3
  66. package/runtime/packages/core/src/external-capability.ts +232 -0
  67. package/runtime/packages/core/src/fleet-config.ts +91 -0
  68. package/runtime/packages/core/src/heartbeat.ts +167 -0
  69. package/runtime/packages/core/src/index.ts +85 -0
  70. package/runtime/packages/core/src/mission-bindings.ts +189 -0
  71. package/runtime/packages/core/src/mission-registry.ts +31 -3
  72. package/runtime/packages/core/src/mission.ts +64 -7
  73. package/runtime/packages/core/src/robots.ts +6 -2
  74. package/runtime/packages/core/src/skill-refs.ts +330 -0
  75. package/runtime/packages/core/src/transport/rosbridge/client.ts +7 -0
  76. package/runtime/pnpm-lock.yaml +9 -0
  77. package/templates/skills/camera/package.json +1 -1
  78. package/templates/skills/depth/package.json +1 -1
  79. package/templates/skills/robot/package.json +1 -1
package/runtime/README.md CHANGED
@@ -33,6 +33,29 @@ With AgenticROS, your robot can describe what it sees, follow intent ("go check
33
33
 
34
34
  AgenticROS is built so that new adapters (LangGraph, OpenAI, local models, voice stacks, etc.) can be added without touching the ROS 2 layer. The core transport and tool contract are platform-agnostic; adapters are thin shims that surface those tools to each agent runtime.
35
35
 
36
+ ## Local VLM (Ollama) — no cloud API required
37
+
38
+ Control your robot with a **local vision-language model** instead of OpenAI or other cloud APIs. AgenticROS robot tools work the same; you point **OpenClaw** or **Hermes** at Ollama on your machine.
39
+
40
+ ```bash
41
+ ollama pull qwen3-vl:8b-instruct # recommended: chat + tools + camera vision
42
+ # or: ollama pull qwen3-vl:2b # smaller hardware
43
+
44
+ npx agenticros init # skip the OpenAI key step when prompted
45
+ # Configure OpenClaw to use Ollama as primary model (see guide below)
46
+ agenticros up sim-amr # or: agenticros up real
47
+ ```
48
+
49
+ Then chat in OpenClaw web UI or Hermes: *"List ROS topics"*, *"drive forward slowly"*, *"what do you see?"*
50
+
51
+ | Path | Best for |
52
+ |------|----------|
53
+ | **OpenClaw + Ollama** | Web chat, teleop, messaging channels, skills |
54
+ | **Hermes + Ollama** | Terminal agent with any Ollama model |
55
+ | **NemoClaw + Ollama** | Sandboxed Jetson / edge (see [docs/nemoclaw.md](docs/nemoclaw.md)) |
56
+
57
+ **Full setup, model picks, multimodal catalog patches, describer, Follow Me VLM, and troubleshooting:** **[docs/local-vlm.md](docs/local-vlm.md)**
58
+
36
59
  ## Architecture
37
60
 
38
61
  ![AgenticROS architecture: agent platforms → adapter packages → shared TypeScript runtime → transports → ROS 2 workspace and robot](docs/images/agenticros-architecture.png)
@@ -103,6 +126,8 @@ On the ROS side, every robot publishes a 1 Hz heartbeat on `<ns>/agenticros/robo
103
126
 
104
127
  `mission_cancel({ mission_id })` flips an in-process cancellation token; the runner stops at the next step boundary and marks remaining steps `cancelled`. When the [shared memory backend](#memory-optional) is on, every step is also written to `mission:<id>` in long-term memory, tagged with `step:<status>` and `capability:<id>`. A different agent — different process, different vendor — can `memory_recall` later and reconstruct exactly what happened, so two agents can collaborate on or hand off a mission.
105
128
 
129
+ **How-to:** step-by-step fleet setup, declarative plans, NL goals, cancel, and handoff — **[docs/missions.md](docs/missions.md)**. Runnable walkthrough: **[examples/find-and-approach/README.md](examples/find-and-approach/README.md)**.
130
+
106
131
  Full architecture + design trade-offs: **[docs/strategy-ai-agents-plus-ros.md](docs/strategy-ai-agents-plus-ros.md)**.
107
132
 
108
133
  ## Repository layout
@@ -112,7 +137,7 @@ Full architecture + design trade-offs: **[docs/strategy-ai-agents-plus-ros.md](d
112
137
  - `**packages/agenticros-claude-code**` — MCP server for Claude Code + Claude desktop / Dispatch (tools only; no config UI).
113
138
  - `**packages/agenticros-gemini**` — Gemini CLI (function calling; no MCP).
114
139
  - `**ros2_ws/**` — ROS2 workspace: `agenticros_msgs`, `agenticros_bringup` (Gazebo + RViz + rosbridge launches), `agenticros_discovery`, `agenticros_agent`, `agenticros_follow_me`.
115
- - `**docs/**` — Architecture, skills, robot setup, Zenoh, teleop.
140
+ - `**docs/**` — Architecture, skills, robot setup, Zenoh, teleop, **[local VLM / Ollama](docs/local-vlm.md)**.
116
141
  - `**scripts/**` — Workspace setup, gateway plugin config, run demos.
117
142
  - `**docker/**` — Docker Compose and Dockerfiles for ROS2 + plugin images.
118
143
  - `**examples/**` — Example projects.
@@ -138,7 +163,7 @@ The first run launches the interactive menu:
138
163
  ? What would you like to do?
139
164
  Launch with real robot
140
165
  ❯ Launch with simulation
141
- First-time setup (workspace + OpenClaw plugin + Codex MCP + API key)
166
+ First-time setup (workspace + OpenClaw plugin + Codex MCP + optional API key)
142
167
  Manage skills (2 registered, 0 available, 0 broken)
143
168
  Stop everything
144
169
  Doctor (health check)
@@ -146,8 +171,8 @@ The first run launches the interactive menu:
146
171
  Tail logs
147
172
  ```
148
173
 
149
- Pick **First-time setup** once (workspace + OpenClaw plugin + optional Codex MCP + API key, all
150
- idempotent), then choose how you want to run:
174
+ Pick **First-time setup** once (workspace + OpenClaw plugin + optional Codex MCP + optional API key, all
175
+ idempotent). **Using local Ollama instead of OpenAI?** Skip the API key step — see **[Local VLM (Ollama)](#local-vlm-ollama--no-cloud-api-required)**. Then choose how you want to run:
151
176
 
152
177
  | You want to … | Pick |
153
178
  |---|---|
@@ -163,7 +188,7 @@ so **Stop everything** cleanly tears the demo down.
163
188
  Prefer scripted invocations? Every menu item maps to a direct command:
164
189
 
165
190
  ```bash
166
- npx agenticros init # one-time workspace + plugin + Codex/Hermes MCP + API key
191
+ npx agenticros init # one-time workspace + plugin + Codex/Hermes MCP (+ optional API key)
167
192
  agenticros codex setup # register AgenticROS MCP for OpenAI Codex CLI
168
193
  agenticros codex doctor # validate ~/.codex/config.toml
169
194
  agenticros hermes setup # register AgenticROS MCP for Hermes Agent
@@ -212,7 +237,7 @@ needing token auth: run `node scripts/agenticros-proxy.cjs 18790` and open
212
237
  See **[docs/openclaw-releases-and-plugin-routes.md](docs/openclaw-releases-and-plugin-routes.md)**
213
238
  and **[docs/teleop.md](docs/teleop.md)**.
214
239
 
215
- See **`docs/`** for robot setup, skills, teleop, simulation internals, and Docker.
240
+ See **`docs/`** for robot setup, **[missions](docs/missions.md)**, skills, teleop, simulation internals, and Docker.
216
241
 
217
242
  ## RViz2 and Gazebo (TurtleBot3 + rosbridge)
218
243
 
@@ -64,7 +64,7 @@ skipped (with a checkmark) when already done:
64
64
  3. ROS 2 workspace build (`colcon build --symlink-install`)
65
65
  4. OpenClaw plugin install (via `scripts/setup_gateway_plugin.sh`)
66
66
  5. Robot config (writes `~/.agenticros/config.json`)
67
- 6. OpenAI API key (paste once `scripts/configure_agenticros.sh`)
67
+ 6. OpenAI API key (optional skip when using local Ollama; see [local-vlm.md](local-vlm.md))
68
68
  7. MCP client config (optional — `agenticros mcp setup` for Codex, Hermes, and Claude)
69
69
  8. Final `agenticros doctor` summary
70
70
 
@@ -296,7 +296,7 @@ The message comes from **OpenAI's API limits**, not from AgenticROS. The gateway
296
296
  1. **Confirm it's OpenAI** — Check gateway logs:
297
297
  `journalctl --user -u openclaw-gateway.service -n 100 --no-pager | grep -i "429\|rate\|limit\|error"`
298
298
  2. **Check your OpenAI account** — [Usage](https://platform.openai.com/usage), [API keys](https://platform.openai.com/api-keys). Free/low tiers have strict RPM/TPM and can stay limited for a while.
299
- 3. **What helps** — Wait (RPM/TPM reset in minutes; daily limits at UTC midnight). Or in OpenClaw switch to a model with higher limits if available.
299
+ 3. **What helps** — Wait (RPM/TPM reset in minutes; daily limits at UTC midnight). Or switch OpenClaw to a **local Ollama VLM** — see [local-vlm.md](local-vlm.md). Or in OpenClaw switch to a model with higher limits if available.
300
300
  4. **AgenticROS** — We only trim context/tool output to use fewer tokens; we can't remove OpenAI's limits.
301
301
 
302
302
  ### 404 errors when installing rosbridge_suite
@@ -21,6 +21,8 @@
21
21
  "ros2_find_robots_for",
22
22
  "run_mission",
23
23
  "mission_cancel",
24
+ "mission_pause",
25
+ "mission_resume",
24
26
  "ros2_camera_snapshot",
25
27
  "ros2_depth_distance",
26
28
  "memory_remember",
@@ -30,7 +32,8 @@
30
32
  "follow_robot",
31
33
  "follow_me_see",
32
34
  "ollama_status",
33
- "find_object"
35
+ "find_object",
36
+ "scan_surroundings"
34
37
  ]
35
38
  },
36
39
  "configSchema": {
@@ -178,6 +181,149 @@
178
181
  }
179
182
  }
180
183
  },
184
+ "robots": {
185
+ "type": "array",
186
+ "description": "Multi-robot fleet list. When non-empty, used by ros2_list_robots and per-tool robot_id. When empty, the legacy config.robot entry is used. Manage via `agenticros robots` CLI.",
187
+ "default": [],
188
+ "items": {
189
+ "type": "object",
190
+ "required": [
191
+ "id"
192
+ ],
193
+ "properties": {
194
+ "id": {
195
+ "type": "string",
196
+ "description": "Stable identifier referenced by robot_id tool arguments"
197
+ },
198
+ "name": {
199
+ "type": "string",
200
+ "default": "Robot"
201
+ },
202
+ "namespace": {
203
+ "type": "string",
204
+ "default": ""
205
+ },
206
+ "cameraTopic": {
207
+ "type": "string",
208
+ "default": ""
209
+ },
210
+ "default": {
211
+ "type": "boolean",
212
+ "description": "When true, this robot is active even if not first in the list"
213
+ },
214
+ "kind": {
215
+ "type": "string",
216
+ "description": "Robot kind for ros2_find_robots_for (e.g. amr, arm, drone, rover)",
217
+ "default": "amr"
218
+ },
219
+ "sensors": {
220
+ "type": "object",
221
+ "properties": {
222
+ "has_realsense": {
223
+ "type": "boolean",
224
+ "default": false
225
+ },
226
+ "has_lidar": {
227
+ "type": "boolean",
228
+ "default": false
229
+ },
230
+ "has_arm": {
231
+ "type": "boolean",
232
+ "default": false
233
+ }
234
+ }
235
+ },
236
+ "capabilities": {
237
+ "type": "array",
238
+ "description": "Optional per-robot capability allowlist for ros2_find_robots_for",
239
+ "items": {
240
+ "type": "string"
241
+ }
242
+ },
243
+ "transport": {
244
+ "type": "object",
245
+ "description": "Per-robot transport override (inherits global transport when omitted)",
246
+ "properties": {
247
+ "mode": {
248
+ "type": "string",
249
+ "enum": [
250
+ "rosbridge",
251
+ "local",
252
+ "webrtc",
253
+ "zenoh"
254
+ ]
255
+ },
256
+ "zenoh": {
257
+ "type": "object",
258
+ "properties": {
259
+ "routerEndpoint": {
260
+ "type": "string"
261
+ },
262
+ "domainId": {
263
+ "type": "number"
264
+ },
265
+ "keyFormat": {
266
+ "type": "string",
267
+ "enum": [
268
+ "ros2dds",
269
+ "rmw_zenoh"
270
+ ]
271
+ },
272
+ "bridgeNamespace": {
273
+ "type": "string"
274
+ }
275
+ }
276
+ },
277
+ "rosbridge": {
278
+ "type": "object",
279
+ "properties": {
280
+ "url": {
281
+ "type": "string"
282
+ },
283
+ "reconnect": {
284
+ "type": "boolean"
285
+ },
286
+ "reconnectInterval": {
287
+ "type": "number"
288
+ }
289
+ }
290
+ },
291
+ "local": {
292
+ "type": "object",
293
+ "properties": {
294
+ "domainId": {
295
+ "type": "number"
296
+ }
297
+ }
298
+ },
299
+ "webrtc": {
300
+ "type": "object",
301
+ "properties": {
302
+ "signalingUrl": {
303
+ "type": "string"
304
+ },
305
+ "apiUrl": {
306
+ "type": "string"
307
+ },
308
+ "robotId": {
309
+ "type": "string"
310
+ },
311
+ "robotKey": {
312
+ "type": "string"
313
+ },
314
+ "iceServers": {
315
+ "type": "array",
316
+ "items": {
317
+ "type": "object"
318
+ }
319
+ }
320
+ }
321
+ }
322
+ }
323
+ }
324
+ }
325
+ }
326
+ },
181
327
  "teleop": {
182
328
  "type": "object",
183
329
  "description": "Teleop web app: camera and twist controls",
@@ -131,6 +131,8 @@ test("plugin: register() is synchronous and produces the base 15-tool surface",
131
131
  "run_mission",
132
132
  // Phase 1.f — cancel an in-flight mission by id.
133
133
  "mission_cancel",
134
+ "mission_pause",
135
+ "mission_resume",
134
136
  ];
135
137
  for (const name of expected) {
136
138
  assert.ok(toolNames.includes(name), `Expected base tool ${name} in registered set`);
@@ -1,5 +1,5 @@
1
1
  import type { OpenClawPluginApi } from "./plugin-api.js";
2
- import { parseConfig, isCdrTypeSupported, agenticROSBannerLines } from "@agenticros/core";
2
+ import { parseConfig, isCdrTypeSupported, agenticROSBannerLines, applyCachedSkillRefs } from "@agenticros/core";
3
3
  import { readAgenticROSConfigFromFile } from "./config-file.js";
4
4
  import { registerService } from "./service.js";
5
5
  import { registerTools } from "./tools/index.js";
@@ -36,6 +36,13 @@ export default {
36
36
  api.logger.warn("AgenticROS: could not read config from file: " + msg + " — using gateway pluginConfig.");
37
37
  config = parseConfig(api.pluginConfig ?? {});
38
38
  }
39
+ // Merge skillRefs that are already cached (no network in sync register).
40
+ config = applyCachedSkillRefs(config);
41
+ if ((config.skillRefs?.length ?? 0) > 0) {
42
+ api.logger.info(
43
+ `AgenticROS: skillRefs=${config.skillRefs!.length} (cached paths merged into skillPaths; run skills install to warm cache)`,
44
+ );
45
+ }
39
46
  const mode = config.transport?.mode ?? "local";
40
47
  const zenohEndpoint = config.zenoh?.routerEndpoint ?? "";
41
48
  api.logger.info(`AgenticROS: transport mode=${mode}${mode === "zenoh" && zenohEndpoint ? ` endpoint=${zenohEndpoint}` : ""}`);
@@ -1,6 +1,6 @@
1
1
  import type { OpenClawPluginApi, HttpRouteHandler, HttpRouteResponse } from "./plugin-api.js";
2
2
  import type { AgenticROSConfig } from "@agenticros/core";
3
- import { parseConfig } from "@agenticros/core";
3
+ import { parseConfig, prepareConfigForPersistence } from "@agenticros/core";
4
4
  import {
5
5
  readOpenClawConfig,
6
6
  writeAgenticROSConfig,
@@ -172,8 +172,9 @@ export function registerRoutes(api: OpenClawPluginApi, config: AgenticROSConfig)
172
172
  api.logger.warn("AgenticROS config read failed: " + readMsg);
173
173
  return { success: false, error: readMsg, statusCode: 500 };
174
174
  }
175
+ let combined: Record<string, unknown>;
175
176
  try {
176
- const combined =
177
+ combined =
177
178
  existingAgenticROS && typeof existingAgenticROS === "object"
178
179
  ? mergeIncomingAgenticROSConfig(existingAgenticROS, body)
179
180
  : body;
@@ -192,7 +193,7 @@ export function registerRoutes(api: OpenClawPluginApi, config: AgenticROSConfig)
192
193
  merged.webrtc.robotKey = existingKey;
193
194
  }
194
195
  try {
195
- writeAgenticROSConfig(merged as unknown as Record<string, unknown>);
196
+ writeAgenticROSConfig(prepareConfigForPersistence(merged, combined));
196
197
  } catch (err) {
197
198
  const msg = err instanceof ConfigFileError ? err.message : (err instanceof Error ? err.message : "Failed to write config");
198
199
  api.logger.warn("AgenticROS config write failed: " + msg);
@@ -14,6 +14,8 @@ import { registerDiscoverRobotsTool } from "./ros2-discover.js";
14
14
  import { registerFindRobotsForTool } from "./ros2-find-robots-for.js";
15
15
  import { registerMissionTool, type ToolRegistry } from "./ros2-mission.js";
16
16
  import { registerMissionCancelTool } from "./mission-cancel.js";
17
+ import { registerMissionPauseTool } from "./mission-pause.js";
18
+ import { registerMissionResumeTool } from "./mission-resume.js";
17
19
 
18
20
  /**
19
21
  * Wrap the OpenClaw API so every registerTool() call is also recorded in
@@ -21,11 +23,8 @@ import { registerMissionCancelTool } from "./mission-cancel.js";
21
23
  * dispatch sub-tool calls by name (e.g. capability "drive_base" routes
22
24
  * to the registered "ros2_publish" tool's execute()).
23
25
  *
24
- * We keep this internal skills that register their own tools via
25
- * `api.registerTool` won't appear in the registry, which is fine for
26
- * v1: today the mission runner only supports the eight intrinsic
27
- * capability bindings declared in ros2-mission.ts. Phase 1.d will
28
- * extend this to capture skill-declared tools as well.
26
+ * Skill tools registered via `api.registerTool` are captured too, so
27
+ * `buildMissionBindings` can resolve `ros2_<capability_id>` when present.
29
28
  */
30
29
  function wrapApiWithToolCapture(api: OpenClawPluginApi): {
31
30
  wrappedApi: OpenClawPluginApi;
@@ -63,4 +62,6 @@ export function registerTools(api: OpenClawPluginApi, config: AgenticROSConfig):
63
62
  registerFindRobotsForTool(wrappedApi, config);
64
63
  registerMissionTool(wrappedApi, config, registry);
65
64
  registerMissionCancelTool(wrappedApi);
65
+ registerMissionPauseTool(wrappedApi);
66
+ registerMissionResumeTool(wrappedApi);
66
67
  }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Tool: mission_pause — pause a running mission at the next step boundary.
3
+ */
4
+
5
+ import { Type } from "@sinclair/typebox";
6
+ import type { OpenClawPluginApi } from "../plugin-api.js";
7
+ import { getMissionRegistry } from "../mission-registry.js";
8
+
9
+ export function registerMissionPauseTool(api: OpenClawPluginApi): void {
10
+ api.registerTool({
11
+ name: "mission_pause",
12
+ label: "Pause Mission",
13
+ description:
14
+ "Pause a mission that's currently running in this OpenClaw plugin process. " +
15
+ "Pass the mission_id returned by run_mission. The runner waits at the next step " +
16
+ "boundary until mission_resume (or mission_cancel). Idempotent if already paused.",
17
+ parameters: Type.Object({
18
+ mission_id: Type.String({
19
+ description: "The mission_id echoed by run_mission. Required.",
20
+ }),
21
+ reason: Type.Optional(
22
+ Type.String({
23
+ description: "Optional free-text reason — surfaced in the paused transcript entry.",
24
+ }),
25
+ ),
26
+ }),
27
+
28
+ async execute(_toolCallId, params) {
29
+ const missionId = typeof params["mission_id"] === "string" ? (params["mission_id"] as string).trim() : "";
30
+ if (!missionId) {
31
+ const text = "mission_pause requires 'mission_id' (a non-empty string returned by run_mission).";
32
+ return { content: [{ type: "text", text }], details: { success: false, error: text } };
33
+ }
34
+ const reason = typeof params["reason"] === "string" ? (params["reason"] as string) : undefined;
35
+ const outcome = getMissionRegistry().pause(missionId, reason);
36
+ const details = {
37
+ success: true,
38
+ mission_id: missionId,
39
+ found: outcome.found,
40
+ already_paused: outcome.alreadyPaused,
41
+ reason: reason ?? null,
42
+ };
43
+ return {
44
+ content: [{ type: "text", text: JSON.stringify(details) }],
45
+ details,
46
+ };
47
+ },
48
+ });
49
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Tool: mission_resume — resume a paused mission.
3
+ */
4
+
5
+ import { Type } from "@sinclair/typebox";
6
+ import type { OpenClawPluginApi } from "../plugin-api.js";
7
+ import { getMissionRegistry } from "../mission-registry.js";
8
+
9
+ export function registerMissionResumeTool(api: OpenClawPluginApi): void {
10
+ api.registerTool({
11
+ name: "mission_resume",
12
+ label: "Resume Mission",
13
+ description:
14
+ "Resume a mission previously paused with mission_pause. Pass the mission_id " +
15
+ "returned by run_mission. Idempotent if the mission is not paused.",
16
+ parameters: Type.Object({
17
+ mission_id: Type.String({
18
+ description: "The mission_id echoed by run_mission. Required.",
19
+ }),
20
+ }),
21
+
22
+ async execute(_toolCallId, params) {
23
+ const missionId = typeof params["mission_id"] === "string" ? (params["mission_id"] as string).trim() : "";
24
+ if (!missionId) {
25
+ const text = "mission_resume requires 'mission_id' (a non-empty string returned by run_mission).";
26
+ return { content: [{ type: "text", text }], details: { success: false, error: text } };
27
+ }
28
+ const outcome = getMissionRegistry().resume(missionId);
29
+ const details = {
30
+ success: true,
31
+ mission_id: missionId,
32
+ found: outcome.found,
33
+ was_paused: outcome.wasPaused,
34
+ };
35
+ return {
36
+ content: [{ type: "text", text: JSON.stringify(details) }],
37
+ details,
38
+ };
39
+ },
40
+ });
41
+ }
@@ -16,7 +16,7 @@
16
16
  import { Type } from "@sinclair/typebox";
17
17
  import type { OpenClawPluginApi } from "../plugin-api.js";
18
18
  import type { AgenticROSConfig, Capability } from "@agenticros/core";
19
- import { listAllCapabilities } from "@agenticros/core";
19
+ import { listCapabilitiesWithDiscoverable } from "@agenticros/core";
20
20
  import { ROBOT_ID_SCHEMA, resolveRobotForTool } from "./_robot-helpers.js";
21
21
 
22
22
  export function registerCapabilitiesTool(
@@ -29,29 +29,26 @@ export function registerCapabilitiesTool(
29
29
  description:
30
30
  "List the high-level capabilities (named verbs) this robot can perform — built-in verbs like " +
31
31
  "drive_base / take_snapshot / measure_depth plus every capability declared by installed " +
32
- "AgenticROS skills (e.g. follow_person, find_object). PREFER this over ros2_list_topics for " +
33
- "high-level planning: capabilities are agent-meaningful verbs with typed inputs/outputs, " +
34
- "not raw topic names. Returns one structured response listing every capability the robot " +
35
- "supports right now. Pass robot_id (from ros2_list_robots) to scope to a specific robot; " +
36
- "today every robot exposes the same capabilities, but the API is in place for per-robot " +
37
- "capability declarations.",
32
+ "AgenticROS skills (e.g. follow_person, find_object). Also includes discoverable marketplace " +
33
+ "capabilities (discoverable:true, install_ref) that are not installed yet so you can propose " +
34
+ "`agenticros skills install <install_ref>`. PREFER this over ros2_list_topics for high-level " +
35
+ "planning. Pass robot_id to scope to a specific robot when using a multi-robot fleet.",
38
36
  parameters: Type.Object({ ...ROBOT_ID_SCHEMA }),
39
37
 
40
38
  async execute(_toolCallId, params) {
41
- // robot_id is validated even though the response doesn't depend on
42
- // it yet — unknown ids surface as a tool error, matching every
43
- // other tool in the suite.
44
39
  const resolved = resolveRobotForTool(config, params);
45
40
  if ("error" in resolved) return resolved.error;
46
41
 
47
- const caps: Capability[] = listAllCapabilities(config);
42
+ const caps = await listCapabilitiesWithDiscoverable(config);
48
43
  const intrinsic = caps.filter((c) => c.source?.kind === "builtin").length;
49
- const skill = caps.filter((c) => c.source?.kind === "skill").length;
44
+ const skill = caps.filter((c) => c.installed !== false && c.source?.kind === "skill").length;
45
+ const discoverable = caps.filter((c) => c.discoverable === true).length;
50
46
  const result = {
51
47
  success: true,
52
48
  total: caps.length,
53
49
  intrinsic_count: intrinsic,
54
50
  skill_count: skill,
51
+ discoverable_count: discoverable,
55
52
  capabilities: caps,
56
53
  };
57
54
  return {