|
|
@@ -2,6 +2,7 @@ using Inversion;
|
|
|
using Spry;
|
|
|
using Spry.Authentication;
|
|
|
using Statum;
|
|
|
+using ClankerSaddle.Data;
|
|
|
using ClankerSaddle.Services;
|
|
|
|
|
|
namespace ClankerSaddle {
|
|
|
@@ -184,6 +185,114 @@ namespace ClankerSaddle {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Re-offers a failed goal's supervision (plan 14 B1, the goal
|
|
|
+ * page's parity with the task page's Retry): the goal revives to
|
|
|
+ * 'active' and its run task re-queues for another attempt — the
|
|
|
+ * failure summary moves into the run's comment history exactly
|
|
|
+ * like {@link RetryTaskAction}.
|
|
|
+ */
|
|
|
+ public class RetryGoalRunAction : SealedGoalAction {
|
|
|
+
|
|
|
+ private GoalService goals = inject<GoalService>();
|
|
|
+ private TaskService tasks = inject<TaskService>();
|
|
|
+ private SaddleService saddles = inject<SaddleService>();
|
|
|
+ private InvercargillSql.Orm.OrmSession db = Inversion.inject<InvercargillSql.Orm.OrmSession>();
|
|
|
+ private UserService users = inject<UserService>();
|
|
|
+
|
|
|
+ public override async DirectiveBuilder handle() throws GLib.Error {
|
|
|
+ var guard = yield SpryAuth.require_login(directives(), request, users);
|
|
|
+ if (guard != null) {
|
|
|
+ return (!)guard;
|
|
|
+ }
|
|
|
+ int64 goal_id = sealed_goal_id();
|
|
|
+ try {
|
|
|
+ var goal = yield goals.get_goal(goal_id);
|
|
|
+ if (goal == null) {
|
|
|
+ return directives().notify("error", @"No goal exists with id $(goal_id)")
|
|
|
+ .navigate("/goals");
|
|
|
+ }
|
|
|
+ if (((!)goal).status != GOAL_FAILED) {
|
|
|
+ return directives().notify("error", "Only failed goals can retry supervision")
|
|
|
+ .navigate(@"/goals/$(goal_id)");
|
|
|
+ }
|
|
|
+ var run = yield goals.goal_run_task(goal_id);
|
|
|
+ if (run == null || ((!)run).status != STATUS_FAILED) {
|
|
|
+ return directives().notify("error",
|
|
|
+ "The goal agent's run task is not in a failed state")
|
|
|
+ .navigate(@"/goals/$(goal_id)");
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ yield saddles.abort_live_session(tasks, ((!)run).id, "supervision retried");
|
|
|
+ } catch (Error e) {
|
|
|
+ warning("[ClankerSaddle] Goal-retry abort of run task %s failed: %s",
|
|
|
+ ((!)run).id.to_string(), e.message);
|
|
|
+ }
|
|
|
+ yield tasks.retry_task(((!)run).id);
|
|
|
+ ((!)goal).status = GOAL_ACTIVE;
|
|
|
+ ((!)goal).completed_at = null;
|
|
|
+ yield db.update_async<Goal>((!)goal);
|
|
|
+ yield goals.record_event(goal_id, "retry",
|
|
|
+ @"Supervision retried — the goal agent runs again (run task #$(((!)run).id))",
|
|
|
+ ((!)run).id);
|
|
|
+ return directives().notify("success",
|
|
|
+ @"Goal '$(((!)goal).title)' — supervision requeued for another attempt")
|
|
|
+ .navigate(@"/goals/$(goal_id)");
|
|
|
+ } catch (Error e) {
|
|
|
+ return directives().notify("error", @"Could not retry supervision: $(e.message)")
|
|
|
+ .navigate(@"/goals/$(goal_id)");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The goal page's requeue-to-resume (plan 14 B1, parity with the
|
|
|
+ * task page's ResumeTaskAction): the goal agent's run is stale or
|
|
|
+ * parked on a dead saddle — abort its session best-effort, close
|
|
|
+ * it and requeue the run; the next dispatch restores the daemon's
|
|
|
+ * workspace context (restore-on-dispatch).
|
|
|
+ */
|
|
|
+ public class RequeueGoalRunAction : SealedGoalAction {
|
|
|
+
|
|
|
+ private GoalService goals = inject<GoalService>();
|
|
|
+ private TaskService tasks = inject<TaskService>();
|
|
|
+ private SaddleService saddles = inject<SaddleService>();
|
|
|
+ private UserService users = inject<UserService>();
|
|
|
+
|
|
|
+ public override async DirectiveBuilder handle() throws GLib.Error {
|
|
|
+ var guard = yield SpryAuth.require_login(directives(), request, users);
|
|
|
+ if (guard != null) {
|
|
|
+ return (!)guard;
|
|
|
+ }
|
|
|
+ int64 goal_id = sealed_goal_id();
|
|
|
+ try {
|
|
|
+ var goal = yield goals.get_goal(goal_id);
|
|
|
+ if (goal == null) {
|
|
|
+ return directives().notify("error", @"No goal exists with id $(goal_id)")
|
|
|
+ .navigate("/goals");
|
|
|
+ }
|
|
|
+ var run = yield goals.goal_run_task(goal_id);
|
|
|
+ if (run == null || ((!)run).status == STATUS_DONE
|
|
|
+ || ((!)run).status == STATUS_FAILED) {
|
|
|
+ return directives().notify("error",
|
|
|
+ "The goal agent's run is not requeueable")
|
|
|
+ .navigate(@"/goals/$(goal_id)");
|
|
|
+ }
|
|
|
+ yield saddles.abort_live_session(tasks, ((!)run).id, "requeued");
|
|
|
+ yield tasks.requeue_task(((!)run).id);
|
|
|
+ yield goals.record_event(goal_id, "requeue",
|
|
|
+ @"Run requeued — the daemon restores the agent's workspace on the next dispatch (run task #$(((!)run).id))",
|
|
|
+ ((!)run).id);
|
|
|
+ return directives().notify("success",
|
|
|
+ "Run requeued; the daemon restores the agent's context on the next dispatch")
|
|
|
+ .navigate(@"/goals/$(goal_id)");
|
|
|
+ } catch (Error e) {
|
|
|
+ return directives().notify("error", @"Could not requeue the run: $(e.message)")
|
|
|
+ .navigate(@"/goals/$(goal_id)");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Posts a comment on a goal (the goal page's thread; a selection
|
|
|
* quote rides along when the popover opened one).
|