Do you need those firstUrl and lastUrl aggregations to be terms aggregations? Because if you change those to min and max aggregations the solution is simple:
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "page.time": {
            "gte": "now-w",
            "lte": "now"
          }
        }
      }
    }
  },
  "aggs": {
    "sessionLengthData": {
      "terms": {
        "field": "sId.keyword",
        "size": 10
      },
      "aggs": {
        "firstUrl": {
          "min": {
            "field": "page.time"
          }
        },
        "lastUrl": {
          "max": {
            "field": "page.time"
          }
        },
        "sessionLength": {
          "bucket_script": {
            "buckets_path": {
              "startTime": "firstUrl",
              "endTime": "lastUrl"
            },
            "script": "params.endTime - params.startTime"
          }
        }
      }
    }
  }
}