@@ -15,6 +15,7 @@ import * as Path from "effect/Path";
1515import * as Fiber from "effect/Fiber" ;
1616import * as Layer from "effect/Layer" ;
1717import * as Schema from "effect/Schema" ;
18+ import * as TestClock from "effect/testing/TestClock" ;
1819import * as HttpClient from "effect/http/HttpClient" ;
1920import * as HttpClientResponse from "effect/http/HttpClientResponse" ;
2021import * as ChildProcess from "effect/process/ChildProcess" ;
@@ -821,7 +822,7 @@ describe("AcpRegistrySupport", () => {
821822 ) ;
822823 } ) ;
823824
824- it . effect ( "refreshes explicit searches and coalesces concurrent refreshes " , ( ) => {
825+ it . effect ( "searches reuse an index fetched in the last five minutes " , ( ) => {
825826 const agent = makeAgent ( { npx : { package : "@example/acp@1.2.3" } } ) ;
826827 let requests = 0 ;
827828 return Effect . gen ( function * ( ) {
@@ -840,6 +841,11 @@ describe("AcpRegistrySupport", () => {
840841 ) ;
841842 expect ( requests ) . toBe ( 1 ) ;
842843
844+ yield * TestClock . adjust ( "4 minutes" ) ;
845+ yield * resolver . search ( { query : "other" } ) ;
846+ expect ( requests ) . toBe ( 1 ) ;
847+
848+ yield * TestClock . adjust ( "1 minute" ) ;
843849 yield * resolver . search ( { query : "example" } ) ;
844850 expect ( requests ) . toBe ( 2 ) ;
845851 } ) . pipe (
@@ -855,6 +861,94 @@ describe("AcpRegistrySupport", () => {
855861 ) ;
856862 } ) ;
857863
864+ it . effect ( "searches retry the network after falling back to the cached index" , ( ) => {
865+ const agent = makeAgent ( { npx : { package : "@example/acp@1.2.3" } } ) ;
866+ let requests = 0 ;
867+ return Effect . gen ( function * ( ) {
868+ const fileSystem = yield * FileSystem . FileSystem ;
869+ const cacheDir = yield * fileSystem . makeTempDirectoryScoped ( {
870+ prefix : "t3-acp-registry-retry-" ,
871+ } ) ;
872+ yield * fileSystem . makeDirectory ( `${ cacheDir } /acp-registry` , { recursive : true } ) ;
873+ yield * fileSystem . writeFileString (
874+ `${ cacheDir } /acp-registry/registry.json` ,
875+ makeRegistry ( agent ) ,
876+ ) ;
877+ const resolver = yield * AcpRegistrySupport . makeAcpRegistryCatalog ( {
878+ cacheDir,
879+ toolsDir : `${ cacheDir } /tools` ,
880+ registryUrl,
881+ } ) ;
882+ const offline = yield * resolver . search ( { query : "example" } ) ;
883+ expect ( offline . agents . map ( ( result ) => result . id ) ) . toEqual ( [ agent . id ] ) ;
884+ expect ( requests ) . toBe ( 1 ) ;
885+
886+ yield * resolver . search ( { query : "example" } ) ;
887+ expect ( requests ) . toBe ( 2 ) ;
888+ yield * resolver . search ( { query : "example" } ) ;
889+ expect ( requests ) . toBe ( 2 ) ;
890+ } ) . pipe (
891+ Effect . scoped ,
892+ Effect . provide (
893+ layerResolver ( ( request ) => {
894+ requests += 1 ;
895+ return Effect . succeed (
896+ HttpClientResponse . fromWeb (
897+ request ,
898+ requests === 1
899+ ? new Response ( "unavailable" , { status : 503 } )
900+ : new Response ( makeRegistry ( agent ) ) ,
901+ ) ,
902+ ) ;
903+ } ) ,
904+ ) ,
905+ ) ;
906+ } ) ;
907+
908+ it . effect ( "searches retry the network after a refresh falls back within the reuse window" , ( ) => {
909+ const agent = makeAgent ( { npx : { package : "@example/acp@1.2.3" } } ) ;
910+ let requests = 0 ;
911+ let online = true ;
912+ return Effect . gen ( function * ( ) {
913+ const fileSystem = yield * FileSystem . FileSystem ;
914+ const cacheDir = yield * fileSystem . makeTempDirectoryScoped ( {
915+ prefix : "t3-acp-registry-refresh-fallback-" ,
916+ } ) ;
917+ const resolver = yield * AcpRegistrySupport . makeAcpRegistryCatalog ( {
918+ cacheDir,
919+ toolsDir : `${ cacheDir } /tools` ,
920+ registryUrl,
921+ } ) ;
922+ yield * resolver . search ( { query : "example" } ) ;
923+ expect ( requests ) . toBe ( 1 ) ;
924+
925+ // Prepare always refreshes; an unknown agent stops it right after the refresh.
926+ online = false ;
927+ const missing = yield * resolver . prepare ( { agentId : "missing-agent" } ) . pipe ( Effect . flip ) ;
928+ expect ( missing . reason ) . toBe ( "agent_not_found" ) ;
929+ expect ( requests ) . toBe ( 2 ) ;
930+
931+ online = true ;
932+ yield * resolver . search ( { query : "example" } ) ;
933+ expect ( requests ) . toBe ( 3 ) ;
934+ } ) . pipe (
935+ Effect . scoped ,
936+ Effect . provide (
937+ layerResolver ( ( request ) => {
938+ requests += 1 ;
939+ return Effect . succeed (
940+ HttpClientResponse . fromWeb (
941+ request ,
942+ online
943+ ? new Response ( makeRegistry ( agent ) )
944+ : new Response ( "unavailable" , { status : 503 } ) ,
945+ ) ,
946+ ) ;
947+ } ) ,
948+ ) ,
949+ ) ;
950+ } ) ;
951+
858952 it . effect ( "filters runner recipes that the environment cannot prepare" , ( ) => {
859953 const runnerAgent = makeAgent ( { npx : { package : "@example/acp@1.2.3" } } ) ;
860954 const binaryAgent = {
0 commit comments