@leadbay/mcp 0.39.5 → 0.39.7

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/bin.js CHANGED
@@ -6169,13 +6169,11 @@ key. It survives a misspelled company name and is what turns "not in your
6169
6169
  list" into an answer. With only a contact email, pass \`email\`: the company
6170
6170
  domain is derived from it, consumer mailboxes ignored.
6171
6171
 
6172
- When the registry cannot pick one company it returns \`{resolution:
6173
- "ambiguous", query, candidates:[\u2026]}\` instead of a card. Ask which one; never
6174
- guess from \`score\`.
6175
-
6176
- \`LEAD_NOT_FOUND\` is not a dead end: its hint names the field that would have
6177
- found it \u2014 \`website\` or \`registry_number\`, both params. Ask for it and call
6178
- again. Do not offer an import before asking.
6172
+ Both \`resolution\` answers below are successes, not cards. \`"ambiguous"\`
6173
+ carries \`candidates[]\`: ask which one, never guess from \`score\`.
6174
+ \`"not_found"\` carries \`summary\` + \`next_step\`: nobody has it, so say that and
6175
+ do what \`next_step\` says \u2014 usually ask for the param \`would_help\` names, then
6176
+ call again. Do not offer an import before asking.
6179
6177
 
6180
6178
  ---
6181
6179
 
@@ -6316,7 +6314,7 @@ out?"\`
6316
6314
 
6317
6315
  When \`resolution\` is \`"ambiguous"\`, render no card: use \`ask_user_input_v0\`,
6318
6316
  ONE \`single_select\` question ("Which one?"), one short label per candidate
6319
- combining \`name\` and \`location\`.
6317
+ combining \`name\` and \`location\`. \`"not_found"\` renders no card either.
6320
6318
 
6321
6319
  When \`_meta.match_candidates\` is non-empty, prepend one extra NEXT STEPS row:
6322
6320
 
@@ -7088,17 +7086,18 @@ WHEN NOT TO USE: the user wants a lead list (leadbay_pull_leads / leadbay_pull_f
7088
7086
  `;
