@leadbay/mcp 0.30.0 → 0.31.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/bin.js +244 -4
- package/dist/http-server.js +236 -4
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -6832,8 +6832,10 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
|
6832
6832
|
|
|
6833
6833
|
\`queued\` \u2192 \u2705 "Queued <N> extra leads on lens <id>. Pull in ~30s." Do NOT
|
|
6834
6834
|
list \`accepted_seeds\`; they're internal.
|
|
6835
|
-
\`
|
|
6836
|
-
|
|
6835
|
+
\`no_candidates\` \u2192 \u26D4 surface \`reason.message\`, name the criteria, offer
|
|
6836
|
+
\`leadbay_adjust_audience\`. Never re-call this tool on that lens.
|
|
6837
|
+
\`quota_exceeded\` \u2192 three options via the host's choice widget
|
|
6838
|
+
(smaller count / wait for reset / upgrade).
|
|
6837
6839
|
\`refresh_in_progress\` \u2192 "lens is filling, retry in a minute".
|
|
6838
6840
|
\`no_valid_seeds\` \u2192 silently re-call \`leadbay_seed_candidates\`, retry once.
|
|
6839
6841
|
|
|
@@ -6853,6 +6855,9 @@ Queue an additive extra-refill on a lens \u2014 more leads on the same criteria,
|
|
|
6853
6855
|
- \`status: "quota_exceeded"\` \u2014 daily LENS_EXTRA_REFILL hit. Response carries \`quota: {used_today, resets_at}\` + a \`message\` to surface. **Render three options via your host's choice widget (\`ask_user_input_v0\` or \`AskUserQuestion\`)**: (1) smaller \`extra_count\`, (2) wait until \`resets_at\`, (3) upgrade plan (TIER1=150, TIER2=1000). Do NOT silently retry.
|
|
6854
6856
|
- \`status: "refresh_in_progress"\` \u2014 a refresh or extra-refill is already running. Tell the user to wait and call \`leadbay_pull_leads\` in ~30s.
|
|
6855
6857
|
- \`status: "no_valid_seeds"\` \u2014 seeds went stale. Silently re-call \`leadbay_seed_candidates\` and retry once; only surface to the user if the second attempt also fails.
|
|
6858
|
+
- \`status: "no_candidates"\` \u2014 **the refill was NOT queued.** The lens's candidate pool is empty, so a refill would report success, consume no quota and deliver nothing. \`reason\` carries the same \`{code, message, retryable, criteria?, narrow_locations?}\` shape \`leadbay_pull_leads\` returns in \`empty_reason\`, with \`retryable: false\`. **Stop. Do not re-call this tool on this lens** \u2014 the outcome cannot change until the audience changes. Surface \`reason.message\`, name the criteria in play, and offer \`leadbay_adjust_audience\` (or \`leadbay_pull_followups\` when \`reason.code\` is \`no_new_leads\` and the lens already holds leads).
|
|
6859
|
+
|
|
6860
|
+
**Extendability is checked before the write.** Every response carries \`available_count\` \u2014 how many leads a refill could still draw, read from the lens's own pool. \`0\` means the call was refused (\`no_candidates\`); \`null\` means the pool could not be read and the refill was queued anyway. An empty lens is NOT evidence of a broken refill: it is usually a lens that never had candidates. Reach for \`leadbay_adjust_audience\`, not another \`leadbay_extend_lens\`.
|
|
6856
6861
|
|
|
6857
6862
|
WHEN TO USE: when the user has a bigger appetite than the daily lens fill delivers \u2014 they want MORE of the same kind of leads, on demand. Canonical phrasings: "I want more leads on this lens", "extend the lens", "give me a bigger batch today". The \`leadbay_extend_my_lens\` prompt is the user-facing entry point that orchestrates the whole flow.
|
|
6858
6863
|
|
|
@@ -6893,6 +6898,8 @@ Pick the row matching the response \`status\`. Seed-picking is internal; do NOT
|
|
|
6893
6898
|
| \`quota_exceeded\` | "Upgrade plan for a higher daily limit" | (no call \u2014 direct user to contact account manager / sales) |
|
|
6894
6899
|
| \`refresh_in_progress\` | "Lens is already filling \u2014 pull leads in a minute" | \`leadbay_pull_leads()\` (after a short wait) |
|
|
6895
6900
|
| \`no_valid_seeds\` | (silent retry \u2014 re-call \`leadbay_seed_candidates\` then \`leadbay_extend_lens\`) | internal \u2014 only surface if the second attempt also fails |
|
|
6901
|
+
| \`no_candidates\` | "Widen the audience \u2014 this lens has nothing left to add" | \`leadbay_adjust_audience()\` \u2014 never \`leadbay_extend_lens\` again |
|
|
6902
|
+
| \`no_candidates\` (\`reason.code: no_new_leads\`) | "Work the leads already in the lens" | \`leadbay_pull_followups()\` |
|
|
6896
6903
|
|
|
6897
6904
|
If nothing matches cleanly, default to "pull leads now to see what's queued" \u2014 never invent a tool that doesn't exist.
|
|
6898
6905
|
`;
|
|
@@ -8596,6 +8603,11 @@ WHEN NOT TO USE: when the user has named a specific lens \u2014 pass \`lensId\`
|
|
|
8596
8603
|
|
|
8597
8604
|
The active lens can change between calls (5-min cache + backend \`last_requested_lens\`). If a multi-step workflow depends on staying on one lens, **capture \`response.lens.id\` from the first response and pass it as the \`lensId\` argument on every subsequent Leadbay call** \u2014 including re-pulls, bulk qualifies, and research. (Field-name caveat: response nests it as \`lens.id\`; the parameter is \`lensId\`.) Re-pulling without \`lensId\` after a long-running tool may silently switch to a different lens and discard prior work.
|
|
8598
8605
|
|
|
8606
|
+
**EMPTY BATCH \u2014 route on \`empty_reason\`, never loop.** When \`leads\` is empty the response carries \`empty_reason: {code, message, retryable, criteria?, narrow_locations?}\`. \`retryable\` is the only field that decides what you do next:
|
|
8607
|
+
|
|
8608
|
+
- \`retryable: true\` (always \`code: "computing"\`) \u2014 the lens is still building. Say so, pull ONCE more in ~30s. Do not call it empty.
|
|
8609
|
+
- \`retryable: false\` \u2014 no amount of re-pulling, lens-switching or \`leadbay_extend_lens\` can produce leads on these criteria. **Stop calling tools.** Surface \`message\` to the user, name the criteria from \`criteria\` (and \`narrow_locations\` first when present \u2014 a city-scale geo scope is the usual culprit), and offer \`leadbay_adjust_audience\` to widen. A refill on a zero-candidate lens answers "queued", consumes no quota and delivers nothing, so retrying reads as progress while achieving none (product#3995).
|
|
8610
|
+
|
|
8599
8611
|
---
|
|
8600
8612
|
|
|
8601
8613
|
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
@@ -16481,6 +16493,111 @@ var init_prepare_outreach = __esm({
|
|
|
16481
16493
|
}
|
|
16482
16494
|
});
|
|
16483
16495
|
|
|
16496
|
+
// ../core/dist/composite/_empty-lens-reason.js
|
|
16497
|
+
function criteriaOf(filter) {
|
|
16498
|
+
return filter?.lens_filter?.items?.flatMap((i) => i.criteria ?? []) ?? [];
|
|
16499
|
+
}
|
|
16500
|
+
function summariseCriteria(criteria) {
|
|
16501
|
+
const out = {};
|
|
16502
|
+
for (const c of criteria) {
|
|
16503
|
+
if (c.type === "sector_ids") {
|
|
16504
|
+
const key = c.is_excluded ? "excluded_sector_ids" : "sector_ids";
|
|
16505
|
+
out[key] = [...out[key] ?? [], ...c.sectors ?? []];
|
|
16506
|
+
} else if (c.type === "location_ids") {
|
|
16507
|
+
const key = c.is_excluded ? "excluded_location_ids" : "location_ids";
|
|
16508
|
+
out[key] = [...out[key] ?? [], ...c.locations ?? []];
|
|
16509
|
+
} else if (c.type === "size" && !c.is_excluded) {
|
|
16510
|
+
out.sizes = [...out.sizes ?? [], ...c.sizes ?? []];
|
|
16511
|
+
}
|
|
16512
|
+
}
|
|
16513
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
16514
|
+
}
|
|
16515
|
+
function narrowLocationsOf(filter, criteria) {
|
|
16516
|
+
const included = new Set(criteria.filter((c) => c.type === "location_ids" && !c.is_excluded).flatMap((c) => c.locations ?? []));
|
|
16517
|
+
if (included.size === 0)
|
|
16518
|
+
return [];
|
|
16519
|
+
const results = filter?.locations?.results ?? [];
|
|
16520
|
+
return results.filter((r) => typeof r.id === "string" && included.has(r.id) && typeof r.level === "number" && r.level >= CITY_LEVEL).map((r) => ({
|
|
16521
|
+
id: r.id,
|
|
16522
|
+
name: typeof r.name === "string" ? r.name : "",
|
|
16523
|
+
level: r.level
|
|
16524
|
+
}));
|
|
16525
|
+
}
|
|
16526
|
+
function narrowGeoSentence(narrow) {
|
|
16527
|
+
const names = narrow.map((n) => n.name).filter(Boolean);
|
|
16528
|
+
if (names.length === 0)
|
|
16529
|
+
return "";
|
|
16530
|
+
return ` Its geography is pinned to ${names.join(", ")} \u2014 a city-scale area or smaller, which on an empty lens is almost always the criterion to relax first.`;
|
|
16531
|
+
}
|
|
16532
|
+
async function readAudienceShape(client, lensId) {
|
|
16533
|
+
let filter = null;
|
|
16534
|
+
try {
|
|
16535
|
+
filter = await client.request("GET", `/lenses/${lensId}/filter`);
|
|
16536
|
+
} catch {
|
|
16537
|
+
}
|
|
16538
|
+
const criteria = criteriaOf(filter);
|
|
16539
|
+
const summary = summariseCriteria(criteria);
|
|
16540
|
+
const narrow = narrowLocationsOf(filter, criteria);
|
|
16541
|
+
return {
|
|
16542
|
+
...summary ? { criteria: summary } : {},
|
|
16543
|
+
...narrow.length > 0 ? { narrow_locations: narrow } : {},
|
|
16544
|
+
geoSentence: narrowGeoSentence(narrow)
|
|
16545
|
+
};
|
|
16546
|
+
}
|
|
16547
|
+
async function diagnoseEmptyLens(client, lensId, computing) {
|
|
16548
|
+
if (computing.wishlist || computing.scores) {
|
|
16549
|
+
return {
|
|
16550
|
+
code: "computing",
|
|
16551
|
+
retryable: true,
|
|
16552
|
+
message: "This lens is still computing its leads. Pull again in ~30s \u2014 do NOT report it as empty yet."
|
|
16553
|
+
};
|
|
16554
|
+
}
|
|
16555
|
+
let row;
|
|
16556
|
+
try {
|
|
16557
|
+
const lenses = await client.request("GET", "/lenses");
|
|
16558
|
+
row = lenses.find((l) => String(l.id) === String(lensId));
|
|
16559
|
+
} catch {
|
|
16560
|
+
}
|
|
16561
|
+
const { geoSentence: geo, ...extras } = await readAudienceShape(client, lensId);
|
|
16562
|
+
const summary = extras.criteria;
|
|
16563
|
+
if (row?.not_enough_lead_candidates) {
|
|
16564
|
+
return {
|
|
16565
|
+
code: "no_candidates",
|
|
16566
|
+
retryable: false,
|
|
16567
|
+
message: "This lens's criteria match no companies in the database, so it cannot fill." + geo + " Tell the user and offer to widen the audience (leadbay_adjust_audience) \u2014 extending or re-pulling will not help.",
|
|
16568
|
+
...extras
|
|
16569
|
+
};
|
|
16570
|
+
}
|
|
16571
|
+
if (row?.not_enough_new_leads) {
|
|
16572
|
+
return {
|
|
16573
|
+
code: "no_new_leads",
|
|
16574
|
+
retryable: false,
|
|
16575
|
+
message: "Every company matching this lens has already been delivered \u2014 there are no NEW leads left on these criteria. Tell the user; offer to widen the audience (leadbay_adjust_audience) or work the existing leads via leadbay_pull_followups.",
|
|
16576
|
+
...extras
|
|
16577
|
+
};
|
|
16578
|
+
}
|
|
16579
|
+
if (summary) {
|
|
16580
|
+
return {
|
|
16581
|
+
code: "audience_too_narrow",
|
|
16582
|
+
retryable: false,
|
|
16583
|
+
message: "This lens is finished computing and holds zero leads: its criteria intersect to nothing." + geo + " Tell the user which criteria are in play and offer to widen the audience (leadbay_adjust_audience). Do NOT call leadbay_extend_lens \u2014 a refill on a zero-candidate lens reports queued, consumes no quota, and delivers nothing.",
|
|
16584
|
+
...extras
|
|
16585
|
+
};
|
|
16586
|
+
}
|
|
16587
|
+
return {
|
|
16588
|
+
code: "unknown",
|
|
16589
|
+
retryable: false,
|
|
16590
|
+
message: "This lens is finished computing and holds zero leads, and carries no audience criteria that would explain it. Report this to the user rather than retrying; leadbay_report_friction is the way to flag it to the Leadbay team."
|
|
16591
|
+
};
|
|
16592
|
+
}
|
|
16593
|
+
var CITY_LEVEL;
|
|
16594
|
+
var init_empty_lens_reason = __esm({
|
|
16595
|
+
"../core/dist/composite/_empty-lens-reason.js"() {
|
|
16596
|
+
"use strict";
|
|
16597
|
+
CITY_LEVEL = 7;
|
|
16598
|
+
}
|
|
16599
|
+
});
|
|
16600
|
+
|
|
16484
16601
|
// ../core/dist/composite/pull-leads.js
|
|
16485
16602
|
function normalizeLinkedinPage3(v) {
|
|
16486
16603
|
if (v == null)
|
|
@@ -16561,6 +16678,7 @@ var init_pull_leads = __esm({
|
|
|
16561
16678
|
"../core/dist/composite/pull-leads.js"() {
|
|
16562
16679
|
"use strict";
|
|
16563
16680
|
init_agent_memory();
|
|
16681
|
+
init_empty_lens_reason();
|
|
16564
16682
|
init_tool_descriptions_generated();
|
|
16565
16683
|
pullLeads = {
|
|
16566
16684
|
name: "leadbay_pull_leads",
|
|
@@ -16626,6 +16744,34 @@ var init_pull_leads = __esm({
|
|
|
16626
16744
|
type: "boolean",
|
|
16627
16745
|
description: "True if scoring is still running."
|
|
16628
16746
|
},
|
|
16747
|
+
empty_reason: {
|
|
16748
|
+
type: ["object", "null"],
|
|
16749
|
+
description: "Why this LENS holds zero leads. null whenever leads were returned, and null when this page is empty only because it is past the end of a non-empty lens. `retryable` is the field to route on: true ONLY on code=computing (pull again in ~30s). On every other code re-pulling and leadbay_extend_lens are both futile \u2014 a refill on a zero-candidate lens answers 'queued', consumes no quota and delivers nothing \u2014 so surface `message` to the user and offer leadbay_adjust_audience instead of retrying.",
|
|
16750
|
+
properties: {
|
|
16751
|
+
code: {
|
|
16752
|
+
type: "string",
|
|
16753
|
+
description: "computing | no_candidates | no_new_leads | audience_too_narrow | unknown"
|
|
16754
|
+
},
|
|
16755
|
+
message: {
|
|
16756
|
+
type: "string",
|
|
16757
|
+
description: "The line to surface to the user."
|
|
16758
|
+
},
|
|
16759
|
+
retryable: {
|
|
16760
|
+
type: "boolean",
|
|
16761
|
+
description: "True only while the lens is still computing. False means no amount of re-pulling or extending can produce leads."
|
|
16762
|
+
},
|
|
16763
|
+
criteria: {
|
|
16764
|
+
type: "object",
|
|
16765
|
+
description: "The lens criteria in play \u2014 what the user would have to relax. Present when the lens carries any."
|
|
16766
|
+
},
|
|
16767
|
+
narrow_locations: {
|
|
16768
|
+
type: "array",
|
|
16769
|
+
description: "Include-locations that resolved to a city-scale area or smaller ({id, name, level}). On an empty lens, name these first: this is the fingerprint of a whole-country location that fell through to a same-named village (product#3951).",
|
|
16770
|
+
items: { type: "object" }
|
|
16771
|
+
}
|
|
16772
|
+
},
|
|
16773
|
+
required: ["code", "message", "retryable"]
|
|
16774
|
+
},
|
|
16629
16775
|
next_steps: {
|
|
16630
16776
|
type: ["object", "null"],
|
|
16631
16777
|
description: "Ready-made NEXT STEPS for the host's choice widget. Each option has a SHORT `label` (\u22645 words, fits AskUserQuestion's label cap on Claude cowork/Claude Code) and a full `description`. For AskUserQuestion (cowork/Claude Code) pass each option as {label, description}. For ask_user_input_v0 (Claude chat/ChatGPT, string-only options) use the `description` as the option string. Use these VERBATIM, in order \u2014 do NOT re-derive, reword, or render as prose when a widget tool exists. options[0] is the artifact offer (build the lead triage board) whenever the batch is non-empty; options[1] is the enrich offer (kind:enrich_top_leads \u2014 route it to leadbay_enrich_titles scoped to the leadIds JUST shown (pass leads[].id + the pinned lens.id) with NO titles, so it runs the no-spend discovery preview; quota is only spent after the user picks titles + confirms channels on a follow-up call). When the batch is empty but the lens is still computing (computing_wishlist/computing_scores true), this carries a 'Re-pull in ~30s' option (kind:repull_computing) plus 'Refine audience' \u2014 render the widget so the user waits rather than seeing 'no leads.' null only when the batch is empty AND nothing is computing (a genuinely empty / over-narrow lens).",
|
|
@@ -16726,6 +16872,11 @@ var init_pull_leads = __esm({
|
|
|
16726
16872
|
computingWishlist: res.computing_wishlist,
|
|
16727
16873
|
computingScores: res.computing_scores
|
|
16728
16874
|
});
|
|
16875
|
+
const lensIsEmpty = leadCount === 0 && (res.pagination?.total ?? 0) === 0;
|
|
16876
|
+
const emptyReason = lensIsEmpty ? await diagnoseEmptyLens(client, lensId, {
|
|
16877
|
+
wishlist: res.computing_wishlist,
|
|
16878
|
+
scores: res.computing_scores
|
|
16879
|
+
}) : null;
|
|
16729
16880
|
return withAgentMemoryMeta(client, {
|
|
16730
16881
|
lens: { id: lensId },
|
|
16731
16882
|
leads: res.items.map((lead) => ({
|
|
@@ -16737,6 +16888,7 @@ var init_pull_leads = __esm({
|
|
|
16737
16888
|
next_page: nextPage,
|
|
16738
16889
|
computing_wishlist: res.computing_wishlist,
|
|
16739
16890
|
computing_scores: res.computing_scores,
|
|
16891
|
+
empty_reason: emptyReason,
|
|
16740
16892
|
next_steps: nextSteps,
|
|
16741
16893
|
_meta: {
|
|
16742
16894
|
region: client.region,
|
|
@@ -24036,11 +24188,54 @@ async function readExtraRefillQuota(client) {
|
|
|
24036
24188
|
return { count: null, resets_at: null };
|
|
24037
24189
|
}
|
|
24038
24190
|
}
|
|
24191
|
+
async function readAvailablePool(client, lensId) {
|
|
24192
|
+
try {
|
|
24193
|
+
const preview = await client.request("GET", `/lenses/${lensId}/extra_refill_preview`);
|
|
24194
|
+
return typeof preview?.available_count === "number" ? preview.available_count : null;
|
|
24195
|
+
} catch {
|
|
24196
|
+
return null;
|
|
24197
|
+
}
|
|
24198
|
+
}
|
|
24199
|
+
async function readLensLeadTotal(client, lensId) {
|
|
24200
|
+
try {
|
|
24201
|
+
const page = await client.request("GET", `/lenses/${lensId}/leads/wishlist?count=1&page=0`);
|
|
24202
|
+
return typeof page?.pagination?.total === "number" ? page.pagination.total : null;
|
|
24203
|
+
} catch {
|
|
24204
|
+
return null;
|
|
24205
|
+
}
|
|
24206
|
+
}
|
|
24207
|
+
function noCandidatesReason(held, shape) {
|
|
24208
|
+
const { geoSentence, ...extras } = shape;
|
|
24209
|
+
const futile = " Extending again is futile \u2014 a refill on a lens with an empty candidate pool reports queued, consumes no quota and delivers nothing.";
|
|
24210
|
+
if (held === 0) {
|
|
24211
|
+
return {
|
|
24212
|
+
code: "audience_too_narrow",
|
|
24213
|
+
retryable: false,
|
|
24214
|
+
message: "This lens holds no leads and has none left to add: its criteria intersect to nothing." + geoSentence + futile + " Tell the user which criteria are in play and offer to widen the audience (leadbay_adjust_audience).",
|
|
24215
|
+
...extras
|
|
24216
|
+
};
|
|
24217
|
+
}
|
|
24218
|
+
if (held !== null) {
|
|
24219
|
+
return {
|
|
24220
|
+
code: "no_new_leads",
|
|
24221
|
+
retryable: false,
|
|
24222
|
+
message: `Every company matching this lens has already been delivered \u2014 all ${held} of them \u2014 so there is nothing left to add.` + futile + " Tell the user; offer to widen the audience (leadbay_adjust_audience) or work the leads already in the lens (leadbay_pull_followups).",
|
|
24223
|
+
...extras
|
|
24224
|
+
};
|
|
24225
|
+
}
|
|
24226
|
+
return {
|
|
24227
|
+
code: "no_candidates",
|
|
24228
|
+
retryable: false,
|
|
24229
|
+
message: "This lens has no candidates left to add." + geoSentence + futile + " Tell the user and offer to widen the audience (leadbay_adjust_audience).",
|
|
24230
|
+
...extras
|
|
24231
|
+
};
|
|
24232
|
+
}
|
|
24039
24233
|
var extendLens;
|
|
24040
24234
|
var init_extend_lens = __esm({
|
|
24041
24235
|
"../core/dist/composite/extend-lens.js"() {
|
|
24042
24236
|
"use strict";
|
|
24043
24237
|
init_tool_descriptions_generated();
|
|
24238
|
+
init_empty_lens_reason();
|
|
24044
24239
|
extendLens = {
|
|
24045
24240
|
name: "leadbay_extend_lens",
|
|
24046
24241
|
annotations: {
|
|
@@ -24077,7 +24272,7 @@ var init_extend_lens = __esm({
|
|
|
24077
24272
|
properties: {
|
|
24078
24273
|
status: {
|
|
24079
24274
|
type: "string",
|
|
24080
|
-
description: "queued | quota_exceeded | refresh_in_progress | no_valid_seeds"
|
|
24275
|
+
description: "queued | no_candidates | quota_exceeded | refresh_in_progress | no_valid_seeds"
|
|
24081
24276
|
},
|
|
24082
24277
|
lens: {
|
|
24083
24278
|
type: "object",
|
|
@@ -24092,6 +24287,35 @@ var init_extend_lens = __esm({
|
|
|
24092
24287
|
type: "string",
|
|
24093
24288
|
description: "Human-readable summary. On error statuses, this is the line to surface to the user."
|
|
24094
24289
|
},
|
|
24290
|
+
available_count: {
|
|
24291
|
+
type: ["number", "null"],
|
|
24292
|
+
description: "How many leads a refill on this lens could still draw, read from /extra_refill_preview before queueing. 0 means the refill was NOT queued (status=no_candidates). null means the pool could not be read and the refill was queued anyway."
|
|
24293
|
+
},
|
|
24294
|
+
reason: {
|
|
24295
|
+
type: "object",
|
|
24296
|
+
description: "Only present on status=no_candidates. Same shape and `code` vocabulary as leadbay_pull_leads' empty_reason, so one routing rule covers both tools.",
|
|
24297
|
+
properties: {
|
|
24298
|
+
code: {
|
|
24299
|
+
type: "string",
|
|
24300
|
+
description: "audience_too_narrow (lens holds nothing and its criteria intersect to nothing) | no_new_leads (everything matching has already been delivered) | no_candidates (pool is empty; which of the two could not be determined)"
|
|
24301
|
+
},
|
|
24302
|
+
message: { type: "string" },
|
|
24303
|
+
retryable: {
|
|
24304
|
+
type: "boolean",
|
|
24305
|
+
description: "Always false here. Re-calling leadbay_extend_lens cannot change the outcome \u2014 widen the audience instead."
|
|
24306
|
+
},
|
|
24307
|
+
criteria: {
|
|
24308
|
+
type: "object",
|
|
24309
|
+
description: "The criteria in play, so the agent can name them."
|
|
24310
|
+
},
|
|
24311
|
+
narrow_locations: {
|
|
24312
|
+
type: "array",
|
|
24313
|
+
description: "Include-locations that resolved to city-scale or smaller \u2014 the usual thing to relax first.",
|
|
24314
|
+
items: { type: "object" }
|
|
24315
|
+
}
|
|
24316
|
+
},
|
|
24317
|
+
required: ["code", "message", "retryable"]
|
|
24318
|
+
},
|
|
24095
24319
|
quota: {
|
|
24096
24320
|
type: "object",
|
|
24097
24321
|
description: "Only present on status=quota_exceeded. Shows the org's daily LENS_EXTRA_REFILL state.",
|
|
@@ -24105,6 +24329,21 @@ var init_extend_lens = __esm({
|
|
|
24105
24329
|
},
|
|
24106
24330
|
execute: async (client, params) => {
|
|
24107
24331
|
const lensId = params.lensId ?? await client.resolveDefaultLens();
|
|
24332
|
+
const availableCount = await readAvailablePool(client, lensId);
|
|
24333
|
+
if (availableCount === 0) {
|
|
24334
|
+
const [shape, held] = await Promise.all([
|
|
24335
|
+
readAudienceShape(client, lensId),
|
|
24336
|
+
readLensLeadTotal(client, lensId)
|
|
24337
|
+
]);
|
|
24338
|
+
const reason = noCandidatesReason(held, shape);
|
|
24339
|
+
return {
|
|
24340
|
+
status: "no_candidates",
|
|
24341
|
+
lens: { id: lensId },
|
|
24342
|
+
available_count: 0,
|
|
24343
|
+
reason,
|
|
24344
|
+
message: reason.message
|
|
24345
|
+
};
|
|
24346
|
+
}
|
|
24108
24347
|
const body = {
|
|
24109
24348
|
seed_lead_ids: params.seed_lead_ids ?? []
|
|
24110
24349
|
};
|
|
@@ -24116,6 +24355,7 @@ var init_extend_lens = __esm({
|
|
|
24116
24355
|
return {
|
|
24117
24356
|
status: "queued",
|
|
24118
24357
|
lens: { id: lensId },
|
|
24358
|
+
available_count: availableCount,
|
|
24119
24359
|
accepted_seeds: res.accepted_seeds,
|
|
24120
24360
|
message: "Extra refill queued. Leads stream in asynchronously \u2014 call leadbay_pull_leads in ~30s to see them."
|
|
24121
24361
|
};
|
|
@@ -31730,7 +31970,7 @@ var OAUTH_BASE_URLS = {
|
|
|
31730
31970
|
fr: "https://staging.api.leadbay.app"
|
|
31731
31971
|
}
|
|
31732
31972
|
};
|
|
31733
|
-
var VERSION = "0.
|
|
31973
|
+
var VERSION = "0.31.1";
|
|
31734
31974
|
var HELP = `
|
|
31735
31975
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
31736
31976
|
|
package/dist/http-server.js
CHANGED
|
@@ -9160,8 +9160,10 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
|
9160
9160
|
|
|
9161
9161
|
\`queued\` \u2192 \u2705 "Queued <N> extra leads on lens <id>. Pull in ~30s." Do NOT
|
|
9162
9162
|
list \`accepted_seeds\`; they're internal.
|
|
9163
|
-
\`
|
|
9164
|
-
|
|
9163
|
+
\`no_candidates\` \u2192 \u26D4 surface \`reason.message\`, name the criteria, offer
|
|
9164
|
+
\`leadbay_adjust_audience\`. Never re-call this tool on that lens.
|
|
9165
|
+
\`quota_exceeded\` \u2192 three options via the host's choice widget
|
|
9166
|
+
(smaller count / wait for reset / upgrade).
|
|
9165
9167
|
\`refresh_in_progress\` \u2192 "lens is filling, retry in a minute".
|
|
9166
9168
|
\`no_valid_seeds\` \u2192 silently re-call \`leadbay_seed_candidates\`, retry once.
|
|
9167
9169
|
|
|
@@ -9181,6 +9183,9 @@ Queue an additive extra-refill on a lens \u2014 more leads on the same criteria,
|
|
|
9181
9183
|
- \`status: "quota_exceeded"\` \u2014 daily LENS_EXTRA_REFILL hit. Response carries \`quota: {used_today, resets_at}\` + a \`message\` to surface. **Render three options via your host's choice widget (\`ask_user_input_v0\` or \`AskUserQuestion\`)**: (1) smaller \`extra_count\`, (2) wait until \`resets_at\`, (3) upgrade plan (TIER1=150, TIER2=1000). Do NOT silently retry.
|
|
9182
9184
|
- \`status: "refresh_in_progress"\` \u2014 a refresh or extra-refill is already running. Tell the user to wait and call \`leadbay_pull_leads\` in ~30s.
|
|
9183
9185
|
- \`status: "no_valid_seeds"\` \u2014 seeds went stale. Silently re-call \`leadbay_seed_candidates\` and retry once; only surface to the user if the second attempt also fails.
|
|
9186
|
+
- \`status: "no_candidates"\` \u2014 **the refill was NOT queued.** The lens's candidate pool is empty, so a refill would report success, consume no quota and deliver nothing. \`reason\` carries the same \`{code, message, retryable, criteria?, narrow_locations?}\` shape \`leadbay_pull_leads\` returns in \`empty_reason\`, with \`retryable: false\`. **Stop. Do not re-call this tool on this lens** \u2014 the outcome cannot change until the audience changes. Surface \`reason.message\`, name the criteria in play, and offer \`leadbay_adjust_audience\` (or \`leadbay_pull_followups\` when \`reason.code\` is \`no_new_leads\` and the lens already holds leads).
|
|
9187
|
+
|
|
9188
|
+
**Extendability is checked before the write.** Every response carries \`available_count\` \u2014 how many leads a refill could still draw, read from the lens's own pool. \`0\` means the call was refused (\`no_candidates\`); \`null\` means the pool could not be read and the refill was queued anyway. An empty lens is NOT evidence of a broken refill: it is usually a lens that never had candidates. Reach for \`leadbay_adjust_audience\`, not another \`leadbay_extend_lens\`.
|
|
9184
9189
|
|
|
9185
9190
|
WHEN TO USE: when the user has a bigger appetite than the daily lens fill delivers \u2014 they want MORE of the same kind of leads, on demand. Canonical phrasings: "I want more leads on this lens", "extend the lens", "give me a bigger batch today". The \`leadbay_extend_my_lens\` prompt is the user-facing entry point that orchestrates the whole flow.
|
|
9186
9191
|
|
|
@@ -9221,6 +9226,8 @@ Pick the row matching the response \`status\`. Seed-picking is internal; do NOT
|
|
|
9221
9226
|
| \`quota_exceeded\` | "Upgrade plan for a higher daily limit" | (no call \u2014 direct user to contact account manager / sales) |
|
|
9222
9227
|
| \`refresh_in_progress\` | "Lens is already filling \u2014 pull leads in a minute" | \`leadbay_pull_leads()\` (after a short wait) |
|
|
9223
9228
|
| \`no_valid_seeds\` | (silent retry \u2014 re-call \`leadbay_seed_candidates\` then \`leadbay_extend_lens\`) | internal \u2014 only surface if the second attempt also fails |
|
|
9229
|
+
| \`no_candidates\` | "Widen the audience \u2014 this lens has nothing left to add" | \`leadbay_adjust_audience()\` \u2014 never \`leadbay_extend_lens\` again |
|
|
9230
|
+
| \`no_candidates\` (\`reason.code: no_new_leads\`) | "Work the leads already in the lens" | \`leadbay_pull_followups()\` |
|
|
9224
9231
|
|
|
9225
9232
|
If nothing matches cleanly, default to "pull leads now to see what's queued" \u2014 never invent a tool that doesn't exist.
|
|
9226
9233
|
`;
|
|
@@ -10924,6 +10931,11 @@ WHEN NOT TO USE: when the user has named a specific lens \u2014 pass \`lensId\`
|
|
|
10924
10931
|
|
|
10925
10932
|
The active lens can change between calls (5-min cache + backend \`last_requested_lens\`). If a multi-step workflow depends on staying on one lens, **capture \`response.lens.id\` from the first response and pass it as the \`lensId\` argument on every subsequent Leadbay call** \u2014 including re-pulls, bulk qualifies, and research. (Field-name caveat: response nests it as \`lens.id\`; the parameter is \`lensId\`.) Re-pulling without \`lensId\` after a long-running tool may silently switch to a different lens and discard prior work.
|
|
10926
10933
|
|
|
10934
|
+
**EMPTY BATCH \u2014 route on \`empty_reason\`, never loop.** When \`leads\` is empty the response carries \`empty_reason: {code, message, retryable, criteria?, narrow_locations?}\`. \`retryable\` is the only field that decides what you do next:
|
|
10935
|
+
|
|
10936
|
+
- \`retryable: true\` (always \`code: "computing"\`) \u2014 the lens is still building. Say so, pull ONCE more in ~30s. Do not call it empty.
|
|
10937
|
+
- \`retryable: false\` \u2014 no amount of re-pulling, lens-switching or \`leadbay_extend_lens\` can produce leads on these criteria. **Stop calling tools.** Surface \`message\` to the user, name the criteria from \`criteria\` (and \`narrow_locations\` first when present \u2014 a city-scale geo scope is the usual culprit), and offer \`leadbay_adjust_audience\` to widen. A refill on a zero-candidate lens answers "queued", consumes no quota and delivers nothing, so retrying reads as progress while achieving none (product#3995).
|
|
10938
|
+
|
|
10927
10939
|
---
|
|
10928
10940
|
|
|
10929
10941
|
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
@@ -18328,6 +18340,105 @@ var prepareOutreach = {
|
|
|
18328
18340
|
}
|
|
18329
18341
|
};
|
|
18330
18342
|
|
|
18343
|
+
// ../core/dist/composite/_empty-lens-reason.js
|
|
18344
|
+
var CITY_LEVEL = 7;
|
|
18345
|
+
function criteriaOf(filter) {
|
|
18346
|
+
return filter?.lens_filter?.items?.flatMap((i) => i.criteria ?? []) ?? [];
|
|
18347
|
+
}
|
|
18348
|
+
function summariseCriteria(criteria) {
|
|
18349
|
+
const out = {};
|
|
18350
|
+
for (const c of criteria) {
|
|
18351
|
+
if (c.type === "sector_ids") {
|
|
18352
|
+
const key = c.is_excluded ? "excluded_sector_ids" : "sector_ids";
|
|
18353
|
+
out[key] = [...out[key] ?? [], ...c.sectors ?? []];
|
|
18354
|
+
} else if (c.type === "location_ids") {
|
|
18355
|
+
const key = c.is_excluded ? "excluded_location_ids" : "location_ids";
|
|
18356
|
+
out[key] = [...out[key] ?? [], ...c.locations ?? []];
|
|
18357
|
+
} else if (c.type === "size" && !c.is_excluded) {
|
|
18358
|
+
out.sizes = [...out.sizes ?? [], ...c.sizes ?? []];
|
|
18359
|
+
}
|
|
18360
|
+
}
|
|
18361
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
18362
|
+
}
|
|
18363
|
+
function narrowLocationsOf(filter, criteria) {
|
|
18364
|
+
const included = new Set(criteria.filter((c) => c.type === "location_ids" && !c.is_excluded).flatMap((c) => c.locations ?? []));
|
|
18365
|
+
if (included.size === 0)
|
|
18366
|
+
return [];
|
|
18367
|
+
const results = filter?.locations?.results ?? [];
|
|
18368
|
+
return results.filter((r) => typeof r.id === "string" && included.has(r.id) && typeof r.level === "number" && r.level >= CITY_LEVEL).map((r) => ({
|
|
18369
|
+
id: r.id,
|
|
18370
|
+
name: typeof r.name === "string" ? r.name : "",
|
|
18371
|
+
level: r.level
|
|
18372
|
+
}));
|
|
18373
|
+
}
|
|
18374
|
+
function narrowGeoSentence(narrow) {
|
|
18375
|
+
const names = narrow.map((n) => n.name).filter(Boolean);
|
|
18376
|
+
if (names.length === 0)
|
|
18377
|
+
return "";
|
|
18378
|
+
return ` Its geography is pinned to ${names.join(", ")} \u2014 a city-scale area or smaller, which on an empty lens is almost always the criterion to relax first.`;
|
|
18379
|
+
}
|
|
18380
|
+
async function readAudienceShape(client, lensId) {
|
|
18381
|
+
let filter = null;
|
|
18382
|
+
try {
|
|
18383
|
+
filter = await client.request("GET", `/lenses/${lensId}/filter`);
|
|
18384
|
+
} catch {
|
|
18385
|
+
}
|
|
18386
|
+
const criteria = criteriaOf(filter);
|
|
18387
|
+
const summary = summariseCriteria(criteria);
|
|
18388
|
+
const narrow = narrowLocationsOf(filter, criteria);
|
|
18389
|
+
return {
|
|
18390
|
+
...summary ? { criteria: summary } : {},
|
|
18391
|
+
...narrow.length > 0 ? { narrow_locations: narrow } : {},
|
|
18392
|
+
geoSentence: narrowGeoSentence(narrow)
|
|
18393
|
+
};
|
|
18394
|
+
}
|
|
18395
|
+
async function diagnoseEmptyLens(client, lensId, computing) {
|
|
18396
|
+
if (computing.wishlist || computing.scores) {
|
|
18397
|
+
return {
|
|
18398
|
+
code: "computing",
|
|
18399
|
+
retryable: true,
|
|
18400
|
+
message: "This lens is still computing its leads. Pull again in ~30s \u2014 do NOT report it as empty yet."
|
|
18401
|
+
};
|
|
18402
|
+
}
|
|
18403
|
+
let row;
|
|
18404
|
+
try {
|
|
18405
|
+
const lenses = await client.request("GET", "/lenses");
|
|
18406
|
+
row = lenses.find((l) => String(l.id) === String(lensId));
|
|
18407
|
+
} catch {
|
|
18408
|
+
}
|
|
18409
|
+
const { geoSentence: geo, ...extras } = await readAudienceShape(client, lensId);
|
|
18410
|
+
const summary = extras.criteria;
|
|
18411
|
+
if (row?.not_enough_lead_candidates) {
|
|
18412
|
+
return {
|
|
18413
|
+
code: "no_candidates",
|
|
18414
|
+
retryable: false,
|
|
18415
|
+
message: "This lens's criteria match no companies in the database, so it cannot fill." + geo + " Tell the user and offer to widen the audience (leadbay_adjust_audience) \u2014 extending or re-pulling will not help.",
|
|
18416
|
+
...extras
|
|
18417
|
+
};
|
|
18418
|
+
}
|
|
18419
|
+
if (row?.not_enough_new_leads) {
|
|
18420
|
+
return {
|
|
18421
|
+
code: "no_new_leads",
|
|
18422
|
+
retryable: false,
|
|
18423
|
+
message: "Every company matching this lens has already been delivered \u2014 there are no NEW leads left on these criteria. Tell the user; offer to widen the audience (leadbay_adjust_audience) or work the existing leads via leadbay_pull_followups.",
|
|
18424
|
+
...extras
|
|
18425
|
+
};
|
|
18426
|
+
}
|
|
18427
|
+
if (summary) {
|
|
18428
|
+
return {
|
|
18429
|
+
code: "audience_too_narrow",
|
|
18430
|
+
retryable: false,
|
|
18431
|
+
message: "This lens is finished computing and holds zero leads: its criteria intersect to nothing." + geo + " Tell the user which criteria are in play and offer to widen the audience (leadbay_adjust_audience). Do NOT call leadbay_extend_lens \u2014 a refill on a zero-candidate lens reports queued, consumes no quota, and delivers nothing.",
|
|
18432
|
+
...extras
|
|
18433
|
+
};
|
|
18434
|
+
}
|
|
18435
|
+
return {
|
|
18436
|
+
code: "unknown",
|
|
18437
|
+
retryable: false,
|
|
18438
|
+
message: "This lens is finished computing and holds zero leads, and carries no audience criteria that would explain it. Report this to the user rather than retrying; leadbay_report_friction is the way to flag it to the Leadbay team."
|
|
18439
|
+
};
|
|
18440
|
+
}
|
|
18441
|
+
|
|
18331
18442
|
// ../core/dist/composite/pull-leads.js
|
|
18332
18443
|
function normalizeLinkedinPage3(v) {
|
|
18333
18444
|
if (v == null)
|
|
@@ -18467,6 +18578,34 @@ var pullLeads = {
|
|
|
18467
18578
|
type: "boolean",
|
|
18468
18579
|
description: "True if scoring is still running."
|
|
18469
18580
|
},
|
|
18581
|
+
empty_reason: {
|
|
18582
|
+
type: ["object", "null"],
|
|
18583
|
+
description: "Why this LENS holds zero leads. null whenever leads were returned, and null when this page is empty only because it is past the end of a non-empty lens. `retryable` is the field to route on: true ONLY on code=computing (pull again in ~30s). On every other code re-pulling and leadbay_extend_lens are both futile \u2014 a refill on a zero-candidate lens answers 'queued', consumes no quota and delivers nothing \u2014 so surface `message` to the user and offer leadbay_adjust_audience instead of retrying.",
|
|
18584
|
+
properties: {
|
|
18585
|
+
code: {
|
|
18586
|
+
type: "string",
|
|
18587
|
+
description: "computing | no_candidates | no_new_leads | audience_too_narrow | unknown"
|
|
18588
|
+
},
|
|
18589
|
+
message: {
|
|
18590
|
+
type: "string",
|
|
18591
|
+
description: "The line to surface to the user."
|
|
18592
|
+
},
|
|
18593
|
+
retryable: {
|
|
18594
|
+
type: "boolean",
|
|
18595
|
+
description: "True only while the lens is still computing. False means no amount of re-pulling or extending can produce leads."
|
|
18596
|
+
},
|
|
18597
|
+
criteria: {
|
|
18598
|
+
type: "object",
|
|
18599
|
+
description: "The lens criteria in play \u2014 what the user would have to relax. Present when the lens carries any."
|
|
18600
|
+
},
|
|
18601
|
+
narrow_locations: {
|
|
18602
|
+
type: "array",
|
|
18603
|
+
description: "Include-locations that resolved to a city-scale area or smaller ({id, name, level}). On an empty lens, name these first: this is the fingerprint of a whole-country location that fell through to a same-named village (product#3951).",
|
|
18604
|
+
items: { type: "object" }
|
|
18605
|
+
}
|
|
18606
|
+
},
|
|
18607
|
+
required: ["code", "message", "retryable"]
|
|
18608
|
+
},
|
|
18470
18609
|
next_steps: {
|
|
18471
18610
|
type: ["object", "null"],
|
|
18472
18611
|
description: "Ready-made NEXT STEPS for the host's choice widget. Each option has a SHORT `label` (\u22645 words, fits AskUserQuestion's label cap on Claude cowork/Claude Code) and a full `description`. For AskUserQuestion (cowork/Claude Code) pass each option as {label, description}. For ask_user_input_v0 (Claude chat/ChatGPT, string-only options) use the `description` as the option string. Use these VERBATIM, in order \u2014 do NOT re-derive, reword, or render as prose when a widget tool exists. options[0] is the artifact offer (build the lead triage board) whenever the batch is non-empty; options[1] is the enrich offer (kind:enrich_top_leads \u2014 route it to leadbay_enrich_titles scoped to the leadIds JUST shown (pass leads[].id + the pinned lens.id) with NO titles, so it runs the no-spend discovery preview; quota is only spent after the user picks titles + confirms channels on a follow-up call). When the batch is empty but the lens is still computing (computing_wishlist/computing_scores true), this carries a 'Re-pull in ~30s' option (kind:repull_computing) plus 'Refine audience' \u2014 render the widget so the user waits rather than seeing 'no leads.' null only when the batch is empty AND nothing is computing (a genuinely empty / over-narrow lens).",
|
|
@@ -18567,6 +18706,11 @@ var pullLeads = {
|
|
|
18567
18706
|
computingWishlist: res.computing_wishlist,
|
|
18568
18707
|
computingScores: res.computing_scores
|
|
18569
18708
|
});
|
|
18709
|
+
const lensIsEmpty = leadCount === 0 && (res.pagination?.total ?? 0) === 0;
|
|
18710
|
+
const emptyReason = lensIsEmpty ? await diagnoseEmptyLens(client, lensId, {
|
|
18711
|
+
wishlist: res.computing_wishlist,
|
|
18712
|
+
scores: res.computing_scores
|
|
18713
|
+
}) : null;
|
|
18570
18714
|
return withAgentMemoryMeta(client, {
|
|
18571
18715
|
lens: { id: lensId },
|
|
18572
18716
|
leads: res.items.map((lead) => ({
|
|
@@ -18578,6 +18722,7 @@ var pullLeads = {
|
|
|
18578
18722
|
next_page: nextPage,
|
|
18579
18723
|
computing_wishlist: res.computing_wishlist,
|
|
18580
18724
|
computing_scores: res.computing_scores,
|
|
18725
|
+
empty_reason: emptyReason,
|
|
18581
18726
|
next_steps: nextSteps,
|
|
18582
18727
|
_meta: {
|
|
18583
18728
|
region: client.region,
|
|
@@ -25006,6 +25151,48 @@ async function readExtraRefillQuota(client) {
|
|
|
25006
25151
|
return { count: null, resets_at: null };
|
|
25007
25152
|
}
|
|
25008
25153
|
}
|
|
25154
|
+
async function readAvailablePool(client, lensId) {
|
|
25155
|
+
try {
|
|
25156
|
+
const preview = await client.request("GET", `/lenses/${lensId}/extra_refill_preview`);
|
|
25157
|
+
return typeof preview?.available_count === "number" ? preview.available_count : null;
|
|
25158
|
+
} catch {
|
|
25159
|
+
return null;
|
|
25160
|
+
}
|
|
25161
|
+
}
|
|
25162
|
+
async function readLensLeadTotal(client, lensId) {
|
|
25163
|
+
try {
|
|
25164
|
+
const page = await client.request("GET", `/lenses/${lensId}/leads/wishlist?count=1&page=0`);
|
|
25165
|
+
return typeof page?.pagination?.total === "number" ? page.pagination.total : null;
|
|
25166
|
+
} catch {
|
|
25167
|
+
return null;
|
|
25168
|
+
}
|
|
25169
|
+
}
|
|
25170
|
+
function noCandidatesReason(held, shape) {
|
|
25171
|
+
const { geoSentence, ...extras } = shape;
|
|
25172
|
+
const futile = " Extending again is futile \u2014 a refill on a lens with an empty candidate pool reports queued, consumes no quota and delivers nothing.";
|
|
25173
|
+
if (held === 0) {
|
|
25174
|
+
return {
|
|
25175
|
+
code: "audience_too_narrow",
|
|
25176
|
+
retryable: false,
|
|
25177
|
+
message: "This lens holds no leads and has none left to add: its criteria intersect to nothing." + geoSentence + futile + " Tell the user which criteria are in play and offer to widen the audience (leadbay_adjust_audience).",
|
|
25178
|
+
...extras
|
|
25179
|
+
};
|
|
25180
|
+
}
|
|
25181
|
+
if (held !== null) {
|
|
25182
|
+
return {
|
|
25183
|
+
code: "no_new_leads",
|
|
25184
|
+
retryable: false,
|
|
25185
|
+
message: `Every company matching this lens has already been delivered \u2014 all ${held} of them \u2014 so there is nothing left to add.` + futile + " Tell the user; offer to widen the audience (leadbay_adjust_audience) or work the leads already in the lens (leadbay_pull_followups).",
|
|
25186
|
+
...extras
|
|
25187
|
+
};
|
|
25188
|
+
}
|
|
25189
|
+
return {
|
|
25190
|
+
code: "no_candidates",
|
|
25191
|
+
retryable: false,
|
|
25192
|
+
message: "This lens has no candidates left to add." + geoSentence + futile + " Tell the user and offer to widen the audience (leadbay_adjust_audience).",
|
|
25193
|
+
...extras
|
|
25194
|
+
};
|
|
25195
|
+
}
|
|
25009
25196
|
var extendLens = {
|
|
25010
25197
|
name: "leadbay_extend_lens",
|
|
25011
25198
|
annotations: {
|
|
@@ -25042,7 +25229,7 @@ var extendLens = {
|
|
|
25042
25229
|
properties: {
|
|
25043
25230
|
status: {
|
|
25044
25231
|
type: "string",
|
|
25045
|
-
description: "queued | quota_exceeded | refresh_in_progress | no_valid_seeds"
|
|
25232
|
+
description: "queued | no_candidates | quota_exceeded | refresh_in_progress | no_valid_seeds"
|
|
25046
25233
|
},
|
|
25047
25234
|
lens: {
|
|
25048
25235
|
type: "object",
|
|
@@ -25057,6 +25244,35 @@ var extendLens = {
|
|
|
25057
25244
|
type: "string",
|
|
25058
25245
|
description: "Human-readable summary. On error statuses, this is the line to surface to the user."
|
|
25059
25246
|
},
|
|
25247
|
+
available_count: {
|
|
25248
|
+
type: ["number", "null"],
|
|
25249
|
+
description: "How many leads a refill on this lens could still draw, read from /extra_refill_preview before queueing. 0 means the refill was NOT queued (status=no_candidates). null means the pool could not be read and the refill was queued anyway."
|
|
25250
|
+
},
|
|
25251
|
+
reason: {
|
|
25252
|
+
type: "object",
|
|
25253
|
+
description: "Only present on status=no_candidates. Same shape and `code` vocabulary as leadbay_pull_leads' empty_reason, so one routing rule covers both tools.",
|
|
25254
|
+
properties: {
|
|
25255
|
+
code: {
|
|
25256
|
+
type: "string",
|
|
25257
|
+
description: "audience_too_narrow (lens holds nothing and its criteria intersect to nothing) | no_new_leads (everything matching has already been delivered) | no_candidates (pool is empty; which of the two could not be determined)"
|
|
25258
|
+
},
|
|
25259
|
+
message: { type: "string" },
|
|
25260
|
+
retryable: {
|
|
25261
|
+
type: "boolean",
|
|
25262
|
+
description: "Always false here. Re-calling leadbay_extend_lens cannot change the outcome \u2014 widen the audience instead."
|
|
25263
|
+
},
|
|
25264
|
+
criteria: {
|
|
25265
|
+
type: "object",
|
|
25266
|
+
description: "The criteria in play, so the agent can name them."
|
|
25267
|
+
},
|
|
25268
|
+
narrow_locations: {
|
|
25269
|
+
type: "array",
|
|
25270
|
+
description: "Include-locations that resolved to city-scale or smaller \u2014 the usual thing to relax first.",
|
|
25271
|
+
items: { type: "object" }
|
|
25272
|
+
}
|
|
25273
|
+
},
|
|
25274
|
+
required: ["code", "message", "retryable"]
|
|
25275
|
+
},
|
|
25060
25276
|
quota: {
|
|
25061
25277
|
type: "object",
|
|
25062
25278
|
description: "Only present on status=quota_exceeded. Shows the org's daily LENS_EXTRA_REFILL state.",
|
|
@@ -25070,6 +25286,21 @@ var extendLens = {
|
|
|
25070
25286
|
},
|
|
25071
25287
|
execute: async (client, params) => {
|
|
25072
25288
|
const lensId = params.lensId ?? await client.resolveDefaultLens();
|
|
25289
|
+
const availableCount = await readAvailablePool(client, lensId);
|
|
25290
|
+
if (availableCount === 0) {
|
|
25291
|
+
const [shape, held] = await Promise.all([
|
|
25292
|
+
readAudienceShape(client, lensId),
|
|
25293
|
+
readLensLeadTotal(client, lensId)
|
|
25294
|
+
]);
|
|
25295
|
+
const reason = noCandidatesReason(held, shape);
|
|
25296
|
+
return {
|
|
25297
|
+
status: "no_candidates",
|
|
25298
|
+
lens: { id: lensId },
|
|
25299
|
+
available_count: 0,
|
|
25300
|
+
reason,
|
|
25301
|
+
message: reason.message
|
|
25302
|
+
};
|
|
25303
|
+
}
|
|
25073
25304
|
const body = {
|
|
25074
25305
|
seed_lead_ids: params.seed_lead_ids ?? []
|
|
25075
25306
|
};
|
|
@@ -25081,6 +25312,7 @@ var extendLens = {
|
|
|
25081
25312
|
return {
|
|
25082
25313
|
status: "queued",
|
|
25083
25314
|
lens: { id: lensId },
|
|
25315
|
+
available_count: availableCount,
|
|
25084
25316
|
accepted_seeds: res.accepted_seeds,
|
|
25085
25317
|
message: "Extra refill queued. Leads stream in asynchronously \u2014 call leadbay_pull_leads in ~30s to see them."
|
|
25086
25318
|
};
|
|
@@ -28307,7 +28539,7 @@ function parseWriteEnv(env = process.env) {
|
|
|
28307
28539
|
}
|
|
28308
28540
|
|
|
28309
28541
|
// src/http-server.ts
|
|
28310
|
-
var VERSION = true ? "0.
|
|
28542
|
+
var VERSION = true ? "0.31.1" : "0.0.0-dev";
|
|
28311
28543
|
var PORT = Number(process.env.PORT ?? 8080);
|
|
28312
28544
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
28313
28545
|
var logger = {
|
package/dist/installer-gui.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leadbay/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
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",
|