interactive_plan.go RawHistoryFolder
1package modes
2
3import "github.com/bnema/zut/packages/core"
4
5// RefreshPlan reseeds the plan counters and the per-call checklist snapshot map
6// after a session switch. The CLI seeds the agent's core plan state before it
7// commits a loaded session; this reads it back and installs the snapshots the
8// CLI reconstructed from the session's full transcript.
9//
10// It mirrors RefreshGoal: the CLI owns agent/session swapping and calls this
11// once the swap is committed. snapshots must come from the untrimmed transcript:
12// a resumed agent only holds the trim window, and replaying a delta against an
13// empty base rebuilds the wrong checklist.
14func (i *Interactive) RefreshPlan(snapshots map[string]core.PlanUpdate) {
15 var steps []core.PlanStep
16 if i.cfg.CurrentPlan != nil {
17 // CurrentPlan reaches back into the live agent, so read it before
18 // taking i.mu to keep the lock order one-directional.
19 steps = i.cfg.CurrentPlan()
20 }
21 i.applyPlanState(steps, snapshots)
22}
23
24// applyPlanState installs the plan counters and snapshot map. The revision bump
25// is part of msgVisualKey, so every cached checklist row is invalidated.
26func (i *Interactive) applyPlanState(steps []core.PlanStep, snapshots map[string]core.PlanUpdate) {
27 i.mu.Lock()
28 i.planSnapshots = snapshots
29 i.planRevision++
30 i.planCurrent, i.planTotal = planProgress(steps)
31 i.mu.Unlock()
32 i.invalidate()
33}
34
35// PlanStatus reports the session-scoped plan counters along with the number of
36// reconstructed per-call checklist snapshots. Hosts and tests read the plan
37// view through it instead of reaching into the private render state.
38func (i *Interactive) PlanStatus() (current, total, snapshots int) {
39 i.mu.Lock()
40 defer i.mu.Unlock()
41 return i.planCurrent, i.planTotal, len(i.planSnapshots)
42}
43
44// seedPlanState populates the initial plan counters and snapshot map during
45// construction, before the first render. cfg.CurrentPlan reads the live agent
46// (not i), so this may run while the Interactive is still private.
47//
48// InitialPlanSnapshots, when supplied by the CLI, was reconstructed from the
49// session's full transcript; it is the only correct source after a resume whose
50// trim window starts mid-plan. Without it (embedders, tests) the agent's own
51// messages are the best available transcript.
52func (i *Interactive) seedPlanState() {
53 if i.agent != nil {
54 snapshots := i.cfg.InitialPlanSnapshots
55 if snapshots == nil {
56 snapshots = core.PlanUpdateSnapshots(i.agent.Messages())
57 }
58 i.planSnapshots = snapshots
59 // Advance rather than assign so a re-seed on a live instance cannot
60 // move the revision backwards past a render that already observed it.
61 i.planRevision++
62 }
63 if i.cfg.CurrentPlan != nil {
64 i.planCurrent, i.planTotal = planProgress(i.cfg.CurrentPlan())
65 }
66}