ed-mcp 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -28752,7 +28752,7 @@ async function canonical(p) {
28752
28752
  }
28753
28753
 
28754
28754
  // src/version.ts
28755
- var VERSION = "0.5.0";
28755
+ var VERSION = "0.5.1";
28756
28756
 
28757
28757
  // src/config.ts
28758
28758
  var ConfigError = class extends Error {
@@ -32201,12 +32201,32 @@ var SweepCache = class {
32201
32201
  find(centre, radiusLy) {
32202
32202
  this.dropExpired();
32203
32203
  const hit = this.sweeps.find(
32204
- (s) => distanceLy(s.centre, centre) + radiusLy <= s.radiusLy + 1e-9
32204
+ (s) => distanceLy(s.centre, centre) + radiusLy <= s.coveredLy + 1e-9
32205
32205
  );
32206
32206
  return hit?.systems.filter((s) => distanceLy(s.coords, centre) <= radiusLy);
32207
32207
  }
32208
- add(centre, radiusLy, systems) {
32209
- this.sweeps.push({ centre, radiusLy, systems, storedAt: this.now() });
32208
+ add(centre, radiusLy, systems, key) {
32209
+ const storedAt = key ? this.sweeps.find((s) => s.resume?.key === key)?.storedAt : void 0;
32210
+ if (key) this.sweeps = this.sweeps.filter((s) => s.resume?.key !== key);
32211
+ this.push({ centre, coveredLy: radiusLy, systems, storedAt: storedAt ?? this.now() });
32212
+ }
32213
+ savePartial(p) {
32214
+ const storedAt = this.sweeps.find((s) => s.resume?.key === p.key)?.storedAt ?? this.now();
32215
+ this.sweeps = this.sweeps.filter((s) => s.resume?.key !== p.key);
32216
+ this.push({
32217
+ centre: p.centre,
32218
+ coveredLy: p.coveredLy,
32219
+ systems: p.systems,
32220
+ storedAt,
32221
+ resume: p
32222
+ });
32223
+ }
32224
+ partial(key) {
32225
+ this.dropExpired();
32226
+ return this.sweeps.find((s) => s.resume?.key === key)?.resume;
32227
+ }
32228
+ push(s) {
32229
+ this.sweeps.push(s);
32210
32230
  while (this.sweeps.length > this.maxSweeps) this.sweeps.shift();
32211
32231
  }
32212
32232
  clear() {
@@ -32216,7 +32236,7 @@ var SweepCache = class {
32216
32236
  }
32217
32237
  stats() {
32218
32238
  this.dropExpired();
32219
- const oldest = this.sweeps[0]?.storedAt;
32239
+ const oldest = this.sweeps.length ? Math.min(...this.sweeps.map((s) => s.storedAt)) : void 0;
32220
32240
  return {
32221
32241
  name: this.name,
32222
32242
  entries: this.sweeps.length,
@@ -32430,25 +32450,27 @@ var SpanshClient = class {
32430
32450
  );
32431
32451
  return detail.record.needs_permit ?? null;
32432
32452
  }
32433
- /** Populated systems within radiusLy of near. Stops paging at opts.deadline; a partial result is not cached. */
32453
+ /** Populated systems within radiusLy of near. Stops paging at opts.deadline; a partial result is cached and the next call resumes it. */
32434
32454
  async populatedSystems(near, radiusLy, opts = {}) {
32435
32455
  const centre = (await this.system(near, 0)).coords;
32436
32456
  const cached2 = this.sweepCache.find(centre, radiusLy);
32437
- if (cached2) return { centre, systems: cached2, total: cached2.length, partial: false };
32457
+ if (cached2)
32458
+ return { centre, systems: cached2, total: cached2.length, partial: false, coveredLy: radiusLy };
32438
32459
  const key = `${near.toLowerCase()}:${radiusLy}`;
32439
32460
  let running = this.sweeps.get(key);
32440
32461
  if (!running) {
32441
- running = this.sweepPages(near, radiusLy, centre, opts).finally(
32462
+ running = this.sweepPages(near, radiusLy, centre, key, opts).finally(
32442
32463
  () => this.sweeps.delete(key)
32443
32464
  );
32444
32465
  this.sweeps.set(key, running);
32445
32466
  }
32446
32467
  return running;
32447
32468
  }
32448
- async sweepPages(near, radiusLy, centre, opts) {
32449
- const systems = [];
32450
- const seen = /* @__PURE__ */ new Set();
32451
- let total = 0;
32469
+ async sweepPages(near, radiusLy, centre, key, opts) {
32470
+ const resume = this.sweepCache.partial(key);
32471
+ const systems = resume ? [...resume.systems] : [];
32472
+ const seen = new Set(systems.map((s) => s.id64));
32473
+ let total = resume?.total ?? 0;
32452
32474
  let partial2 = false;
32453
32475
  for (let page2 = 0; ; page2++) {
32454
32476
  if (page2 > 0 && opts.deadline !== void 0 && this.now() >= opts.deadline) {
@@ -32457,7 +32479,8 @@ var SpanshClient = class {
32457
32479
  }
32458
32480
  const body = {
32459
32481
  filters: {
32460
- distance: { min: 0, max: radiusLy },
32482
+ // Resuming by distance, not page: Spansh's list can shift between calls an hour apart.
32483
+ distance: { min: resume?.coveredLy ?? 0, max: radiusLy },
32461
32484
  population: { comparison: "<=>", value: [1, 1e11] }
32462
32485
  },
32463
32486
  sort: [{ distance: { direction: "asc" } }],
@@ -32476,7 +32499,7 @@ var SpanshClient = class {
32476
32499
  }
32477
32500
  throw err;
32478
32501
  }
32479
- total = res.count;
32502
+ if (!resume) total = res.count;
32480
32503
  const results = res.results ?? [];
32481
32504
  for (const raw of results) {
32482
32505
  const s = mapSystem(raw);
@@ -32487,8 +32510,17 @@ var SpanshClient = class {
32487
32510
  }
32488
32511
  if (results.length < SWEEP_PAGE_SIZE) break;
32489
32512
  }
32490
- if (!partial2) this.sweepCache.add(centre, radiusLy, systems);
32491
- return { centre, systems, total, partial: partial2 };
32513
+ if (!partial2) {
32514
+ this.sweepCache.add(centre, radiusLy, systems, key);
32515
+ return { centre, systems, total, partial: partial2, coveredLy: radiusLy };
32516
+ }
32517
+ const last = systems[systems.length - 1];
32518
+ const coveredLy = Math.max(
32519
+ resume?.coveredLy ?? 0,
32520
+ last ? distanceLy(last.coords, centre) - 0.01 : 0
32521
+ );
32522
+ this.sweepCache.savePartial({ key, centre, coveredLy, systems, total });
32523
+ return { centre, systems, total, partial: partial2, coveredLy };
32492
32524
  }
32493
32525
  async route(q, opts = {}) {
32494
32526
  const form = new URLSearchParams({
@@ -46726,6 +46758,7 @@ function systemsByState(systems, centre, radiusLy, q) {
46726
46758
  // src/tools/systems.ts
46727
46759
  var TIME_LIMIT_MS2 = 2e4;
46728
46760
  var MASSACRE_MARGIN_LY = 2 * SOURCE_RANGE_LY;
46761
+ var partialNote = (coveredLy) => `Spansh returned systems out to ${Math.floor(coveredLy * 10) / 10} ly within the time limit. Call again to continue the search.`;
46729
46762
  var STALE_HOURS = 72;
46730
46763
  var FAR_STAR_LS = 1e3;
46731
46764
  var Radius = external_exports.number().min(1).max(50).default(50);
@@ -46809,12 +46842,17 @@ function registerSystemTools(server, deps) {
46809
46842
  const sweep = await deps.spansh.populatedSystems(near, a.radius_ly, {
46810
46843
  deadline: now() + TIME_LIMIT_MS2
46811
46844
  });
46812
- const matches2 = systemsByState(sweep.systems, sweep.centre, a.radius_ly, q);
46845
+ const matches2 = systemsByState(
46846
+ sweep.systems,
46847
+ sweep.centre,
46848
+ Math.min(a.radius_ly, sweep.coveredLy),
46849
+ q
46850
+ );
46813
46851
  const shown = matches2.slice(0, a.limit);
46814
46852
  const stale = shown.filter((m) => (ageHours2(m.system) ?? 0) > STALE_HOURS).length;
46815
46853
  if (stale) notes.push(`${stale} results have data more than ${STALE_HOURS / 24} days old.`);
46816
46854
  if (sweep.partial) {
46817
- notes.push("Spansh did not return every system in time; results may be missing.");
46855
+ notes.push(partialNote(sweep.coveredLy));
46818
46856
  }
46819
46857
  return {
46820
46858
  near,
@@ -46822,6 +46860,7 @@ function registerSystemTools(server, deps) {
46822
46860
  travel: describeRules(rules).travel,
46823
46861
  total_matches: matches2.length,
46824
46862
  partial: sweep.partial,
46863
+ ...sweep.partial ? { covered_ly: Math.floor(sweep.coveredLy * 10) / 10 } : {},
46825
46864
  systems: shown.map((m) => {
46826
46865
  const s = m.system;
46827
46866
  const war = m.factions.some(
@@ -46886,7 +46925,9 @@ function registerSystemTools(server, deps) {
46886
46925
  deadline: now() + TIME_LIMIT_MS2
46887
46926
  });
46888
46927
  const { targets, leftOutSpread } = massacreTargets(sweep.systems, sweep.centre, {
46889
- radiusLy: a.radius_ly,
46928
+ // Past covered_ly - margin a source could have a pirate neighbour that was not fetched.
46929
+ // A negative radius lists nothing, not even a target at distance 0.
46930
+ radiusLy: sweep.partial ? Math.min(a.radius_ly, sweep.coveredLy - MASSACRE_MARGIN_LY) : a.radius_ly,
46890
46931
  maxStarLs: a.max_star_ls
46891
46932
  });
46892
46933
  notes.push(
@@ -46898,15 +46939,14 @@ function registerSystemTools(server, deps) {
46898
46939
  );
46899
46940
  }
46900
46941
  if (sweep.partial) {
46901
- notes.push(
46902
- "Spansh did not return every system in time: a source can look primary when a pirate faction near the edge was missed."
46903
- );
46942
+ notes.push(partialNote(sweep.coveredLy));
46904
46943
  }
46905
46944
  return {
46906
46945
  near,
46907
46946
  searched: sweep.systems.length,
46908
46947
  left_out_spread: leftOutSpread,
46909
46948
  partial: sweep.partial,
46949
+ ...sweep.partial ? { covered_ly: Math.floor(sweep.coveredLy * 10) / 10 } : {},
46910
46950
  travel: describeRules(rules).travel,
46911
46951
  targets: targets.slice(0, a.limit).map((t) => ({
46912
46952
  target_faction: t.faction,