7089
7087
  leadbay_tour_plan = `## WHEN TO USE
7090
7088
 
7091
- Trigger phrases: "visiting <city> in <N> days", "I'm in <city> next week / Tuesday \u2014 who's worth meeting", "I'm going to <city> \u2014 who should I see", "who's worth meeting in <city>", "field tour in <city>", "plan a tour in <city>", "who should I meet in <city>", "customers plus prospects in <city>", "tour itinerary".
7089
+ Trigger phrases: "visiting <city> in <N> days", "I'm in <city> next week / Tuesday \u2014 who's worth meeting", "I'm going to <city> \u2014 who should I see", "who's worth meeting in <city>", "field tour in <city>", "plan a tour in <city>", "who should I meet in <city>", "prospects within <N> km of <city>", "<city> and the surrounding area", "customers plus prospects in <city>", "tour itinerary".
7092
7090
 
7093
7091
  Do NOT use for: "follow-ups only, no new prospects" \u2192 \`leadbay_followups_map\`; "new leads only" \u2192 \`leadbay_pull_leads\`; "research one account" \u2192 \`leadbay_research_lead_by_id\`.
7094
7092
 
7095
- Prefer when: user wants known accounts plus new discoveries in one geographic itinerary; NEVER a country name \u2014 unlike the Monitor tools, do NOT omit \`city\`; a city-less tour is arbitrary nationwide leads, so ask which city or region
7093
+ Prefer when: known accounts plus new discoveries in one itinerary; pass \`radius_km\` when they name a radius; NEVER a country name, and do NOT omit \`city\`: a city-less tour is arbitrary nationwide leads, so ask which city or region
7096
7094
 
7097
7095
  Examples that SHOULD invoke this tool:
7098
7096
  - "I'm flying to Limoges in 4 days \u2014 give me 3 customers, 3 qualified prospects, and 3 new high-potential."
7099
7097
  - "I'm in San Francisco next Tuesday. Who's worth meeting?"
7100
7098
  - "Plan my tour next Tuesday in Lyon: known accounts plus discoveries."
7101
7099
  - "Build a mixed itinerary for Berlin \u2014 I want both follow-ups and fresh leads."
7100
+ - "J'ai un rdv le 19 ao\xFBt \xE0 Colmar \u2014 trouve-moi les prospects dans un rayon de 10km."
7102
7101
 
7103
7102
  Examples that should NOT invoke this tool (sound similar, route elsewhere):
7104
7103
  - "Show me my follow-ups for the SF trip."
@@ -7153,6 +7152,8 @@ same instruction in its \`hint\`.
7153
7152
 
7154
7153
  **Counts**: \`followups_count\` (default 6 \u2014 generous so the agent can split into "customers + qualified" client-side) and \`discover_count\` (default 6 after client-side geo filter). The composite over-pulls Discover (30 raw) because the wishlist endpoint has no server-side geo filter \u2014 it then keeps the leads whose own \`location.city\` names the requested city, and falls back to \`location.state\` only when no city matched (which is what a regional ask like "Texas" or "\xCEle-de-France" looks like). \`location.country\` is never consulted. \`discover_filter_note\` reports the ratio and which field carried it, so the agent can be honest about coverage. **When it says no Discover lead is in the city, say that** \u2014 return the Monitor half and offer \`leadbay_find_new_leads\` for that city. Never fill the gap with leads from elsewhere.
7155
7154
 
7155
+ **The next town over**: a tour is a day of driving, so after the town's own leads are found the composite adds the Discover leads whose own coordinates put them within **\`radius_km\` (default 20)** of it \u2014 West Sacramento on a tour of Sacramento, Courbevoie and Ivry-sur-Seine on a tour of Paris. The town's own leads always come first. **Pass the user's own number when they give one** ("dans un rayon de 10km autour de Colmar" \u2192 \`radius_km: 10\`; "within 15 miles" \u2192 \`radius_km: 24\`), and \`radius_km: 0\` to keep the tour strictly inside the named town. The radius applies to Discover leads only; the Monitor half is scoped server-side and is untouched. When \`discover_filter_note\` splits the stops into "N in '<city>' and M in <other towns>", **repeat that split** \u2014 say which town each nearby stop is actually in rather than presenting every stop as being in the city the user named.
7156
+
7156
7157
  **What \`tour_plan\` does NOT do**: it doesn't persist the tour as a campaign artifact. To do that \u2014 create a "Limoges Tour \u2013 May 24" campaign and attach the selected accounts \u2014 chain into \`leadbay_create_campaign({lead_ids: [...selected_ids], name: 'Limoges Tour \u2013 <date>'})\` after the user picks. See the \`leadbay_plan_tour_in_city\` prompt for the full end-to-end orchestrator.
7157
7158
 
7158
7159
  ---
@@ -15370,23 +15371,75 @@ function stateFieldOf(lead) {
15370
15371
  const state = lead?.location?.state;
15371
15372
  return typeof state === "string" ? normalizeGeo(state) : "";
15372
15373
  }
15373
- function filterDiscoverByCity(leads, cityHint) {
15374
+ function posOf(lead) {
15375
+ const pos = lead?.location?.pos;
15376
+ const valid = Array.isArray(pos) && pos.length === 2 && pos.every((n) => typeof n === "number" && Number.isFinite(n));
15377
+ return valid ? [pos[0], pos[1]] : null;
15378
+ }
15379
+ function distanceKm(a, b) {
15380
+ const toRad = (d) => d * Math.PI / 180;
15381
+ const dLat = toRad(b[0] - a[0]);
15382
+ const dLng = toRad(b[1] - a[1]);
15383
+ const h = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(a[0])) * Math.cos(toRad(b[0])) * Math.sin(dLng / 2) ** 2;
15384
+ return 2 * 6371 * Math.asin(Math.min(1, Math.sqrt(h)));
15385
+ }
15386
+ function withinRadius(leads, anchors, radiusKm) {
15387
+ if (anchors.length === 0 || !(radiusKm > 0))
15388
+ return [];
15389
+ return leads.filter((l) => {
15390
+ const p = posOf(l);
15391
+ return p !== null && anchors.some((a) => distanceKm(a, p) <= radiusKm);
15392
+ });
15393
+ }
15394
+ function filterDiscoverByCity(leads, cityHint, monitorLeads, radiusKm) {
15374
15395
  const hint = cityHint ? cityHintCore(cityHint) : { name: "", isCity: false };
15375
15396
  if (!hint.name)
15376
- return { leads, matchedOn: null };
15397
+ return { leads, matchedOn: null, nearby: [], anchored: false };
15377
15398
  const cityOf = (l) => typeof l?.location?.city === "string" ? townName(l.location.city) : "";
15399
+ const positions = (ls) => ls.map(posOf).filter((p) => p !== null);
15378
15400
  const byCity = leads.filter((l) => cityOf(l) === hint.name);
15379
- if (byCity.length > 0)
15380
- return { leads: byCity, matchedOn: "city" };
15381
- if (hint.isCity)
15382
- return { leads: [], matchedOn: "city" };
15383
- const byState = leads.filter((l) => stateFieldOf(l) === hint.name);
15384
- return { leads: byState, matchedOn: "state" };
15401
+ const anchors = positions([
15402
+ ...byCity,
15403
+ ...monitorLeads.filter((l) => cityOf(l) === hint.name)
15404
+ ]);
15405
+ if (byCity.length > 0) {
15406
+ const inTown = new Set(byCity);
15407
+ const nearby2 = withinRadius(leads.filter((l) => !inTown.has(l)), anchors, radiusKm);
15408
+ return { leads: [...byCity, ...nearby2], matchedOn: "city", nearby: nearby2, anchored: true };
15409
+ }
15410
+ const anchored = anchors.length > 0;
15411
+ if (!hint.isCity) {
15412
+ const byState = leads.filter((l) => stateFieldOf(l) === hint.name);
15413
+ if (byState.length > 0)
15414
+ return { leads: byState, matchedOn: "state", nearby: [], anchored };
15415
+ }
15416
+ const nearby = withinRadius(leads, anchors, radiusKm);
15417
+ if (nearby.length > 0)
15418
+ return { leads: nearby, matchedOn: "city", nearby, anchored };
15419
+ return {
15420
+ leads: [],
15421
+ matchedOn: hint.isCity ? "city" : "state",
15422
+ nearby: [],
15423
+ anchored
15424
+ };
15425
+ }
15426
+ function townsOf(leads) {
15427
+ const seen = [];
15428
+ let unnamed = 0;
15429
+ for (const l of leads) {
15430
+ const city = typeof l?.location?.city === "string" ? l.location.city.trim() : "";
15431
+ const name = city.replace(/^(?:City|Town|Village|Borough|Township|Municipality) of /i, "");
15432
+ if (!name)
15433
+ unnamed += 1;
15434
+ else if (!seen.includes(name))
15435
+ seen.push(name);
15436
+ }
15437
+ const rest = unnamed > 0 ? `${unnamed} whose record names no town` : "";
15438
+ return [seen.join(", "), rest].filter(Boolean).join(", plus ");
15385
15439
  }
15386
15440
  function toMapLocation(lead, mode) {
15387
- const pos = lead?.location?.pos;
15388
- const valid = Array.isArray(pos) && pos.length === 2 && pos.every((n) => typeof n === "number");
15389
- if (!valid)
15441
+ const pos = posOf(lead);
15442
+ if (pos === null)
15390
15443
  return null;
15391
15444
  const loc = lead.location;
15392
15445
  const c = lead.recommended_contact;
@@ -15431,7 +15484,7 @@ function buildMap(monitorLeads, discoverLeads2) {
15431
15484
  }
15432
15485
  };
15433
15486
  }
15434
- var DEFAULT_FOLLOWUPS_COUNT, DEFAULT_DISCOVER_COUNT, DISCOVER_OVER_PULL, ADMIN_PREFIX, tourPlan;
15487
+ var DEFAULT_FOLLOWUPS_COUNT, DEFAULT_DISCOVER_COUNT, DISCOVER_OVER_PULL, DEFAULT_RADIUS_KM, ADMIN_PREFIX, tourPlan;
15435
15488
  var init_tour_plan = __esm({
15436
15489
  "../core/dist/composite/tour-plan.js"() {
15437
15490
  "use strict";
@@ -15444,6 +15497,7 @@ var init_tour_plan = __esm({
15444
15497
  DEFAULT_FOLLOWUPS_COUNT = 6;
15445
15498
  DEFAULT_DISCOVER_COUNT = 6;
15446
15499
  DISCOVER_OVER_PULL = 30;
15500
+ DEFAULT_RADIUS_KM = 20;
15447
15501
  ADMIN_PREFIX = /^(?:city|town|village|borough|township|municipality) of /;
15448
15502
  tourPlan = {
15449
15503
  name: "leadbay_tour_plan",
@@ -15473,6 +15527,10 @@ var init_tour_plan = __esm({
15473
15527
  discover_count: {
15474
15528
  type: "number",
15475
15529
  description: `Top-N Discover leads (active lens wishlist) to return after client-side city filter. Default ${DEFAULT_DISCOVER_COUNT}.`
15530
+ },
15531
+ radius_km: {
15532
+ type: "number",
15533
+ description: `How far outside the named town a Discover stop may be, in km. Default ${DEFAULT_RADIUS_KM}, which is roughly a metro area \u2014 it is what puts West Sacramento on a tour of Sacramento and Courbevoie on a tour of Paris. Pass the user's own number when they name one ("dans un rayon de 10km", "zone 20km", "within 15 miles" \u2192 24). Pass 0 to keep the tour inside the town's own name. Leads in the town itself always come first; the radius only adds to them. It applies to Discover leads only \u2014 the Monitor half is scoped server-side and is untouched.`
15476
15534
  }
15477
15535
  },
15478
15536
  additionalProperties: false
@@ -15489,12 +15547,12 @@ var init_tour_plan = __esm({
15489
15547
  },
15490
15548
  discover_leads: {
15491
15549
  type: "array",
15492
- description: "Fresh Discover leads from the active lens, filtered client-side to match the city. Pulls a larger candidate set internally to compensate for the missing server-side geo filter.",
15550
+ description: "Fresh Discover leads from the active lens, filtered client-side to match the city, then extended with the ones whose own coordinates put them within `radius_km` of it. The town's own leads come first. Pulls a larger candidate set internally to compensate for the missing server-side geo filter.",
15493
15551
  items: { type: "object" }
15494
15552
  },
15495
15553
  discover_filter_note: {
15496
15554
  type: "string",
15497
- description: "Human-readable summary of the client-side geo filter applied to Discover leads (e.g. 'matched 3/30 by city/state')."
15555
+ description: "Human-readable summary of the client-side geo filter applied to Discover leads. Says how many stops are in the named town, how many are within `radius_km` of it, and which towns those are in \u2014 repeat that split to the user rather than presenting every stop as being in the city they named."
15498
15556
  },
15499
15557
  map_locations: {
15500
15558
  type: "array",
@@ -15618,12 +15676,21 @@ var init_tour_plan = __esm({
15618
15676
  const cityName = params.city && !/^\d+$/.test(params.city.trim()) ? params.city : void 0;
15619
15677
  const knownId = params.city_id ?? (cityName ? void 0 : params.city);
15620
15678
  const idOnly = Boolean(knownId) && !cityName;
15621
- const { leads: filtered, matchedOn } = idOnly ? { leads: [], matchedOn: null } : filterDiscoverByCity(rawDiscover, cityName);
15679
+ const radiusKm = params.radius_km ?? DEFAULT_RADIUS_KM;
15680
+ const { leads: filtered, matchedOn, nearby, anchored } = idOnly ? {
15681
+ leads: [],
15682
+ matchedOn: null,
15683
+ nearby: [],
15684
+ anchored: false
15685
+ } : filterDiscoverByCity(rawDiscover, cityName, monitorLeads, radiusKm);
15622
15686
  const discoverLeads2 = filtered.slice(0, discoverCount);
15623
15687
  const pulledLensId = leadsResult.status === "fulfilled" ? leadsResult.value?.lens?.id : null;
15624
15688
  if (pulledLensId != null) {
15625
15689
  reportLeadInteractions(client, pulledLensId, discoverLeads2.map((l) => l.id), ["LEAD_SEEN"], ctx?.logger);
15626
15690
  }
15691
+ const nearbySet = new Set(nearby);
15692
+ const nearShown = discoverLeads2.filter((l) => nearbySet.has(l));
15693
+ const inTownShown = discoverLeads2.length - nearShown.length;
15627
15694
  let filterNote;
15628
15695
  if (idOnly) {
15629
15696
  filterNote = `Discover leads need the NAME of the place, and this call passed an area id (${knownId}) and no name. Re-call with \`city\` set to the name of that area, keeping \`city_id\` so the Monitor half stays on the area you picked. The follow-ups below are already scoped to it.`;
