MCPcopy Create free account
hub / github.com/diillson/chatcli / runPlanFirstIfApplicable

Method runPlanFirstIfApplicable

cli/agent_plan_first.go:43–152  ·  view source on GitHub ↗

runPlanFirstIfApplicable checks the quality config and the one-shot plan flag, then optionally runs a structured Plan-and-Solve cycle. All exits are silent (the only side effect is appending to history) because Plan-First is meant to be a behind-the-scenes accelerator, not a UI feature. /config qua

(ctx context.Context, userQuery string)

Source from the content-addressed store, hash-verified

41// The plan is rendered for the user and the ReAct loop is skipped (see
42// planDryRunHandled).
43func (a *AgentMode) runPlanFirstIfApplicable(ctx context.Context, userQuery string) {
44 if a.agentDispatcher == nil || a.agentRegistry == nil {
45 return
46 }
47
48 // Safety: any return path below must leave the spinner off, otherwise
49 // the /r/033[K repaint can overlap a downstream security approval
50 // prompt and swallow the user's response.
51 defer a.cli.animation.StopThinkingAnimation()
52
53 // One-shot triggers from /plan beat the config; clear them after read
54 // so a subsequent /agent invocation behaves normally.
55 forced := a.cli.pendingPlanFirst
56 dryRun := a.cli.pendingPlanDryRun
57 a.cli.pendingPlanFirst = false
58 a.cli.pendingPlanDryRun = false
59
60 if !forced && !dryRun && !quality.ShouldPlanFirst(a.qualityConfig.PlanFirst, userQuery) {
61 return
62 }
63
64 planner, ok := a.agentRegistry.Get(workers.AgentTypePlanner)
65 if !ok {
66 a.logger.Warn("Plan-First skipped: planner agent not registered")
67 return
68 }
69 _ = planner // signature contract; dispatcher resolves the agent again
70
71 a.logger.Info("Plan-First triggered",
72 zap.Bool("forced", forced),
73 zap.Bool("dry_run", dryRun),
74 zap.String("mode", a.qualityConfig.PlanFirst.Mode),
75 zap.Int("complexity", quality.ComplexityScore(userQuery)))
76
77 // Step 1: ask the planner for a structured JSON plan via the
78 // dispatcher so model routing, effort hints, policy and reasoning
79 // auto-enable all fire correctly.
80 plannerCall := workers.AgentCall{
81 Agent: workers.AgentTypePlanner,
82 Task: workers.PlannerStructuredOutputDirective + "\n" + userQuery,
83 ID: "plan-first",
84 }
85 a.cli.animation.ShowThinkingAnimation(i18n.T("plan_first.spinner_planning"))
86 planResults := a.agentDispatcher.Dispatch(ctx, []workers.AgentCall{plannerCall})
87 a.cli.animation.StopThinkingAnimation()
88 if len(planResults) == 0 || planResults[0].Error != nil {
89 var errMsg string
90 if len(planResults) > 0 {
91 errMsg = planResults[0].Error.Error()
92 }
93 a.logger.Warn("Plan-First aborted: planner call failed",
94 zap.String("error", errMsg))
95 return
96 }
97
98 // Dry-run branch: render the plan to the user and stop. No execution,
99 // no orchestrator hand-off. The caller (Run) will skip the ReAct loop
100 // when planDryRunHandled is set.

Callers 1

RunMethod · 0.95

Calls 15

renderPlanPreviewMethod · 0.95
RunFromPlannerOutputMethod · 0.95
ShouldPlanFirstFunction · 0.92
ComplexityScoreFunction · 0.92
TFunction · 0.92
NewPlanRunnerFunction · 0.92
colorizeFunction · 0.85
truncatePlannerOutputFunction · 0.85
StopThinkingAnimationMethod · 0.80
WarnMethod · 0.80
ShowThinkingAnimationMethod · 0.80
GetMethod · 0.65

Tested by

no test coverage detected