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 := m.opts.Agent
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 := a.Save(m.opts.SessionDir, m.opts.SessionID, m.opts.Root, m.opts.Client.Model()); saveErr != nil {
err = fmt.Errorf("saving session: %w", saveErr)
}
}
return compactDone{did: did, err: err}
}
return tea.Batch(m.sync(), m.spin.Tick, 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+")")
}