@@ -15631,10 +15698,16 @@ var init_tour_plan = __esm({
15631
15698
  filterNote = `No city filter applied; returning top ${discoverLeads2.length} Discover leads.`;
15632
15699
  } else if (matchedOn === null) {
15633
15700
  filterNote = `No usable city filter in '${params.city}'; returning top ${discoverLeads2.length} Discover leads.`;
15701
+ } else if (filtered.length === 0 && anchored && radiusKm > 0) {
15702
+ filterNote = `No Discover lead in the active lens names '${params.city}' as its town, and none is within ${radiusKm} km of the leads that do (checked ${rawDiscover.length} candidates). Say so; do NOT present leads from elsewhere as if they were in '${params.city}'.`;
15634
15703
  } else if (filtered.length === 0) {
15635
15704
  filterNote = `No Discover lead in the active lens is in '${params.city}' (checked ${rawDiscover.length} candidates by city, then by state/region). Say so; do NOT present leads from elsewhere as if they were in '${params.city}'.`;
15705
+ } else if (nearShown.length > 0 && inTownShown === 0) {
15706
+ filterNote = `No Discover lead in the active lens names '${params.city}' as its town; ${nearby.length}/${rawDiscover.length} are within ${radiusKm} km of it. Returning top ${discoverLeads2.length}, in ${townsOf(nearShown)}. Name the town each stop is actually in rather than calling them all '${params.city}'.`;
15707
+ } else if (nearShown.length > 0) {
15708
+ filterNote = `Matched ${filtered.length - nearby.length}/${rawDiscover.length} Discover leads to '${params.city}' by city, plus ${nearby.length} within ${radiusKm} km of it. Returning top ${discoverLeads2.length}: ${inTownShown} in '${params.city}' and ${nearShown.length} in ${townsOf(nearShown)}. Name the town each nearby stop is in.`;
15636
15709
  } else {
15637
- filterNote = `Matched ${filtered.length}/${rawDiscover.length} Discover leads to '${params.city}' by ${matchedOn === "city" ? "city" : "state/region"}; returning top ${discoverLeads2.length}.`;
15710
+ filterNote = `Matched ${filtered.length - nearby.length}/${rawDiscover.length} Discover leads to '${params.city}' by ${matchedOn === "city" ? "city" : "state/region"}; returning top ${discoverLeads2.length}.`;
15638
15711
  }
15639
15712
  return {
15640
15713
  city: params.city ?? null,
@@ -17122,7 +17195,7 @@ var init_research_lead_by_name_fuzzy = __esm({
17122
17195
  // of a research card.
17123
17196
  outputSchema: {
17124
17197
  type: "object",
17125
- description: "Same shape as leadbay_research_lead_by_id, with _meta.resolved_from='companyName'|'resolver', _meta.resolved_query='<needle>', _meta.resolved_matched_on=[...], and _meta.match_candidates=[{leadId,name,score}] populated. When the registry resolver cannot pick one company, returns {resolution:'ambiguous', query, candidates:[{leadId,name,website,location,registry_ids,score,matched_on}]} instead \u2014 ask the user which one, then call leadbay_research_lead_by_id.",
17198
+ description: "Same shape as leadbay_research_lead_by_id, with _meta.resolved_from='companyName'|'resolver', _meta.resolved_query='<needle>', _meta.resolved_matched_on=[...], and _meta.match_candidates=[{leadId,name,score}] populated. When the registry resolver cannot pick one company, returns {resolution:'ambiguous', query, candidates:[{leadId,name,website,location,registry_ids,score,matched_on}]} instead \u2014 ask the user which one, then call leadbay_research_lead_by_id. When neither the user's leads nor the registry hold the company, returns {resolution:'not_found', query, summary, would_help, next_step} \u2014 a successful answer, not an error: do what next_step says, which is normally to ask the user for the field would_help names ('website' or 'registry_number') and call again. would_help is empty when the search was scoped to one lens, where the remedy is to drop the scope, not to ask the user for a field.",
17126
17199
  additionalProperties: true
17127
17200
  },
17128
17201
  execute: async (client, params, ctx) => {
@@ -17136,7 +17209,18 @@ var init_research_lead_by_name_fuzzy = __esm({
17136
17209
  if (scoped.length > 0) {
17137
17210
  return await delegate(scoped, params.lensId);
17138
17211
  }
17139
- throw client.makeError("LEAD_NOT_FOUND", `No lead matching "${query}" in lens ${params.lensId}`, "This lookup was intentionally restricted to the supplied lens. Omit lensId to search your visible leads across Discover, Monitor, and Activate and then the Leadbay company registry.");
17212
+ return {
17213
+ resolution: "not_found",
17214
+ query,
17215
+ summary: `No lead matching "${query}" in lens ${params.lensId}`,
17216
+ would_help: [],
17217
+ next_step: "This lookup was intentionally restricted to the supplied lens. Omit lensId to search your visible leads across Discover, Monitor, and Activate and then the Leadbay company registry.",
17218
+ _meta: {
17219
+ region: client.region,
17220
+ lens_id: params.lensId,
17221
+ resolved_query: query
17222
+ }
17223
+ };
17140
17224
  }
17141
17225
  async function delegate(matches, fallbackLens) {
17142
17226
  const [primary, ...rest] = matches;
@@ -17231,7 +17315,23 @@ var init_research_lead_by_name_fuzzy = __esm({
17231
17315
  const wanted = resolved.type === "none" && resolved.would_help.length > 0 ? resolved.would_help : ["website", "registry_number"];
17232
17316
  const asks = wanted.map((f) => f === "registry_number" ? "a registry number (SIREN/SIRET) for `registry_number`" : f === "website" ? "the company website for `website`" : `\`${f}\``).join(" or ");
17233
17317
  const hint = resolved.type === "unidentifiable" ? `The registry could not identify a company from this input (${resolved.reason}). Ask the user for ${asks}, then call this tool again with it.` : `The registry found no company for what was supplied. It would match on ${asks}. Ask the user for that \u2014 "what's their website?" usually settles it \u2014 then call this tool again. Do not offer an import before asking.`;
17234
- throw client.makeError("LEAD_NOT_FOUND", `No company matching "${query}" ${searched}`, hint, "POST /leads/resolve");
17318
+ if (!corpusSearched) {
17319
+ throw client.makeError("LEAD_NOT_FOUND", `No company matching "${query}" ${searched}`, `${hint} But this lookup did not complete \u2014 the lead search route was unreachable, so their own leads were never checked. Retry once before telling the user the company is absent.`, "POST /leads/resolve");
17320
+ }
17321
+ return {
17322
+ resolution: "not_found",
17323
+ query,
17324
+ resolver_payload: payload,
17325
+ summary: `No company matching "${query}" ${searched}`,
17326
+ would_help: wanted,
17327
+ next_step: hint,
17328
+ _meta: {
17329
+ region: client.region,
17330
+ resolved_from: "resolver",
17331
+ resolved_query: query,
17332
+ endpoint: "POST /leads/resolve"
17333
+ }
17334
+ };
17235
17335
  }
17236
17336
  };
17237
17337
  }
@@ -27260,7 +27360,7 @@ Split the returned \`monitor_leads\` into two buckets client-side using their en
27260
27360
 
27261
27361
  \`discover_leads\` are the **New** bucket.
27262
27362
 
27263
- Aim for a 3+3+3 split if possible. If the customers bucket has fewer than 3, fill from qualified. If discover_filter_note indicates a low match ratio for the city, mention it: "Only N/30 fresh leads matched your city" \u2014 better honest than padded.
27363
+ Aim for a 3+3+3 split if possible. If the customers bucket has fewer than 3, fill from qualified. If discover_filter_note indicates a low match ratio for the city, mention it: "Only N/30 fresh leads matched your city" \u2014 better honest than padded. When it splits the stops into the ones in the city and the ones within \`radius_km\` of it, repeat that split and name the town each nearby stop is in \u2014 a company in Courbevoie is a stop on a Paris day, but it is not in Paris. If I named a radius ("within 10 km", "rayon de 20km"), pass it as \`radius_km\`.
27264
27364
 
27265
27365
  # PHASE 3 \u2014 PRESENT THE ITINERARY + OFFER THE MAP
27266
27366
 
@@ -27839,10 +27939,12 @@ When the response carries \`social_urls\` (the post-fix multi-platform URL block
27839
27939
 
27840
27940
 
27841
27941
  # PHASE 2 \u2014 NOT FOUND
27842
- If the resolver returns \`LEAD_NOT_FOUND\`, read its hint: it names the field
27843
- that would have found the company (\`would_help\`, usually \`website\`). **Ask the
27844
- user for that field first** \u2014 "what's their website?" \u2014 and call the tool again
27845
- with it. Only when they cannot supply it should you say both their leads and
27942
+ If the resolver returns \`resolution: "not_found"\`, that call SUCCEEDED \u2014 it is
27943
+ the answer, not a failure. Do what its \`next_step\` says. Usually that is to ask
27944
+ for the field \`would_help\` names (normally \`website\`): **ask the user for it
27945
+ first** \u2014 "what's their website?" \u2014 and call the tool again with it. When
27946
+ \`would_help\` is empty the search was scoped to one lens, and \`next_step\` says
27947
+ to drop the scope rather than to ask the user for anything. Only when they cannot supply it should you say both their leads and
27846
27948
  the Leadbay registry were searched.
27847
27949
  **Do NOT call \`leadbay_import_and_qualify\` automatically.**
27848
27950
  Offer to import and qualify as a separate, explicit next step; only call it
@@ -32035,7 +32137,7 @@ var OAUTH_BASE_URLS = {
32035
32137
  fr: "https://staging.api.leadbay.app"
32036
32138
  }
32037
32139
  };
32038
- var VERSION = "0.39.5";
32140
+ var VERSION = "0.39.7";
32039
32141
  var HELP = `
32040
32142
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
32041
32143
 
@@ -1531,7 +1531,7 @@ Split the returned \`monitor_leads\` into two buckets client-side using their en
1531
1531
 
1532
1532
  \`discover_leads\` are the **New** bucket.
1533
1533
 
1534
- Aim for a 3+3+3 split if possible. If the customers bucket has fewer than 3, fill from qualified. If discover_filter_note indicates a low match ratio for the city, mention it: "Only N/30 fresh leads matched your city" \u2014 better honest than padded.
1534
+ Aim for a 3+3+3 split if possible. If the customers bucket has fewer than 3, fill from qualified. If discover_filter_note indicates a low match ratio for the city, mention it: "Only N/30 fresh leads matched your city" \u2014 better honest than padded. When it splits the stops into the ones in the city and the ones within \`radius_km\` of it, repeat that split and name the town each nearby stop is in \u2014 a company in Courbevoie is a stop on a Paris day, but it is not in Paris. If I named a radius ("within 10 km", "rayon de 20km"), pass it as \`radius_km\`.
1535
1535
 
1536
1536
  # PHASE 3 \u2014 PRESENT THE ITINERARY + OFFER THE MAP
1537
1537
 
@@ -2110,10 +2110,12 @@ When the response carries \`social_urls\` (the post-fix multi-platform URL block
2110
2110
 
2111
2111
 
2112
2112
  # PHASE 2 \u2014 NOT FOUND
2113
- If the resolver returns \`LEAD_NOT_FOUND\`, read its hint: it names the field
2114
- that would have found the company (\`would_help\`, usually \`website\`). **Ask the
2115
- user for that field first** \u2014 "what's their website?" \u2014 and call the tool again
2116
- with it. Only when they cannot supply it should you say both their leads and
2113
+ If the resolver returns \`resolution: "not_found"\`, that call SUCCEEDED \u2014 it is
2114
+ the answer, not a failure. Do what its \`next_step\` says. Usually that is to ask
2115
+ for the field \`would_help\` names (normally \`website\`): **ask the user for it
2116
+ first** \u2014 "what's their website?" \u2014 and call the tool again with it. When
2117
+ \`would_help\` is empty the search was scoped to one lens, and \`next_step\` says
2118
+ to drop the scope rather than to ask the user for anything. Only when they cannot supply it should you say both their leads and
2117
2119
  the Leadbay registry were searched.
2118
2120
  **Do NOT call \`leadbay_import_and_qualify\` automatically.**
2119
2121
  Offer to import and qualify as a separate, explicit next step; only call it
@@ -9117,13 +9119,11 @@ key. It survives a misspelled company name and is what turns "not in your
9117
9119
  list" into an answer. With only a contact email, pass \`email\`: the company
9118
9120
  domain is derived from it, consumer mailboxes ignored.
9119
9121
 
9120
- When the registry cannot pick one company it returns \`{resolution:
9121
- "ambiguous", query, candidates:[\u2026]}\` instead of a card. Ask which one; never
9122
- guess from \`score\`.
9123
-
9124
- \`LEAD_NOT_FOUND\` is not a dead end: its hint names the field that would have
9125
- found it \u2014 \`website\` or \`registry_number\`, both params. Ask for it and call
9126
- again. Do not offer an import before asking.
9122
+ Both \`resolution\` answers below are successes, not cards. \`"ambiguous"\`
9123
+ carries \`candidates[]\`: ask which one, never guess from \`score\`.
9124
+ \`"not_found"\` carries \`summary\` + \`next_step\`: nobody has it, so say that and
9125
+ do what \`next_step\` says \u2014 usually ask for the param \`would_help\` names, then
9126
+ call again. Do not offer an import before asking.
9127
9127
 
9128
9128
  ---
9129
9129
 
@@ -9264,7 +9264,7 @@ out?"\`
9264
9264
 
9265
9265
  When \`resolution\` is \`"ambiguous"\`, render no card: use \`ask_user_input_v0\`,
9266
9266
  ONE \`single_select\` question ("Which one?"), one short label per candidate
9267
- combining \`name\` and \`location\`.
9267
+ combining \`name\` and \`location\`. \`"not_found"\` renders no card either.
9268
9268
 
9269
9269
  When \`_meta.match_candidates\` is non-empty, prepend one extra NEXT STEPS row:
9270
9270
 
@@ -10036,17 +10036,18 @@ WHEN NOT TO USE: the user wants a lead list (leadbay_pull_leads / leadbay_pull_f
10036
10036
  `;
10037
10037
  var leadbay_tour_plan = `## WHEN TO USE
10038
10038
 
10039
- Trigger phrases: "visiting <city> in <N> days", "I'm in <city> next week / Tuesday \u2014 who's worth meeting", "I'm going to <city> \u2014 who should I see", "who's worth meeting in <city>", "field tour in <city>", "plan a tour in <city>", "who should I meet in <city>", "customers plus prospects in <city>", "tour itinerary".
10039
+ Trigger phrases: "visiting <city> in <N> days", "I'm in <city> next week / Tuesday \u2014 who's worth meeting", "I'm going to <city> \u2014 who should I see", "who's worth meeting in <city>", "field tour in <city>", "plan a tour in <city>", "who should I meet in <city>", "prospects within <N> km of <city>", "<city> and the surrounding area", "customers plus prospects in <city>", "tour itinerary".
10040
10040
 
10041
10041
  Do NOT use for: "follow-ups only, no new prospects" \u2192 \`leadbay_followups_map\`; "new leads only" \u2192 \`leadbay_pull_leads\`; "research one account" \u2192 \`leadbay_research_lead_by_id\`.
10042
10042
 
10043
- Prefer when: user wants known accounts plus new discoveries in one geographic itinerary; NEVER a country name \u2014 unlike the Monitor tools, do NOT omit \`city\`; a city-less tour is arbitrary nationwide leads, so ask which city or region
10043
+ Prefer when: known accounts plus new discoveries in one itinerary; pass \`radius_km\` when they name a radius; NEVER a country name, and do NOT omit \`city\`: a city-less tour is arbitrary nationwide leads, so ask which city or region
10044
10044
 
10045
10045
  Examples that SHOULD invoke this tool:
10046
10046
  - "I'm flying to Limoges in 4 days \u2014 give me 3 customers, 3 qualified prospects, and 3 new high-potential."
10047
10047
  - "I'm in San Francisco next Tuesday. Who's worth meeting?"
10048
10048
  - "Plan my tour next Tuesday in Lyon: known accounts plus discoveries."
10049
10049
  - "Build a mixed itinerary for Berlin \u2014 I want both follow-ups and fresh leads."
10050
+ - "J'ai un rdv le 19 ao\xFBt \xE0 Colmar \u2014 trouve-moi les prospects dans un rayon de 10km."
10050
10051
 
10051
10052
  Examples that should NOT invoke this tool (sound similar, route elsewhere):
10052
10053
  - "Show me my follow-ups for the SF trip."
@@ -10101,6 +10102,8 @@ same instruction in its \`hint\`.
10101
10102
 
10102
10103
  **Counts**: \`followups_count\` (default 6 \u2014 generous so the agent can split into "customers + qualified" client-side) and \`discover_count\` (default 6 after client-side geo filter). The composite over-pulls Discover (30 raw) because the wishlist endpoint has no server-side geo filter \u2014 it then keeps the leads whose own \`location.city\` names the requested city, and falls back to \`location.state\` only when no city matched (which is what a regional ask like "Texas" or "\xCEle-de-France" looks like). \`location.country\` is never consulted. \`discover_filter_note\` reports the ratio and which field carried it, so the agent can be honest about coverage. **When it says no Discover lead is in the city, say that** \u2014 return the Monitor half and offer \`leadbay_find_new_leads\` for that city. Never fill the gap with leads from elsewhere.
10103
10104
 
10105
+ **The next town over**: a tour is a day of driving, so after the town's own leads are found the composite adds the Discover leads whose own coordinates put them within **\`radius_km\` (default 20)** of it \u2014 West Sacramento on a tour of Sacramento, Courbevoie and Ivry-sur-Seine on a tour of Paris. The town's own leads always come first. **Pass the user's own number when they give one** ("dans un rayon de 10km autour de Colmar" \u2192 \`radius_km: 10\`; "within 15 miles" \u2192 \`radius_km: 24\`), and \`radius_km: 0\` to keep the tour strictly inside the named town. The radius applies to Discover leads only; the Monitor half is scoped server-side and is untouched. When \`discover_filter_note\` splits the stops into "N in '<city>' and M in <other towns>", **repeat that split** \u2014 say which town each nearby stop is actually in rather than presenting every stop as being in the city the user named.
10106
+
10104
10107
  **What \`tour_plan\` does NOT do**: it doesn't persist the tour as a campaign artifact. To do that \u2014 create a "Limoges Tour \u2013 May 24" campaign and attach the selected accounts \u2014 chain into \`leadbay_create_campaign({lead_ids: [...selected_ids], name: 'Limoges Tour \u2013 <date>'})\` after the user picks. See the \`leadbay_plan_tour_in_city\` prompt for the full end-to-end orchestrator.
10105
10108
 
10106
10109
  ---
@@ -17758,6 +17761,7 @@ var followupsMap = {
17758
17761
  var DEFAULT_FOLLOWUPS_COUNT = 6;
17759
17762
  var DEFAULT_DISCOVER_COUNT = 6;
17760
17763
  var DISCOVER_OVER_PULL = 30;
17764
+ var DEFAULT_RADIUS_KM = 20;
17761
17765
  function normalizeGeo(value) {
17762
17766
  return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
17763
17767
  }
@@ -17782,23 +17786,75 @@ function stateFieldOf(lead) {
17782
17786
  const state = lead?.location?.state;
17783
17787
  return typeof state === "string" ? normalizeGeo(state) : "";
17784
17788
  }
17785
- function filterDiscoverByCity(leads, cityHint) {
17789
+ function posOf(lead) {
17790
+ const pos = lead?.location?.pos;
17791
+ const valid = Array.isArray(pos) && pos.length === 2 && pos.every((n) => typeof n === "number" && Number.isFinite(n));
17792
+ return valid ? [pos[0], pos[1]] : null;
17793
+ }
17794
+ function distanceKm(a, b) {
17795
+ const toRad = (d) => d * Math.PI / 180;
17796
+ const dLat = toRad(b[0] - a[0]);
17797
+ const dLng = toRad(b[1] - a[1]);
17798
+ const h = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(a[0])) * Math.cos(toRad(b[0])) * Math.sin(dLng / 2) ** 2;
17799
+ return 2 * 6371 * Math.asin(Math.min(1, Math.sqrt(h)));
17800
+ }
17801
+ function withinRadius(leads, anchors, radiusKm) {
17802
+ if (anchors.length === 0 || !(radiusKm > 0))
17803
+ return [];
17804
+ return leads.filter((l) => {
17805
+ const p = posOf(l);
17806
+ return p !== null && anchors.some((a) => distanceKm(a, p) <= radiusKm);
17807
+ });
17808
+ }
17809
+ function filterDiscoverByCity(leads, cityHint, monitorLeads, radiusKm) {
17786
17810
  const hint = cityHint ? cityHintCore(cityHint) : { name: "", isCity: false };
17787
17811
  if (!hint.name)
17788
- return { leads, matchedOn: null };
17812
+ return { leads, matchedOn: null, nearby: [], anchored: false };
17789
17813
  const cityOf = (l) => typeof l?.location?.city === "string" ? townName(l.location.city) : "";
17814
+ const positions = (ls) => ls.map(posOf).filter((p) => p !== null);
17790
17815
  const byCity = leads.filter((l) => cityOf(l) === hint.name);
17791
- if (byCity.length > 0)
17792
- return { leads: byCity, matchedOn: "city" };
17793
- if (hint.isCity)
17794
- return { leads: [], matchedOn: "city" };
17795
- const byState = leads.filter((l) => stateFieldOf(l) === hint.name);
17796
- return { leads: byState, matchedOn: "state" };
17816
+ const anchors = positions([
17817
+ ...byCity,
17818
+ ...monitorLeads.filter((l) => cityOf(l) === hint.name)
17819
+ ]);
17820
+ if (byCity.length > 0) {
17821
+ const inTown = new Set(byCity);
17822
+ const nearby2 = withinRadius(leads.filter((l) => !inTown.has(l)), anchors, radiusKm);
17823
+ return { leads: [...byCity, ...nearby2], matchedOn: "city", nearby: nearby2, anchored: true };
17824
+ }
17825
+ const anchored = anchors.length > 0;
17826
+ if (!hint.isCity) {
17827
+ const byState = leads.filter((l) => stateFieldOf(l) === hint.name);
17828
+ if (byState.length > 0)
17829
+ return { leads: byState, matchedOn: "state", nearby: [], anchored };
17830
+ }
17831
+ const nearby = withinRadius(leads, anchors, radiusKm);
17832
+ if (nearby.length > 0)
17833
+ return { leads: nearby, matchedOn: "city", nearby, anchored };
17834
+ return {
17835
+ leads: [],
17836
+ matchedOn: hint.isCity ? "city" : "state",
17837
+ nearby: [],
17838
+ anchored
17839
+ };
17840
+ }
17841
+ function townsOf(leads) {
17842
+ const seen = [];
17843
+ let unnamed = 0;
17844
+ for (const l of leads) {
17845
+ const city = typeof l?.location?.city === "string" ? l.location.city.trim() : "";
17846
+ const name = city.replace(/^(?:City|Town|Village|Borough|Township|Municipality) of /i, "");
17847
+ if (!name)
17848
+ unnamed += 1;
17849
+ else if (!seen.includes(name))
17850
+ seen.push(name);
17851
+ }
17852
+ const rest = unnamed > 0 ? `${unnamed} whose record names no town` : "";
17853
+ return [seen.join(", "), rest].filter(Boolean).join(", plus ");
17797
17854
  }
17798
17855
  function toMapLocation(lead, mode) {
17799
- const pos = lead?.location?.pos;
17800
- const valid = Array.isArray(pos) && pos.length === 2 && pos.every((n) => typeof n === "number");
17801
- if (!valid)
17856
+ const pos = posOf(lead);
17857
+ if (pos === null)
17802
17858
  return null;
17803
17859
  const loc = lead.location;
17804
17860
  const c = lead.recommended_contact;
@@ -17871,6 +17927,10 @@ var tourPlan = {
17871
17927
  discover_count: {
17872
17928
  type: "number",
17873
17929
  description: `Top-N Discover leads (active lens wishlist) to return after client-side city filter. Default ${DEFAULT_DISCOVER_COUNT}.`
17930
+ },
17931
+ radius_km: {
17932
+ type: "number",
17933
+ description: `How far outside the named town a Discover stop may be, in km. Default ${DEFAULT_RADIUS_KM}, which is roughly a metro area \u2014 it is what puts West Sacramento on a tour of Sacramento and Courbevoie on a tour of Paris. Pass the user's own number when they name one ("dans un rayon de 10km", "zone 20km", "within 15 miles" \u2192 24). Pass 0 to keep the tour inside the town's own name. Leads in the town itself always come first; the radius only adds to them. It applies to Discover leads only \u2014 the Monitor half is scoped server-side and is untouched.`
17874
17934
  }
17875
17935
  },
17876
17936
  additionalProperties: false
@@ -17887,12 +17947,12 @@ var tourPlan = {
17887
17947
  },
17888
17948
  discover_leads: {
17889
17949
  type: "array",
17890
- description: "Fresh Discover leads from the active lens, filtered client-side to match the city. Pulls a larger candidate set internally to compensate for the missing server-side geo filter.",
17950
+ description: "Fresh Discover leads from the active lens, filtered client-side to match the city, then extended with the ones whose own coordinates put them within `radius_km` of it. The town's own leads come first. Pulls a larger candidate set internally to compensate for the missing server-side geo filter.",
17891
17951
  items: { type: "object" }
17892
17952
  },
17893
17953
  discover_filter_note: {
17894
17954
  type: "string",
17895
- description: "Human-readable summary of the client-side geo filter applied to Discover leads (e.g. 'matched 3/30 by city/state')."
17955
+ description: "Human-readable summary of the client-side geo filter applied to Discover leads. Says how many stops are in the named town, how many are within `radius_km` of it, and which towns those are in \u2014 repeat that split to the user rather than presenting every stop as being in the city they named."
17896
17956
  },
17897
17957
  map_locations: {
17898
17958
  type: "array",
@@ -18016,12 +18076,21 @@ var tourPlan = {
18016
18076
  const cityName = params.city && !/^\d+$/.test(params.city.trim()) ? params.city : void 0;
18017
18077
  const knownId = params.city_id ?? (cityName ? void 0 : params.city);
18018
18078
  const idOnly = Boolean(knownId) && !cityName;
18019
- const { leads: filtered, matchedOn } = idOnly ? { leads: [], matchedOn: null } : filterDiscoverByCity(rawDiscover, cityName);
18079
+ const radiusKm = params.radius_km ?? DEFAULT_RADIUS_KM;
18080
+ const { leads: filtered, matchedOn, nearby, anchored } = idOnly ? {
18081
+ leads: [],
18082
+ matchedOn: null,
18083
+ nearby: [],
18084
+ anchored: false
18085
+ } : filterDiscoverByCity(rawDiscover, cityName, monitorLeads, radiusKm);
18020
18086
  const discoverLeads2 = filtered.slice(0, discoverCount);
18021
18087
  const pulledLensId = leadsResult.status === "fulfilled" ? leadsResult.value?.lens?.id : null;
18022
18088
  if (pulledLensId != null) {
18023
18089
  reportLeadInteractions(client, pulledLensId, discoverLeads2.map((l) => l.id), ["LEAD_SEEN"], ctx?.logger);
18024
18090
  }
18091
+ const nearbySet = new Set(nearby);
18092
+ const nearShown = discoverLeads2.filter((l) => nearbySet.has(l));
18093
+ const inTownShown = discoverLeads2.length - nearShown.length;
18025
18094
  let filterNote;
18026
18095
  if (idOnly) {
18027
18096
  filterNote = `Discover leads need the NAME of the place, and this call passed an area id (${knownId}) and no name. Re-call with \`city\` set to the name of that area, keeping \`city_id\` so the Monitor half stays on the area you picked. The follow-ups below are already scoped to it.`;
@@ -18029,10 +18098,16 @@ var tourPlan = {
18029
18098
  filterNote = `No city filter applied; returning top ${discoverLeads2.length} Discover leads.`;
18030
18099
  } else if (matchedOn === null) {
18031
18100
  filterNote = `No usable city filter in '${params.city}'; returning top ${discoverLeads2.length} Discover leads.`;
18101
+ } else if (filtered.length === 0 && anchored && radiusKm > 0) {
18102
+ filterNote = `No Discover lead in the active lens names '${params.city}' as its town, and none is within ${radiusKm} km of the leads that do (checked ${rawDiscover.length} candidates). Say so; do NOT present leads from elsewhere as if they were in '${params.city}'.`;
18032
18103
  } else if (filtered.length === 0) {
18033
18104
  filterNote = `No Discover lead in the active lens is in '${params.city}' (checked ${rawDiscover.length} candidates by city, then by state/region). Say so; do NOT present leads from elsewhere as if they were in '${params.city}'.`;
18105
+ } else if (nearShown.length > 0 && inTownShown === 0) {
18106
+ filterNote = `No Discover lead in the active lens names '${params.city}' as its town; ${nearby.length}/${rawDiscover.length} are within ${radiusKm} km of it. Returning top ${discoverLeads2.length}, in ${townsOf(nearShown)}. Name the town each stop is actually in rather than calling them all '${params.city}'.`;
18107
+ } else if (nearShown.length > 0) {
18108
+ filterNote = `Matched ${filtered.length - nearby.length}/${rawDiscover.length} Discover leads to '${params.city}' by city, plus ${nearby.length} within ${radiusKm} km of it. Returning top ${discoverLeads2.length}: ${inTownShown} in '${params.city}' and ${nearShown.length} in ${townsOf(nearShown)}. Name the town each nearby stop is in.`;
18034
18109
  } else {
18035
- filterNote = `Matched ${filtered.length}/${rawDiscover.length} Discover leads to '${params.city}' by ${matchedOn === "city" ? "city" : "state/region"}; returning top ${discoverLeads2.length}.`;
18110
+ filterNote = `Matched ${filtered.length - nearby.length}/${rawDiscover.length} Discover leads to '${params.city}' by ${matchedOn === "city" ? "city" : "state/region"}; returning top ${discoverLeads2.length}.`;
18036
18111
  }
18037
18112
  return {
18038
18113
  city: params.city ?? null,
@@ -19447,7 +19522,7 @@ var researchLeadByNameFuzzy = {
19447
19522
  // of a research card.
19448
19523
  outputSchema: {
19449
19524
  type: "object",
19450
- description: "Same shape as leadbay_research_lead_by_id, with _meta.resolved_from='companyName'|'resolver', _meta.resolved_query='<needle>', _meta.resolved_matched_on=[...], and _meta.match_candidates=[{leadId,name,score}] populated. When the registry resolver cannot pick one company, returns {resolution:'ambiguous', query, candidates:[{leadId,name,website,location,registry_ids,score,matched_on}]} instead \u2014 ask the user which one, then call leadbay_research_lead_by_id.",
19525
+ description: "Same shape as leadbay_research_lead_by_id, with _meta.resolved_from='companyName'|'resolver', _meta.resolved_query='<needle>', _meta.resolved_matched_on=[...], and _meta.match_candidates=[{leadId,name,score}] populated. When the registry resolver cannot pick one company, returns {resolution:'ambiguous', query, candidates:[{leadId,name,website,location,registry_ids,score,matched_on}]} instead \u2014 ask the user which one, then call leadbay_research_lead_by_id. When neither the user's leads nor the registry hold the company, returns {resolution:'not_found', query, summary, would_help, next_step} \u2014 a successful answer, not an error: do what next_step says, which is normally to ask the user for the field would_help names ('website' or 'registry_number') and call again. would_help is empty when the search was scoped to one lens, where the remedy is to drop the scope, not to ask the user for a field.",
19451
19526
  additionalProperties: true
19452
19527
  },
19453
19528
  execute: async (client, params, ctx) => {
@@ -19461,7 +19536,18 @@ var researchLeadByNameFuzzy = {
19461
19536
  if (scoped.length > 0) {
19462
19537
  return await delegate(scoped, params.lensId);
19463
19538
  }
19464
- throw client.makeError("LEAD_NOT_FOUND", `No lead matching "${query}" in lens ${params.lensId}`, "This lookup was intentionally restricted to the supplied lens. Omit lensId to search your visible leads across Discover, Monitor, and Activate and then the Leadbay company registry.");
19539
+ return {
19540
+ resolution: "not_found",
19541
+ query,
19542
+ summary: `No lead matching "${query}" in lens ${params.lensId}`,
19543
+ would_help: [],
19544
+ next_step: "This lookup was intentionally restricted to the supplied lens. Omit lensId to search your visible leads across Discover, Monitor, and Activate and then the Leadbay company registry.",
19545
+ _meta: {
19546
+ region: client.region,
19547
+ lens_id: params.lensId,
19548
+ resolved_query: query
19549
+ }
19550
+ };
19465
19551
  }
19466
19552
  async function delegate(matches, fallbackLens) {
19467
19553
  const [primary, ...rest] = matches;
@@ -19556,7 +19642,23 @@ var researchLeadByNameFuzzy = {
19556
19642
  const wanted = resolved.type === "none" && resolved.would_help.length > 0 ? resolved.would_help : ["website", "registry_number"];
19557
19643
  const asks = wanted.map((f) => f === "registry_number" ? "a registry number (SIREN/SIRET) for `registry_number`" : f === "website" ? "the company website for `website`" : `\`${f}\``).join(" or ");
19558
19644
  const hint = resolved.type === "unidentifiable" ? `The registry could not identify a company from this input (${resolved.reason}). Ask the user for ${asks}, then call this tool again with it.` : `The registry found no company for what was supplied. It would match on ${asks}. Ask the user for that \u2014 "what's their website?" usually settles it \u2014 then call this tool again. Do not offer an import before asking.`;
19559
- throw client.makeError("LEAD_NOT_FOUND", `No company matching "${query}" ${searched}`, hint, "POST /leads/resolve");
19645
+ if (!corpusSearched) {
19646
+ throw client.makeError("LEAD_NOT_FOUND", `No company matching "${query}" ${searched}`, `${hint} But this lookup did not complete \u2014 the lead search route was unreachable, so their own leads were never checked. Retry once before telling the user the company is absent.`, "POST /leads/resolve");
19647
+ }
19648
+ return {
19649
+ resolution: "not_found",
19650
+ query,
19651
+ resolver_payload: payload,
19652
+ summary: `No company matching "${query}" ${searched}`,
19653
+ would_help: wanted,
19654
+ next_step: hint,
19655
+ _meta: {
19656
+ region: client.region,
19657
+ resolved_from: "resolver",
19658
+ resolved_query: query,
19659
+ endpoint: "POST /leads/resolve"
19660
+ }
19661
+ };
19560
19662
  }
19561
19663
  };
19562
19664
 
@@ -29308,7 +29410,7 @@ function parseWriteEnv(env = process.env) {
29308
29410
  }
29309
29411
 
29310
29412
  // src/http-server.ts
29311
- var VERSION = true ? "0.39.5" : "0.0.0-dev";
29413
+ var VERSION = true ? "0.39.7" : "0.0.0-dev";
29312
29414
  var PORT = Number(process.env.PORT ?? 8080);
29313
29415
  var HOST = process.env.HOST ?? "0.0.0.0";
29314
29416
  var logger = {
@@ -1805,7 +1805,7 @@ var init_installer_gui = __esm({
1805
1805
  init_install_dxt();
1806
1806
  init_install_shared();
1807
1807
  init_oauth();
1808
- VERSION = true ? "0.39.5" : "0.0.0-dev";
1808
+ VERSION = true ? "0.39.7" : "0.0.0-dev";
1809
1809
  MESSAGES = {
1810
1810
  en: {
1811
1811
  installer: {
@@ -1068,7 +1068,7 @@ async function oauthLogin(opts) {
1068
1068
  }
1069
1069
 
1070
1070
  // installer/installer-gui.ts
1071
- var VERSION = true ? "0.39.5" : "0.0.0-dev";
1071
+ var VERSION = true ? "0.39.7" : "0.0.0-dev";
1072
1072
  var MESSAGES = {
1073
1073
  en: {
1074
1074
  installer: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.39.5",
3
+ "version": "0.39.7",
4
4
  "mcpName": "io.github.leadbay/leadbay-mcp",
5
5
  "description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
6
6
  "type": "module",