package plan_exec import ( "fmt" "log" "os" "plandex-cli/api" "plandex-cli/auth" "plandex-cli/lib" "plandex-cli/term" "plandex-cli/types" shared "plandex-shared" "github.com/fatih/color" ) func GetOnApplyExecFail(applyFlags types.ApplyFlags, tellFlags types.TellFlags) types.OnApplyExecFailFn { return getOnApplyExecFail(applyFlags, tellFlags, "") } func GetOnApplyExecFailWithCommand(applyFlags types.ApplyFlags, tellFlags types.TellFlags, execCommand string) types.OnApplyExecFailFn { return getOnApplyExecFail(applyFlags, tellFlags, execCommand) } func getOnApplyExecFail(applyFlags types.ApplyFlags, tellFlags types.TellFlags, execCommand string) types.OnApplyExecFailFn { var onExecFail types.OnApplyExecFailFn onExecFail = func(status int, output string, attempt int, toRollback *types.ApplyRollbackPlan, onErr types.OnErrFn, onSuccess func()) { var proceed bool resetAttempts := false if applyFlags.AutoDebug > 0 { if attempt >= applyFlags.AutoDebug { timesLbl := "times" if attempt == 1 { timesLbl = "time" } color.New(term.ColorHiRed, color.Bold).Printf("Commands failed %d %s.\n", attempt, timesLbl) } else { proceed = true } } if !proceed { const ( DebugAndRetry = "Debug and retry once" DebugInFullAutoMode = "Debug in full auto mode" RollbackChangesAndExit = "Rollback changes and exit" ApplyChangesAndExit = "Apply changes and exit" ) opts := []string{ DebugAndRetry, DebugInFullAutoMode, RollbackChangesAndExit, ApplyChangesAndExit, } selection, err := term.SelectFromList("What do you want to do?", opts) if err != nil { term.OutputErrorAndExit("failed to get confirmation user input: %s", err) } switch selection { case DebugAndRetry: proceed = true case DebugInFullAutoMode: proceed = true resetAttempts = true term.StartSpinner("") config, apiErr := api.Client.GetPlanConfig(lib.CurrentPlanId) if apiErr != nil { term.OutputErrorAndExit("failed to get plan config: %s", apiErr) } if config.AutoMode != shared.AutoModeFull { config.SetAutoMode(shared.AutoModeFull) apiErr = api.Client.UpdatePlanConfig(lib.CurrentPlanId, shared.UpdatePlanConfigRequest{ Config: config, }) if apiErr != nil { term.OutputErrorAndExit("failed to update plan config: %s", apiErr) } lib.SetCachedPlanConfig(config) applyFlags.AutoCommit = true applyFlags.AutoConfirm = true applyFlags.AutoExec = true applyFlags.AutoDebug = config.AutoDebugTries tellFlags.AutoApply = true tellFlags.AutoContext = true tellFlags.ExecEnabled = true tellFlags.SmartContext = true term.StopSpinner() fmt.Println() fmt.Println("✅ Full auto mode enabled") fmt.Println() } else { term.StopSpinner() } case RollbackChangesAndExit: if toRollback != nil { lib.Rollback(toRollback, true) } os.Exit(1) case ApplyChangesAndExit: onSuccess() return } } if proceed { if toRollback != nil && toRollback.HasChanges() { lib.Rollback(toRollback, true) } authVars := lib.MustVerifyAuthVarsSilent(auth.Current.IntegratedModelsMode) prompt := fmt.Sprintf("Execution failed with exit status %d. Output:\n\n%s\n\n--\n\n", status, output) tellFlags.IsUserContinue = false if execCommand != "" { tellFlags.IsApplyDebug = false tellFlags.ExecEnabled = false tellFlags.IsUserDebug = true } else { tellFlags.IsApplyDebug = true tellFlags.ExecEnabled = true tellFlags.IsUserDebug = false } log.Printf("Calling TellPlan for next debug attempt") TellPlan(ExecParams{ CurrentPlanId: lib.CurrentPlanId, CurrentBranch: lib.CurrentBranch, AuthVars: authVars, CheckOutdatedContext: func(maybeContexts []*shared.Context, projectPaths *types.ProjectPaths) (bool, bool, error) { return lib.CheckOutdatedContextWithOutput(true, true, maybeContexts, projectPaths) }, }, prompt, tellFlags) log.Printf("Applying plan after tell") if resetAttempts { attempt = 0 } lib.MustApplyPlanAttempt(lib.ApplyPlanParams{ PlanId: lib.CurrentPlanId, Branch: lib.CurrentBranch, ApplyFlags: applyFlags, TellFlags: tellFlags, OnExecFail: onExecFail, ExecCommand: execCommand, }, attempt+1) } } return onExecFail }