@agenticros/core 0.5.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 (151) hide show
  1. package/LICENSE +192 -0
  2. package/README.md +88 -0
  3. package/dist/banner.d.ts +7 -0
  4. package/dist/banner.d.ts.map +1 -0
  5. package/dist/banner.js +25 -0
  6. package/dist/banner.js.map +1 -0
  7. package/dist/capabilities.d.ts +101 -0
  8. package/dist/capabilities.d.ts.map +1 -0
  9. package/dist/capabilities.js +240 -0
  10. package/dist/capabilities.js.map +1 -0
  11. package/dist/cmd-vel-twist.d.ts +9 -0
  12. package/dist/cmd-vel-twist.d.ts.map +1 -0
  13. package/dist/cmd-vel-twist.js +32 -0
  14. package/dist/cmd-vel-twist.js.map +1 -0
  15. package/dist/config.d.ts +1195 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +427 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/discovery.d.ts +113 -0
  20. package/dist/discovery.d.ts.map +1 -0
  21. package/dist/discovery.js +141 -0
  22. package/dist/discovery.js.map +1 -0
  23. package/dist/find-robots-for.d.ts +85 -0
  24. package/dist/find-robots-for.d.ts.map +1 -0
  25. package/dist/find-robots-for.js +120 -0
  26. package/dist/find-robots-for.js.map +1 -0
  27. package/dist/index.d.ts +32 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +20 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/memory/factory.d.ts +18 -0
  32. package/dist/memory/factory.d.ts.map +1 -0
  33. package/dist/memory/factory.js +51 -0
  34. package/dist/memory/factory.js.map +1 -0
  35. package/dist/memory/index.d.ts +3 -0
  36. package/dist/memory/index.d.ts.map +1 -0
  37. package/dist/memory/index.js +2 -0
  38. package/dist/memory/index.js.map +1 -0
  39. package/dist/memory/local/provider.d.ts +29 -0
  40. package/dist/memory/local/provider.d.ts.map +1 -0
  41. package/dist/memory/local/provider.js +202 -0
  42. package/dist/memory/local/provider.js.map +1 -0
  43. package/dist/memory/mem0/provider.d.ts +66 -0
  44. package/dist/memory/mem0/provider.d.ts.map +1 -0
  45. package/dist/memory/mem0/provider.js +310 -0
  46. package/dist/memory/mem0/provider.js.map +1 -0
  47. package/dist/memory/types.d.ts +91 -0
  48. package/dist/memory/types.d.ts.map +1 -0
  49. package/dist/memory/types.js +10 -0
  50. package/dist/memory/types.js.map +1 -0
  51. package/dist/mission-registry.d.ts +94 -0
  52. package/dist/mission-registry.d.ts.map +1 -0
  53. package/dist/mission-registry.js +123 -0
  54. package/dist/mission-registry.js.map +1 -0
  55. package/dist/mission-transcript-sink.d.ts +33 -0
  56. package/dist/mission-transcript-sink.d.ts.map +1 -0
  57. package/dist/mission-transcript-sink.js +76 -0
  58. package/dist/mission-transcript-sink.js.map +1 -0
  59. package/dist/mission.d.ts +281 -0
  60. package/dist/mission.d.ts.map +1 -0
  61. package/dist/mission.js +331 -0
  62. package/dist/mission.js.map +1 -0
  63. package/dist/planner/index.d.ts +84 -0
  64. package/dist/planner/index.d.ts.map +1 -0
  65. package/dist/planner/index.js +486 -0
  66. package/dist/planner/index.js.map +1 -0
  67. package/dist/robots.d.ts +133 -0
  68. package/dist/robots.d.ts.map +1 -0
  69. package/dist/robots.js +220 -0
  70. package/dist/robots.js.map +1 -0
  71. package/dist/topic-utils.d.ts +47 -0
  72. package/dist/topic-utils.d.ts.map +1 -0
  73. package/dist/topic-utils.js +136 -0
  74. package/dist/topic-utils.js.map +1 -0
  75. package/dist/transport/factory.d.ts +10 -0
  76. package/dist/transport/factory.d.ts.map +1 -0
  77. package/dist/transport/factory.js +40 -0
  78. package/dist/transport/factory.js.map +1 -0
  79. package/dist/transport/local/conversion.d.ts +30 -0
  80. package/dist/transport/local/conversion.d.ts.map +1 -0
  81. package/dist/transport/local/conversion.js +340 -0
  82. package/dist/transport/local/conversion.js.map +1 -0
  83. package/dist/transport/local/entities.d.ts +34 -0
  84. package/dist/transport/local/entities.d.ts.map +1 -0
  85. package/dist/transport/local/entities.js +108 -0
  86. package/dist/transport/local/entities.js.map +1 -0
  87. package/dist/transport/local/transport.d.ts +53 -0
  88. package/dist/transport/local/transport.d.ts.map +1 -0
  89. package/dist/transport/local/transport.js +344 -0
  90. package/dist/transport/local/transport.js.map +1 -0
  91. package/dist/transport/rosbridge/actions.d.ts +30 -0
  92. package/dist/transport/rosbridge/actions.d.ts.map +1 -0
  93. package/dist/transport/rosbridge/actions.js +61 -0
  94. package/dist/transport/rosbridge/actions.js.map +1 -0
  95. package/dist/transport/rosbridge/adapter.d.ts +29 -0
  96. package/dist/transport/rosbridge/adapter.d.ts.map +1 -0
  97. package/dist/transport/rosbridge/adapter.js +108 -0
  98. package/dist/transport/rosbridge/adapter.js.map +1 -0
  99. package/dist/transport/rosbridge/client.d.ts +55 -0
  100. package/dist/transport/rosbridge/client.d.ts.map +1 -0
  101. package/dist/transport/rosbridge/client.js +375 -0
  102. package/dist/transport/rosbridge/client.js.map +1 -0
  103. package/dist/transport/rosbridge/services.d.ts +14 -0
  104. package/dist/transport/rosbridge/services.d.ts.map +1 -0
  105. package/dist/transport/rosbridge/services.js +25 -0
  106. package/dist/transport/rosbridge/services.js.map +1 -0
  107. package/dist/transport/rosbridge/topics.d.ts +28 -0
  108. package/dist/transport/rosbridge/topics.d.ts.map +1 -0
  109. package/dist/transport/rosbridge/topics.js +59 -0
  110. package/dist/transport/rosbridge/topics.js.map +1 -0
  111. package/dist/transport/rosbridge/types.d.ts +87 -0
  112. package/dist/transport/rosbridge/types.d.ts.map +1 -0
  113. package/dist/transport/rosbridge/types.js +6 -0
  114. package/dist/transport/rosbridge/types.js.map +1 -0
  115. package/dist/transport/transport.d.ts +39 -0
  116. package/dist/transport/transport.d.ts.map +1 -0
  117. package/dist/transport/transport.js +2 -0
  118. package/dist/transport/transport.js.map +1 -0
  119. package/dist/transport/types.d.ts +100 -0
  120. package/dist/transport/types.d.ts.map +1 -0
  121. package/dist/transport/types.js +5 -0
  122. package/dist/transport/types.js.map +1 -0
  123. package/dist/transport/webrtc/signaling-client.d.ts +38 -0
  124. package/dist/transport/webrtc/signaling-client.d.ts.map +1 -0
  125. package/dist/transport/webrtc/signaling-client.js +161 -0
  126. package/dist/transport/webrtc/signaling-client.js.map +1 -0
  127. package/dist/transport/webrtc/signaling-types.d.ts +112 -0
  128. package/dist/transport/webrtc/signaling-types.d.ts.map +1 -0
  129. package/dist/transport/webrtc/signaling-types.js +8 -0
  130. package/dist/transport/webrtc/signaling-types.js.map +1 -0
  131. package/dist/transport/webrtc/transport.d.ts +67 -0
  132. package/dist/transport/webrtc/transport.d.ts.map +1 -0
  133. package/dist/transport/webrtc/transport.js +406 -0
  134. package/dist/transport/webrtc/transport.js.map +1 -0
  135. package/dist/transport/zenoh/adapter.d.ts +47 -0
  136. package/dist/transport/zenoh/adapter.d.ts.map +1 -0
  137. package/dist/transport/zenoh/adapter.js +296 -0
  138. package/dist/transport/zenoh/adapter.js.map +1 -0
  139. package/dist/transport/zenoh/cdr.d.ts +17 -0
  140. package/dist/transport/zenoh/cdr.d.ts.map +1 -0
  141. package/dist/transport/zenoh/cdr.js +170 -0
  142. package/dist/transport/zenoh/cdr.js.map +1 -0
  143. package/dist/transport/zenoh/keys.d.ts +21 -0
  144. package/dist/transport/zenoh/keys.d.ts.map +1 -0
  145. package/dist/transport/zenoh/keys.js +44 -0
  146. package/dist/transport/zenoh/keys.js.map +1 -0
  147. package/dist/transport-pool.d.ts +74 -0
  148. package/dist/transport-pool.d.ts.map +1 -0
  149. package/dist/transport-pool.js +135 -0
  150. package/dist/transport-pool.js.map +1 -0
  151. package/package.json +80 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Multi-robot support — Phase 1.d of the AgenticROS strategy.
