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
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-robot isolation tests for the follow-me + find-object skill registries.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1.d-extend asserts that each robot keeps its own independent
|
|
5
|
+
* follow-me loop instance, keyed by `robot.id`. If two robots ever share a
|
|
6
|
+
* single loop, starting follow-me on one would silently steer the other
|
|
7
|
+
* and `ros2_follow_me_status` would conflate state — exactly the regression
|
|
8
|
+
* this suite is designed to catch.
|
|
9
|
+
*
|
|
10
|
+
* These tests run as pure registry checks — they construct a minimal mock
|
|
11
|
+
* `RosTransport` so the loop constructors don't reach for the network, and
|
|
12
|
+
* they reset the registry between tests so suites stay deterministic.
|
|
13
|
+
*/
|
|
14
|
+
import { test } from "node:test";
|
|
15
|
+
import assert from "node:assert/strict";
|
|
16
|
+
import type { ResolvedRobot, RosTransport } from "@agenticros/core";
|
|
17
|
+
import {
|
|
18
|
+
getFollowMeDepth,
|
|
19
|
+
_resetFollowMeDepthRegistry,
|
|
20
|
+
} from "../follow-me/depth-loop.js";
|
|
21
|
+
import {
|
|
22
|
+
getFollowMeLocal,
|
|
23
|
+
_resetFollowMeLocalRegistry,
|
|
24
|
+
} from "../follow-me/loop.js";
|
|
25
|
+
import { parseConfig } from "@agenticros/core";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Minimal `RosTransport` stub. The follow-me loops only touch the transport
|
|
29
|
+
* inside `start()` (subscribe + publish) — the constructor + registry path
|
|
30
|
+
* never call into it. We provide a no-op surface so TypeScript is happy and
|
|
31
|
+
* any accidental call surfaces as an obvious "TypeError: ... not a function".
|
|
32
|
+
*/
|
|
33
|
+
function stubTransport(): RosTransport {
|
|
34
|
+
// The follow-me loop constructors don't touch the transport — they only
|
|
35
|
+
// call into it from `start()`. We cast through `unknown` so the test
|
|
36
|
+
// doesn't have to track the full RosTransport surface area; any
|
|
37
|
+
// accidental method call surfaces as a clean "not a function" error.
|
|
38
|
+
return {} as unknown as RosTransport;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function makeRobot(id: string, namespace: string): ResolvedRobot {
|
|
42
|
+
return {
|
|
43
|
+
id,
|
|
44
|
+
name: id,
|
|
45
|
+
namespace,
|
|
46
|
+
cameraTopic: "",
|
|
47
|
+
kind: "amr",
|
|
48
|
+
sensors: { has_realsense: false, has_lidar: false, has_arm: false },
|
|
49
|
+
source: "config",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const CONFIG = parseConfig({ robot: { namespace: "robot-a" } });
|
|
54
|
+
|
|
55
|
+
test("follow-me-registry: getFollowMeDepth returns the same instance for the same robot.id", () => {
|
|
56
|
+
_resetFollowMeDepthRegistry();
|
|
57
|
+
const transport = stubTransport();
|
|
58
|
+
const robotA = makeRobot("robot-a", "robot-a");
|
|
59
|
+
const first = getFollowMeDepth(robotA, CONFIG, transport);
|
|
60
|
+
const second = getFollowMeDepth(robotA, CONFIG, transport);
|
|
61
|
+
assert.strictEqual(
|
|
62
|
+
first,
|
|
63
|
+
second,
|
|
64
|
+
"two calls with the same robot.id must return the identical loop instance",
|
|
65
|
+
);
|
|
66
|
+
assert.equal(first.robotId, "robot-a", "instance should remember its robot.id");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("follow-me-registry: getFollowMeDepth returns DIFFERENT instances for DIFFERENT robot.ids", () => {
|
|
70
|
+
_resetFollowMeDepthRegistry();
|
|
71
|
+
const transport = stubTransport();
|
|
72
|
+
const robotA = makeRobot("robot-a", "robot-a");
|
|
73
|
+
const robotB = makeRobot("robot-b", "robot-b");
|
|
74
|
+
const loopA = getFollowMeDepth(robotA, CONFIG, transport);
|
|
75
|
+
const loopB = getFollowMeDepth(robotB, CONFIG, transport);
|
|
76
|
+
assert.notStrictEqual(loopA, loopB, "each robot must get its own independent loop");
|
|
77
|
+
assert.equal(loopA.robotId, "robot-a");
|
|
78
|
+
assert.equal(loopB.robotId, "robot-b");
|
|
79
|
+
// And a follow-up lookup must still return each robot's own loop, not
|
|
80
|
+
// the most-recently-created one.
|
|
81
|
+
assert.strictEqual(getFollowMeDepth(robotA, CONFIG, transport), loopA);
|
|
82
|
+
assert.strictEqual(getFollowMeDepth(robotB, CONFIG, transport), loopB);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("follow-me-registry: getFollowMeLocal returns the same instance for the same robot.id", () => {
|
|
86
|
+
_resetFollowMeLocalRegistry();
|
|
87
|
+
const transport = stubTransport();
|
|
88
|
+
const robotA = makeRobot("robot-a", "robot-a");
|
|
89
|
+
const first = getFollowMeLocal(robotA, CONFIG, transport);
|
|
90
|
+
const second = getFollowMeLocal(robotA, CONFIG, transport);
|
|
91
|
+
assert.strictEqual(
|
|
92
|
+
first,
|
|
93
|
+
second,
|
|
94
|
+
"two calls with the same robot.id must return the identical loop instance",
|
|
95
|
+
);
|
|
96
|
+
assert.equal(first.robotId, "robot-a", "instance should remember its robot.id");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("follow-me-registry: getFollowMeLocal returns DIFFERENT instances for DIFFERENT robot.ids", () => {
|
|
100
|
+
_resetFollowMeLocalRegistry();
|
|
101
|
+
const transport = stubTransport();
|
|
102
|
+
const robotA = makeRobot("robot-a", "robot-a");
|
|
103
|
+
const robotB = makeRobot("robot-b", "robot-b");
|
|
104
|
+
const loopA = getFollowMeLocal(robotA, CONFIG, transport);
|
|
105
|
+
const loopB = getFollowMeLocal(robotB, CONFIG, transport);
|
|
106
|
+
assert.notStrictEqual(loopA, loopB, "each robot must get its own independent loop");
|
|
107
|
+
assert.equal(loopA.robotId, "robot-a");
|
|
108
|
+
assert.equal(loopB.robotId, "robot-b");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("follow-me-registry: status() defaults to disabled on a fresh per-robot instance", () => {
|
|
112
|
+
_resetFollowMeDepthRegistry();
|
|
113
|
+
const transport = stubTransport();
|
|
114
|
+
const robot = makeRobot("robot-a", "robot-a");
|
|
115
|
+
const loop = getFollowMeDepth(robot, CONFIG, transport);
|
|
116
|
+
const s = loop.status();
|
|
117
|
+
assert.equal(s.enabled, false, "a freshly-registered loop should not be enabled");
|
|
118
|
+
assert.equal(s.tracking, false, "a freshly-registered loop should not be tracking");
|
|
119
|
+
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* camera feed, then stop. Used by the ros2_find_object MCP tool.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { AgenticROSConfig, RosTransport } from "@agenticros/core";
|
|
6
|
+
import type { AgenticROSConfig, ResolvedRobot, RosTransport } from "@agenticros/core";
|
|
7
7
|
import { resolveCameraSubscribeTopic, toNamespacedTopic } from "@agenticros/core";
|
|
8
8
|
import {
|
|
9
9
|
ROS_MSG_COMPRESSED_IMAGE,
|
|
@@ -48,6 +48,7 @@ export interface FindObjectResult {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export async function findObject(
|
|
51
|
+
robot: ResolvedRobot,
|
|
51
52
|
config: AgenticROSConfig,
|
|
52
53
|
transport: RosTransport,
|
|
53
54
|
opts: FindObjectOptions,
|
|
@@ -78,10 +79,10 @@ export async function findObject(
|
|
|
78
79
|
const detector = new PersonDetector({ scoreThreshold: minConfidence });
|
|
79
80
|
await detector.load();
|
|
80
81
|
|
|
81
|
-
const cmdVelTopic = resolveCmdVelTopic(config);
|
|
82
|
+
const cmdVelTopic = resolveCmdVelTopic(config, robot);
|
|
82
83
|
const colorTopic = resolveCameraSubscribeTopic(
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
robot.namespace,
|
|
85
|
+
robot.cameraTopic.trim() || DEFAULT_COLOR_TOPIC,
|
|
85
86
|
);
|
|
86
87
|
|
|
87
88
|
const startedAt = Date.now();
|
|
@@ -178,9 +179,9 @@ function sleep(ms: number): Promise<void> {
|
|
|
178
179
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
function resolveCmdVelTopic(config: AgenticROSConfig): string {
|
|
182
|
+
function resolveCmdVelTopic(config: AgenticROSConfig, robot: ResolvedRobot): string {
|
|
182
183
|
const raw = (config.teleop?.cmdVelTopic ?? "").trim() || "/cmd_vel";
|
|
183
|
-
const namespaced = toNamespacedTopic(
|
|
184
|
+
const namespaced = toNamespacedTopic(robot.namespace, raw);
|
|
184
185
|
const match = namespaced.match(/^\/([^/]+)\/cmd_vel$/i);
|
|
185
186
|
const segment = match?.[1] ?? "";
|
|
186
187
|
if (match && !segment.toLowerCase().startsWith("robot")) {
|
|
@@ -20,7 +20,12 @@
|
|
|
20
20
|
* person re-identification.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import type {
|
|
23
|
+
import type {
|
|
24
|
+
AgenticROSConfig,
|
|
25
|
+
ResolvedRobot,
|
|
26
|
+
RosTransport,
|
|
27
|
+
Subscription,
|
|
28
|
+
} from "@agenticros/core";
|
|
24
29
|
import { resolveCameraSubscribeTopic, toNamespacedTopic } from "@agenticros/core";
|
|
25
30
|
import {
|
|
26
31
|
ROS_MSG_IMAGE,
|
|
@@ -93,10 +98,16 @@ export class FollowMeDepth {
|
|
|
93
98
|
private tracking = false;
|
|
94
99
|
|
|
95
100
|
constructor(
|
|
101
|
+
private readonly robot: ResolvedRobot,
|
|
96
102
|
private readonly config: AgenticROSConfig,
|
|
97
103
|
private readonly transport: RosTransport,
|
|
98
104
|
) {}
|
|
99
105
|
|
|
106
|
+
/** Stable id of the robot this loop is bound to. Used by the registry below. */
|
|
107
|
+
get robotId(): string {
|
|
108
|
+
return this.robot.id;
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
async start(opts: StartOptions = {}): Promise<void> {
|
|
101
112
|
if (this.enabled) return;
|
|
102
113
|
this.lastError = null;
|
|
@@ -145,7 +156,7 @@ export class FollowMeDepth {
|
|
|
145
156
|
}
|
|
146
157
|
|
|
147
158
|
private subscribeDepth(): void {
|
|
148
|
-
const depthTopic = resolveCameraSubscribeTopic(this.
|
|
159
|
+
const depthTopic = resolveCameraSubscribeTopic(this.robot.namespace, DEFAULT_DEPTH_TOPIC);
|
|
149
160
|
this.depthSub = this.transport.subscribe(
|
|
150
161
|
{ topic: depthTopic, type: ROS_MSG_IMAGE },
|
|
151
162
|
(msg) => {
|
|
@@ -228,7 +239,7 @@ export class FollowMeDepth {
|
|
|
228
239
|
}
|
|
229
240
|
|
|
230
241
|
private async publishTwist(twist: Twist): Promise<void> {
|
|
231
|
-
const topic = resolveCmdVelTopic(this.config);
|
|
242
|
+
const topic = resolveCmdVelTopic(this.config, this.robot);
|
|
232
243
|
const message = {
|
|
233
244
|
linear: { x: twist.linearX, y: 0, z: 0 },
|
|
234
245
|
angular: { x: 0, y: 0, z: twist.angularZ },
|
|
@@ -401,9 +412,9 @@ function sampleCellBand(
|
|
|
401
412
|
return n;
|
|
402
413
|
}
|
|
403
414
|
|
|
404
|
-
function resolveCmdVelTopic(config: AgenticROSConfig): string {
|
|
415
|
+
function resolveCmdVelTopic(config: AgenticROSConfig, robot: ResolvedRobot): string {
|
|
405
416
|
const raw = (config.teleop?.cmdVelTopic ?? "").trim() || "/cmd_vel";
|
|
406
|
-
const namespaced = toNamespacedTopic(
|
|
417
|
+
const namespaced = toNamespacedTopic(robot.namespace, raw);
|
|
407
418
|
const match = namespaced.match(/^\/([^/]+)\/cmd_vel$/i);
|
|
408
419
|
const segment = match?.[1] ?? "";
|
|
409
420
|
if (match && !segment.toLowerCase().startsWith("robot")) {
|
|
@@ -412,9 +423,30 @@ function resolveCmdVelTopic(config: AgenticROSConfig): string {
|
|
|
412
423
|
return namespaced;
|
|
413
424
|
}
|
|
414
425
|
|
|
415
|
-
|
|
426
|
+
/**
|
|
427
|
+
* Per-robot registry. Each robot gets its own `FollowMeDepth` instance the
|
|
428
|
+
* first time anyone asks for one — multi-robot Phase 1.d-extend.
|
|
429
|
+
*
|
|
430
|
+
* Keying by `robot.id` (not the namespace string) so two configs that resolve
|
|
431
|
+
* to the same id share the same loop, and a config swap that *changes* the id
|
|
432
|
+
* yields a fresh loop with fresh subscriptions.
|
|
433
|
+
*/
|
|
434
|
+
const instances = new Map<string, FollowMeDepth>();
|
|
435
|
+
|
|
436
|
+
export function getFollowMeDepth(
|
|
437
|
+
robot: ResolvedRobot,
|
|
438
|
+
config: AgenticROSConfig,
|
|
439
|
+
transport: RosTransport,
|
|
440
|
+
): FollowMeDepth {
|
|
441
|
+
let entry = instances.get(robot.id);
|
|
442
|
+
if (!entry) {
|
|
443
|
+
entry = new FollowMeDepth(robot, config, transport);
|
|
444
|
+
instances.set(robot.id, entry);
|
|
445
|
+
}
|
|
446
|
+
return entry;
|
|
447
|
+
}
|
|
416
448
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
449
|
+
/** Test-only: clear the registry so suites can run in isolation. */
|
|
450
|
+
export function _resetFollowMeDepthRegistry(): void {
|
|
451
|
+
instances.clear();
|
|
420
452
|
}
|
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
* This is the `mode: 'local'` alternative to the agenticros_follow_me ROS2 node.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
AgenticROSConfig,
|
|
10
|
+
ResolvedRobot,
|
|
11
|
+
RosTransport,
|
|
12
|
+
Subscription,
|
|
13
|
+
} from "@agenticros/core";
|
|
9
14
|
import { resolveCameraSubscribeTopic, toNamespacedTopic } from "@agenticros/core";
|
|
10
15
|
import {
|
|
11
16
|
ROS_MSG_COMPRESSED_IMAGE,
|
|
@@ -75,10 +80,16 @@ export class FollowMeLocal {
|
|
|
75
80
|
private tracking = false;
|
|
76
81
|
|
|
77
82
|
constructor(
|
|
83
|
+
private readonly robot: ResolvedRobot,
|
|
78
84
|
private readonly config: AgenticROSConfig,
|
|
79
85
|
private readonly transport: RosTransport,
|
|
80
86
|
) {}
|
|
81
87
|
|
|
88
|
+
/** Stable id of the robot this loop is bound to. Used by the registry below. */
|
|
89
|
+
get robotId(): string {
|
|
90
|
+
return this.robot.id;
|
|
91
|
+
}
|
|
92
|
+
|
|
82
93
|
async start(opts: StartOptions = {}): Promise<void> {
|
|
83
94
|
if (this.enabled) return;
|
|
84
95
|
this.lastError = null;
|
|
@@ -129,9 +140,9 @@ export class FollowMeLocal {
|
|
|
129
140
|
}
|
|
130
141
|
|
|
131
142
|
private subscribeFrames(): void {
|
|
132
|
-
const colorTopicRaw =
|
|
133
|
-
const colorTopic = resolveCameraSubscribeTopic(this.
|
|
134
|
-
const depthTopic = resolveCameraSubscribeTopic(this.
|
|
143
|
+
const colorTopicRaw = this.robot.cameraTopic.trim() || DEFAULT_COLOR_TOPIC;
|
|
144
|
+
const colorTopic = resolveCameraSubscribeTopic(this.robot.namespace, colorTopicRaw);
|
|
145
|
+
const depthTopic = resolveCameraSubscribeTopic(this.robot.namespace, DEFAULT_DEPTH_TOPIC);
|
|
135
146
|
const isCompressed = colorTopic.includes("compressed");
|
|
136
147
|
|
|
137
148
|
this.colorSub = this.transport.subscribe(
|
|
@@ -261,7 +272,7 @@ export class FollowMeLocal {
|
|
|
261
272
|
}
|
|
262
273
|
|
|
263
274
|
private async publishTwist(twist: Twist): Promise<void> {
|
|
264
|
-
const topic = resolveCmdVelTopic(this.config);
|
|
275
|
+
const topic = resolveCmdVelTopic(this.config, this.robot);
|
|
265
276
|
const message = {
|
|
266
277
|
linear: { x: twist.linearX, y: 0, z: 0 },
|
|
267
278
|
angular: { x: 0, y: 0, z: twist.angularZ },
|
|
@@ -310,9 +321,9 @@ function sampleDepthRegion(d: LatestDepth, x0: number, y0: number, x1: number, y
|
|
|
310
321
|
return out;
|
|
311
322
|
}
|
|
312
323
|
|
|
313
|
-
function resolveCmdVelTopic(config: AgenticROSConfig): string {
|
|
324
|
+
function resolveCmdVelTopic(config: AgenticROSConfig, robot: ResolvedRobot): string {
|
|
314
325
|
const raw = (config.teleop?.cmdVelTopic ?? "").trim() || "/cmd_vel";
|
|
315
|
-
const namespaced = toNamespacedTopic(
|
|
326
|
+
const namespaced = toNamespacedTopic(robot.namespace, raw);
|
|
316
327
|
// Apply same uuid → robot<uuid-no-dashes> rewrite as ros2_publish handler.
|
|
317
328
|
const match = namespaced.match(/^\/([^/]+)\/cmd_vel$/i);
|
|
318
329
|
const segment = match?.[1] ?? "";
|
|
@@ -322,9 +333,27 @@ function resolveCmdVelTopic(config: AgenticROSConfig): string {
|
|
|
322
333
|
return namespaced;
|
|
323
334
|
}
|
|
324
335
|
|
|
325
|
-
|
|
336
|
+
/**
|
|
337
|
+
* Per-robot registry. Each robot gets its own `FollowMeLocal` instance (with
|
|
338
|
+
* its own YOLO detector + camera subs) the first time anyone asks for one.
|
|
339
|
+
* Multi-robot Phase 1.d-extend.
|
|
340
|
+
*/
|
|
341
|
+
const instances = new Map<string, FollowMeLocal>();
|
|
342
|
+
|
|
343
|
+
export function getFollowMeLocal(
|
|
344
|
+
robot: ResolvedRobot,
|
|
345
|
+
config: AgenticROSConfig,
|
|
346
|
+
transport: RosTransport,
|
|
347
|
+
): FollowMeLocal {
|
|
348
|
+
let entry = instances.get(robot.id);
|
|
349
|
+
if (!entry) {
|
|
350
|
+
entry = new FollowMeLocal(robot, config, transport);
|
|
351
|
+
instances.set(robot.id, entry);
|
|
352
|
+
}
|
|
353
|
+
return entry;
|
|
354
|
+
}
|
|
326
355
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
356
|
+
/** Test-only: clear the registry so suites can run in isolation. */
|
|
357
|
+
export function _resetFollowMeLocalRegistry(): void {
|
|
358
|
+
instances.clear();
|
|
330
359
|
}
|
|
@@ -15,7 +15,7 @@ import type { AgenticROSConfig } from "@agenticros/core";
|
|
|
15
15
|
import { renderAgenticROSBanner } from "@agenticros/core";
|
|
16
16
|
import { loadConfig } from "./config.js";
|
|
17
17
|
import { connect, disconnect } from "./transport.js";
|
|
18
|
-
import { TOOLS, handleToolCall, MEMORY_TOOL_NAMES } from "./tools.js";
|
|
18
|
+
import { TOOLS, handleToolCall, MEMORY_TOOL_NAMES, NO_TRANSPORT_TOOL_NAMES } from "./tools.js";
|
|
19
19
|
import { ensureMemory } from "./memory.js";
|
|
20
20
|
|
|
21
21
|
let config: AgenticROSConfig | null = null;
|
|
@@ -81,10 +81,10 @@ function main(): void {
|
|
|
81
81
|
process.stderr.write(
|
|
82
82
|
`[AgenticROS] Config: robot.namespace=${ns ? `"${ns}"` : '""'} → ${ns ? `/${ns}/cmd_vel` : "/cmd_vel"}\n`,
|
|
83
83
|
);
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
if (!
|
|
84
|
+
// Tools that don't need the ROS transport (memory + capabilities) are
|
|
85
|
+
// self-contained — skip `ensureConnected()` so they work even when
|
|
86
|
+
// zenohd is down. Every other tool needs the transport up.
|
|
87
|
+
if (!NO_TRANSPORT_TOOL_NAMES.has(name)) {
|
|
88
88
|
await ensureConnected();
|
|
89
89
|
}
|
|
90
90
|
const result = await handleToolCall(name, args ?? {}, config);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-process singleton mission registry for the Claude Code MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Each `run_mission` invocation registers a fresh `mission_id` here so
|
|
5
|
+
* a sibling `mission_cancel` MCP call can flip the cancellation token
|
|
6
|
+
* mid-run. The runner picks that up at the next step boundary and
|
|
7
|
+
* gracefully marks remaining steps as "cancelled".
|
|
8
|
+
*
|
|
9
|
+
* Lives at the module scope (not as a per-request object) because MCP
|
|
10
|
+
* tool calls are dispatched independently — the cancel tool has no
|
|
11
|
+
* other way to find the in-flight mission. See
|
|
12
|
+
* docs/strategy-ai-agents-plus-ros.md §4 Phase 1.f.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { MissionRegistry } from "@agenticros/core";
|
|
16
|
+
|
|
17
|
+
const registry = new MissionRegistry();
|
|
18
|
+
|
|
19
|
+
export function getMissionRegistry(): MissionRegistry {
|
|
20
|
+
return registry;
|
|
21
|
+
}
|