11package com .pyritone .bridge ;
22
3- import com .google .gson .JsonArray ;
43import com .google .gson .JsonElement ;
5- import com .google .gson .JsonNull ;
64import com .google .gson .JsonObject ;
75import com .pyritone .bridge .command .PyritoneCommand ;
86import com .pyritone .bridge .config .BridgeConfig ;
1412import com .pyritone .bridge .runtime .TaskRegistry ;
1513import com .pyritone .bridge .runtime .TaskSnapshot ;
1614import com .pyritone .bridge .runtime .TaskState ;
15+ import com .pyritone .bridge .runtime .TaskLifecycleResolver ;
1716import com .pyritone .bridge .runtime .WatchPatternRegistry ;
1817import net .fabricmc .api .ClientModInitializer ;
1918import net .fabricmc .fabric .api .client .event .lifecycle .v1 .ClientTickEvents ;
@@ -43,6 +42,7 @@ public final class PyritoneBridgeClientMod implements ClientModInitializer {
4342 private String serverVersion ;
4443
4544 private final TaskRegistry taskRegistry = new TaskRegistry ();
45+ private final TaskLifecycleResolver taskLifecycleResolver = new TaskLifecycleResolver ();
4646 private final WatchPatternRegistry watchPatternRegistry = new WatchPatternRegistry ();
4747
4848 private BaritoneGateway baritoneGateway ;
@@ -65,6 +65,7 @@ public void onInitializeClient() {
6565 ClientTickEvents .END_CLIENT_TICK .register (client -> {
6666 baritoneGateway .tickRegisterPathListener ();
6767 baritoneGateway .tickApplyPyritoneChatBranding ();
68+ tickTaskLifecycle ();
6869 });
6970 ClientSendMessageEvents .ALLOW_CHAT .register (this ::handleOutgoingChat );
7071 ClientSendMessageEvents .ALLOW_COMMAND .register (this ::handleOutgoingCommand );
@@ -277,13 +278,17 @@ private JsonObject handleBaritoneExecute(String id, JsonObject params) {
277278 emitTaskEvent ("task.canceled" , startResult .replacedTask (), "replaced" );
278279 }
279280
281+ taskLifecycleResolver .start (startResult .startedTask ().taskId ());
280282 emitTaskEvent ("task.started" , startResult .startedTask (), "dispatched" );
281283
282284 BaritoneGateway .Outcome outcome = baritoneGateway .executeRaw (command );
283285 if (!outcome .ok ()) {
284286 emitPyritoneNotice ("Python execute failed: " + compactCommand (outcome .message ()));
285287 Optional <TaskSnapshot > failed = taskRegistry .transitionActive (TaskState .FAILED , outcome .message ());
286- failed .ifPresent (snapshot -> emitTaskEvent ("task.failed" , snapshot , "execute_failed" ));
288+ failed .ifPresent (snapshot -> {
289+ taskLifecycleResolver .clearForTask (snapshot .taskId ());
290+ emitTaskEvent ("task.failed" , snapshot , "execute_failed" );
291+ });
287292 return ProtocolCodec .errorResponse (id , "EXECUTION_FAILED" , outcome .message ());
288293 }
289294
@@ -317,13 +322,14 @@ private JsonObject handleTaskCancel(String id) {
317322 return ProtocolCodec .errorResponse (id , "EXECUTION_FAILED" , outcome .message ());
318323 }
319324
320- Optional <TaskSnapshot > canceled = taskRegistry .transitionActive (TaskState .CANCELED , outcome .message ());
321- canceled .ifPresent (snapshot -> emitTaskEvent ("task.canceled" , snapshot , "cancel_requested" ));
325+ taskLifecycleResolver .markApiCancelRequested (active .orElseThrow ().taskId ());
326+ Optional <TaskSnapshot > updated = taskRegistry .updateActiveDetail ("Cancel requested by API" );
327+ updated .ifPresent (snapshot -> emitTaskEvent ("task.progress" , snapshot , "cancel_requested" ));
322328 emitPyritoneNotice ("Python cancel accepted" );
323329
324330 JsonObject result = new JsonObject ();
325331 result .addProperty ("canceled" , true );
326- result .add ("task" , canceled .<JsonElement >map (TaskSnapshot ::toJson ).orElse (JsonNull . INSTANCE ));
332+ result .add ("task" , updated .<JsonElement >map (TaskSnapshot ::toJson ).orElse (active . orElseThrow (). toJson () ));
327333 return ProtocolCodec .successResponse (id , result );
328334 }
329335
@@ -340,16 +346,41 @@ private void onPathEvent(String pathEventName) {
340346 return ;
341347 }
342348
343- switch (pathEventName ) {
344- case "AT_GOAL" -> taskRegistry .transitionActive (TaskState .COMPLETED , "Reached goal" )
345- .ifPresent (snapshot -> emitTaskEvent ("task.completed" , snapshot , pathEventName ));
346- case "CANCELED" -> taskRegistry .transitionActive (TaskState .CANCELED , "Baritone canceled" )
347- .ifPresent (snapshot -> emitTaskEvent ("task.canceled" , snapshot , pathEventName ));
348- case "CALC_FAILED" , "NEXT_CALC_FAILED" -> taskRegistry .transitionActive (TaskState .FAILED , pathEventName )
349- .ifPresent (snapshot -> emitTaskEvent ("task.failed" , snapshot , pathEventName ));
350- default -> taskRegistry .updateActiveDetail ("Path event: " + pathEventName )
351- .ifPresent (snapshot -> emitTaskEvent ("task.progress" , snapshot , pathEventName ));
349+ TaskSnapshot current = active .orElseThrow ();
350+ taskLifecycleResolver .recordPathEvent (current .taskId (), pathEventName );
351+ taskRegistry .updateActiveDetail (pathEventDetail (pathEventName ))
352+ .ifPresent (snapshot -> emitTaskEvent ("task.progress" , snapshot , pathEventName ));
353+ }
354+
355+ private void tickTaskLifecycle () {
356+ Optional <TaskSnapshot > active = taskRegistry .active ();
357+ if (active .isEmpty ()) {
358+ taskLifecycleResolver .clear ();
359+ return ;
352360 }
361+
362+ TaskSnapshot current = active .orElseThrow ();
363+ Optional <TaskLifecycleResolver .TerminalDecision > terminal = taskLifecycleResolver .evaluate (
364+ current .taskId (),
365+ baritoneGateway .activitySnapshot ()
366+ );
367+
368+ if (terminal .isEmpty ()) {
369+ return ;
370+ }
371+
372+ TaskLifecycleResolver .TerminalDecision decision = terminal .orElseThrow ();
373+ taskRegistry .transitionActive (decision .state (), decision .detail ())
374+ .ifPresent (snapshot -> emitTaskEvent (decision .eventName (), snapshot , decision .stage ()));
375+ }
376+
377+ private static String pathEventDetail (String pathEventName ) {
378+ return switch (pathEventName ) {
379+ case "AT_GOAL" -> "Reached goal (awaiting stable idle)" ;
380+ case "CANCELED" -> "Baritone canceled (awaiting stable idle)" ;
381+ case "CALC_FAILED" , "NEXT_CALC_FAILED" -> "Path calculation failed (awaiting stable idle)" ;
382+ default -> "Path event: " + pathEventName ;
383+ };
353384 }
354385
355386 private void emitTaskEvent (String eventName , TaskSnapshot taskSnapshot , String stage ) {
0 commit comments