package tui import ( "errors" "strings" "testing" "harness/internal/agent" ) // /compact rewrites the history the in-flight request is built from, so it has // to wait for the turn to end — the same reasoning that makes /setup refuse. func TestCompactRefusesMidTurn(t *testing.T) { m := newTestModel(t) m.phase = phaseRunning m.submit("/compact") if m.compacting { t.Error("/compact should not have started while a turn is running") } if out := m.tr.render(); !strings.Contains(out, "interrupt first") { t.Errorf("expected a refusal naming the way out, got %q", out) } } func TestCompactStartsWhenIdle(t *testing.T) { m := newTestModel(t) m.submit("/compact") if !m.compacting { t.Fatal("/compact should put the model into its compacting state") } if !strings.Contains(plain(m.activity()), "Compacting") { t.Errorf("the activity line should say what is happening, got %q", plain(m.activity())) } } // The context pill reports the last turn's input size. After a manual // compaction no request follows to correct it, so leaving it alone would park // a pre-compaction number on screen indefinitely. func TestCompactClearsTheContextPill(t *testing.T) { m := newTestModel(t) m.compacting = true m.ctxTokens = 970_000 m.Update(compactDone{did: true}) if m.compacting { t.Error("compactDone should end the compacting state") } if m.ctxTokens != 0 { t.Errorf("ctxTokens = %d, want 0: the pre-compaction figure is no longer true", m.ctxTokens) } if _, ok := m.contextPill(); ok { t.Error("the pill should be hidden until the next turn measures the window again") } } func TestCompactReportsNothingToFold(t *testing.T) { m := newTestModel(t) m.compacting = true m.ctxTokens = 1234 m.Update(compactDone{did: false}) if out := m.tr.render(); !strings.Contains(out, "nothing to compact") { t.Errorf("expected a notice that nothing was folded, got %q", out) } if m.ctxTokens != 1234 { t.Error("nothing was folded, so the context pill still reports the truth") } } func TestCompactReportsFailure(t *testing.T) { m := newTestModel(t) m.compacting = true m.Update(compactDone{err: errors.New("the endpoint said no")}) out := m.tr.render() if !strings.Contains(out, "the endpoint said no") { t.Errorf("a failed compaction should say why, got %q", out) } if m.compacting { t.Error("a failed compaction still ends the compacting state") } } // A notice from the agent loop — the window is full and nothing can be folded // — has to reach the transcript, or the next request fails with no explanation. func TestAgentNoticeReachesTheTranscript(t *testing.T) { m := newTestModel(t) m.Update(evNotice{text: "the context window is 97% full"}) if out := m.tr.render(); !strings.Contains(out, "97% full") { t.Errorf("expected the notice in the transcript, got %q", out) } } // A turn stopped for want of room has two ways out, and neither is obvious // from the agent's message on its own — the agent package does not know what a // slash command is. func TestContextFullErrorOffersTheWayOut(t *testing.T) { m := newTestModel(t) m.Update(turnDone{err: &agent.ContextFullError{Used: 970_000, Window: 1_000_000, Floor: 0.97}}) out := m.tr.render() for _, want := range []string{"97% full", "/compact", "/clear"} { if !strings.Contains(out, want) { t.Errorf("the transcript should mention %q:\n%s", want, out) } } }