agenticros 0.1.18 → 0.2.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.
- package/dist/__tests__/robot-config.test.d.ts +2 -0
- package/dist/__tests__/robot-config.test.d.ts.map +1 -0
- package/dist/__tests__/robot-config.test.js +374 -0
- package/dist/__tests__/robot-config.test.js.map +1 -0
- package/dist/__tests__/transport-shorthand.test.d.ts +2 -0
- package/dist/__tests__/transport-shorthand.test.d.ts.map +1 -0
- package/dist/__tests__/transport-shorthand.test.js +88 -0
- package/dist/__tests__/transport-shorthand.test.js.map +1 -0
- package/dist/commands/robots.d.ts +57 -0
- package/dist/commands/robots.d.ts.map +1 -0
- package/dist/commands/robots.js +678 -0
- package/dist/commands/robots.js.map +1 -0
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +125 -2
- package/dist/commands/skills.js.map +1 -1
- package/dist/index.js +29 -1
- package/dist/index.js.map +1 -1
- package/dist/menu.js +22 -4
- package/dist/menu.js.map +1 -1
- package/dist/util/marketplace.d.ts +57 -0
- package/dist/util/marketplace.d.ts.map +1 -0
- package/dist/util/marketplace.js +144 -0
- package/dist/util/marketplace.js.map +1 -0
- package/dist/util/mcp-discovery.d.ts +57 -0
- package/dist/util/mcp-discovery.d.ts.map +1 -0
- package/dist/util/mcp-discovery.js +153 -0
- package/dist/util/mcp-discovery.js.map +1 -0
- package/dist/util/robot-config.d.ts +167 -0
- package/dist/util/robot-config.d.ts.map +1 -0
- package/dist/util/robot-config.js +375 -0
- package/dist/util/robot-config.js.map +1 -0
- package/dist/util/skills.d.ts +6 -6
- package/dist/util/skills.d.ts.map +1 -1
- package/dist/util/skills.js +14 -9
- package/dist/util/skills.js.map +1 -1
- package/dist/util/transport-shorthand.d.ts +66 -0
- package/dist/util/transport-shorthand.d.ts.map +1 -0
- package/dist/util/transport-shorthand.js +102 -0
- package/dist/util/transport-shorthand.js.map +1 -0
- package/package.json +12 -10
- package/runtime/BUNDLE.json +1 -1
- package/runtime/README.md +62 -1
- package/runtime/package.json +6 -1
- package/runtime/packages/agenticros/openclaw.plugin.json +7 -1
- package/runtime/packages/agenticros/package.json +2 -1
- package/runtime/packages/agenticros/src/__tests__/capabilities-plugin.test.ts +568 -0
- package/runtime/packages/agenticros/src/config-page.ts +1 -1
- package/runtime/packages/agenticros/src/mission-registry.ts +22 -0
- package/runtime/packages/agenticros/src/service.ts +120 -2
- package/runtime/packages/agenticros/src/skill-loader.ts +109 -30
- package/runtime/packages/agenticros/src/tools/_robot-helpers.ts +68 -0
- package/runtime/packages/agenticros/src/tools/index.ts +49 -9
- package/runtime/packages/agenticros/src/tools/mission-cancel.ts +70 -0
- package/runtime/packages/agenticros/src/tools/ros2-action.ts +11 -4
- package/runtime/packages/agenticros/src/tools/ros2-camera.ts +10 -4
- package/runtime/packages/agenticros/src/tools/ros2-capabilities.ts +63 -0
- package/runtime/packages/agenticros/src/tools/ros2-depth-distance.ts +11 -4
- package/runtime/packages/agenticros/src/tools/ros2-discover.ts +65 -0
- package/runtime/packages/agenticros/src/tools/ros2-find-robots-for.ts +128 -0
- package/runtime/packages/agenticros/src/tools/ros2-mission.ts +311 -0
- package/runtime/packages/agenticros/src/tools/ros2-param.ts +20 -7
- package/runtime/packages/agenticros/src/tools/ros2-publish.ts +15 -8
- package/runtime/packages/agenticros/src/tools/ros2-robots.ts +52 -0
- package/runtime/packages/agenticros/src/tools/ros2-service.ts +11 -4
- package/runtime/packages/agenticros/src/tools/ros2-subscribe.ts +11 -4
- package/runtime/packages/agenticros-claude-code/README.md +52 -9
- package/runtime/packages/agenticros-claude-code/dist/__tests__/capabilities-mcp.test.d.ts +2 -0
- package/runtime/packages/agenticros-claude-code/dist/__tests__/capabilities-mcp.test.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/__tests__/capabilities-mcp.test.js +773 -0
- package/runtime/packages/agenticros-claude-code/dist/__tests__/capabilities-mcp.test.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/__tests__/follow-me-registry.test.d.ts +2 -0
- package/runtime/packages/agenticros-claude-code/dist/__tests__/follow-me-registry.test.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/__tests__/follow-me-registry.test.js +97 -0
- package/runtime/packages/agenticros-claude-code/dist/__tests__/follow-me-registry.test.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.d.ts +2 -2
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.d.ts.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.js +5 -5
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.js.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/follow-me/depth-loop.d.ts +109 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/depth-loop.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/depth-loop.js +392 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/depth-loop.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.d.ts +8 -3
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.d.ts.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.js +30 -12
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.js.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/index.js +5 -5
- package/runtime/packages/agenticros-claude-code/dist/index.js.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/mission-registry.d.ts +16 -0
- package/runtime/packages/agenticros-claude-code/dist/mission-registry.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/mission-registry.js +19 -0
- package/runtime/packages/agenticros-claude-code/dist/mission-registry.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.d.ts +9 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.d.ts.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/tools.js +700 -54
- package/runtime/packages/agenticros-claude-code/dist/tools.js.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/transport.d.ts +28 -8
- package/runtime/packages/agenticros-claude-code/dist/transport.d.ts.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/transport.js +37 -34
- package/runtime/packages/agenticros-claude-code/dist/transport.js.map +1 -1
- package/runtime/packages/agenticros-claude-code/package.json +2 -1
- package/runtime/packages/agenticros-claude-code/src/__tests__/capabilities-mcp.test.ts +965 -0
- package/runtime/packages/agenticros-claude-code/src/__tests__/follow-me-registry.test.ts +119 -0
- package/runtime/packages/agenticros-claude-code/src/find-object/find-object.ts +7 -6
- package/runtime/packages/agenticros-claude-code/src/follow-me/depth-loop.ts +41 -9
- package/runtime/packages/agenticros-claude-code/src/follow-me/loop.ts +40 -11
- package/runtime/packages/agenticros-claude-code/src/index.ts +5 -5
- package/runtime/packages/agenticros-claude-code/src/mission-registry.ts +21 -0
- package/runtime/packages/agenticros-claude-code/src/tools.ts +668 -56
- package/runtime/packages/agenticros-claude-code/src/transport.ts +43 -43
- package/runtime/packages/agenticros-gemini/package.json +1 -0
- package/runtime/packages/agenticros-gemini/src/__tests__/mission-cancel.test.ts +197 -0
- package/runtime/packages/agenticros-gemini/src/__tests__/mission-transcripts.test.ts +178 -0
- package/runtime/packages/agenticros-gemini/src/tools.ts +504 -21
- package/runtime/packages/agenticros-gemini/src/transport.ts +40 -43
- package/runtime/packages/core/LICENSE +192 -0
- package/runtime/packages/core/README.md +88 -0
- package/runtime/packages/core/package.json +36 -3
- package/runtime/packages/core/src/__tests__/capabilities.test.ts +266 -0
- package/runtime/packages/core/src/__tests__/discovery.test.ts +206 -0
- package/runtime/packages/core/src/__tests__/find-robots-for.test.ts +258 -0
- package/runtime/packages/core/src/__tests__/mission-cancel-transcript.test.ts +371 -0
- package/runtime/packages/core/src/__tests__/mission-transcript-sink.test.ts +189 -0
- package/runtime/packages/core/src/__tests__/mission.test.ts +501 -0
- package/runtime/packages/core/src/__tests__/planner.test.ts +279 -0
- package/runtime/packages/core/src/__tests__/robots.test.ts +406 -0
- package/runtime/packages/core/src/__tests__/topic-utils.test.ts +105 -0
- package/runtime/packages/core/src/__tests__/transport-pool.test.ts +295 -0
- package/runtime/packages/core/src/capabilities.ts +314 -0
- package/runtime/packages/core/src/config.ts +221 -39
- package/runtime/packages/core/src/discovery.ts +193 -0
- package/runtime/packages/core/src/find-robots-for.ts +164 -0
- package/runtime/packages/core/src/index.ts +67 -1
- package/runtime/packages/core/src/mission-registry.ts +142 -0
- package/runtime/packages/core/src/mission-transcript-sink.ts +81 -0
- package/runtime/packages/core/src/mission.ts +574 -0
- package/runtime/packages/core/src/planner/index.ts +564 -0
- package/runtime/packages/core/src/robots.ts +282 -0
- package/runtime/packages/core/src/topic-utils.ts +47 -9
- package/runtime/packages/core/src/transport-pool.ts +171 -0
- package/runtime/ros2_ws/src/agenticros_discovery/agenticros_discovery/discovery_node.py +108 -13
- package/runtime/ros2_ws/src/agenticros_msgs/CMakeLists.txt +1 -0
- package/runtime/ros2_ws/src/agenticros_msgs/msg/RobotInfo.msg +44 -0
- package/runtime/scripts/__tests__/refresh-skill-deps.test.mjs +222 -0
- package/runtime/scripts/configure_agenticros.sh +0 -0
- package/runtime/scripts/configure_for_sim.sh +0 -0
- package/runtime/scripts/fix-openclaw-control-ui-path.sh +0 -0
- package/runtime/scripts/install_cli.sh +0 -0
- package/runtime/scripts/install_rosbridge_from_source.sh +0 -0
- package/runtime/scripts/onboard_robot.sh +0 -0
- package/runtime/scripts/pack-runtime.mjs +0 -0
- package/runtime/scripts/refresh-skill-deps.mjs +381 -0
- package/runtime/scripts/run_demo_native.sh +0 -0
- package/runtime/scripts/run_nemoclaw_host_stack.sh +0 -0
- package/runtime/scripts/run_robot_rosbridge.sh +0 -0
- package/runtime/scripts/setup_gateway_plugin.sh +32 -5
- package/runtime/scripts/setup_robot.sh +0 -0
- package/runtime/scripts/setup_workspace.sh +0 -0
- package/runtime/scripts/sim/run_sim.sh +0 -0
- package/runtime/scripts/smoke_test_nemoclaw.sh +0 -0
- package/runtime/scripts/start_demo.sh +0 -0
- package/runtime/scripts/sync-skill-tools.mjs +69 -12
- package/runtime/scripts/use-openclaw-2026.2.26.sh +0 -0
- package/runtime/scripts/use-openclaw-2026.3.11.sh +0 -0
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { TransportConfig } from "@agenticros/core";
|
|
2
|
-
import type { RosTransport } from "@agenticros/core";
|
|
3
|
-
import {
|
|
2
|
+
import type { RosTransport, ResolvedRobot } from "@agenticros/core";
|
|
3
|
+
import {
|
|
4
|
+
createTransport,
|
|
5
|
+
getTransportConfig,
|
|
6
|
+
getTransportConfigForRobot,
|
|
7
|
+
hasRobotTransportOverride,
|
|
8
|
+
} from "@agenticros/core";
|
|
4
9
|
import type { OpenClawPluginApi } from "./plugin-api.js";
|
|
5
10
|
import type { PluginLogger } from "./plugin-api.js";
|
|
6
11
|
import type { AgenticROSConfig } from "@agenticros/core";
|
|
@@ -238,6 +243,112 @@ async function ensureTransportConnected(
|
|
|
238
243
|
await next;
|
|
239
244
|
}
|
|
240
245
|
|
|
246
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
247
|
+
// Phase 1.d-pool: per-robot transport override pool.
|
|
248
|
+
//
|
|
249
|
+
// The existing `transport` singleton above (with eager-retry / preflight /
|
|
250
|
+
// poll-reconnect) stays the source of truth for every robot WITHOUT a
|
|
251
|
+
// per-robot `transport` override in config. That's the common case and
|
|
252
|
+
// the plugin's hard-won robustness lives in that singleton.
|
|
253
|
+
//
|
|
254
|
+
// For robots that DO declare an override we maintain a smaller, lazier
|
|
255
|
+
// pool here. Override entries don't (yet) get the eager-connect /
|
|
256
|
+
// preflight / polling treatment — they connect on first tool call, fail
|
|
257
|
+
// loudly to the agent on connect timeout, and self-heal on the next
|
|
258
|
+
// acquire. That's intentional: most fleets have one or two override
|
|
259
|
+
// robots and the agent-facing error path is sufficient feedback.
|
|
260
|
+
//
|
|
261
|
+
// Pool key: `robot.id` (validated by `hasRobotTransportOverride`).
|
|
262
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
const overrideTransports = new Map<string, RosTransport>();
|
|
265
|
+
const overrideInFlight = new Map<string, Promise<RosTransport>>();
|
|
266
|
+
const OVERRIDE_CONNECT_TIMEOUT_MS = 15_000;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Return the right transport for `robot`. Per-robot tools call this
|
|
270
|
+
* INSTEAD of `getTransport()` so a robot with a custom transport gets
|
|
271
|
+
* its own connection. Robots without an override fall through to the
|
|
272
|
+
* service-managed singleton with all its retry / preflight machinery.
|
|
273
|
+
*
|
|
274
|
+
* Throws when the singleton is requested but the service hasn't
|
|
275
|
+
* connected yet — same contract as `getTransport()`.
|
|
276
|
+
*
|
|
277
|
+
* Throws (or surfaces as a tool error after the timeout) when an
|
|
278
|
+
* override entry can't connect — the agent sees a clean failure and
|
|
279
|
+
* can fall back to a different robot id.
|
|
280
|
+
*/
|
|
281
|
+
export async function getTransportForRobot(
|
|
282
|
+
config: AgenticROSConfig,
|
|
283
|
+
robot: ResolvedRobot,
|
|
284
|
+
): Promise<RosTransport> {
|
|
285
|
+
if (!hasRobotTransportOverride(config, robot.id)) {
|
|
286
|
+
// Fast path: no override → reuse the service-managed singleton
|
|
287
|
+
// (with its retries and preflight). Same as the pre-pool behaviour.
|
|
288
|
+
return getTransport();
|
|
289
|
+
}
|
|
290
|
+
return acquireOverrideTransport(config, robot);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function acquireOverrideTransport(
|
|
294
|
+
config: AgenticROSConfig,
|
|
295
|
+
robot: ResolvedRobot,
|
|
296
|
+
): Promise<RosTransport> {
|
|
297
|
+
const cached = overrideTransports.get(robot.id);
|
|
298
|
+
if (cached && cached.getStatus() === "connected") return cached;
|
|
299
|
+
|
|
300
|
+
// Concurrent first-acquires share one connect promise so two near-
|
|
301
|
+
// simultaneous tool calls don't double-open the same Zenoh socket.
|
|
302
|
+
const flight = overrideInFlight.get(robot.id);
|
|
303
|
+
if (flight) return flight;
|
|
304
|
+
|
|
305
|
+
if (cached) {
|
|
306
|
+
// Stale entry — drop cleanly so a fresh build can proceed.
|
|
307
|
+
overrideTransports.delete(robot.id);
|
|
308
|
+
cached.disconnect().catch(() => {});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const connectFlight = (async (): Promise<RosTransport> => {
|
|
312
|
+
const transportCfg = buildTransportConfig(getTransportConfigForRobot(config, robot.id));
|
|
313
|
+
const t = await createTransport(transportCfg);
|
|
314
|
+
await connectWithTimeout(t, OVERRIDE_CONNECT_TIMEOUT_MS);
|
|
315
|
+
overrideTransports.set(robot.id, t);
|
|
316
|
+
return t;
|
|
317
|
+
})();
|
|
318
|
+
overrideInFlight.set(robot.id, connectFlight);
|
|
319
|
+
try {
|
|
320
|
+
return await connectFlight;
|
|
321
|
+
} finally {
|
|
322
|
+
overrideInFlight.delete(robot.id);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function connectWithTimeout(t: RosTransport, timeoutMs: number): Promise<void> {
|
|
327
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
328
|
+
setTimeout(
|
|
329
|
+
() =>
|
|
330
|
+
reject(
|
|
331
|
+
new Error(
|
|
332
|
+
`Per-robot transport connect timed out after ${timeoutMs / 1000}s. ` +
|
|
333
|
+
"Check the robot's transport override in config (router endpoint, mode).",
|
|
334
|
+
),
|
|
335
|
+
),
|
|
336
|
+
timeoutMs,
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
await Promise.race([t.connect(), timeout]);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function drainOverridePool(): Promise<void> {
|
|
343
|
+
const pending: Promise<unknown>[] = [];
|
|
344
|
+
for (const t of overrideTransports.values()) {
|
|
345
|
+
pending.push(t.disconnect().catch(() => {}));
|
|
346
|
+
}
|
|
347
|
+
overrideTransports.clear();
|
|
348
|
+
overrideInFlight.clear();
|
|
349
|
+
await Promise.all(pending);
|
|
350
|
+
}
|
|
351
|
+
|
|
241
352
|
const RETRY_INTERVAL_MS = 10000;
|
|
242
353
|
const DISCONNECTED_POLL_MS = 15000;
|
|
243
354
|
|
|
@@ -283,6 +394,13 @@ export function registerService(api: OpenClawPluginApi, config: AgenticROSConfig
|
|
|
283
394
|
currentMode = null;
|
|
284
395
|
api.logger.info("ROS2 transport disconnected");
|
|
285
396
|
}
|
|
397
|
+
// Drain any per-robot override connections too — otherwise they'd
|
|
398
|
+
// leak open Zenoh sockets across plugin reloads.
|
|
399
|
+
const overrideCount = overrideTransports.size;
|
|
400
|
+
if (overrideCount > 0) {
|
|
401
|
+
await drainOverridePool();
|
|
402
|
+
api.logger.info(`Disconnected ${overrideCount} per-robot override transport(s)`);
|
|
403
|
+
}
|
|
286
404
|
},
|
|
287
405
|
});
|
|
288
406
|
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
|
|
21
21
|
import { createRequire } from "node:module";
|
|
22
22
|
import { pathToFileURL } from "node:url";
|
|
23
|
-
import { readFileSync,
|
|
24
|
-
import { join } from "node:path";
|
|
23
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
24
|
+
import { dirname, join } from "node:path";
|
|
25
25
|
import type { OpenClawPluginApi } from "./plugin-api.js";
|
|
26
26
|
import type { AgenticROSConfig } from "@agenticros/core";
|
|
27
27
|
import type { RegisterSkill, SkillContext } from "./skill-api.js";
|
|
@@ -41,12 +41,40 @@ export function getLoadedSkillIds(): string[] {
|
|
|
41
41
|
return [...loadedSkillIds];
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Read the `agenticros` block from a skill's package.json. The block is now
|
|
46
|
+
* the single source of truth for the skill id, display name, and capability
|
|
47
|
+
* manifest. The legacy `agenticrosSkill: true | { capabilities }` form is
|
|
48
|
+
* NOT supported — every skill must declare a fully-formed `agenticros` block.
|
|
49
|
+
*/
|
|
50
|
+
interface AgenticROSBlock {
|
|
51
|
+
id: string;
|
|
52
|
+
displayName?: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
categories?: string[];
|
|
55
|
+
screenshots?: string[];
|
|
56
|
+
demoVideoUrl?: string;
|
|
57
|
+
capabilities?: unknown[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface SkillManifest {
|
|
61
|
+
name?: string;
|
|
62
|
+
main?: string;
|
|
63
|
+
agenticros?: AgenticROSBlock;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readSkillManifest(pkgJsonPath: string): SkillManifest | null {
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(readFileSync(pkgJsonPath, "utf-8")) as SkillManifest;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
48
71
|
}
|
|
49
|
-
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isValidBlock(block: unknown): block is AgenticROSBlock {
|
|
75
|
+
if (!block || typeof block !== "object") return false;
|
|
76
|
+
const b = block as { id?: unknown };
|
|
77
|
+
return typeof b.id === "string" && /^[a-z0-9][a-z0-9-]*$/.test(b.id);
|
|
50
78
|
}
|
|
51
79
|
|
|
52
80
|
/**
|
|
@@ -56,7 +84,7 @@ function deriveSkillId(packageName: string): string {
|
|
|
56
84
|
* — including any `api.registerTool(...)` the skill makes — completes before
|
|
57
85
|
* we return, and therefore before the OpenClaw host snapshots `captured.tools`.
|
|
58
86
|
*
|
|
59
|
-
* Returns the
|
|
87
|
+
* Returns the declared skill id on success, or `null` (with a logged warning /
|
|
60
88
|
* error) on any failure mode: missing export, top-level await (which surfaces
|
|
61
89
|
* as `ERR_REQUIRE_ASYNC_MODULE`), or thrown initialization error.
|
|
62
90
|
*/
|
|
@@ -66,8 +94,8 @@ function loadSkillModuleSync(
|
|
|
66
94
|
context: SkillContext,
|
|
67
95
|
entryPath: string,
|
|
68
96
|
packageName: string,
|
|
97
|
+
skillId: string,
|
|
69
98
|
): string | null {
|
|
70
|
-
const skillId = deriveSkillId(packageName);
|
|
71
99
|
let mod: unknown;
|
|
72
100
|
try {
|
|
73
101
|
mod = require(entryPath);
|
|
@@ -110,36 +138,80 @@ function loadSkillModuleSync(
|
|
|
110
138
|
}
|
|
111
139
|
}
|
|
112
140
|
|
|
141
|
+
interface ResolvedSkill {
|
|
142
|
+
entry: string;
|
|
143
|
+
packageName: string;
|
|
144
|
+
skillId: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
113
147
|
/**
|
|
114
|
-
* Resolve
|
|
148
|
+
* Resolve an npm package name to a `ResolvedSkill` (entry + name + skill id).
|
|
149
|
+
* Reads the package's package.json to extract `pkg.agenticros.id`; refuses
|
|
150
|
+
* to register the skill if the block is missing or malformed.
|
|
115
151
|
*/
|
|
116
|
-
function
|
|
152
|
+
function resolveSkillByPackage(
|
|
153
|
+
api: OpenClawPluginApi,
|
|
154
|
+
packageName: string,
|
|
155
|
+
): ResolvedSkill | null {
|
|
156
|
+
let entry: string;
|
|
117
157
|
try {
|
|
118
|
-
|
|
119
|
-
return pkgPath;
|
|
158
|
+
entry = require.resolve(packageName, { paths: [process.cwd()] });
|
|
120
159
|
} catch {
|
|
160
|
+
api.logger.warn(`Skill package not found: ${packageName}`);
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
// Walk up from the resolved entry to find the package.json.
|
|
164
|
+
const pkgJsonPath = findPackageJsonForEntry(entry);
|
|
165
|
+
if (!pkgJsonPath) {
|
|
166
|
+
api.logger.warn(`Skill ${packageName}: cannot locate package.json next to ${entry}`);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const manifest = readSkillManifest(pkgJsonPath);
|
|
170
|
+
if (!manifest || !isValidBlock(manifest.agenticros)) {
|
|
171
|
+
api.logger.warn(
|
|
172
|
+
`Skill ${packageName}: missing or invalid \`agenticros\` block in package.json. ` +
|
|
173
|
+
"Declare an `agenticros.id` (kebab-case) to register as a skill.",
|
|
174
|
+
);
|
|
121
175
|
return null;
|
|
122
176
|
}
|
|
177
|
+
return {
|
|
178
|
+
entry,
|
|
179
|
+
packageName: manifest.name ?? packageName,
|
|
180
|
+
skillId: manifest.agenticros!.id,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Walk up from `entry` (a file inside the package) to find its package.json. */
|
|
185
|
+
function findPackageJsonForEntry(entry: string): string | null {
|
|
186
|
+
let dir = dirname(entry);
|
|
187
|
+
for (let i = 0; i < 6; i++) {
|
|
188
|
+
const candidate = join(dir, "package.json");
|
|
189
|
+
if (existsSync(candidate)) return candidate;
|
|
190
|
+
const parent = dirname(dir);
|
|
191
|
+
if (parent === dir) return null;
|
|
192
|
+
dir = parent;
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
123
195
|
}
|
|
124
196
|
|
|
125
197
|
/**
|
|
126
|
-
* Scan a directory for package.json with
|
|
198
|
+
* Scan a directory for a package.json with a valid `agenticros` block and
|
|
199
|
+
* return its entry path + declared id.
|
|
127
200
|
*/
|
|
128
|
-
function findSkillInPath(dirPath: string):
|
|
201
|
+
function findSkillInPath(dirPath: string): ResolvedSkill | null {
|
|
129
202
|
if (!existsSync(dirPath)) return null;
|
|
130
203
|
const pkgPath = join(dirPath, "package.json");
|
|
131
204
|
if (!existsSync(pkgPath)) return null;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
135
|
-
} catch {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
if (!pkg.agenticrosSkill) return null;
|
|
205
|
+
const pkg = readSkillManifest(pkgPath);
|
|
206
|
+
if (!pkg || !isValidBlock(pkg.agenticros)) return null;
|
|
139
207
|
const main = pkg.main ?? "index.js";
|
|
140
208
|
const entry = join(dirPath, main);
|
|
141
209
|
if (!existsSync(entry)) return null;
|
|
142
|
-
return {
|
|
210
|
+
return {
|
|
211
|
+
entry,
|
|
212
|
+
packageName: pkg.name ?? "unknown",
|
|
213
|
+
skillId: pkg.agenticros!.id,
|
|
214
|
+
};
|
|
143
215
|
}
|
|
144
216
|
|
|
145
217
|
/**
|
|
@@ -166,19 +238,25 @@ export function loadSkills(
|
|
|
166
238
|
loadedSkillIds.length = 0;
|
|
167
239
|
|
|
168
240
|
for (const pkgName of packages) {
|
|
169
|
-
const
|
|
170
|
-
if (!
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
241
|
+
const resolved = resolveSkillByPackage(api, pkgName);
|
|
242
|
+
if (!resolved) continue;
|
|
243
|
+
const skillId = loadSkillModuleSync(
|
|
244
|
+
api,
|
|
245
|
+
config,
|
|
246
|
+
context,
|
|
247
|
+
resolved.entry,
|
|
248
|
+
resolved.packageName,
|
|
249
|
+
resolved.skillId,
|
|
250
|
+
);
|
|
175
251
|
if (skillId) loadedSkillIds.push(skillId);
|
|
176
252
|
}
|
|
177
253
|
|
|
178
254
|
for (const dir of paths) {
|
|
179
255
|
const resolved = findSkillInPath(dir);
|
|
180
256
|
if (!resolved) {
|
|
181
|
-
api.logger.warn(
|
|
257
|
+
api.logger.warn(
|
|
258
|
+
`No agenticros skill in path: ${dir} (missing \`agenticros\` block in package.json).`,
|
|
259
|
+
);
|
|
182
260
|
continue;
|
|
183
261
|
}
|
|
184
262
|
const skillId = loadSkillModuleSync(
|
|
@@ -187,6 +265,7 @@ export function loadSkills(
|
|
|
187
265
|
context,
|
|
188
266
|
resolved.entry,
|
|
189
267
|
resolved.packageName,
|
|
268
|
+
resolved.skillId,
|
|
190
269
|
);
|
|
191
270
|
if (skillId && !loadedSkillIds.includes(skillId)) loadedSkillIds.push(skillId);
|
|
192
271
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 1.d helper — shared by every OpenClaw ROS2 tool that accepts an
|
|
3
|
+
* optional `robot_id` parameter. Keeping it in one place means the
|
|
4
|
+
* schema definition and resolution error format stay consistent across
|
|
5
|
+
* tools and match the claude-code adapter.
|
|
6
|
+
*
|
|
7
|
+
* Usage in a tool's parameters:
|
|
8
|
+
*
|
|
9
|
+
* parameters: Type.Object({
|
|
10
|
+
* topic: Type.String({ description: "..." }),
|
|
11
|
+
* ...ROBOT_ID_SCHEMA,
|
|
12
|
+
* })
|
|
13
|
+
*
|
|
14
|
+
* Usage in execute():
|
|
15
|
+
*
|
|
16
|
+
* const resolved = resolveRobotForTool(config, params);
|
|
17
|
+
* if ("error" in resolved) return resolved.error;
|
|
18
|
+
* const ns = resolved.robot.namespace;
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { Type } from "@sinclair/typebox";
|
|
22
|
+
import { resolveRobotFromArgs, type ResolvedRobot } from "@agenticros/core";
|
|
23
|
+
import type { AgenticROSConfig } from "@agenticros/core";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* TypeBox schema fragment — spread into a tool's parameters object to
|
|
27
|
+
* advertise an optional `robot_id` argument.
|
|
28
|
+
*/
|
|
29
|
+
export const ROBOT_ID_SCHEMA = {
|
|
30
|
+
robot_id: Type.Optional(
|
|
31
|
+
Type.String({
|
|
32
|
+
description:
|
|
33
|
+
"Optional robot id (from ros2_list_robots) to scope this call. When omitted, the active robot is used.",
|
|
34
|
+
}),
|
|
35
|
+
),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Try to resolve the target robot from the tool's params. On success
|
|
40
|
+
* returns `{ robot }`. On unknown-id failure returns `{ error }` —
|
|
41
|
+
* already shaped as an OpenClaw tool response (content + details) so
|
|
42
|
+
* the caller can `return resolved.error` directly. The error text comes
|
|
43
|
+
* from `@agenticros/core/resolveRobot` and already lists known ids +
|
|
44
|
+
* recommends ros2_list_robots, so the agent can self-correct.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveRobotForTool(
|
|
47
|
+
config: AgenticROSConfig,
|
|
48
|
+
params: Record<string, unknown>,
|
|
49
|
+
):
|
|
50
|
+
| { robot: ResolvedRobot }
|
|
51
|
+
| {
|
|
52
|
+
error: {
|
|
53
|
+
content: Array<{ type: "text"; text: string }>;
|
|
54
|
+
details: { success: false; error: string };
|
|
55
|
+
};
|
|
56
|
+
} {
|
|
57
|
+
try {
|
|
58
|
+
return { robot: resolveRobotFromArgs(config, params) };
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
61
|
+
return {
|
|
62
|
+
error: {
|
|
63
|
+
content: [{ type: "text", text: msg }],
|
|
64
|
+
details: { success: false, error: msg },
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from "../plugin-api.js";
|
|
1
|
+
import type { AgentTool, OpenClawPluginApi } from "../plugin-api.js";
|
|
2
2
|
import type { AgenticROSConfig } from "@agenticros/core";
|
|
3
3
|
import { registerPublishTool } from "./ros2-publish.js";
|
|
4
4
|
import { registerSubscribeTool } from "./ros2-subscribe.js";
|
|
@@ -8,6 +8,39 @@ import { registerParamTools } from "./ros2-param.js";
|
|
|
8
8
|
import { registerIntrospectTool } from "./ros2-introspect.js";
|
|
9
9
|
import { registerCameraTool } from "./ros2-camera.js";
|
|
10
10
|
import { registerDepthDistanceTool } from "./ros2-depth-distance.js";
|
|
11
|
+
import { registerCapabilitiesTool } from "./ros2-capabilities.js";
|
|
12
|
+
import { registerRobotsTool } from "./ros2-robots.js";
|
|
13
|
+
import { registerDiscoverRobotsTool } from "./ros2-discover.js";
|
|
14
|
+
import { registerFindRobotsForTool } from "./ros2-find-robots-for.js";
|
|
15
|
+
import { registerMissionTool, type ToolRegistry } from "./ros2-mission.js";
|
|
16
|
+
import { registerMissionCancelTool } from "./mission-cancel.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Wrap the OpenClaw API so every registerTool() call is also recorded in
|
|
20
|
+
* a local tool registry. The mission runner uses the registry to
|
|
21
|
+
* dispatch sub-tool calls by name (e.g. capability "drive_base" routes
|
|
22
|
+
* to the registered "ros2_publish" tool's execute()).
|
|
23
|
+
*
|
|
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.
|
|
29
|
+
*/
|
|
30
|
+
function wrapApiWithToolCapture(api: OpenClawPluginApi): {
|
|
31
|
+
wrappedApi: OpenClawPluginApi;
|
|
32
|
+
registry: ToolRegistry;
|
|
33
|
+
} {
|
|
34
|
+
const registry: ToolRegistry = new Map();
|
|
35
|
+
const wrappedApi: OpenClawPluginApi = {
|
|
36
|
+
...api,
|
|
37
|
+
registerTool: (tool: AgentTool, opts) => {
|
|
38
|
+
registry.set(tool.name, tool);
|
|
39
|
+
api.registerTool(tool, opts);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
return { wrappedApi, registry };
|
|
43
|
+
}
|
|
11
44
|
|
|
12
45
|
/**
|
|
13
46
|
* Register core ROS2 tools with the OpenClaw AI agent.
|
|
@@ -15,12 +48,19 @@ import { registerDepthDistanceTool } from "./ros2-depth-distance.js";
|
|
|
15
48
|
* Memory tools register asynchronously from index.ts after initMemory resolves.
|
|
16
49
|
*/
|
|
17
50
|
export function registerTools(api: OpenClawPluginApi, config: AgenticROSConfig): void {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
51
|
+
const { wrappedApi, registry } = wrapApiWithToolCapture(api);
|
|
52
|
+
registerPublishTool(wrappedApi, config);
|
|
53
|
+
registerSubscribeTool(wrappedApi, config);
|
|
54
|
+
registerServiceTool(wrappedApi, config);
|
|
55
|
+
registerActionTool(wrappedApi, config);
|
|
56
|
+
registerParamTools(wrappedApi, config);
|
|
57
|
+
registerIntrospectTool(wrappedApi);
|
|
58
|
+
registerCameraTool(wrappedApi, config);
|
|
59
|
+
registerDepthDistanceTool(wrappedApi, config);
|
|
60
|
+
registerCapabilitiesTool(wrappedApi, config);
|
|
61
|
+
registerRobotsTool(wrappedApi, config);
|
|
62
|
+
registerDiscoverRobotsTool(wrappedApi, config);
|
|
63
|
+
registerFindRobotsForTool(wrappedApi, config);
|
|
64
|
+
registerMissionTool(wrappedApi, config, registry);
|
|
65
|
+
registerMissionCancelTool(wrappedApi);
|
|
26
66
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: mission_cancel — Phase 1.f of the AgenticROS strategy.
|
|
3
|
+
*
|
|
4
|
+
* Cancel a mission that's currently running in this OpenClaw plugin
|
|
5
|
+
* process. Pass the `mission_id` returned by `run_mission`. The mission
|
|
6
|
+
* runner stops at the next step boundary (the in-flight step finishes
|
|
7
|
+
* naturally — per-tool preemption is out of scope for Phase 1.f),
|
|
8
|
+
* marks remaining steps as "cancelled", and returns a result with
|
|
9
|
+
* `status: "cancelled"`.
|
|
10
|
+
*
|
|
11
|
+
* Idempotent / safe-by-default:
|
|
12
|
+
* - Unknown mission_id → `found: false`, never an error
|
|
13
|
+
* - Calling twice on the same id → second call returns
|
|
14
|
+
* `already_cancelled: true` and is otherwise a no-op
|
|
15
|
+
* - No transport required — this only mutates an in-process
|
|
16
|
+
* `MissionRegistry`
|
|
17
|
+
*
|
|
18
|
+
* Mirrored across all three adapters (Claude Code, OpenClaw, Gemini)
|
|
19
|
+
* with identical request/response shapes — see
|
|
20
|
+
* `packages/agenticros-claude-code/src/tools.ts` for the canonical one.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { Type } from "@sinclair/typebox";
|
|
24
|
+
import type { OpenClawPluginApi } from "../plugin-api.js";
|
|
25
|
+
import { getMissionRegistry } from "../mission-registry.js";
|
|
26
|
+
|
|
27
|
+
export function registerMissionCancelTool(api: OpenClawPluginApi): void {
|
|
28
|
+
api.registerTool({
|
|
29
|
+
name: "mission_cancel",
|
|
30
|
+
label: "Cancel Mission",
|
|
31
|
+
description:
|
|
32
|
+
"Cancel a mission that's currently running in this OpenClaw plugin process. " +
|
|
33
|
+
"Pass the mission_id returned by run_mission. The mission runner stops at the next " +
|
|
34
|
+
"step boundary (the in-flight step finishes naturally), marks remaining steps as " +
|
|
35
|
+
"'cancelled', and returns. If the mission has already finished (or the id is unknown), " +
|
|
36
|
+
"this is a no-op that returns found=false. Optional 'reason' is recorded in the " +
|
|
37
|
+
"cancelled step results for traceability.",
|
|
38
|
+
parameters: Type.Object({
|
|
39
|
+
mission_id: Type.String({
|
|
40
|
+
description: "The mission_id echoed by run_mission. Required.",
|
|
41
|
+
}),
|
|
42
|
+
reason: Type.Optional(
|
|
43
|
+
Type.String({
|
|
44
|
+
description: "Optional free-text reason — surfaced in the cancelled mission result.",
|
|
45
|
+
}),
|
|
46
|
+
),
|
|
47
|
+
}),
|
|
48
|
+
|
|
49
|
+
async execute(_toolCallId, params) {
|
|
50
|
+
const missionId = typeof params["mission_id"] === "string" ? (params["mission_id"] as string).trim() : "";
|
|
51
|
+
if (!missionId) {
|
|
52
|
+
const text = "mission_cancel requires 'mission_id' (a non-empty string returned by run_mission).";
|
|
53
|
+
return { content: [{ type: "text", text }], details: { success: false, error: text } };
|
|
54
|
+
}
|
|
55
|
+
const reason = typeof params["reason"] === "string" ? (params["reason"] as string) : undefined;
|
|
56
|
+
const outcome = getMissionRegistry().cancel(missionId, reason);
|
|
57
|
+
const details = {
|
|
58
|
+
success: true,
|
|
59
|
+
mission_id: missionId,
|
|
60
|
+
found: outcome.found,
|
|
61
|
+
already_cancelled: outcome.alreadyCancelled,
|
|
62
|
+
reason: reason ?? null,
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: JSON.stringify(details) }],
|
|
66
|
+
details,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -2,7 +2,8 @@ import { Type } from "@sinclair/typebox";
|
|
|
2
2
|
import type { OpenClawPluginApi } from "../plugin-api.js";
|
|
3
3
|
import type { AgenticROSConfig } from "@agenticros/core";
|
|
4
4
|
import { toNamespacedTopic } from "@agenticros/core";
|
|
5
|
-
import {
|
|
5
|
+
import { getTransportForRobot } from "../service.js";
|
|
6
|
+
import { ROBOT_ID_SCHEMA, resolveRobotForTool } from "./_robot-helpers.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Register the ros2_action_goal tool with the AI agent.
|
|
@@ -14,22 +15,28 @@ export function registerActionTool(api: OpenClawPluginApi, config: AgenticROSCon
|
|
|
14
15
|
label: "ROS2 Action Goal",
|
|
15
16
|
description:
|
|
16
17
|
"Send a goal to a ROS2 action server and stream feedback. " +
|
|
17
|
-
"Use this for long-running operations like navigation or arm movements."
|
|
18
|
+
"Use this for long-running operations like navigation or arm movements. " +
|
|
19
|
+
"Pass robot_id (from ros2_list_robots) to target a specific robot.",
|
|
18
20
|
parameters: Type.Object({
|
|
19
21
|
action: Type.String({ description: "The ROS2 action server name (e.g., '/navigate_to_pose')" }),
|
|
20
22
|
actionType: Type.String({ description: "The ROS2 action type (e.g., 'nav2_msgs/action/NavigateToPose')" }),
|
|
21
23
|
goal: Type.Record(Type.String(), Type.Unknown(), {
|
|
22
24
|
description: "The action goal parameters",
|
|
23
25
|
}),
|
|
26
|
+
...ROBOT_ID_SCHEMA,
|
|
24
27
|
}),
|
|
25
28
|
|
|
26
29
|
async execute(_toolCallId, params) {
|
|
30
|
+
const resolved = resolveRobotForTool(config, params);
|
|
31
|
+
if ("error" in resolved) return resolved.error;
|
|
32
|
+
const { robot } = resolved;
|
|
33
|
+
|
|
27
34
|
const rawAction = params["action"] as string;
|
|
28
|
-
const action = toNamespacedTopic(
|
|
35
|
+
const action = toNamespacedTopic(robot.namespace, rawAction);
|
|
29
36
|
const actionType = params["actionType"] as string;
|
|
30
37
|
const goal = params["goal"] as Record<string, unknown>;
|
|
31
38
|
|
|
32
|
-
const transport =
|
|
39
|
+
const transport = await getTransportForRobot(config, robot);
|
|
33
40
|
const actionResult = await transport.sendActionGoal({
|
|
34
41
|
action,
|
|
35
42
|
actionType,
|
|
@@ -9,11 +9,12 @@ import {
|
|
|
9
9
|
mimeTypeForSnapshotBase64,
|
|
10
10
|
rosNumericField,
|
|
11
11
|
} from "@agenticros/ros-camera";
|
|
12
|
-
import {
|
|
12
|
+
import { getTransportForRobot } from "../service.js";
|
|
13
13
|
import { normalizePluginToolImageBase64 } from "../plugin-image-base64.js";
|
|
14
14
|
import { trimJpegToLastEoi } from "../image-binary-trim.js";
|
|
15
15
|
import { storeCameraSnapshot } from "../camera-snapshot-cache.js";
|
|
16
16
|
import { describeImageBestEffort } from "../describer.js";
|
|
17
|
+
import { ROBOT_ID_SCHEMA, resolveRobotForTool } from "./_robot-helpers.js";
|
|
17
18
|
|
|
18
19
|
/** Known camera topic patterns for common setups (e.g. RealSense). */
|
|
19
20
|
export const REALSENSE_CAMERA_TOPICS = {
|
|
@@ -64,20 +65,25 @@ export function registerCameraTool(api: OpenClawPluginApi, config: AgenticROSCon
|
|
|
64
65
|
),
|
|
65
66
|
),
|
|
66
67
|
timeout: Type.Optional(Type.Number({ description: "Timeout in milliseconds (default: 10000)" })),
|
|
68
|
+
...ROBOT_ID_SCHEMA,
|
|
67
69
|
}),
|
|
68
70
|
|
|
69
71
|
async execute(_toolCallId, params) {
|
|
72
|
+
const resolved = resolveRobotForTool(config, params);
|
|
73
|
+
if ("error" in resolved) return resolved.error;
|
|
74
|
+
const { robot } = resolved;
|
|
75
|
+
|
|
70
76
|
const defaultTopic =
|
|
71
|
-
(
|
|
77
|
+
(robot.cameraTopic ?? "").trim() || "/camera/camera/color/image_raw/compressed";
|
|
72
78
|
const rawTopic = (params["topic"] as string | undefined) ?? defaultTopic;
|
|
73
|
-
const topic = resolveCameraSubscribeTopic(
|
|
79
|
+
const topic = resolveCameraSubscribeTopic(robot.namespace, rawTopic);
|
|
74
80
|
const rawMsgType = params["message_type"] as string | undefined;
|
|
75
81
|
const messageType: "CompressedImage" | "Image" =
|
|
76
82
|
rawMsgType === "Image" ? "Image" : "CompressedImage";
|
|
77
83
|
const timeout = (params["timeout"] as number | undefined) ?? 10000;
|
|
78
84
|
|
|
79
85
|
try {
|
|
80
|
-
const transport =
|
|
86
|
+
const transport = await getTransportForRobot(config, robot);
|
|
81
87
|
const typeSel = messageType === "Image" ? ROS_MSG_IMAGE : ROS_MSG_COMPRESSED_IMAGE;
|
|
82
88
|
|
|
83
89
|
const result = await new Promise<{
|