3
+ *
4
+ * For most deployments today the gateway talks to a single robot, so
5
+ * config.robot (a single object) is all that's used. Phase 1.d adds a
6
+ * `robots` array to the config so a single gateway / chat session can
7
+ * address multiple robots by id.
8
+ *
9
+ * This module is the source of truth for that resolution: from the raw
10
+ * AgenticROSConfig, what robots does the agent see, and which one is the
11
+ * active default? Adapters call into it from:
12
+ * - `ros2_list_robots` (this phase) — list everything the gateway knows about.
13
+ * - `ros2_list_capabilities(robot_id?)` (next iteration) — scope to one robot.
14
+ * - `run_mission` (next iteration) — `mission.robot_id` field.
15
+ * - per-tool `robot_id` (next iteration) — `ros2_publish`, `ros2_subscribe_once`,
16
+ * `ros2_camera_snapshot`, etc.
17
+ *
18
+ * Backwards compatibility: when `robots` is empty (i.e. nothing in
19
+ * config.robots), this module synthesises a one-entry list from the
20
+ * legacy `config.robot` object so old configs and the single-robot
21
+ * mental model keep working unchanged. The synthesised entry is marked
22
+ * default so it's selected by `getActiveRobotId()`.
23
+ */
24
+ import type { AgenticROSConfig } from "./config.js";
25
+ import type { TransportConfig } from "./transport/types.js";
26
+ /** Sensor/hardware tags on a robot (Phase 1.e). */
27
+ export interface RobotSensors {
28
+ /** Intel RealSense depth+RGB camera. */
29
+ has_realsense: boolean;
30
+ /** 2D or 3D LiDAR sensor. */
31
+ has_lidar: boolean;
32
+ /** Robotic manipulator (arm) attached. */
33
+ has_arm: boolean;
34
+ }
35
+ /** A resolved robot — what the agent + adapters operate on. */
36
+ export interface ResolvedRobot {
37
+ /** Stable, human-readable identifier. Falls back to namespace when not set. */
38
+ id: string;
39
+ /** Display name used by the agent in chat replies. */
40
+ name: string;
41
+ /** ROS2 topic namespace prefix (e.g. "robot3946b404..." → /robot3946.../cmd_vel). */
42
+ namespace: string;
43
+ /** Default camera topic for `ros2_camera_snapshot`. Empty string when unset. */
44
+ cameraTopic: string;
45
+ /**
46
+ * Phase 1.e robot kind ("amr" | "arm" | "drone" | "rover" | …).
47
+ * Free-form string. Defaults to "amr" for back-compat (the legacy
48
+ * fallback synthesises this too).
49
+ */
50
+ kind: string;
51
+ /** Phase 1.e sensor/hardware tags. Defaults to all-false. */
52
+ sensors: RobotSensors;
53
+ /**
54
+ * Phase 1.e optional per-robot capability allowlist. When `undefined`
55
+ * the robot inherits the global capability registry (the common
56
+ * case); when set it's the exact list `ros2_find_robots_for` will
57
+ * filter against for this robot.
58
+ */
59
+ capabilities?: string[];
60
+ /**
61
+ * Source tag — handy for diagnostics ("Why is this robot in the list?").
62
+ * - "config": from config.robots[]
63
+ * - "legacy": synthesised from the legacy single config.robot object
64
+ */
65
+ source: "config" | "legacy";
66
+ }
67
+ export declare function listRobots(config: AgenticROSConfig): ResolvedRobot[];
68
+ /**
69
+ * Pick the active robot's id according to these rules, in order:
70
+ * 1. `override` argument (when truthy) — usually a per-tool-call robot_id.
71
+ * 2. An entry in config.robots with `default: true`.
72
+ * 3. The first entry in the resolved list (which includes the legacy
73
+ * fallback when nothing else is configured).
74
+ *
75
+ * Throws when no robots exist at all — that should be impossible because
76
+ * the legacy fallback always produces at least one — but the guard is
77
+ * there for robustness against a corrupted config.
78
+ */
79
+ export declare function getActiveRobotId(config: AgenticROSConfig, override?: string): string;
80
+ /**
81
+ * Convenience wrapper for tool handlers: extract an optional `robot_id`
82
+ * (a string) from a tool-args record and call `resolveRobot`. Returns
83
+ * the active robot when `robot_id` is missing or not a string.
84
+ *
85
+ * Throws — with the known robot ids listed in the error — when
86
+ * `robot_id` is set to an unknown value. Adapters surface that as a
87
+ * tool error so the agent self-corrects via `ros2_list_robots`.
88
+ */
89
+ export declare function resolveRobotFromArgs(config: AgenticROSConfig, args: Record<string, unknown>): ResolvedRobot;
90
+ /**
91
+ * Resolve a robot to its full record by id. When `robotId` is omitted or
92
+ * empty, the active robot is returned. When `robotId` is provided but
93
+ * doesn't match any configured robot, throws with the available ids in
94
+ * the message so the agent can correct itself.
95
+ */
96
+ export declare function resolveRobot(config: AgenticROSConfig, robotId?: string): ResolvedRobot;
97
+ /**
98
+ * Phase 1.d-resolve helper: compute the effective `TransportConfig` for
99
+ * a given robot, honouring an optional per-robot override in
100
+ * `config.robots[i].transport`.
101
+ *
102
+ * Precedence (most specific wins):
103
+ * 1. The robot's `transport.<mode>` sub-section (e.g. the override's
104
+ * `zenoh: { routerEndpoint: ... }`).
105
+ * 2. The top-level `config.<mode>` section (e.g. the global
106
+ * `config.zenoh`).
107
+ *
108
+ * When the robot has no `transport` override at all, this returns the
109
+ * global transport from `getTransportConfig(config)` — so single-robot
110
+ * deployments behave exactly as before.
111
+ *
112
+ * Why this lives here (and not next to `createTransport`):
113
+ * - The resolver owns "which robot are we talking about?"; the
114
+ * transport factory owns "given a TransportConfig, build an
115
+ * instance". Mixing them couples a multi-robot decision into the
116
+ * low-level transport layer, which is wrong: adapters that don't
117
+ * care about per-robot transports keep using `createTransport` and
118
+ * never see this helper.
119
+ * - Callers that DO want per-robot pools call this to materialise the
120
+ * right `TransportConfig` per id, then hand it to `createTransport`.
121
+ *
122
+ * Throws when `robotId` is given but doesn't match any configured robot
123
+ * (delegated to `resolveRobot`'s error path).
124
+ */
125
+ export declare function getTransportConfigForRobot(config: AgenticROSConfig, robotId?: string): TransportConfig;
126
+ /**
127
+ * True when the named robot (or active robot when `robotId` is omitted)
128
+ * has a per-robot transport override. Adapters use this as a cheap
129
+ * branch to decide between "reuse the global transport" (fast path) and
130
+ * "build a per-robot one via `getTransportConfigForRobot`" (pool path).
131
+ */
132
+ export declare function hasRobotTransportOverride(config: AgenticROSConfig, robotId?: string): boolean;
133
+ //# sourceMappingURL=robots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,mDAAmD;AACnD,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,aAAa,EAAE,OAAO,CAAC;IACvB,6BAA6B;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,+DAA+D;AAC/D,MAAM,WAAW,aAAa;IAC5B,+EAA+E;IAC/E,EAAE,EAAE,MAAM,CAAC;IACX,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,OAAO,EAAE,YAAY,CAAC;IACtB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;OAIG;IACH,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAC7B;AAiBD,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,aAAa,EAAE,CAoCpE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAUpF;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,aAAa,CAIf;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,CAiBtF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,gBAAgB,EACxB,OAAO,CAAC,EAAE,MAAM,GACf,eAAe,CAuCjB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,gBAAgB,EACxB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAUT"}
package/dist/robots.js ADDED
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Multi-robot support — Phase 1.d of the AgenticROS strategy.
3
+ *
4
+ * For most deployments today the gateway talks to a single robot, so
5
+ * config.robot (a single object) is all that's used. Phase 1.d adds a
6
+ * `robots` array to the config so a single gateway / chat session can
7
+ * address multiple robots by id.
8
+ *
9
+ * This module is the source of truth for that resolution: from the raw
10
+ * AgenticROSConfig, what robots does the agent see, and which one is the
11
+ * active default? Adapters call into it from:
12
+ * - `ros2_list_robots` (this phase) — list everything the gateway knows about.
13
+ * - `ros2_list_capabilities(robot_id?)` (next iteration) — scope to one robot.
14
+ * - `run_mission` (next iteration) — `mission.robot_id` field.
15
+ * - per-tool `robot_id` (next iteration) — `ros2_publish`, `ros2_subscribe_once`,
16
+ * `ros2_camera_snapshot`, etc.
17
+ *
18
+ * Backwards compatibility: when `robots` is empty (i.e. nothing in
19
+ * config.robots), this module synthesises a one-entry list from the
20
+ * legacy `config.robot` object so old configs and the single-robot
21
+ * mental model keep working unchanged. The synthesised entry is marked
22
+ * default so it's selected by `getActiveRobotId()`.
23
+ */
24
+ import { getTransportConfig } from "./config.js";
25
+ /**
26
+ * Read every robot the config knows about, in declaration order, with
27
+ * the legacy single-robot fallback applied when config.robots is empty.
28
+ *
29
+ * Never throws — an empty list is returned when neither config.robots
30
+ * nor config.robot.namespace is meaningfully set (and the synthesised
31
+ * fallback uses "default" as the id in that degenerate case).
32
+ */
33
+ /** All-false default for {@link RobotSensors}. */
34
+ const DEFAULT_SENSORS = {
35
+ has_realsense: false,
36
+ has_lidar: false,
37
+ has_arm: false,
38
+ };
39
+ export function listRobots(config) {
40
+ const explicit = Array.isArray(config.robots) ? config.robots : [];
41
+ if (explicit.length > 0) {
42
+ return explicit.map((r) => ({
43
+ id: String(r.id),
44
+ name: r.name ?? "Robot",
45
+ namespace: r.namespace ?? "",
46
+ cameraTopic: r.cameraTopic ?? "",
47
+ kind: r.kind ?? "amr",
48
+ sensors: { ...DEFAULT_SENSORS, ...(r.sensors ?? {}) },
49
+ capabilities: r.capabilities,
50
+ source: "config",
51
+ }));
52
+ }
53
+ // Legacy fallback — synthesise one entry from config.robot. The
54
+ // legacy schema doesn't carry kind/sensors/capabilities, so we
55
+ // default them: kind="amr" (every existing real-robot deployment we
56
+ // ship is an AMR with RealSense) and sensors all-false (which is
57
+ // overly conservative but won't surface a false positive in
58
+ // ros2_find_robots_for). Users on multi-robot deployments will have
59
+ // promoted into config.robots[] anyway, where the fields are
60
+ // explicit.
61
+ const legacy = config.robot ?? { name: "Robot", namespace: "", cameraTopic: "" };
62
+ const id = (legacy.namespace?.trim() || "default");
63
+ return [
64
+ {
65
+ id,
66
+ name: legacy.name ?? "Robot",
67
+ namespace: legacy.namespace ?? "",
68
+ cameraTopic: legacy.cameraTopic ?? "",
69
+ kind: "amr",
70
+ sensors: { ...DEFAULT_SENSORS },
71
+ source: "legacy",
72
+ },
73
+ ];
74
+ }
75
+ /**
76
+ * Pick the active robot's id according to these rules, in order:
77
+ * 1. `override` argument (when truthy) — usually a per-tool-call robot_id.
78
+ * 2. An entry in config.robots with `default: true`.
79
+ * 3. The first entry in the resolved list (which includes the legacy
80
+ * fallback when nothing else is configured).
81
+ *
82
+ * Throws when no robots exist at all — that should be impossible because
83
+ * the legacy fallback always produces at least one — but the guard is
84
+ * there for robustness against a corrupted config.
85
+ */
86
+ export function getActiveRobotId(config, override) {
87
+ if (override && override.trim().length > 0)
88
+ return override.trim();
89
+ const explicit = Array.isArray(config.robots) ? config.robots : [];
90
+ const flagged = explicit.find((r) => r.default === true);
91
+ if (flagged)
92
+ return flagged.id;
93
+ const robots = listRobots(config);
94
+ if (robots.length === 0) {
95
+ throw new Error("No robots configured (config.robots is empty and config.robot has no namespace).");
96
+ }
97
+ return robots[0].id;
98
+ }
99
+ /**
100
+ * Convenience wrapper for tool handlers: extract an optional `robot_id`
101
+ * (a string) from a tool-args record and call `resolveRobot`. Returns
102
+ * the active robot when `robot_id` is missing or not a string.
103
+ *
104
+ * Throws — with the known robot ids listed in the error — when
105
+ * `robot_id` is set to an unknown value. Adapters surface that as a
106
+ * tool error so the agent self-corrects via `ros2_list_robots`.
107
+ */
108
+ export function resolveRobotFromArgs(config, args) {
109
+ const raw = args["robot_id"];
110
+ const robotId = typeof raw === "string" ? raw : undefined;
111
+ return resolveRobot(config, robotId);
112
+ }
113
+ /**
114
+ * Resolve a robot to its full record by id. When `robotId` is omitted or
115
+ * empty, the active robot is returned. When `robotId` is provided but
116
+ * doesn't match any configured robot, throws with the available ids in
117
+ * the message so the agent can correct itself.
118
+ */
119
+ export function resolveRobot(config, robotId) {
120
+ const robots = listRobots(config);
121
+ if (!robotId || robotId.trim().length === 0) {
122
+ const activeId = getActiveRobotId(config);
123
+ const found = robots.find((r) => r.id === activeId);
124
+ if (!found) {
125
+ throw new Error(`Active robot id "${activeId}" is not in the resolved list. This is a bug.`);
126
+ }
127
+ return found;
128
+ }
129
+ const trimmed = robotId.trim();
130
+ const found = robots.find((r) => r.id === trimmed);
131
+ if (!found) {
132
+ const known = robots.map((r) => r.id).join(", ");
133
+ throw new Error(`Unknown robot_id "${trimmed}". Known ids: ${known || "(none)"}. Use ros2_list_robots to discover.`);
134
+ }
135
+ return found;
136
+ }
137
+ /**
138
+ * Phase 1.d-resolve helper: compute the effective `TransportConfig` for
139
+ * a given robot, honouring an optional per-robot override in
140
+ * `config.robots[i].transport`.
141
+ *
142
+ * Precedence (most specific wins):
143
+ * 1. The robot's `transport.<mode>` sub-section (e.g. the override's
144
+ * `zenoh: { routerEndpoint: ... }`).
145
+ * 2. The top-level `config.<mode>` section (e.g. the global
146
+ * `config.zenoh`).
147
+ *
148
+ * When the robot has no `transport` override at all, this returns the
149
+ * global transport from `getTransportConfig(config)` — so single-robot
150
+ * deployments behave exactly as before.
151
+ *
152
+ * Why this lives here (and not next to `createTransport`):
153
+ * - The resolver owns "which robot are we talking about?"; the
154
+ * transport factory owns "given a TransportConfig, build an
155
+ * instance". Mixing them couples a multi-robot decision into the
156
+ * low-level transport layer, which is wrong: adapters that don't
157
+ * care about per-robot transports keep using `createTransport` and
158
+ * never see this helper.
159
+ * - Callers that DO want per-robot pools call this to materialise the
160
+ * right `TransportConfig` per id, then hand it to `createTransport`.
161
+ *
162
+ * Throws when `robotId` is given but doesn't match any configured robot
163
+ * (delegated to `resolveRobot`'s error path).
164
+ */
165
+ export function getTransportConfigForRobot(config, robotId) {
166
+ // No explicit robots[] AND no override possible — fast path.
167
+ const explicit = Array.isArray(config.robots) ? config.robots : [];
168
+ if (explicit.length === 0)
169
+ return getTransportConfig(config);
170
+ // Resolve the id to the *raw* entry (not the synthesised ResolvedRobot,
171
+ // because that strips the override). `resolveRobot` validates the id
172
+ // and throws on unknown — we reuse it for that side effect.
173
+ const robot = resolveRobot(config, robotId);
174
+ const raw = explicit.find((r) => r.id === robot.id);
175
+ if (!raw || !raw.transport)
176
+ return getTransportConfig(config);
177
+ // Build a synthetic config whose top-level transport mirrors the
178
+ // override, then run it through the standard `getTransportConfig`
179
+ // path. This keeps the "what shape does TransportConfig take?" logic
180
+ // in one place — if a new transport mode is added, only
181
+ // getTransportConfig needs to change.
182
+ const override = raw.transport;
183
+ const synthetic = {
184
+ ...config,
185
+ transport: { mode: override.mode },
186
+ rosbridge: override.mode === "rosbridge" && override.rosbridge
187
+ ? { ...config.rosbridge, ...override.rosbridge }
188
+ : config.rosbridge,
189
+ local: override.mode === "local" && override.local
190
+ ? { ...config.local, ...override.local }
191
+ : config.local,
192
+ zenoh: override.mode === "zenoh" && override.zenoh
193
+ ? { ...config.zenoh, ...override.zenoh }
194
+ : config.zenoh,
195
+ webrtc: override.mode === "webrtc" && override.webrtc
196
+ ? { ...config.webrtc, ...override.webrtc }
197
+ : config.webrtc,
198
+ };
199
+ return getTransportConfig(synthetic);
200
+ }
201
+ /**
202
+ * True when the named robot (or active robot when `robotId` is omitted)
203
+ * has a per-robot transport override. Adapters use this as a cheap
204
+ * branch to decide between "reuse the global transport" (fast path) and
205
+ * "build a per-robot one via `getTransportConfigForRobot`" (pool path).
206
+ */
207
+ export function hasRobotTransportOverride(config, robotId) {
208
+ const explicit = Array.isArray(config.robots) ? config.robots : [];
209
+ if (explicit.length === 0)
210
+ return false;
211
+ try {
212
+ const robot = resolveRobot(config, robotId);
213
+ const raw = explicit.find((r) => r.id === robot.id);
214
+ return Boolean(raw?.transport);
215
+ }
216
+ catch {
217
+ return false;
218
+ }
219
+ }
220
+ //# sourceMappingURL=robots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"robots.js","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import type { AgenticROSConfig } from "./config.js";
2
+ /**
3
+ * Apply robot namespace to a topic (or service/action name) when configured.
4
+ * If the namespace is set and the name is root-level (e.g. cmd_vel, battery_state),
5
+ * returns /<namespace>/<name>. Otherwise returns the normalized name as-is.
6
+ *
7
+ * The first argument may be either an `AgenticROSConfig` (legacy single-
8
+ * robot path) or a bare namespace string (used by per-tool `robot_id`
9
+ * routing — pass `resolveRobot(config, robot_id).namespace`).
10
+ *
11
+ * Example: namespace "robot-uuid", topic "/cmd_vel" -> "/robot-uuid/cmd_vel"
12
+ * Example: namespace "", topic "/cmd_vel" -> "/cmd_vel"
13
+ * Example: namespace "robot-uuid", topic "/robot-uuid/odom" -> "/robot-uuid/odom" (unchanged)
14
+ */
15
+ export declare function toNamespacedTopic(target: AgenticROSConfig | string, topic: string): string;
16
+ /**
17
+ * Apply robot namespace to any topic when configured (for transport subscribe/publish).
18
+ * Use this when the robot publishes/subscribes all topics under a namespace (e.g. Zenoh with
19
+ * zenoh-bridge-ros2dds or rmw_zenoh). If a namespace is set, returns /<namespace>/<topic>
20
+ * unless the topic already starts with /<namespace>/.
21
+ *
22
+ * The first argument may be either an `AgenticROSConfig` or a bare
23
+ * namespace string (per-robot routing).
24
+ *
25
+ * Example: namespace "robot-uuid", topic "/cmd_vel" -> "/robot-uuid/cmd_vel"
26
+ * Example: namespace "robot-uuid", topic "/camera/camera/color/image_raw/compressed" -> "/robot-uuid/camera/camera/color/image_raw/compressed"
27
+ * Example: namespace "robot-uuid", topic "/robot-uuid/odom" -> "/robot-uuid/odom" (unchanged)
28
+ */
29
+ export declare function toNamespacedTopicFull(target: AgenticROSConfig | string, topic: string): string;
30
+ /**
31
+ * Canonical topic string for teleop UI and ?topic= query params: leading slash, no robot namespace prefix.
32
+ * Subscribe/publish still uses {@link toNamespacedTopicFull} on the server so Zenoh keys match the bridge.
33
+ *
34
+ * Teleop is single-robot today, so this still takes the full config.
35
+ */
36
+ export declare function toTeleopCameraTopicShort(config: AgenticROSConfig, topic: string): string;
37
+ /**
38
+ * ROS topic to use when subscribing to camera streams (Zenoh / DDS).
39
+ * Unlike {@link toNamespacedTopicFull}, common sensor topics stay at the graph root (`/camera/...`, `/zed/...`)
40
+ * even when the namespace is set for cmd_vel. If the topic already starts with `/<namespace>/`, it is left as-is.
41
+ * Other multi-segment paths get the namespace prefix (same as Full) for odd layouts.
42
+ *
43
+ * The first argument may be either an `AgenticROSConfig` or a bare
44
+ * namespace string (per-robot routing).
45
+ */
46
+ export declare function resolveCameraSubscribeTopic(target: AgenticROSConfig | string, topic: string): string;
47
+ //# sourceMappingURL=topic-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topic-utils.d.ts","sourceRoot":"","sources":["../src/topic-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA4CpD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAO1F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ9F;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAYxF;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAcpG"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Topic-utility module — the single source of truth for namespace
3
+ * prefixing across all adapters and the mission runner.
4
+ *
5
+ * Phase 1.d (multi-robot) extends every public helper to accept *either*
6
+ * the full AgenticROSConfig (uses config.robot.namespace, the legacy
7
+ * single-robot behaviour) *or* a bare namespace string. The string-arg
8
+ * form is what per-tool `robot_id` routing goes through: callers resolve
9
+ * a `ResolvedRobot` via `resolveRobot(config, robot_id)` and pass
10
+ * `robot.namespace` directly.
11
+ *
12
+ * Every existing call site continues to work unchanged because the
13
+ * config-arg form is preserved verbatim.
14
+ */
15
+ /**
16
+ * Normalize a ROS 2 topic name to a canonical form (leading slash, no trailing slash).
17
+ */
18
+ function normalizeTopic(topic) {
19
+ const t = topic.trim().replace(/^\/+/, "").replace(/\/+$/, "");
20
+ return t ? `/${t}` : "/";
21
+ }
22
+ /**
23
+ * Return true if the topic is "root-level" (single segment, e.g. cmd_vel, battery_state).
24
+ */
25
+ function isRootLevelTopic(normalized) {
26
+ const withoutLeading = normalized.replace(/^\/+/, "");
27
+ return withoutLeading.length > 0 && !withoutLeading.includes("/");
28
+ }
29
+ /**
30
+ * Resolve a namespace from either a string or an AgenticROSConfig. The
31
+ * config form pulls from config.robot.namespace (legacy single-robot
32
+ * behaviour); the string form is taken verbatim. Empty/whitespace
33
+ * collapses to "" so the rest of the prefix logic short-circuits.
34
+ */
35
+ function resolveNamespace(target) {
36
+ if (typeof target === "string")
37
+ return target.trim();
38
+ return (target.robot?.namespace ?? "").trim();
39
+ }
40
+ /**
41
+ * Apply robot namespace to a topic (or service/action name) when configured.
42
+ * If the namespace is set and the name is root-level (e.g. cmd_vel, battery_state),
43
+ * returns /<namespace>/<name>. Otherwise returns the normalized name as-is.
44
+ *
45
+ * The first argument may be either an `AgenticROSConfig` (legacy single-
46
+ * robot path) or a bare namespace string (used by per-tool `robot_id`
47
+ * routing — pass `resolveRobot(config, robot_id).namespace`).
48
+ *
49
+ * Example: namespace "robot-uuid", topic "/cmd_vel" -> "/robot-uuid/cmd_vel"
50
+ * Example: namespace "", topic "/cmd_vel" -> "/cmd_vel"
51
+ * Example: namespace "robot-uuid", topic "/robot-uuid/odom" -> "/robot-uuid/odom" (unchanged)
52
+ */
53
+ export function toNamespacedTopic(target, topic) {
54
+ const normalized = normalizeTopic(topic);
55
+ const ns = resolveNamespace(target);
56
+ if (!ns)
57
+ return normalized;
58
+ if (!isRootLevelTopic(normalized))
59
+ return normalized;
60
+ const segment = normalized.replace(/^\/+/, "");
61
+ return `/${ns}/${segment}`;
62
+ }
63
+ /**
64
+ * Apply robot namespace to any topic when configured (for transport subscribe/publish).
65
+ * Use this when the robot publishes/subscribes all topics under a namespace (e.g. Zenoh with
66
+ * zenoh-bridge-ros2dds or rmw_zenoh). If a namespace is set, returns /<namespace>/<topic>
67
+ * unless the topic already starts with /<namespace>/.
68
+ *
69
+ * The first argument may be either an `AgenticROSConfig` or a bare
70
+ * namespace string (per-robot routing).
71
+ *
72
+ * Example: namespace "robot-uuid", topic "/cmd_vel" -> "/robot-uuid/cmd_vel"
73
+ * Example: namespace "robot-uuid", topic "/camera/camera/color/image_raw/compressed" -> "/robot-uuid/camera/camera/color/image_raw/compressed"
74
+ * Example: namespace "robot-uuid", topic "/robot-uuid/odom" -> "/robot-uuid/odom" (unchanged)
75
+ */
76
+ export function toNamespacedTopicFull(target, topic) {
77
+ const normalized = normalizeTopic(topic);
78
+ const ns = resolveNamespace(target);
79
+ if (!ns)
80
+ return normalized;
81
+ const withoutLeading = normalized.replace(/^\/+/, "");
82
+ if (!withoutLeading)
83
+ return normalized;
84
+ if (withoutLeading.startsWith(`${ns}/`) || withoutLeading === ns)
85
+ return normalized;
86
+ return `/${ns}/${withoutLeading}`;
87
+ }
88
+ /**
89
+ * Canonical topic string for teleop UI and ?topic= query params: leading slash, no robot namespace prefix.
90
+ * Subscribe/publish still uses {@link toNamespacedTopicFull} on the server so Zenoh keys match the bridge.
91
+ *
92
+ * Teleop is single-robot today, so this still takes the full config.
93
+ */
94
+ export function toTeleopCameraTopicShort(config, topic) {
95
+ const normalized = normalizeTopic(topic);
96
+ const ns = (config.robot?.namespace ?? "").trim();
97
+ if (!ns)
98
+ return normalized;
99
+ const withoutLeading = normalized.replace(/^\/+/, "");
100
+ if (!withoutLeading)
101
+ return normalized;
102
+ if (withoutLeading === ns)
103
+ return "/";
104
+ if (withoutLeading.startsWith(`${ns}/`)) {
105
+ const rest = withoutLeading.slice(ns.length + 1);
106
+ return rest ? `/${rest}` : "/";
107
+ }
108
+ return normalized;
109
+ }
110
+ /**
111
+ * ROS topic to use when subscribing to camera streams (Zenoh / DDS).
112
+ * Unlike {@link toNamespacedTopicFull}, common sensor topics stay at the graph root (`/camera/...`, `/zed/...`)
113
+ * even when the namespace is set for cmd_vel. If the topic already starts with `/<namespace>/`, it is left as-is.
114
+ * Other multi-segment paths get the namespace prefix (same as Full) for odd layouts.
115
+ *
116
+ * The first argument may be either an `AgenticROSConfig` or a bare
117
+ * namespace string (per-robot routing).
118
+ */
119
+ export function resolveCameraSubscribeTopic(target, topic) {
120
+ const normalized = normalizeTopic(topic);
121
+ const ns = resolveNamespace(target);
122
+ if (!ns)
123
+ return normalized;
124
+ const withoutLeading = normalized.replace(/^\/+/, "");
125
+ if (!withoutLeading)
126
+ return normalized;
127
+ if (withoutLeading === ns || withoutLeading.startsWith(`${ns}/`))
128
+ return normalized;
129
+ const first = withoutLeading.split("/")[0] ?? "";
130
+ /** First path segment for topics that usually remain unprefixed while cmd_vel is namespaced. */
131
+ const globalRoots = new Set(["camera", "zed", "usb_cam", "image_raw", "depth"]);
132
+ if (first && globalRoots.has(first))
133
+ return normalized;
134
+ return `/${ns}/${withoutLeading}`;
135
+ }
136
+ //# sourceMappingURL=topic-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topic-utils.js","sourceRoot":"","sources":["../src/topic-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { TransportConfig } from "./types.js";
2
+ import type { RosTransport } from "./transport.js";
3
+ /**
4
+ * Create a RosTransport instance for the given deployment mode.
5
+ *
6
+ * Uses dynamic import() to load the correct adapter so that
7
+ * unused adapters (and their dependencies) are never loaded.
8
+ */
9
+ export declare function createTransport(config: TransportConfig): Promise<RosTransport>;
10
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/transport/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC,CAqCpF"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Create a RosTransport instance for the given deployment mode.
3
+ *
4
+ * Uses dynamic import() to load the correct adapter so that
5
+ * unused adapters (and their dependencies) are never loaded.
6
+ */
7
+ export async function createTransport(config) {
8
+ switch (config.mode) {
9
+ case "rosbridge": {
10
+ const { RosbridgeTransport } = await import("./rosbridge/adapter.js");
11
+ return new RosbridgeTransport(config.rosbridge);
12
+ }
13
+ case "local": {
14
+ try {
15
+ const { LocalTransport } = await import("./local/transport.js");
16
+ return new LocalTransport(config.local);
17
+ }
18
+ catch (e) {
19
+ if (e?.code === "ERR_MODULE_NOT_FOUND" || e?.code === "MODULE_NOT_FOUND") {
20
+ throw new Error('Mode A (local) requires the "rclnodejs" package. ' +
21
+ "Install it with: pnpm add rclnodejs (with ROS2 workspace sourced)");
22
+ }
23
+ throw e;
24
+ }
25
+ }
26
+ case "webrtc": {
27
+ const { WebRTCTransport } = await import("./webrtc/transport.js");
28
+ return new WebRTCTransport(config.webrtc);
29
+ }
30
+ case "zenoh": {
31
+ const { ZenohTransport } = await import("./zenoh/adapter.js");
32
+ return new ZenohTransport(config.zenoh);
33
+ }
34
+ default: {
35
+ const _exhaustive = config;
36
+ throw new Error(`Unknown transport mode: ${_exhaustive.mode}`);
37
+ }
38
+ }
39
+ }
40
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/transport/factory.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAuB;IAC3D,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;YACtE,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAClD,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,IAAI,CAAC;gBACH,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;gBAChE,OAAO,IAAI,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC1C,CAAC;YAAC,OAAO,CAAM,EAAE,CAAC;gBAChB,IAAI,CAAC,EAAE,IAAI,KAAK,sBAAsB,IAAI,CAAC,EAAE,IAAI,KAAK,kBAAkB,EAAE,CAAC;oBACzE,MAAM,IAAI,KAAK,CACb,mDAAmD;wBACjD,mEAAmE,CACtE,CAAC;gBACJ,CAAC;gBACD,MAAM,CAAC,CAAC;YACV,CAAC;QACH,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;YAClE,OAAO,IAAI,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5C,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC9D,OAAO,IAAI,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QAED,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,WAAW,GAAU,MAAM,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,2BAA4B,WAA+B,CAAC,IAAI,EAAE,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Message conversion between plain JS objects and rclnodejs typed messages.
3
+ *
4
+ * The RosTransport interface works with `Record<string, unknown>`, but
5
+ * rclnodejs works with typed message class instances. This module bridges
6
+ * the two — analogous to rosbridge_library's `dict_to_msg` / `msg_to_dict`.
7
+ */
8
+ /**
9
+ * Load a ROS2 message/service/action class via rclnodejs, with caching.
10
+ */
11
+ export declare function loadMessageClass(typeStr: string): any;
12
+ /**
13
+ * Convert a plain JS object to an rclnodejs message instance.
14
+ *
15
+ * Recursively assigns fields from `obj` onto a new message instance,
16
+ * handling nested sub-messages (e.g. Twist.linear is a Vector3).
17
+ */
18
+ export declare function toRosMessage(typeStr: string, obj: Record<string, unknown>): any;
19
+ /**
20
+ * Convert an rclnodejs message instance to a plain JS object.
21
+ *
22
+ * Uses `toPlainObject()` if available (rclnodejs >= 0.21), otherwise
23
+ * falls back to manual recursive field extraction.
24
+ */
25
+ export declare function fromRosMessage(msg: any, rosTypeHint?: string): Record<string, unknown>;
26
+ /**
27
+ * Clear the type cache. Called during shutdown.
28
+ */
29
+ export declare function clearTypeCache(): void;
30
+ //# sourceMappingURL=conversion.d.ts.map