// This file makes runSession satisfy cli.Host: the accessor and mutator methods // let the /status command (and future /goal, /btw) read the session's live // collaborators and mutable state through the cli.Host contract rather than the // concrete aggregate — the same seam the REPL's replDeps implements. The // compile-time assertion below fails the build if runSession drifts out of // conformance. package tui import ( "bufio" "fmt" "io" "sync" "time" "github.com/smallnest/pigo/internal/agentcore" "github.com/smallnest/pigo/internal/agenttool" "github.com/smallnest/pigo/internal/cli" "github.com/smallnest/pigo/internal/cli/run" "github.com/smallnest/pigo/internal/compaction" "github.com/smallnest/pigo/internal/hooks" "github.com/smallnest/pigo/internal/plugin" "github.com/smallnest/pigo/internal/provider" "github.com/smallnest/pigo/internal/runtime" "github.com/smallnest/pigo/internal/session" "github.com/smallnest/pigo/internal/trust" ) var _ cli.Host = (*runSession)(nil) func (s *runSession) Store() *session.Store { return s.store } func (s *runSession) Header() session.SessionHeader { return s.header } func (s *runSession) AgentCtx() *agentcore.AgentContext { return s.agentCtx } func (s *runSession) Live() *cli.LiveConfig { return s.live } func (s *runSession) Registry() *agenttool.ToolRegistry { return s.reg } func (s *runSession) Reminders() *runtime.ReminderRegistry { return s.reminders } func (s *runSession) Slash() *runtime.SlashRegistry { return s.slash } func (s *runSession) Creds() *provider.CredentialStore { return s.creds } func (s *runSession) Notifier() *plugin.EventNotifier { return nil } func (s *runSession) NotifierHandle() func(agentcore.AgentEvent) { return s.onEvent } func (s *runSession) Trust() *trust.Manager { return s.trust } func (s *runSession) Goal() *agenttool.GoalState { return nil } func (s *runSession) Telemetry() *cli.TelemetryHolder { return s.telemetry } func (s *runSession) Dispatcher() *hooks.Dispatcher { return s.dispatcher } func (s *runSession) HookDeps() run.HookDeps { return s.hookDeps } func (s *runSession) Cwd() string { return s.cwd } func (s *runSession) Input() *bufio.Reader { return nil } func (s *runSession) ConfirmMu() *sync.Mutex { return nil } func (s *runSession) CurLeaf() string { return s.curLeaf } func (s *runSession) SetCurLeaf(id string) { s.curLeaf = id } func (s *runSession) Persisted() int { return s.persisted } func (s *runSession) SetPersisted(n int) { s.persisted = n } func (s *runSession) LastBtw() *agentcore.AgentContext { return s.lastBtw } func (s *runSession) SetLastBtw(ctx *agentcore.AgentContext) { s.lastBtw = ctx } func (s *runSession) LastBtwBase() int { return s.lastBtwBase } func (s *runSession) SetLastBtwBase(n int) { s.lastBtwBase = n } // renderSession writes the /session summary (US-009, #125) to out — the same // format the REPL's runSession prints: session id, message count, estimated // token usage, model/provider, creation time, and compaction-checkpoint count. // It lives on runSession so the TUI's /session intercept and the REPL share one // rendering; counts derive from the in-memory context (the source of truth for // the live turn), so unsaved messages are counted too. func (s *runSession) renderSession(out io.Writer) { msgs := s.agentCtx.Messages tokens := compaction.EstimateContextTokens(msgs).Tokens compactions := 0 for _, m := range msgs { if _, ok := m.(agentcore.CompactionMessage); ok { compactions++ } } fmt.Fprintf(out, "session: %s\n", s.header.ID) fmt.Fprintf(out, "messages: %d\n", len(msgs)) fmt.Fprintf(out, "tokens (est): %d\n", tokens) model := s.live.Model providerName := s.live.ProviderName if model == "" { model = s.header.Model } if providerName == "" { providerName = s.header.Provider } fmt.Fprintf(out, "model: %s (provider: %s)\n", model, providerName) if !s.header.CreatedAt.IsZero() { fmt.Fprintf(out, "created: %s\n", s.header.CreatedAt.Format(time.RFC3339)) } fmt.Fprintf(out, "compactions: %d\n", compactions) }