1212 build_start_goal_host_surface_selection_packet ,
1313 render_start_goal_guided_markdown ,
1414)
15+ from ..control_plane .effect_runtime import (
16+ EffectRuntimeStartupError ,
17+ MINIMUM_NODE_VERSION_TEXT ,
18+ )
1519from ._host_thread import current_host_thread_id
1620
1721PrintPayload = Callable [
2731)
2832_FINE_GRAINED_PREFIX = re .compile (r"\A--fine-grained(?P<remainder>(?:\s[\s\S]*)?)\Z" )
2933
34+ _EFFECT_RUNTIME_STARTUP_REMEDIATION_BY_CODE = {
35+ "node_unavailable" : (
36+ f"Install or activate Node.js { MINIMUM_NODE_VERSION_TEXT } or newer "
37+ "on PATH, then run `loopx doctor --deep` and retry "
38+ "`loopx start-goal --guided`."
39+ ),
40+ "startup_lock_timeout" : (
41+ "Another LoopX TypeScript control-plane runtime may be starting. Run "
42+ "`loopx doctor --deep`, wait for the active startup to settle, and retry "
43+ "`loopx start-goal --guided`."
44+ ),
45+ "runtime_launch_failed" : (
46+ "Run `loopx doctor --deep` and retry `loopx start-goal --guided`. If the "
47+ "runtime still cannot start, repair or reinstall LoopX."
48+ ),
49+ "runtime_exited_before_ready" : (
50+ "Run `loopx doctor --deep` and retry `loopx start-goal --guided`. If the "
51+ "runtime exits again, repair or reinstall LoopX."
52+ ),
53+ "runtime_startup_timeout" : (
54+ "Run `loopx doctor --deep` and retry `loopx start-goal --guided`. If "
55+ "startup continues to time out, repair or reinstall LoopX."
56+ ),
57+ "runtime_request_failed" : (
58+ "Run `loopx doctor --deep` and retry `loopx start-goal --guided`. If "
59+ "requests continue to fail, repair or reinstall LoopX."
60+ ),
61+ }
62+ _EFFECT_RUNTIME_STARTUP_DEFAULT_REMEDIATION = (
63+ "Run `loopx doctor --deep` and retry `loopx start-goal --guided`. If the "
64+ "TypeScript control-plane runtime remains unavailable, repair or reinstall LoopX."
65+ )
66+
67+
68+ def _effect_runtime_startup_recommended_action (diagnostic_code : str ) -> str :
69+ return _EFFECT_RUNTIME_STARTUP_REMEDIATION_BY_CODE .get (
70+ diagnostic_code ,
71+ _EFFECT_RUNTIME_STARTUP_DEFAULT_REMEDIATION ,
72+ )
73+
74+
75+ def _effect_runtime_startup_failure_payload (
76+ exc : EffectRuntimeStartupError ,
77+ ) -> dict [str , object ]:
78+ diagnostic_code = getattr (exc , "diagnostic_code" , "runtime_startup_failed" )
79+ return {
80+ "ok" : False ,
81+ "schema_version" : "loopx_start_goal_guided_v0" ,
82+ "error" : "LoopX TypeScript control-plane runtime is unavailable" ,
83+ "diagnostic_code" : diagnostic_code ,
84+ "runtime_requirement" : {
85+ "runtime" : "node" ,
86+ "minimum_node_version" : MINIMUM_NODE_VERSION_TEXT ,
87+ "required_for" : ["start-goal" , "control_plane" ],
88+ },
89+ "recommended_action" : _effect_runtime_startup_recommended_action (
90+ diagnostic_code
91+ ),
92+ }
93+
94+
95+ def _render_start_goal_markdown (payload : dict [str , object ]) -> str :
96+ if payload .get ("ok" ) is not False :
97+ return render_start_goal_guided_markdown (payload )
98+ lines = ["# Guided Start Goal" , "" , f"- error: `{ payload .get ('error' )} `" ]
99+ for field in ("diagnostic_code" , "recommended_action" , "suggested_command" ):
100+ value = payload .get (field )
101+ if not value :
102+ continue
103+ rendered = value if field == "recommended_action" else f"`{ value } `"
104+ lines .append (f"- { field } : { rendered } " )
105+ return "\n " .join (lines ) + "\n "
106+
30107
31108def add_capability_route_argument (parser : argparse .ArgumentParser ) -> None :
32109 parser .add_argument (
@@ -203,7 +280,7 @@ def handle_start_goal_command(
203280 "error" : "`loopx start-goal` currently requires --guided" ,
204281 "suggested_command" : "loopx start-goal --guided --goal-text '<goal text>'" ,
205282 }
206- print_payload (payload , args .format , render_start_goal_guided_markdown )
283+ print_payload (payload , args .format , _render_start_goal_markdown )
207284 return 2
208285 try :
209286 goal_text , capability_route , fine_grained = _resolve_start_goal_input (args )
@@ -217,39 +294,49 @@ def handle_start_goal_command(
217294 "--slash-command-arguments='<exact /loopx arguments>'"
218295 ),
219296 }
220- print_payload (payload , args .format , render_start_goal_guided_markdown )
297+ print_payload (payload , args .format , _render_start_goal_markdown )
221298 return 2
222299 if not args .host_surface :
223- payload = build_start_goal_host_surface_selection_packet (
300+ try :
301+ payload = build_start_goal_host_surface_selection_packet (
302+ project = Path (args .project ),
303+ goal_id = args .goal_id ,
304+ agent_id = args .agent_id ,
305+ thread_id = current_host_thread_id (args ),
306+ new_peer = bool (getattr (args , "new_peer" , False )),
307+ cli_bin = args .cli_bin ,
308+ goal_text = goal_text ,
309+ available_capabilities = args .available_capabilities ,
310+ capability_route = capability_route ,
311+ fine_grained = fine_grained ,
312+ include_command_pack_detail = bool (args .include_command_pack_detail ),
313+ runtime_root_arg = runtime_root_arg ,
314+ )
315+ except EffectRuntimeStartupError as exc :
316+ payload = _effect_runtime_startup_failure_payload (exc )
317+ print_payload (payload , args .format , _render_start_goal_markdown )
318+ return 1
319+ print_payload (payload , args .format , _render_start_goal_markdown )
320+ return 0
321+ try :
322+ payload = build_start_goal_guided_packet (
224323 project = Path (args .project ),
225324 goal_id = args .goal_id ,
226325 agent_id = args .agent_id ,
227326 thread_id = current_host_thread_id (args ),
228327 new_peer = bool (getattr (args , "new_peer" , False )),
229328 cli_bin = args .cli_bin ,
329+ host_surface = args .host_surface ,
230330 goal_text = goal_text ,
231331 available_capabilities = args .available_capabilities ,
232332 capability_route = capability_route ,
233333 fine_grained = fine_grained ,
234334 include_command_pack_detail = bool (args .include_command_pack_detail ),
235335 runtime_root_arg = runtime_root_arg ,
236336 )
237- print_payload (payload , args .format , render_start_goal_guided_markdown )
238- return 0
239- payload = build_start_goal_guided_packet (
240- project = Path (args .project ),
241- goal_id = args .goal_id ,
242- agent_id = args .agent_id ,
243- thread_id = current_host_thread_id (args ),
244- new_peer = bool (getattr (args , "new_peer" , False )),
245- cli_bin = args .cli_bin ,
246- host_surface = args .host_surface ,
247- goal_text = goal_text ,
248- available_capabilities = args .available_capabilities ,
249- capability_route = capability_route ,
250- fine_grained = fine_grained ,
251- include_command_pack_detail = bool (args .include_command_pack_detail ),
252- runtime_root_arg = runtime_root_arg ,
253- )
254- print_payload (payload , args .format , render_start_goal_guided_markdown )
337+ except EffectRuntimeStartupError as exc :
338+ payload = _effect_runtime_startup_failure_payload (exc )
339+ print_payload (payload , args .format , _render_start_goal_markdown )
340+ return 1
341+ print_payload (payload , args .format , _render_start_goal_markdown )
255342 return 0
0 commit comments