package tui import ( "context" "fmt" "time" tea "github.com/charmbracelet/bubbletea" ) // compactDone carries a finished /compact back to the update loop. did is // false when the agent found nothing worth folding, which is not an error and // must not be reported as one. type compactDone struct { did bool err error } // startCompact answers /compact. It is a model call with the whole older half // of the conversation in it, so it gets the spinner and the interrupt key that // a turn gets — the difference is that no prompt goes with it and no turn // counter moves. func (m *model) startCompact() tea.Cmd { ctx, cancel := context.WithCancel(m.parent) m.cancel = cancel m.compacting = true m.started = time.Now() m.closeMenu() a, save := m.opts.Agent, m.saveSession run := func() tea.Msg { did, err := a.Compact(ctx) cancel() if did && err == nil { // The folded history is what -resume should pick up; leaving the // pre-compaction transcript on disk would undo the work on the // next start. if saveErr := save(); saveErr != nil { err = fmt.Errorf("saving session: %w", saveErr) } } return compactDone{did: did, err: err} } return tea.Batch(m.sync(), m.spin.Tick, m.tag(run)) } // finishCompact lands the result. The agent has already announced a successful // fold through EventCompact, so the only things left to say are the two cases // that produce no such event. func (m *model) finishCompact(msg compactDone) tea.Cmd { m.compacting = false m.interrupting = false m.cancel = nil switch { case msg.err != nil: m.tr.addError("compaction failed: " + msg.err.Error()) case !msg.did: m.tr.addNotice("nothing to compact yet — the conversation is still short enough to keep whole") default: // The context pill reports what the model read last turn. After a // manual compaction nothing re-measures it until the next turn, so // leaving it would park the pre-compaction figure on screen for as // long as the session stays idle. Hiding it says "unknown", which is // the truth; automatic compaction needs no equivalent because the // request that corrects it goes out immediately. m.ctxTokens = 0 } return m.sync() } // compactActivity is the line above the prompt while a compaction runs. func (m *model) compactActivity() string { detail := fmt.Sprintf("%ds · esc to interrupt", int(time.Since(m.started).Seconds())) verb := "Compacting" if m.interrupting { verb = "Interrupting" } return m.spin.View() + " " + styCoral.Render(verb+"…") + styDim.Render(" ("+detail+")") }