agenticros 0.3.3 → 0.3.5
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/README.md +25 -7
- package/dist/__tests__/claude-config.test.d.ts +2 -0
- package/dist/__tests__/claude-config.test.d.ts.map +1 -0
- package/dist/__tests__/claude-config.test.js +77 -0
- package/dist/__tests__/claude-config.test.js.map +1 -0
- package/dist/__tests__/mcp-setup.test.d.ts +2 -0
- package/dist/__tests__/mcp-setup.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-setup.test.js +23 -0
- package/dist/__tests__/mcp-setup.test.js.map +1 -0
- package/dist/commands/claude.d.ts +16 -0
- package/dist/commands/claude.d.ts.map +1 -0
- package/dist/commands/claude.js +30 -0
- package/dist/commands/claude.js.map +1 -0
- package/dist/commands/codex.d.ts +4 -4
- package/dist/commands/codex.d.ts.map +1 -1
- package/dist/commands/codex.js +21 -90
- package/dist/commands/codex.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +22 -27
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/hermes.d.ts +3 -3
- package/dist/commands/hermes.d.ts.map +1 -1
- package/dist/commands/hermes.js +13 -76
- package/dist/commands/hermes.js.map +1 -1
- package/dist/commands/init.d.ts +2 -3
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +16 -38
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mcp.d.ts +20 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +31 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -1
- package/dist/util/claude-config.d.ts +47 -0
- package/dist/util/claude-config.d.ts.map +1 -0
- package/dist/util/claude-config.js +227 -0
- package/dist/util/claude-config.js.map +1 -0
- package/dist/util/mcp-setup.d.ts +55 -0
- package/dist/util/mcp-setup.d.ts.map +1 -0
- package/dist/util/mcp-setup.js +235 -0
- package/dist/util/mcp-setup.js.map +1 -0
- package/dist/util/skill-manifest.d.ts.map +1 -1
- package/dist/util/skill-manifest.js +22 -1
- package/dist/util/skill-manifest.js.map +1 -1
- package/package.json +1 -1
- package/runtime/BUNDLE.json +1 -1
- package/runtime/README.md +31 -6
- package/runtime/docs/cli.md +54 -14
- package/runtime/docs/robot-setup.md +1 -1
- package/runtime/packages/agenticros/openclaw.plugin.json +147 -1
- package/runtime/packages/agenticros/src/__tests__/capabilities-plugin.test.ts +2 -0
- package/runtime/packages/agenticros/src/routes.ts +4 -3
- package/runtime/packages/agenticros/src/tools/index.ts +6 -5
- package/runtime/packages/agenticros/src/tools/mission-pause.ts +49 -0
- package/runtime/packages/agenticros/src/tools/mission-resume.ts +41 -0
- package/runtime/packages/agenticros/src/tools/ros2-mission.ts +33 -84
- package/runtime/packages/agenticros-claude-code/README.md +2 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.d.ts.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/tools.js +130 -111
- package/runtime/packages/agenticros-claude-code/dist/tools.js.map +1 -1
- package/runtime/packages/agenticros-claude-code/src/tools.ts +140 -99
- package/runtime/packages/agenticros-gemini/package.json +2 -0
- package/runtime/packages/agenticros-gemini/src/find-object/coco-classes.ts +38 -0
- package/runtime/packages/agenticros-gemini/src/find-object/find-object.ts +191 -0
- package/runtime/packages/agenticros-gemini/src/follow-me/controller.ts +109 -0
- package/runtime/packages/agenticros-gemini/src/follow-me/depth-loop.ts +452 -0
- package/runtime/packages/agenticros-gemini/src/follow-me/detector.ts +303 -0
- package/runtime/packages/agenticros-gemini/src/follow-me/loop.ts +359 -0
- package/runtime/packages/agenticros-gemini/src/tools.ts +351 -90
- package/runtime/packages/core/README.md +1 -1
- package/runtime/packages/core/package.json +1 -1
- package/runtime/packages/core/src/__tests__/config-persistence.test.ts +26 -0
- package/runtime/packages/core/src/__tests__/external-capability.test.ts +72 -0
- package/runtime/packages/core/src/__tests__/heartbeat-fleet.test.ts +152 -0
- package/runtime/packages/core/src/__tests__/mission-bindings.test.ts +101 -0
- package/runtime/packages/core/src/__tests__/mission-pause.test.ts +104 -0
- package/runtime/packages/core/src/capabilities.ts +38 -3
- package/runtime/packages/core/src/capability-schema.ts +58 -0
- package/runtime/packages/core/src/config.ts +20 -0
- package/runtime/packages/core/src/discovery.ts +21 -3
- package/runtime/packages/core/src/external-capability.ts +219 -0
- package/runtime/packages/core/src/fleet-config.ts +91 -0
- package/runtime/packages/core/src/heartbeat.ts +167 -0
- package/runtime/packages/core/src/index.ts +55 -0
- package/runtime/packages/core/src/mission-bindings.ts +189 -0
- package/runtime/packages/core/src/mission-registry.ts +31 -3
- package/runtime/packages/core/src/mission.ts +64 -7
- package/runtime/packages/core/src/robots.ts +6 -2
- package/runtime/packages/core/src/transport/rosbridge/client.ts +7 -0
- package/runtime/pnpm-lock.yaml +6 -0
- package/templates/skills/camera/package.json +1 -1
- package/templates/skills/depth/package.json +1 -1
- package/templates/skills/robot/package.json +1 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability → tool bindings for `run_mission`.
|
|
3
|
+
*
|
|
4
|
+
* Builtins live here once; adapters import `buildMissionBindings()` instead
|
|
5
|
+
* of triplicating MISSION_BINDINGS. Skill-declared capabilities get a
|
|
6
|
+
* default tool name (`ros2_<id>`, or `capability.tool` when set) and a
|
|
7
|
+
* passthrough `buildArgs` that forwards resolved inputs.
|
|
8
|
+
*
|
|
9
|
+
* External ROS-node capabilities map to the synthetic tool
|
|
10
|
+
* `external:<capability_id>` so adapters can dispatch via
|
|
11
|
+
* `executeExternalCapability` before looking up a real tool.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Capability } from "./capabilities.js";
|
|
15
|
+
import type { CapabilityToolBinding, CapabilityToolBindings } from "./mission.js";
|
|
16
|
+
|
|
17
|
+
/** Optional explicit tool name on a capability (skill authors may set this). */
|
|
18
|
+
export type CapabilityWithTool = Capability & { tool?: string };
|
|
19
|
+
|
|
20
|
+
function copyNumber(out: Record<string, unknown>, inputs: Record<string, unknown>, key: string): void {
|
|
21
|
+
if (typeof inputs[key] === "number") out[key] = inputs[key];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function copyString(out: Record<string, unknown>, inputs: Record<string, unknown>, key: string): void {
|
|
25
|
+
if (typeof inputs[key] === "string") out[key] = inputs[key];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function copyBoolean(out: Record<string, unknown>, inputs: Record<string, unknown>, key: string): void {
|
|
29
|
+
if (typeof inputs[key] === "boolean") out[key] = inputs[key];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Built-in capability → tool mappings shared by all adapters. */
|
|
33
|
+
export const BUILTIN_MISSION_BINDINGS: CapabilityToolBindings = {
|
|
34
|
+
drive_base: {
|
|
35
|
+
tool: "ros2_publish",
|
|
36
|
+
buildArgs: (inputs) => {
|
|
37
|
+
const lx = Number(inputs.linear_x ?? 0) || 0;
|
|
38
|
+
const az = Number(inputs.angular_z ?? 0) || 0;
|
|
39
|
+
return {
|
|
40
|
+
topic: "/cmd_vel",
|
|
41
|
+
type: "geometry_msgs/msg/Twist",
|
|
42
|
+
message: {
|
|
43
|
+
linear: { x: lx, y: 0, z: 0 },
|
|
44
|
+
angular: { x: 0, y: 0, z: az },
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
take_snapshot: {
|
|
50
|
+
tool: "ros2_camera_snapshot",
|
|
51
|
+
buildArgs: (inputs) => {
|
|
52
|
+
const out: Record<string, unknown> = {};
|
|
53
|
+
copyString(out, inputs, "topic");
|
|
54
|
+
copyString(out, inputs, "message_type");
|
|
55
|
+
copyNumber(out, inputs, "timeout");
|
|
56
|
+
return out;
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
measure_depth: {
|
|
60
|
+
tool: "ros2_depth_distance",
|
|
61
|
+
buildArgs: (inputs) => {
|
|
62
|
+
const out: Record<string, unknown> = {};
|
|
63
|
+
copyString(out, inputs, "topic");
|
|
64
|
+
copyNumber(out, inputs, "timeout");
|
|
65
|
+
return out;
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
list_topics: {
|
|
69
|
+
tool: "ros2_list_topics",
|
|
70
|
+
buildArgs: () => ({}),
|
|
71
|
+
},
|
|
72
|
+
publish_topic: {
|
|
73
|
+
tool: "ros2_publish",
|
|
74
|
+
buildArgs: (inputs) => ({
|
|
75
|
+
topic: String(inputs.topic ?? ""),
|
|
76
|
+
type: String(inputs.type ?? inputs.msg_type ?? ""),
|
|
77
|
+
message: inputs.message ?? inputs.msg ?? {},
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
subscribe_once: {
|
|
81
|
+
tool: "ros2_subscribe_once",
|
|
82
|
+
buildArgs: (inputs) => {
|
|
83
|
+
const out: Record<string, unknown> = { topic: String(inputs.topic ?? "") };
|
|
84
|
+
copyString(out, inputs, "type");
|
|
85
|
+
copyNumber(out, inputs, "timeout");
|
|
86
|
+
return out;
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
follow_person: {
|
|
90
|
+
tool: "ros2_follow_me_start",
|
|
91
|
+
buildArgs: (inputs) => {
|
|
92
|
+
const out: Record<string, unknown> = {};
|
|
93
|
+
copyNumber(out, inputs, "target_distance");
|
|
94
|
+
copyString(out, inputs, "mode");
|
|
95
|
+
return out;
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
find_object: {
|
|
99
|
+
tool: "ros2_find_object",
|
|
100
|
+
buildArgs: (inputs) => {
|
|
101
|
+
const out: Record<string, unknown> = { target: String(inputs.target ?? "") };
|
|
102
|
+
copyNumber(out, inputs, "angular_speed");
|
|
103
|
+
copyBoolean(out, inputs, "clockwise");
|
|
104
|
+
copyNumber(out, inputs, "timeout_seconds");
|
|
105
|
+
copyNumber(out, inputs, "min_confidence");
|
|
106
|
+
return out;
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/** Synthetic tool prefix for external_ros_node capabilities. */
|
|
112
|
+
export const EXTERNAL_TOOL_PREFIX = "external:";
|
|
113
|
+
|
|
114
|
+
export function externalToolName(capabilityId: string): string {
|
|
115
|
+
return `${EXTERNAL_TOOL_PREFIX}${capabilityId}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function isExternalToolName(toolName: string): boolean {
|
|
119
|
+
return toolName.startsWith(EXTERNAL_TOOL_PREFIX);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function capabilityIdFromExternalTool(toolName: string): string {
|
|
123
|
+
return toolName.slice(EXTERNAL_TOOL_PREFIX.length);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Default tool name for a skill-declared capability.
|
|
128
|
+
* Prefer explicit `tool`, then external synthetic name, else `ros2_<id>`.
|
|
129
|
+
*/
|
|
130
|
+
export function defaultToolForCapability(cap: CapabilityWithTool): string {
|
|
131
|
+
if (typeof cap.tool === "string" && cap.tool.trim().length > 0) {
|
|
132
|
+
return cap.tool.trim();
|
|
133
|
+
}
|
|
134
|
+
if (cap.implementation?.kind === "external_ros_node") {
|
|
135
|
+
return externalToolName(cap.id);
|
|
136
|
+
}
|
|
137
|
+
return `ros2_${cap.id}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Passthrough: forward resolved inputs as tool args (skill tools). */
|
|
141
|
+
export function passthroughBuildArgs(inputs: Record<string, unknown>): Record<string, unknown> {
|
|
142
|
+
return { ...inputs };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface BuildMissionBindingsOptions {
|
|
146
|
+
/**
|
|
147
|
+
* Override tool name resolution for a capability id.
|
|
148
|
+
* Useful when OpenClaw registers a skill tool under a non-default name.
|
|
149
|
+
*/
|
|
150
|
+
toolNameResolver?: (cap: Capability) => string | undefined;
|
|
151
|
+
/**
|
|
152
|
+
* Extra bindings merged last (win over builtins and auto-derived).
|
|
153
|
+
*/
|
|
154
|
+
extra?: CapabilityToolBindings;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Build the full capability → tool map for a mission run.
|
|
159
|
+
*
|
|
160
|
+
* Order: builtins → auto-derived from non-builtin capabilities → extra.
|
|
161
|
+
*/
|
|
162
|
+
export function buildMissionBindings(
|
|
163
|
+
capabilities: readonly Capability[],
|
|
164
|
+
options: BuildMissionBindingsOptions = {},
|
|
165
|
+
): CapabilityToolBindings {
|
|
166
|
+
const out: CapabilityToolBindings = { ...BUILTIN_MISSION_BINDINGS };
|
|
167
|
+
|
|
168
|
+
for (const cap of capabilities) {
|
|
169
|
+
if (cap.source?.kind === "builtin") continue;
|
|
170
|
+
// Skip if already covered by builtins (e.g. follow_person from a skill
|
|
171
|
+
// that duplicates the builtin id — keep the richer builtin binding).
|
|
172
|
+
if (out[cap.id] && BUILTIN_MISSION_BINDINGS[cap.id]) continue;
|
|
173
|
+
|
|
174
|
+
const resolved =
|
|
175
|
+
options.toolNameResolver?.(cap) ?? defaultToolForCapability(cap as CapabilityWithTool);
|
|
176
|
+
|
|
177
|
+
const binding: CapabilityToolBinding = {
|
|
178
|
+
tool: resolved,
|
|
179
|
+
buildArgs: passthroughBuildArgs,
|
|
180
|
+
};
|
|
181
|
+
out[cap.id] = binding;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (options.extra) {
|
|
185
|
+
Object.assign(out, options.extra);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
@@ -35,7 +35,7 @@ export interface MissionRegistryEntry {
|
|
|
35
35
|
name?: string;
|
|
36
36
|
/** ms since epoch when the mission was registered. */
|
|
37
37
|
started_at: number;
|
|
38
|
-
/** The token the mission runner reads each step. */
|
|
38
|
+
/** The token the mission runner reads each step (cancel + pause). */
|
|
39
39
|
cancellation: MissionCancellationToken;
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -45,7 +45,7 @@ export interface MissionRegistryEntry {
|
|
|
45
45
|
* Uses `crypto.randomUUID()` when available (Node 14.17+, Bun, modern
|
|
46
46
|
* browsers); falls back to a Math.random hex string with a `mns_` prefix.
|
|
47
47
|
* Either way the id is opaque to downstream consumers — they just echo
|
|
48
|
-
* it back to `mission_cancel` / `memory_recall`.
|
|
48
|
+
* it back to `mission_cancel` / `mission_pause` / `memory_recall`.
|
|
49
49
|
*/
|
|
50
50
|
export function generateMissionId(): string {
|
|
51
51
|
const g = globalThis as { crypto?: { randomUUID?: () => string } };
|
|
@@ -86,7 +86,7 @@ export class MissionRegistry {
|
|
|
86
86
|
mission_id: missionId,
|
|
87
87
|
name: opts?.name,
|
|
88
88
|
started_at: Date.now(),
|
|
89
|
-
cancellation: { cancelled: false },
|
|
89
|
+
cancellation: { cancelled: false, paused: false },
|
|
90
90
|
};
|
|
91
91
|
this.entries.set(missionId, entry);
|
|
92
92
|
return {
|
|
@@ -112,10 +112,38 @@ export class MissionRegistry {
|
|
|
112
112
|
if (!entry) return { found: false, alreadyCancelled: false };
|
|
113
113
|
const alreadyCancelled = entry.cancellation.cancelled === true;
|
|
114
114
|
entry.cancellation.cancelled = true;
|
|
115
|
+
entry.cancellation.paused = false;
|
|
115
116
|
if (reason !== undefined) entry.cancellation.reason = reason;
|
|
116
117
|
return { found: true, alreadyCancelled };
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Pause the named mission at the next step boundary.
|
|
122
|
+
* Idempotent — pausing an already-paused mission is a no-op.
|
|
123
|
+
*/
|
|
124
|
+
pause(missionId: string, reason?: string): { found: boolean; alreadyPaused: boolean } {
|
|
125
|
+
const entry = this.entries.get(missionId);
|
|
126
|
+
if (!entry) return { found: false, alreadyPaused: false };
|
|
127
|
+
if (entry.cancellation.cancelled) {
|
|
128
|
+
return { found: true, alreadyPaused: entry.cancellation.paused === true };
|
|
129
|
+
}
|
|
130
|
+
const alreadyPaused = entry.cancellation.paused === true;
|
|
131
|
+
entry.cancellation.paused = true;
|
|
132
|
+
if (reason !== undefined) entry.cancellation.reason = reason;
|
|
133
|
+
return { found: true, alreadyPaused };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Resume a paused mission. Idempotent when not paused.
|
|
138
|
+
*/
|
|
139
|
+
resume(missionId: string): { found: boolean; wasPaused: boolean } {
|
|
140
|
+
const entry = this.entries.get(missionId);
|
|
141
|
+
if (!entry) return { found: false, wasPaused: false };
|
|
142
|
+
const wasPaused = entry.cancellation.paused === true;
|
|
143
|
+
entry.cancellation.paused = false;
|
|
144
|
+
return { found: true, wasPaused };
|
|
145
|
+
}
|
|
146
|
+
|
|
119
147
|
/** True when the named mission is currently registered. */
|
|
120
148
|
has(missionId: string): boolean {
|
|
121
149
|
return this.entries.has(missionId);
|
|
@@ -45,29 +45,40 @@
|
|
|
45
45
|
*
|
|
46
46
|
* What's still deferred:
|
|
47
47
|
* - Parallel step execution (today: sequential only).
|
|
48
|
-
* - Per-tool cancellation (cancel TIES to step boundaries today
|
|
49
|
-
*
|
|
48
|
+
* - Per-tool cancellation (cancel TIES to step boundaries today;
|
|
49
|
+
* pause/resume also ties to step boundaries).
|
|
50
50
|
* - Retry / backoff policies.
|
|
51
51
|
*
|
|
52
|
+
* Shipped since the original Phase 1.f header:
|
|
53
|
+
* - Natural-language plan compilation (`compileGoalToMission` + goal arg).
|
|
54
|
+
* - Pause / resume via the same control token (`paused` flag).
|
|
55
|
+
*
|
|
52
56
|
* See: docs/strategy-ai-agents-plus-ros.md §4 (Phase 1.c / 1.f).
|
|
53
57
|
*/
|
|
54
58
|
|
|
55
59
|
import type { Capability } from "./capabilities.js";
|
|
56
60
|
|
|
57
61
|
/**
|
|
58
|
-
* Cancellation token consumed by `runMission`.
|
|
62
|
+
* Cancellation / pause token consumed by `runMission`.
|
|
59
63
|
*
|
|
60
64
|
* Plain object (not AbortController) so it's easy to share across
|
|
61
65
|
* processes via a registry without pulling Web platform shims in. The
|
|
62
|
-
* runner
|
|
63
|
-
* for traceability.
|
|
66
|
+
* runner reads `cancelled` and `paused` at each step boundary.
|
|
64
67
|
*/
|
|
65
68
|
export interface MissionCancellationToken {
|
|
66
69
|
cancelled: boolean;
|
|
67
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* When true, the runner waits at the next step boundary until
|
|
72
|
+
* `paused` is cleared or `cancelled` is set. Phase 1 pause/resume.
|
|
73
|
+
*/
|
|
74
|
+
paused?: boolean;
|
|
75
|
+
/** Optional free-text reason — bubbled up into cancelled / paused results. */
|
|
68
76
|
reason?: string;
|
|
69
77
|
}
|
|
70
78
|
|
|
79
|
+
/** Alias — control token is the same object as the cancellation token. */
|
|
80
|
+
export type MissionControlToken = MissionCancellationToken;
|
|
81
|
+
|
|
71
82
|
/**
|
|
72
83
|
* Per-step transcript sink. Called immediately after a step finishes
|
|
73
84
|
* (including cancelled / skipped steps) so an external store sees the
|
|
@@ -154,8 +165,11 @@ export interface MissionStepResult {
|
|
|
154
165
|
* - "error": tool returned an error or the binding/build threw.
|
|
155
166
|
* - "skipped": earlier step failed with on_fail=stop.
|
|
156
167
|
* - "cancelled": mission was cancelled (Phase 1.f) before this step ran.
|
|
168
|
+
* - "paused": transcript-only marker emitted when the runner entered
|
|
169
|
+
* a pause wait before this step (not a final step outcome
|
|
170
|
+
* in `MissionResult.steps` — those use ok/error/skipped/cancelled).
|
|
157
171
|
*/
|
|
158
|
-
status: "ok" | "error" | "skipped" | "cancelled";
|
|
172
|
+
status: "ok" | "error" | "skipped" | "cancelled" | "paused";
|
|
159
173
|
/** Resolved inputs (with `{{...}}` templates substituted). */
|
|
160
174
|
inputs: Record<string, unknown>;
|
|
161
175
|
/** Outputs parsed from the tool response (may be `undefined` for fire-and-forget). */
|
|
@@ -253,6 +267,31 @@ export type CapabilityToolBindings = Record<string, CapabilityToolBinding>;
|
|
|
253
267
|
|
|
254
268
|
const TEMPLATE_RE = /\{\{\s*([a-zA-Z0-9_]+)\.outputs\.([a-zA-Z0-9_]+)\s*\}\}/g;
|
|
255
269
|
|
|
270
|
+
function sleep(ms: number): Promise<void> {
|
|
271
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const PAUSE_POLL_MS = 100;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Wait while `token.paused` is true. Returns when resumed or cancelled.
|
|
278
|
+
* Emits one transcript entry with status "paused" the first time we enter
|
|
279
|
+
* the wait (so a second agent can see the mission is held).
|
|
280
|
+
*/
|
|
281
|
+
async function waitWhilePaused(
|
|
282
|
+
token: MissionCancellationToken | undefined,
|
|
283
|
+
emitPaused: () => void,
|
|
284
|
+
): Promise<"resumed" | "cancelled"> {
|
|
285
|
+
if (!token?.paused || token.cancelled) {
|
|
286
|
+
return token?.cancelled ? "cancelled" : "resumed";
|
|
287
|
+
}
|
|
288
|
+
emitPaused();
|
|
289
|
+
while (token.paused && !token.cancelled) {
|
|
290
|
+
await sleep(PAUSE_POLL_MS);
|
|
291
|
+
}
|
|
292
|
+
return token.cancelled ? "cancelled" : "resumed";
|
|
293
|
+
}
|
|
294
|
+
|
|
256
295
|
/**
|
|
257
296
|
* Substitute `{{stepId.outputs.field}}` references in `value` using the
|
|
258
297
|
* given step output map. Recurses into nested objects/arrays.
|
|
@@ -404,6 +443,24 @@ export async function runMission(
|
|
|
404
443
|
cancelled = true;
|
|
405
444
|
}
|
|
406
445
|
|
|
446
|
+
// Pause: wait at the step boundary until resume or cancel.
|
|
447
|
+
if (!cancelled && options?.cancellation?.paused) {
|
|
448
|
+
const pauseOutcome = await waitWhilePaused(options.cancellation, () => {
|
|
449
|
+
const pausedMarker: MissionStepResult = {
|
|
450
|
+
id: step.id,
|
|
451
|
+
capability: step.capability,
|
|
452
|
+
status: "paused",
|
|
453
|
+
inputs: {},
|
|
454
|
+
message: `Paused: ${options.cancellation?.reason ?? "paused"}`,
|
|
455
|
+
duration_ms: 0,
|
|
456
|
+
};
|
|
457
|
+
emitTranscript(idx, t0, pausedMarker);
|
|
458
|
+
});
|
|
459
|
+
if (pauseOutcome === "cancelled" || options.cancellation?.cancelled) {
|
|
460
|
+
cancelled = true;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
407
464
|
if (cancelled) {
|
|
408
465
|
const result: MissionStepResult = {
|
|
409
466
|
id: step.id,
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import type { AgenticROSConfig } from "./config.js";
|
|
26
26
|
import { getTransportConfig } from "./config.js";
|
|
27
27
|
import type { TransportConfig } from "./transport/types.js";
|
|
28
|
+
import { applyFleetOverride } from "./fleet-config.js";
|
|
28
29
|
|
|
29
30
|
/** Sensor/hardware tags on a robot (Phase 1.e). */
|
|
30
31
|
export interface RobotSensors {
|
|
@@ -85,7 +86,10 @@ const DEFAULT_SENSORS: RobotSensors = {
|
|
|
85
86
|
};
|
|
86
87
|
|
|
87
88
|
export function listRobots(config: AgenticROSConfig): ResolvedRobot[] {
|
|
88
|
-
|
|
89
|
+
// Phase 1.d — ~/.agenticros/fleet.json (or AGENTICROS_FLEET_PATH) wins
|
|
90
|
+
// over config.robots when present and non-empty.
|
|
91
|
+
const effective = applyFleetOverride(config);
|
|
92
|
+
const explicit = Array.isArray(effective.robots) ? effective.robots : [];
|
|
89
93
|
if (explicit.length > 0) {
|
|
90
94
|
return explicit.map((r) => ({
|
|
91
95
|
id: String(r.id),
|
|
@@ -107,7 +111,7 @@ export function listRobots(config: AgenticROSConfig): ResolvedRobot[] {
|
|
|
107
111
|
// ros2_find_robots_for). Users on multi-robot deployments will have
|
|
108
112
|
// promoted into config.robots[] anyway, where the fields are
|
|
109
113
|
// explicit.
|
|
110
|
-
const legacy =
|
|
114
|
+
const legacy = effective.robot ?? { name: "Robot", namespace: "", cameraTopic: "" };
|
|
111
115
|
const id = (legacy.namespace?.trim() || "default");
|
|
112
116
|
return [
|
|
113
117
|
{
|
|
@@ -46,6 +46,13 @@ export interface PendingRequest {
|
|
|
46
46
|
* fresh CONNECT tunnel.
|
|
47
47
|
*/
|
|
48
48
|
function buildProxyAgentForUrl(wsUrl: string): HttpAgent | null {
|
|
49
|
+
// When NODE_USE_ENV_PROXY=1 (Node 22+ / NemoClaw sandbox), the runtime's
|
|
50
|
+
// built-in EnvHttpProxyAgent handles ws:// tunneling natively and adds the
|
|
51
|
+
// authentication headers the OpenShell proxy requires. A manual CONNECT
|
|
52
|
+
// request (our TunnelAgent below) omits those headers and gets 403 from
|
|
53
|
+
// the proxy. Skip our agent and let Node handle it.
|
|
54
|
+
if (process.env.NODE_USE_ENV_PROXY === "1") return null;
|
|
55
|
+
|
|
49
56
|
let parsed: URL;
|
|
50
57
|
try {
|
|
51
58
|
parsed = new URL(wsUrl);
|
package/runtime/pnpm-lock.yaml
CHANGED
|
@@ -99,6 +99,12 @@ importers:
|
|
|
99
99
|
'@google/genai':
|
|
100
100
|
specifier: ^1.0.0
|
|
101
101
|
version: 1.46.0(@modelcontextprotocol/sdk@1.27.1(zod@3.25.76))
|
|
102
|
+
onnxruntime-node:
|
|
103
|
+
specifier: ^1.20.0
|
|
104
|
+
version: 1.26.0
|
|
105
|
+
sharp:
|
|
106
|
+
specifier: ^0.33.0
|
|
107
|
+
version: 0.33.5
|
|
102
108
|
zod:
|
|
103
109
|
specifier: ^3.24.0
|
|
104
110
|
version: 3.25.76
|