agenticros 0.1.11 → 0.1.12
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agenticros",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgenticROS - agentic AI for ROS-powered robots. Single CLI to launch real-robot or simulated demos, manage configuration, and inspect status.",
|
|
6
6
|
"keywords": [
|
package/runtime/BUNDLE.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"packedAt": "2026-06-06T14:
|
|
2
|
+
"packedAt": "2026-06-06T14:30:19.631Z",
|
|
3
3
|
"repo": "https://github.com/PlaiPin/agenticros",
|
|
4
4
|
"note": "This directory is a snapshot of the agenticros monorepo source. `agenticros init` will copy it to ~/agenticros and run pnpm install + colcon build there.",
|
|
5
5
|
"layout": {
|
|
@@ -360,11 +360,40 @@ export async function handleToolCall(
|
|
|
360
360
|
const topics = await transport.listTopics();
|
|
361
361
|
const MAX = 50;
|
|
362
362
|
const truncated = topics.length > MAX ? topics.slice(0, MAX) : topics;
|
|
363
|
+
|
|
364
|
+
// Surface task-specific control hints so the agent (and any LLM
|
|
365
|
+
// calling list_topics to figure out what's available) doesn't have to
|
|
366
|
+
// memorise the topic surface for each demo. Cheap to compute and only
|
|
367
|
+
// included when matching topics are actually present.
|
|
368
|
+
const hints: Record<string, string> = {};
|
|
369
|
+
const armJoints = topics
|
|
370
|
+
.filter((t) => /^\/arm\/[a-z0-9_]+\/cmd_pos$/i.test(t.name))
|
|
371
|
+
.map((t) => t.name.replace(/^\/arm\/(.+)\/cmd_pos$/, "$1"));
|
|
372
|
+
if (armJoints.length > 0) {
|
|
373
|
+
hints["arm"] =
|
|
374
|
+
`Detected the AgenticROS sim arm (joints: ${armJoints.join(", ")}). ` +
|
|
375
|
+
"To move a joint, call ros2_publish with topic '/arm/<joint>/cmd_pos', " +
|
|
376
|
+
"type 'std_msgs/msg/Float64', message {data: <radians>}. " +
|
|
377
|
+
"Example: rotate shoulder_pan 90 degrees left -> publish {data: 1.5707} " +
|
|
378
|
+
"to /arm/shoulder_pan/cmd_pos. Listen to /joint_states (sensor_msgs/msg/JointState) " +
|
|
379
|
+
"to read current positions.";
|
|
380
|
+
}
|
|
381
|
+
const baseTwistTopics = topics.filter(
|
|
382
|
+
(t) => /\/cmd_vel$/.test(t.name) && t.type === "geometry_msgs/msg/Twist",
|
|
383
|
+
);
|
|
384
|
+
if (baseTwistTopics.length > 0) {
|
|
385
|
+
hints["base"] =
|
|
386
|
+
"To drive the base, publish geometry_msgs/msg/Twist to a cmd_vel topic. " +
|
|
387
|
+
`Available cmd_vel topics: ${baseTwistTopics.map((t) => t.name).join(", ")}. ` +
|
|
388
|
+
"When in sim mode the unnamespaced /cmd_vel is the one the simulator listens on.";
|
|
389
|
+
}
|
|
390
|
+
|
|
363
391
|
const text = JSON.stringify({
|
|
364
392
|
success: true,
|
|
365
393
|
topics: truncated,
|
|
366
394
|
total: topics.length,
|
|
367
395
|
truncated: topics.length > MAX,
|
|
396
|
+
...(Object.keys(hints).length > 0 ? { hints } : {}),
|
|
368
397
|
});
|
|
369
398
|
return { content: [{ type: "text", text }] };
|
|
370
399
|
}
|
|
@@ -91,7 +91,11 @@ export class LocalTransport implements RosTransport {
|
|
|
91
91
|
// Give DDS discovery a brief moment to receive announcements from peers
|
|
92
92
|
// before we report "connected". Without this the very first listTopics()
|
|
93
93
|
// can race the executor and return empty even though peers exist.
|
|
94
|
-
|
|
94
|
+
// 1500ms was chosen empirically on Jetson + Humble where /arm/*/cmd_pos
|
|
95
|
+
// topics from a sim_arm gz_bridge child take ~1.2s to propagate via
|
|
96
|
+
// multicast (vs ~400ms on x86); the prior 750ms cap caused first
|
|
97
|
+
// list_topics() calls to miss them.
|
|
98
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
|
|
95
99
|
|
|
96
100
|
this.setStatus("connected");
|
|
97
101
|
} catch (err) {
|
|
@@ -280,24 +284,41 @@ export class LocalTransport implements RosTransport {
|
|
|
280
284
|
async listTopics(): Promise<TopicInfo[]> {
|
|
281
285
|
this.ensureConnected();
|
|
282
286
|
|
|
283
|
-
// DDS discovery is asynchronous. On a freshly-connected
|
|
284
|
-
// getTopicNamesAndTypes() trickles in results as peers respond -
|
|
285
|
-
// first call may return 0, the second 2, the third all of them.
|
|
286
|
-
//
|
|
287
|
-
//
|
|
287
|
+
// DDS discovery is asynchronous AND stateful. On a freshly-connected
|
|
288
|
+
// node, getTopicNamesAndTypes() trickles in results as peers respond -
|
|
289
|
+
// the first call may return 0, the second 2, the third all of them.
|
|
290
|
+
//
|
|
291
|
+
// Two issues motivated the current tuning:
|
|
292
|
+
//
|
|
293
|
+
// 1. A short single-stable-hit policy (600ms) was returning truncated
|
|
294
|
+
// views to Claude: e.g. with the AMR sim alone it'd see /cmd_vel
|
|
295
|
+
// etc. on first call, then a second call 2s later would see all
|
|
296
|
+
// 29 topics including /arm/*/cmd_pos. Claude only calls
|
|
297
|
+
// list_topics once and was missing arm joint topics entirely.
|
|
298
|
+
//
|
|
299
|
+
// 2. DDS multicast announcements on Jetson take longer than on x86 -
|
|
300
|
+
// we routinely see /arm/* topics appear ~1.5s after first call.
|
|
301
|
+
//
|
|
302
|
+
// Fix: require 3 consecutive matching polls (2 stable hits at 300ms
|
|
303
|
+
// intervals = ~900ms of stability), with a 6 s deadline. Worst case
|
|
304
|
+
// ~900ms latency on a steady graph, ~5s+ if discovery is still
|
|
305
|
+
// streaming. The min-count guard (>=3) avoids exiting early when only
|
|
306
|
+
// /clock + /tf + /tf_static have come through.
|
|
288
307
|
const externalFilter = (t: { name: string; types: string[] }) =>
|
|
289
308
|
!INTERNAL_TOPIC_PREFIXES.some((prefix) => t.name.startsWith(prefix));
|
|
290
309
|
|
|
291
|
-
const deadline = Date.now() +
|
|
310
|
+
const deadline = Date.now() + 6000;
|
|
311
|
+
const MIN_STABLE_COUNT = 3;
|
|
312
|
+
const REQUIRED_STABLE_HITS = 2;
|
|
292
313
|
let raw: Array<{ name: string; types: string[] }> = [];
|
|
293
314
|
let prevCount = -1;
|
|
294
315
|
let stableHits = 0;
|
|
295
316
|
while (Date.now() < deadline) {
|
|
296
317
|
raw = this.node.getTopicNamesAndTypes();
|
|
297
318
|
const externalCount = raw.filter(externalFilter).length;
|
|
298
|
-
if (externalCount === prevCount && externalCount
|
|
319
|
+
if (externalCount === prevCount && externalCount >= MIN_STABLE_COUNT) {
|
|
299
320
|
stableHits++;
|
|
300
|
-
if (stableHits >=
|
|
321
|
+
if (stableHits >= REQUIRED_STABLE_HITS) break;
|
|
301
322
|
} else {
|
|
302
323
|
stableHits = 0;
|
|
303
324
|
prevCount = externalCount;
|