@@ -12,6 +12,9 @@ import dev.ltms.bridged.msg.TurnToken;
import org.junit.jupiter.api.Test ;
import org.slf4j.LoggerFactory ;
import java.util.Set ;
import java.util.regex.Pattern ;
import static org.junit.jupiter.api.Assertions.assertEquals ;
import static org.junit.jupiter.api.Assertions.assertFalse ;
import static org.junit.jupiter.api.Assertions.assertTrue ;
@@ -23,7 +26,7 @@ class CompletionResolverTest {
void skipsTheScrapeWhenNoSendIsWaiting ( ) {
FakeHerdr herdr = new FakeHerdr ( ) ;
Rendezvous rendezvous = new Rendezvous ( ) ; // no waiter opened
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
resolver . resolve ( " term_a " , null ) ; // no in-flight turn captured for this target
@@ -35,7 +38,7 @@ class CompletionResolverTest {
void failSkipsTheScrapeWhenNoSendIsWaiting ( ) {
FakeHerdr herdr = new FakeHerdr ( ) ;
Rendezvous rendezvous = new Rendezvous ( ) ; // no waiter opened
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
resolver . fail ( " term_a " , null ) ; // no in-flight turn, and no registered waiter to fall back to
@@ -47,7 +50,7 @@ class CompletionResolverTest {
void captureBaselineSkipsTheReadWhenNoSendIsWaiting ( ) {
FakeHerdr herdr = new FakeHerdr ( ) . readText ( " ⏺ X \ n❯ " ) ;
Rendezvous rendezvous = new Rendezvous ( ) ; // no waiter opened
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
resolver . captureBaseline ( " term_a " , TestTurnTokens . inert ( " term_a " ) ) ; // no send to attribute a later completion to
@@ -136,7 +139,7 @@ class CompletionResolverTest {
// send must NOT be resolved with the stale answer.
FakeHerdr herdr = new FakeHerdr ( ) . readText ( " ⏺ 391 \ n❯ " ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiter = rendezvous . open ( " term_a " ) ; // a send is blocked on this turn
// The turn as captured at delivery: its waiter, and the previous turn's answer still on screen.
@@ -151,7 +154,7 @@ class CompletionResolverTest {
void resolvesACompletionWhoseScrapeChangedSinceDelivery ( ) {
FakeHerdr herdr = new FakeHerdr ( ) . readText ( " ⏺ No, 391 = 17 × 23. \ n❯ " ) ; // the worker's real answer
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiter = rendezvous . open ( " term_a " ) ;
// Delivery baseline was the previous turn's "391"; the scrape now differs → resolve.
@@ -168,7 +171,7 @@ class CompletionResolverTest {
String block = " ⏺ " + " x " . repeat ( CompletionResolver . MAX_SCRAPE_CHARS + 1 ) + " \ n❯ " ;
FakeHerdr herdr = new FakeHerdr ( ) . readText ( block ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiter = rendezvous . open ( " term_a " ) ;
resolver . resolve ( " term_a " , new CompletionResolver . InFlight ( waiter , null ) ) ;
@@ -182,7 +185,7 @@ class CompletionResolverTest {
void leavesAnUnclippedCompletionPaneTailUnmarked ( ) {
FakeHerdr herdr = new FakeHerdr ( ) . readText ( " ⏺ complete report \ n❯ " ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiter = rendezvous . open ( " term_a " ) ;
resolver . resolve ( " term_a " , new CompletionResolver . InFlight ( waiter , null ) ) ;
@@ -194,7 +197,7 @@ class CompletionResolverTest {
void resolvesSynchronouslyBeforePostTurnContextClearing ( ) {
FakeHerdr herdr = new FakeHerdr ( ) . readText ( " ⏺ previous answer \ n❯ " ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiter = rendezvous . open ( " term_a " ) ;
resolver . captureBaseline ( " term_a " , new TurnToken ( " term_a " , waiter ) ) ;
herdr . readText ( " ⏺ answer that /clear would erase \ n❯ " ) ;
@@ -216,7 +219,7 @@ class CompletionResolverTest {
String longBlock = " ⏺ " + " x " . repeat ( CompletionResolver . MAX_SCRAPE_CHARS + 500 ) + " \ n❯ " ;
FakeHerdr herdr = new FakeHerdr ( ) . readText ( longBlock ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiter = rendezvous . open ( " term_a " ) ; // a send is blocked on this turn
resolver . captureBaseline ( " term_a " , new TurnToken ( " term_a " , waiter ) ) ; // baseline is the clipped >cap block
@@ -236,7 +239,7 @@ class CompletionResolverTest {
// No delivery baseline (e.g. the pre-turn read failed) ⇒ never suppress; the completion resolves.
FakeHerdr herdr = new FakeHerdr ( ) . readText ( " ⏺ hello \ n❯ " ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiter = rendezvous . open ( " term_a " ) ;
resolver . resolve ( " term_a " , new CompletionResolver . InFlight ( waiter , null ) ) ;
@@ -254,7 +257,7 @@ class CompletionResolverTest {
// byte-identical guard would wrongly match the empty tail and suppress.
FakeHerdr herdr = new FakeHerdr ( ) . healthy ( false ) ; // agent.read throws HerdrException
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiter = rendezvous . open ( " term_a " ) ;
var turn = new CompletionResolver . InFlight ( waiter , " " ) ; // empty pane baselined at delivery
@@ -274,7 +277,7 @@ class CompletionResolverTest {
// fail must not overwrite that value, and must not even scrape the worker — nobody needs it.
FakeHerdr herdr = new FakeHerdr ( ) . readText ( " an error screen " ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiter = rendezvous . open ( " term_a " ) ;
var turn = new CompletionResolver . InFlight ( waiter , null ) ;
@@ -295,7 +298,7 @@ class CompletionResolverTest {
// fail falls back to the waiter currently registered on the Rendezvous and fails it.
FakeHerdr herdr = new FakeHerdr ( ) . readText ( " stuck on an error screen " ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiter = rendezvous . open ( " term_a " ) ; // send registered, but no captureBaseline ever ran
resolver . fail ( " term_a " , null ) ; // no in-flight turn → fall back to the registered waiter
@@ -321,7 +324,7 @@ class CompletionResolverTest {
try {
FakeHerdr herdr = new FakeHerdr ( ) . readText ( " stuck on an error screen " ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiter = rendezvous . open ( " term_a " ) ;
resolver . fail ( " term_a " , null ) ;
@@ -349,7 +352,7 @@ class CompletionResolverTest {
// scrape to turn N+1; targeting turn N's captured waiter makes the late completion a no-op.
FakeHerdr herdr = new FakeHerdr ( ) . readText ( " ⏺ turn N answer \ n❯ " ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiterN = rendezvous . open ( " term_a " ) ; // turn N's send
// The turn as the injector captured it at delivery (waiter + pre-turn baseline).
@@ -370,4 +373,88 @@ class CompletionResolverTest {
" turn N stays resolved by its own reply " ) ;
assertTrue ( rendezvous . isWaiting ( " term_a " ) , " turn N+1 is still awaiting its own resolution " ) ;
}
// --- CB-578 stage A: backend-exhausted classification ---------------------------------
@Test
void classifiesAMatchingScrapeAsBackendExhaustedInsteadOfACompletedReply ( ) {
String block = " ⏺ Working on it... \ nThe usage limit has been reached. Try again later. \ n❯ " ;
FakeHerdr herdr = new FakeHerdr ( ) . readText ( block ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
ExhaustedPatternLookup patterns = target - > Pattern . compile ( " usage limit has been reached " ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , patterns ) ;
var waiter = rendezvous . open ( " term_a " ) ;
resolver . resolve ( " term_a " , new CompletionResolver . InFlight ( waiter , null ) ) ;
assertTrue ( waiter . isDone ( ) , " a matching scrape still resolves the blocked send " ) ;
assertEquals ( Rendezvous . Kind . BACKEND_EXHAUSTED , waiter . getNow ( null ) . kind ( ) ,
" not reported as a completed reply — the classification is distinct " ) ;
}
@Test
void theExhaustedReasonCarriesTheMatchedLine ( ) {
String block = " ⏺ Working on it... \ nThe usage limit has been reached. Try again later. \ n❯ " ;
FakeHerdr herdr = new FakeHerdr ( ) . readText ( block ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
ExhaustedPatternLookup patterns = target - > Pattern . compile ( " usage limit has been reached " ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , patterns ) ;
var waiter = rendezvous . open ( " term_a " ) ;
resolver . resolve ( " term_a " , new CompletionResolver . InFlight ( waiter , null ) ) ;
assertEquals ( " backend exhausted (usage limit): The usage limit has been reached. Try again later. " ,
waiter . getNow ( null ) . text ( ) , " the reason names the real cause and carries the matched line " ) ;
}
@Test
void aNonMatchingScrapeResolvesAsAnOrdinaryCompletion ( ) {
FakeHerdr herdr = new FakeHerdr ( ) . readText ( " ⏺ complete report \ n❯ " ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
ExhaustedPatternLookup patterns = target - > Pattern . compile ( " usage limit has been reached " ) ;
CompletionResolver resolver = new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , patterns ) ;
var waiter = rendezvous . open ( " term_a " ) ;
resolver . resolve ( " term_a " , new CompletionResolver . InFlight ( waiter , null ) ) ;
assertEquals ( Rendezvous . Kind . COMPLETION , waiter . getNow ( null ) . kind ( ) ,
" a scrape that does not match the pattern is an ordinary completion " ) ;
assertEquals ( " complete report " , waiter . getNow ( null ) . text ( ) ) ;
}
@Test
void aProfileWithNoConfiguredPatternKeepsTodaysCompletionFallbackUnchanged ( ) {
// Even a scrape that WOULD have matched some other profile's pattern must resolve as a
// plain completion when this target's own profile has none configured (CB-578 criterion 4).
String block = " ⏺ The usage limit has been reached. \ n❯ " ;
FakeHerdr herdr = new FakeHerdr ( ) . readText ( block ) ;
Rendezvous rendezvous = new Rendezvous ( ) ;
CompletionResolver resolver =
new CompletionResolver ( new AgentControl ( herdr ) , rendezvous , ExhaustedPatternLookup . none ( ) ) ;
var waiter = rendezvous . open ( " term_a " ) ;
resolver . resolve ( " term_a " , new CompletionResolver . InFlight ( waiter , null ) ) ;
assertEquals ( Rendezvous . Kind . COMPLETION , waiter . getNow ( null ) . kind ( ) ,
" no pattern configured for this target's profile ⇒ unchanged completion-fallback behaviour " ) ;
assertEquals ( " The usage limit has been reached. " , waiter . getNow ( null ) . text ( ) ) ;
}
@Test
void coverageIsOffWhenNoProfileHasAPatternConfigured ( ) {
assertEquals ( " off (no profile has an exhaustedPattern configured; profiles: [terra]) " ,
CompletionResolver . coverage ( Set . of ( " terra " ) , Set . of ( ) ) ) ;
}
@Test
void coverageIsFullWhenEveryProfileHasAPatternConfigured ( ) {
assertEquals ( " full (all profiles configured: [gx10, terra]) " ,
CompletionResolver . coverage ( Set . of ( " terra " , " gx10 " ) , Set . of ( " terra " , " gx10 " ) ) ) ;
}
@Test
void coverageIsPartialAndNamesWhichProfilesAreConfigured ( ) {
assertEquals ( " partial (configured: [terra]; not configured: [gx10]) " ,
CompletionResolver . coverage ( Set . of ( " terra " , " gx10 " ) , Set . of ( " terra " ) ) ) ;
}
}