|
12 | 12 | build_start_goal_host_surface_selection_packet, |
13 | 13 | render_start_goal_guided_markdown, |
14 | 14 | ) |
| 15 | +from ..control_plane.effect_runtime import ( |
| 16 | + EffectRuntimeStartupError, |
| 17 | + MINIMUM_NODE_VERSION_TEXT, |
| 18 | +) |
15 | 19 | from ._host_thread import current_host_thread_id |
16 | 20 |
|
17 | 21 | PrintPayload = Callable[ |
|
28 | 32 | _FINE_GRAINED_PREFIX = re.compile(r"\A--fine-grained(?P<remainder>(?:\s[\s\S]*)?)\Z") |
29 | 33 |
|
30 | 34 |
|
| 35 | +def _effect_runtime_startup_failure_payload( |
| 36 | + exc: EffectRuntimeStartupError, |
| 37 | +) -> dict[str, object]: |
| 38 | + diagnostic_code = getattr(exc, "diagnostic_code", "runtime_startup_failed") |
| 39 | + return { |
| 40 | + "ok": False, |
| 41 | + "schema_version": "loopx_start_goal_guided_v0", |
| 42 | + "error": "LoopX TypeScript control-plane runtime is unavailable", |
| 43 | + "diagnostic_code": diagnostic_code, |
| 44 | + "runtime_requirement": { |
| 45 | + "runtime": "node", |
| 46 | + "minimum_node_version": MINIMUM_NODE_VERSION_TEXT, |
| 47 | + "required_for": ["start-goal", "control_plane"], |
| 48 | + }, |
| 49 | + "recommended_action": ( |
| 50 | + f"Install or activate Node.js {MINIMUM_NODE_VERSION_TEXT} or newer " |
| 51 | + "on PATH, then run `loopx doctor --deep` and retry " |
| 52 | + "`loopx start-goal --guided`." |
| 53 | + ), |
| 54 | + } |
| 55 | + |
| 56 | + |
31 | 57 | def add_capability_route_argument(parser: argparse.ArgumentParser) -> None: |
32 | 58 | parser.add_argument( |
33 | 59 | "--capability-route", |
@@ -218,34 +244,44 @@ def handle_start_goal_command( |
218 | 244 | print_payload(payload, args.format, render_start_goal_guided_markdown) |
219 | 245 | return 2 |
220 | 246 | if not args.host_surface: |
221 | | - payload = build_start_goal_host_surface_selection_packet( |
| 247 | + try: |
| 248 | + payload = build_start_goal_host_surface_selection_packet( |
| 249 | + project=Path(args.project), |
| 250 | + goal_id=args.goal_id, |
| 251 | + agent_id=args.agent_id, |
| 252 | + thread_id=current_host_thread_id(args), |
| 253 | + new_peer=bool(getattr(args, "new_peer", False)), |
| 254 | + cli_bin=args.cli_bin, |
| 255 | + goal_text=goal_text, |
| 256 | + available_capabilities=args.available_capabilities, |
| 257 | + capability_route=capability_route, |
| 258 | + fine_grained=fine_grained, |
| 259 | + include_command_pack_detail=bool(args.include_command_pack_detail), |
| 260 | + ) |
| 261 | + except EffectRuntimeStartupError as exc: |
| 262 | + payload = _effect_runtime_startup_failure_payload(exc) |
| 263 | + print_payload(payload, args.format, render_start_goal_guided_markdown) |
| 264 | + return 1 |
| 265 | + print_payload(payload, args.format, render_start_goal_guided_markdown) |
| 266 | + return 0 |
| 267 | + try: |
| 268 | + payload = build_start_goal_guided_packet( |
222 | 269 | project=Path(args.project), |
223 | 270 | goal_id=args.goal_id, |
224 | 271 | agent_id=args.agent_id, |
225 | 272 | thread_id=current_host_thread_id(args), |
226 | 273 | new_peer=bool(getattr(args, "new_peer", False)), |
227 | 274 | cli_bin=args.cli_bin, |
| 275 | + host_surface=args.host_surface, |
228 | 276 | goal_text=goal_text, |
229 | 277 | available_capabilities=args.available_capabilities, |
230 | 278 | capability_route=capability_route, |
231 | 279 | fine_grained=fine_grained, |
232 | 280 | include_command_pack_detail=bool(args.include_command_pack_detail), |
233 | 281 | ) |
| 282 | + except EffectRuntimeStartupError as exc: |
| 283 | + payload = _effect_runtime_startup_failure_payload(exc) |
234 | 284 | print_payload(payload, args.format, render_start_goal_guided_markdown) |
235 | | - return 0 |
236 | | - payload = build_start_goal_guided_packet( |
237 | | - project=Path(args.project), |
238 | | - goal_id=args.goal_id, |
239 | | - agent_id=args.agent_id, |
240 | | - thread_id=current_host_thread_id(args), |
241 | | - new_peer=bool(getattr(args, "new_peer", False)), |
242 | | - cli_bin=args.cli_bin, |
243 | | - host_surface=args.host_surface, |
244 | | - goal_text=goal_text, |
245 | | - available_capabilities=args.available_capabilities, |
246 | | - capability_route=capability_route, |
247 | | - fine_grained=fine_grained, |
248 | | - include_command_pack_detail=bool(args.include_command_pack_detail), |
249 | | - ) |
| 285 | + return 1 |
250 | 286 | print_payload(payload, args.format, render_start_goal_guided_markdown) |
251 | 287 | return 0 |
0 commit comments