@leadbay/mcp 0.39.7 → 0.39.9
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/MIGRATION.md +5 -5
- package/dist/bin.js +88 -32
- package/dist/http-server.js +86 -30
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +1 -1
package/MIGRATION.md
CHANGED
|
@@ -32,9 +32,9 @@ field. On the `notification_id` path the job is done when `in_progress` is false
|
|
|
32
32
|
`still_running[]` is empty on that path from the first poll and must not be read
|
|
33
33
|
as "done". On the `lead_ids` path, done means `still_running[]` is empty.
|
|
34
34
|
|
|
35
|
-
`leadbay_bulk_enrich_status` answers `ENRICH_JOB_NO_COUNTERS
|
|
36
|
-
notification carries
|
|
37
|
-
|
|
35
|
+
`leadbay_bulk_enrich_status` no longer answers `ENRICH_JOB_NO_COUNTERS`. An
|
|
36
|
+
enrichment notification never carries per-contact counters, so the tool reads the
|
|
37
|
+
job's lead set back from the backend and answers through the per-lead path.
|
|
38
38
|
|
|
39
39
|
## Errors removed
|
|
40
40
|
|
|
@@ -43,8 +43,8 @@ notification carries no per-contact counters (the common case) — re-call with
|
|
|
43
43
|
no longer exist, and neither does `~/.leadbay/bulks.json` or the
|
|
44
44
|
`LEADBAY_BULK_STORE_ALLOW_MEMORY` escape hatch. The replacements are
|
|
45
45
|
`ENRICH_STATUS_INPUT_REQUIRED`, `ENRICH_JOB_NOT_FOUND`, `ENRICH_JOB_WRONG_KIND`,
|
|
46
|
-
`
|
|
47
|
-
|
|
46
|
+
`QUALIFY_STATUS_INPUT_REQUIRED`, `QUALIFY_JOB_NOT_FOUND` and
|
|
47
|
+
`QUALIFY_JOB_WRONG_KIND`. The 0.5.0 sections below describe the old store and
|
|
48
48
|
are kept as history.
|
|
49
49
|
|
|
50
50
|
---
|
package/dist/bin.js
CHANGED
|
@@ -1982,7 +1982,7 @@ WHEN TO USE: the user asks for a clickable / interactive artifact, dashboard, or
|
|
|
1982
1982
|
|
|
1983
1983
|
WHEN NOT TO USE: the user wants a plain data answer (route to leadbay_pull_leads / leadbay_pull_followups) or to log a single real outreach you just did (leadbay_report_outreach).
|
|
1984
1984
|
`;
|
|
1985
|
-
leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Pass the \`notification_id
|
|
1985
|
+
leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Pass the \`notification_id\`, and/or the \`lead_ids\` + \`titles\` + \`email\` / \`phone\` the launch returned. Either one alone is a valid call: with \`notification_id\` the tool reads the job's lead set back from the backend, so an id kept from an earlier conversation still answers with per-lead progress. Pass \`lead_ids\` as well whenever you still have them \u2014 the job lookup is a scan of your recent notifications, so an archived job may not be found, but the leads always answer. \`titles\` / \`email\` / \`phone\` scope the count to the roles and channel THIS run asked for. When \`include_contacts=true\` (opt-in), includes each contact's email/phone_number/job_title/enrichment.done.
|
|
1986
1986
|
|
|
1987
1987
|
WHEN TO USE: poll this REPEATEDLY after leadbay_enrich_titles returns a \`notification_id\`, staying active until the job is done \u2014 don't stop after one check, and don't hand the turn back to the user while progress is still climbing. "Done" = \`all_done:true\`, OR \`overall_progress.done\` has held steady across several SPACED polls (~15\u201330s apart) over at least ~90s\u20132 min of elapsed time (a reservation the provider never answers stays \`done:false\`, so \`all_done\` can stay false forever \u2014 don't spin indefinitely; a contact with nothing found does flip, to \`done:true\` with \`credits_used:0\`). Do NOT declare a plateau from the first few back-to-back reads: right after launch, \`overall_progress.done\` can sit flat while the backend is still spinning the job up, so space your polls out and give it real elapsed time before treating a flat count as terminal. Also do NOT declare a plateau while the result carries \`partial_failures\` \u2014 a flat \`done\` there means a transient per-lead fetch error (e.g. a 429), NOT an unresolvable contact; keep polling (respecting any \`retry_after\`) or surface it as a temporary status failure, rather than reporting those leads as permanently unresolved. Default \`include_contacts=false\` for the cheap interim polls; set \`include_contacts=true\` on the read you report from to pull each lead's enriched contacts for the completion report.
|
|
1988
1988
|
|
|
@@ -2996,7 +2996,7 @@ Pick the 2-3 options that match what actually happened \u2014 never all seven:
|
|
|
2996
2996
|
|
|
2997
2997
|
| Observation | Suggest | Calls |
|
|
2998
2998
|
|---|---|---|
|
|
2999
|
-
| Job still running (\`still_running: true\`) | "Check on it in ~1 min" | leadbay_lead_job_status(job_id, wait_seconds:
|
|
2999
|
+
| Job still running (\`still_running: true\`) | "Check on it in ~1 min" | leadbay_lead_job_status(job_id, wait_seconds: 45) |
|
|
3000
3000
|
| Free run delivered on-profile leads | "Qualify these N against your criteria (uses quota \u2014 \`dry_run\` first)" | leadbay_qualify_leads(prior_deliveries: {job_id}) |
|
|
3001
3001
|
| Delivered leads look right | "Draft outreach for the top ones" | leadbay_prepare_outreach |
|
|
3002
3002
|
| Delivered 0 or off-profile | "Reshape the example and retry" (name the fix from funnel + scope_notes) | leadbay_find_new_leads (NEW request_id) |
|
|
@@ -3992,7 +3992,7 @@ Trigger phrases: "is the lead search done", "any results yet on that job", "chec
|
|
|
3992
3992
|
|
|
3993
3993
|
Do NOT use for: "is the enrichment done" \u2192 \`leadbay_bulk_enrich_status\`; "is my import done" \u2192 \`leadbay_import_status\`; "is the top-N qualification done" \u2192 \`leadbay_qualify_status\`.
|
|
3994
3994
|
|
|
3995
|
-
Prefer when: a find_new_leads / qualify_leads result carried next_poll \u2014 pass its job_id; use wait_seconds
|
|
3995
|
+
Prefer when: a find_new_leads / qualify_leads result carried next_poll \u2014 pass its job_id; use wait_seconds 45 when the user asked to wait for results.
|
|
3996
3996
|
|
|
3997
3997
|
Examples that SHOULD invoke this tool:
|
|
3998
3998
|
- "Any leads yet from that search you started?"
|
|
@@ -4019,7 +4019,7 @@ usage counters (never render them), and the \`explain\` block
|
|
|
4019
4019
|
(basis, seed strategy, scope notes). Items are immutable once emitted \u2014
|
|
4020
4020
|
polling never re-reads live data, so numbers only ever grow.
|
|
4021
4021
|
|
|
4022
|
-
\`wait_seconds: 0\` (default) answers instantly; set
|
|
4022
|
+
\`wait_seconds: 0\` (default) answers instantly; set 45 (the maximum) to block-wait for
|
|
4023
4023
|
completion when the user asked for results "in this reply". \`since\` (from a
|
|
4024
4024
|
prior poll's \`next_since\`) pages only the new items. Jobs terminalize
|
|
4025
4025
|
server-side: past the 30-min wall clock a job reads \`completed_partial\`
|
|
@@ -4201,7 +4201,7 @@ is a status tool, keep it terse:
|
|
|
4201
4201
|
|
|
4202
4202
|
| Observation | Suggest | Calls |
|
|
4203
4203
|
|---|---|---|
|
|
4204
|
-
| Still running | "Keep waiting (~1 min) or leave it \u2014 results are kept 30 days" | leadbay_lead_job_status(job_id, wait_seconds:
|
|
4204
|
+
| Still running | "Keep waiting (~1 min) or leave it \u2014 results are kept 30 days" | leadbay_lead_job_status(job_id, wait_seconds: 45) |
|
|
4205
4205
|
| Terminal (completed / partial / failed) | Render the delivery per the RENDERING block, then offer the matching find_new_leads / qualify_leads NEXT STEPS | \u2014 |
|
|
4206
4206
|
| \`expired\` (past the 30-day window) | "Re-read the delivered leads from your delivery ledger" \u2014 there is nothing left to render: the job terminalized and its items are no longer listed, so do NOT present an empty delivery as a result | leadbay_qualify_leads(prior_deliveries: {job_id}) |
|
|
4207
4207
|
`;
|
|
@@ -5541,7 +5541,7 @@ Pick the 2-3 options that match what actually happened:
|
|
|
5541
5541
|
|
|
5542
5542
|
| Observation | Suggest | Calls |
|
|
5543
5543
|
|---|---|---|
|
|
5544
|
-
| Job still running | "Check on it in ~1 min" | leadbay_lead_job_status(job_id, wait_seconds:
|
|
5544
|
+
| Job still running | "Check on it in ~1 min" | leadbay_lead_job_status(job_id, wait_seconds: 45) |
|
|
5545
5545
|
| Fit leads with contacts delivered | "Draft outreach for the qualified ones" | leadbay_prepare_outreach |
|
|
5546
5546
|
| Items skipped \`not_in_universe\` | "Import those companies first, then re-qualify" | leadbay_import_leads \u2192 leadbay_qualify_leads |
|
|
5547
5547
|
| Items skipped \`low_confidence_identity\` | "Pick the right match" (show \`resolution.alternatives\`) | leadbay_qualify_leads with the chosen lead_id |
|
|
@@ -7794,7 +7794,7 @@ Pick the 2-3 options that match what actually happened \u2014 never all seven:
|
|
|
7794
7794
|
|
|
7795
7795
|
| Observation | Suggest | Calls |
|
|
7796
7796
|
|---|---|---|
|
|
7797
|
-
| Job still running (\`still_running: true\`) | "Check on it in ~1 min" | leadbay_lead_job_status(job_id, wait_seconds:
|
|
7797
|
+
| Job still running (\`still_running: true\`) | "Check on it in ~1 min" | leadbay_lead_job_status(job_id, wait_seconds: 45) |
|
|
7798
7798
|
| Free run delivered on-profile leads | "Qualify these N against your criteria (uses quota \u2014 \`dry_run\` first)" | leadbay_qualify_leads(prior_deliveries: {job_id}) |
|
|
7799
7799
|
| Delivered leads look right | "Draft outreach for the top ones" | leadbay_prepare_outreach |
|
|
7800
7800
|
| Delivered 0 or off-profile | "Reshape the example and retry" (name the fix from funnel + scope_notes) | leadbay_find_new_leads (NEW request_id) |
|
|
@@ -21087,7 +21087,26 @@ async function pMap(items, fn, concurrency) {
|
|
|
21087
21087
|
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
21088
21088
|
return out;
|
|
21089
21089
|
}
|
|
21090
|
-
|
|
21090
|
+
async function readBulkLeadIds(client, bulkId) {
|
|
21091
|
+
const ids = [];
|
|
21092
|
+
for (let page = 0; page < BULK_LEADS_MAX_PAGES; page += 1) {
|
|
21093
|
+
let res;
|
|
21094
|
+
try {
|
|
21095
|
+
res = await client.request("GET", `/leads/bulk_enrichments/${encodeURIComponent(bulkId)}/leads?contacts=false&count=${BULK_LEADS_PAGE_SIZE}&page=${page}`);
|
|
21096
|
+
} catch {
|
|
21097
|
+
return null;
|
|
21098
|
+
}
|
|
21099
|
+
for (const item of res?.items ?? []) {
|
|
21100
|
+
if (item?.id)
|
|
21101
|
+
ids.push(item.id);
|
|
21102
|
+
}
|
|
21103
|
+
const pages = res?.pagination?.pages ?? 1;
|
|
21104
|
+
if (page + 1 >= pages)
|
|
21105
|
+
return ids;
|
|
21106
|
+
}
|
|
21107
|
+
return null;
|
|
21108
|
+
}
|
|
21109
|
+
var STATUS_FETCH_CONCURRENCY, BULK_LEADS_PAGE_SIZE, BULK_LEADS_MAX_PAGES, bulkEnrichStatus;
|
|
21091
21110
|
var init_bulk_enrich_status = __esm({
|
|
21092
21111
|
"../core/dist/composite/bulk-enrich-status.js"() {
|
|
21093
21112
|
"use strict";
|
|
@@ -21097,6 +21116,8 @@ var init_bulk_enrich_status = __esm({
|
|
|
21097
21116
|
init_credits_helpers();
|
|
21098
21117
|
init_tool_descriptions_generated();
|
|
21099
21118
|
STATUS_FETCH_CONCURRENCY = 5;
|
|
21119
|
+
BULK_LEADS_PAGE_SIZE = 100;
|
|
21120
|
+
BULK_LEADS_MAX_PAGES = 3;
|
|
21100
21121
|
bulkEnrichStatus = {
|
|
21101
21122
|
name: "leadbay_bulk_enrich_status",
|
|
21102
21123
|
annotations: {
|
|
@@ -21112,7 +21133,7 @@ var init_bulk_enrich_status = __esm({
|
|
|
21112
21133
|
properties: {
|
|
21113
21134
|
notification_id: {
|
|
21114
21135
|
type: "string",
|
|
21115
|
-
description: "The `notification_id` returned by leadbay_enrich_titles.
|
|
21136
|
+
description: "The `notification_id` returned by leadbay_enrich_titles. Answers on its own: the tool reads the job's lead set back from the backend, so an id kept from an earlier conversation still gives per-lead progress."
|
|
21116
21137
|
},
|
|
21117
21138
|
lead_ids: {
|
|
21118
21139
|
type: "array",
|
|
@@ -21176,7 +21197,7 @@ var init_bulk_enrich_status = __esm({
|
|
|
21176
21197
|
},
|
|
21177
21198
|
execute: async (client, params, ctx) => {
|
|
21178
21199
|
const includeContacts = params.include_contacts ?? false;
|
|
21179
|
-
|
|
21200
|
+
let leadIds = params.lead_ids ?? [];
|
|
21180
21201
|
const startMs = Date.now();
|
|
21181
21202
|
if (!params.notification_id && leadIds.length === 0) {
|
|
21182
21203
|
return {
|
|
@@ -21189,6 +21210,7 @@ var init_bulk_enrich_status = __esm({
|
|
|
21189
21210
|
let bp = null;
|
|
21190
21211
|
let inProgress = null;
|
|
21191
21212
|
let launchedAt = null;
|
|
21213
|
+
let bulkAnchorId = null;
|
|
21192
21214
|
if (params.notification_id) {
|
|
21193
21215
|
const n = await readNotificationById(client, params.notification_id);
|
|
21194
21216
|
if (n && inferKind(n) !== "bulk_enrich") {
|
|
@@ -21204,6 +21226,7 @@ var init_bulk_enrich_status = __esm({
|
|
|
21204
21226
|
bp = n.bulk_progress;
|
|
21205
21227
|
inProgress = n.in_progress;
|
|
21206
21228
|
launchedAt = n.created_at;
|
|
21229
|
+
bulkAnchorId = anchorIdFor(n, "bulk_enrich");
|
|
21207
21230
|
} else if (leadIds.length === 0) {
|
|
21208
21231
|
return {
|
|
21209
21232
|
error: true,
|
|
@@ -21213,6 +21236,9 @@ var init_bulk_enrich_status = __esm({
|
|
|
21213
21236
|
};
|
|
21214
21237
|
}
|
|
21215
21238
|
}
|
|
21239
|
+
if (!bp && leadIds.length === 0 && bulkAnchorId !== null) {
|
|
21240
|
+
leadIds = await readBulkLeadIds(client, bulkAnchorId) ?? [];
|
|
21241
|
+
}
|
|
21216
21242
|
if (leadIds.length > 0) {
|
|
21217
21243
|
const wantTitles = new Set((params.titles ?? []).map((t) => t.trim().toLowerCase()));
|
|
21218
21244
|
const inThisRun = (c) => {
|
|
@@ -21316,13 +21342,18 @@ var init_bulk_enrich_status = __esm({
|
|
|
21316
21342
|
};
|
|
21317
21343
|
}
|
|
21318
21344
|
if (!bp) {
|
|
21345
|
+
const isReportRead2 = inProgress === false;
|
|
21346
|
+
const creditsRemaining2 = isReportRead2 ? await readCreditsRemaining(client, true) : null;
|
|
21319
21347
|
return {
|
|
21320
|
-
|
|
21321
|
-
|
|
21322
|
-
|
|
21323
|
-
|
|
21348
|
+
notification_id: params.notification_id,
|
|
21349
|
+
...launchedAt ? { launched_at: launchedAt } : {},
|
|
21350
|
+
status: inProgress ? "launched" : "complete",
|
|
21351
|
+
leads: [],
|
|
21352
|
+
overall_progress: { done: 0, total: 0, done_ratio: 0 },
|
|
21324
21353
|
...inProgress !== null ? { in_progress: inProgress } : {},
|
|
21325
|
-
|
|
21354
|
+
all_done: isReportRead2,
|
|
21355
|
+
...isReportRead2 ? { credits_remaining: creditsRemaining2 } : {},
|
|
21356
|
+
counts_hint: "This job's notification carries no per-contact counters and its lead set could not be read back, so done/total are 0. Re-call with the `lead_ids` returned by leadbay_enrich_titles (plus titles/email/phone) to count contacts directly."
|
|
21326
21357
|
};
|
|
21327
21358
|
}
|
|
21328
21359
|
const done = bp.success_count + bp.failure_count + bp.quota_hit_count;
|
|
@@ -21557,6 +21588,21 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
|
|
|
21557
21588
|
const items = current?.lens_filter?.items ?? [];
|
|
21558
21589
|
const item = items[0] ?? { criteria: [] };
|
|
21559
21590
|
const criteria = item.criteria ? [...item.criteria] : [];
|
|
21591
|
+
const dropIds = (type, fromExcluded, ids) => {
|
|
21592
|
+
if (ids.length === 0)
|
|
21593
|
+
return;
|
|
21594
|
+
const idx = criteria.findIndex((c) => c.type === type && !!c.is_excluded === fromExcluded);
|
|
21595
|
+
if (idx < 0)
|
|
21596
|
+
return;
|
|
21597
|
+
const cur = criteria[idx];
|
|
21598
|
+
const key = type === "sector_ids" ? "sectors" : "locations";
|
|
21599
|
+
const held = cur[key] ?? [];
|
|
21600
|
+
const kept = held.filter((s) => !ids.includes(s));
|
|
21601
|
+
if (kept.length === 0)
|
|
21602
|
+
criteria.splice(idx, 1);
|
|
21603
|
+
else
|
|
21604
|
+
criteria[idx] = { ...cur, [key]: kept };
|
|
21605
|
+
};
|
|
21560
21606
|
if (toAddSectors.length > 0) {
|
|
21561
21607
|
const idx = criteria.findIndex((c) => c.type === "sector_ids" && !c.is_excluded);
|
|
21562
21608
|
if (idx >= 0) {
|
|
@@ -21571,6 +21617,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
|
|
|
21571
21617
|
});
|
|
21572
21618
|
}
|
|
21573
21619
|
}
|
|
21620
|
+
dropIds("sector_ids", true, toAddSectors);
|
|
21574
21621
|
if (toExcludeSectors.length > 0) {
|
|
21575
21622
|
const idx = criteria.findIndex((c) => c.type === "sector_ids" && c.is_excluded);
|
|
21576
21623
|
if (idx >= 0) {
|
|
@@ -21585,6 +21632,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
|
|
|
21585
21632
|
});
|
|
21586
21633
|
}
|
|
21587
21634
|
}
|
|
21635
|
+
dropIds("sector_ids", false, toExcludeSectors);
|
|
21588
21636
|
if (toAddLocations.length > 0) {
|
|
21589
21637
|
const idx = criteria.findIndex((c) => c.type === "location_ids" && !c.is_excluded);
|
|
21590
21638
|
if (idx >= 0) {
|
|
@@ -21599,6 +21647,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
|
|
|
21599
21647
|
});
|
|
21600
21648
|
}
|
|
21601
21649
|
}
|
|
21650
|
+
dropIds("location_ids", true, toAddLocations);
|
|
21602
21651
|
if (toExcludeLocations.length > 0) {
|
|
21603
21652
|
const idx = criteria.findIndex((c) => c.type === "location_ids" && c.is_excluded);
|
|
21604
21653
|
if (idx >= 0) {
|
|
@@ -21613,6 +21662,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
|
|
|
21613
21662
|
});
|
|
21614
21663
|
}
|
|
21615
21664
|
}
|
|
21665
|
+
dropIds("location_ids", false, toExcludeLocations);
|
|
21616
21666
|
if (sizes && sizes.length > 0) {
|
|
21617
21667
|
const normalizedSizes = sizes.map((s) => ({
|
|
21618
21668
|
min: s.min ?? 0,
|
|
@@ -22773,12 +22823,15 @@ function normalizeSearchFilters(filters) {
|
|
|
22773
22823
|
}
|
|
22774
22824
|
return out;
|
|
22775
22825
|
}
|
|
22776
|
-
function clampWaitSeconds(requested, fallback) {
|
|
22777
|
-
|
|
22778
|
-
|
|
22779
|
-
|
|
22826
|
+
function clampWaitSeconds(requested, fallback, startedAtMs) {
|
|
22827
|
+
const asked = requested == null || Number.isNaN(requested) ? fallback : requested;
|
|
22828
|
+
const bounded = Math.min(Math.max(asked, 0), MAX_WAIT_SECONDS);
|
|
22829
|
+
if (startedAtMs == null)
|
|
22830
|
+
return bounded;
|
|
22831
|
+
const spentSeconds = (Date.now() - startedAtMs) / 1e3;
|
|
22832
|
+
return Math.max(0, Math.min(bounded, MAX_WAIT_SECONDS - spentSeconds));
|
|
22780
22833
|
}
|
|
22781
|
-
var TERMINAL_JOB_STATES, MCP_JOB_POLL, SNAPSHOT_TIMEOUT_MS, PAGE_LIMIT, MAX_JOB_ITEMS, MIN_PAGES, maxPagesFor, JOB_ID_CHARSET, MAX_JOB_ID_LENGTH, UUID_RE2, LEAD_REASON_CHARS, SIGNAL_CHARS, MAX_SIGNALS, MAX_ALTERNATIVE_CONTACTS, RESULT_CHARS, LEAN_ROW_CHARS, COUNTRY_ALIASES, SUBNATIONAL_EXEMPTIONS, ALL_EXEMPTIONS, COUNTRY_LOCATION_VALUES, MAX_EXCLUDE_LEAD_IDS, MAX_LEAD_REFS;
|
|
22834
|
+
var TERMINAL_JOB_STATES, MCP_JOB_POLL, SNAPSHOT_TIMEOUT_MS, PAGE_LIMIT, MAX_JOB_ITEMS, MIN_PAGES, maxPagesFor, JOB_ID_CHARSET, MAX_JOB_ID_LENGTH, UUID_RE2, LEAD_REASON_CHARS, SIGNAL_CHARS, MAX_SIGNALS, MAX_ALTERNATIVE_CONTACTS, RESULT_CHARS, LEAN_ROW_CHARS, COUNTRY_ALIASES, SUBNATIONAL_EXEMPTIONS, ALL_EXEMPTIONS, COUNTRY_LOCATION_VALUES, MAX_EXCLUDE_LEAD_IDS, MAX_LEAD_REFS, MAX_WAIT_SECONDS;
|
|
22782
22835
|
var init_mcp_job_helpers = __esm({
|
|
22783
22836
|
"../core/dist/composite/_mcp-job-helpers.js"() {
|
|
22784
22837
|
"use strict";
|
|
@@ -22842,6 +22895,7 @@ var init_mcp_job_helpers = __esm({
|
|
|
22842
22895
|
COUNTRY_LOCATION_VALUES = buildCountryLocationValues();
|
|
22843
22896
|
MAX_EXCLUDE_LEAD_IDS = 500;
|
|
22844
22897
|
MAX_LEAD_REFS = 500;
|
|
22898
|
+
MAX_WAIT_SECONDS = 45;
|
|
22845
22899
|
}
|
|
22846
22900
|
});
|
|
22847
22901
|
|
|
@@ -23031,13 +23085,14 @@ var init_find_new_leads = __esm({
|
|
|
23031
23085
|
},
|
|
23032
23086
|
wait_seconds: {
|
|
23033
23087
|
type: "number",
|
|
23034
|
-
description: "How long to poll before returning (default 45
|
|
23088
|
+
description: "How long to poll before returning (default and maximum 45 \u2014 the whole call is bounded by it, so a slow submit shortens the poll; 0 = submit + one snapshot). Free searches usually finish inside the window; qualified exploration can take minutes \u2014 the result then carries still_running:true and the job_id to check with leadbay_lead_job_status."
|
|
23035
23089
|
}
|
|
23036
23090
|
},
|
|
23037
23091
|
required: ["count", "request_id"],
|
|
23038
23092
|
additionalProperties: false
|
|
23039
23093
|
},
|
|
23040
23094
|
execute: async (client, params, ctx) => {
|
|
23095
|
+
const startedAt = Date.now();
|
|
23041
23096
|
params = coerceArrayParams(params, [
|
|
23042
23097
|
"contact_titles",
|
|
23043
23098
|
"channels",
|
|
@@ -23176,7 +23231,7 @@ var init_find_new_leads = __esm({
|
|
|
23176
23231
|
const mocked = mockedSubmitPreview(submit, "leadbay_find_new_leads", client.region);
|
|
23177
23232
|
if (mocked)
|
|
23178
23233
|
return mocked;
|
|
23179
|
-
const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS);
|
|
23234
|
+
const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS, startedAt);
|
|
23180
23235
|
const snapshot = await snapshotAfterSubmit(client, submit.job_id, waitSeconds, ctx, params.count);
|
|
23181
23236
|
const done = TERMINAL_JOB_STATES.has(snapshot.job.state);
|
|
23182
23237
|
const { leads, skipped } = splitItems(snapshot);
|
|
@@ -23221,7 +23276,7 @@ var init_find_new_leads = __esm({
|
|
|
23221
23276
|
// INCREMENTALLY instead of re-reading (and re-rendering) the
|
|
23222
23277
|
// rows already delivered in this response.
|
|
23223
23278
|
since: snapshot.next_since ?? null,
|
|
23224
|
-
suggested_wait_seconds: done ? 0 :
|
|
23279
|
+
suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
|
|
23225
23280
|
},
|
|
23226
23281
|
...sectorNote(attempt.rewritten),
|
|
23227
23282
|
...countryNote,
|
|
@@ -23346,7 +23401,7 @@ function identityAnswer(jobId, snapshot, items, offset) {
|
|
|
23346
23401
|
job_id: jobId,
|
|
23347
23402
|
compact: true,
|
|
23348
23403
|
offset: nextOffset,
|
|
23349
|
-
suggested_wait_seconds: done ? 0 :
|
|
23404
|
+
suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
|
|
23350
23405
|
}
|
|
23351
23406
|
};
|
|
23352
23407
|
}
|
|
@@ -23571,12 +23626,13 @@ var init_qualify_leads = __esm({
|
|
|
23571
23626
|
},
|
|
23572
23627
|
wait_seconds: {
|
|
23573
23628
|
type: "number",
|
|
23574
|
-
description: "How long to poll before returning (default 45
|
|
23629
|
+
description: "How long to poll before returning (default and maximum 45 \u2014 the whole call is bounded by it, so a slow submit shortens the poll; 0 = submit + one snapshot). Large or research-heavy batches can take minutes \u2014 the result then carries still_running:true and the job_id for leadbay_lead_job_status."
|
|
23575
23630
|
}
|
|
23576
23631
|
},
|
|
23577
23632
|
additionalProperties: false
|
|
23578
23633
|
},
|
|
23579
23634
|
execute: async (client, params, ctx) => {
|
|
23635
|
+
const startedAt = Date.now();
|
|
23580
23636
|
params = coerceArrayParams(params, [
|
|
23581
23637
|
"lead_refs",
|
|
23582
23638
|
"contact_titles",
|
|
@@ -23636,7 +23692,7 @@ var init_qualify_leads = __esm({
|
|
|
23636
23692
|
const mocked = mockedSubmitPreview(submit, "leadbay_qualify_leads", client.region);
|
|
23637
23693
|
if (mocked)
|
|
23638
23694
|
return mocked;
|
|
23639
|
-
const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS2);
|
|
23695
|
+
const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS2, startedAt);
|
|
23640
23696
|
const snapshot = await snapshotAfterSubmit(client, submit.job_id, waitSeconds, ctx, submit.items_requested);
|
|
23641
23697
|
const done = TERMINAL_JOB_STATES.has(snapshot.job.state);
|
|
23642
23698
|
const indexed = submit.duplicate ?? false ? remapInputIndexes(snapshot.items, params.lead_refs) : { items: snapshot.items, remapped: true };
|
|
@@ -23713,7 +23769,7 @@ var init_qualify_leads = __esm({
|
|
|
23713
23769
|
// INCREMENTALLY instead of re-reading (and re-rendering) the
|
|
23714
23770
|
// rows already delivered in this response.
|
|
23715
23771
|
since: snapshot.next_since ?? null,
|
|
23716
|
-
suggested_wait_seconds: done ? 0 :
|
|
23772
|
+
suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
|
|
23717
23773
|
},
|
|
23718
23774
|
region: client.region
|
|
23719
23775
|
};
|
|
@@ -23757,7 +23813,7 @@ var init_lead_job_status = __esm({
|
|
|
23757
23813
|
},
|
|
23758
23814
|
wait_seconds: {
|
|
23759
23815
|
type: "number",
|
|
23760
|
-
description: "0 (default) = instant snapshot. >0 = keep polling up to this many seconds until the job is terminal \u2014 use
|
|
23816
|
+
description: "0 (default) = instant snapshot. >0 = keep polling up to this many seconds (maximum 45) until the job is terminal \u2014 use 45 when the user asked to wait for results."
|
|
23761
23817
|
},
|
|
23762
23818
|
compact: {
|
|
23763
23819
|
type: "boolean",
|
|
@@ -23809,7 +23865,7 @@ var init_lead_job_status = __esm({
|
|
|
23809
23865
|
// Same incremental handoff as the submit tools — following
|
|
23810
23866
|
// next_poll without the cursor re-reads the rows just returned.
|
|
23811
23867
|
since: snapshot.next_since ?? null,
|
|
23812
|
-
suggested_wait_seconds: done ? 0 :
|
|
23868
|
+
suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
|
|
23813
23869
|
},
|
|
23814
23870
|
region: client.region
|
|
23815
23871
|
};
|
|
@@ -27229,7 +27285,7 @@ searched. Render the delivery table and judge fit honestly: are these the
|
|
|
27229
27285
|
kind of companies I asked for?
|
|
27230
27286
|
|
|
27231
27287
|
- **\`still_running: true\`** \u2192 the job is ALIVE. Do not judge the seed and do
|
|
27232
|
-
not relaunch \u2014 poll \`leadbay_lead_job_status\` (\`wait_seconds:
|
|
27288
|
+
not relaunch \u2014 poll \`leadbay_lead_job_status\` (\`wait_seconds: 45\`) until
|
|
27233
27289
|
it goes terminal, reporting progress. Relaunching now burns an active-job
|
|
27234
27290
|
slot and rate-limit budget on a search that may be about to deliver.
|
|
27235
27291
|
- **On-profile** (terminal) \u2192 offer Phase 4.
|
|
@@ -27287,7 +27343,7 @@ When I want qualification evidence and/or reachable contacts:
|
|
|
27287
27343
|
\`exploration_cap\` can examine more than that, so send the most recent 500
|
|
27288
27344
|
rejects rather than an over-long list the tool refuses outright.
|
|
27289
27345
|
3. While the job runs, poll with \`leadbay_lead_job_status\`
|
|
27290
|
-
(\`wait_seconds:
|
|
27346
|
+
(\`wait_seconds: 45\`); report progress, not silence.
|
|
27291
27347
|
|
|
27292
27348
|
# PHASE 5 \u2014 DELIVER
|
|
27293
27349
|
|
|
@@ -32137,7 +32193,7 @@ var OAUTH_BASE_URLS = {
|
|
|
32137
32193
|
fr: "https://staging.api.leadbay.app"
|
|
32138
32194
|
}
|
|
32139
32195
|
};
|
|
32140
|
-
var VERSION = "0.39.
|
|
32196
|
+
var VERSION = "0.39.9";
|
|
32141
32197
|
var HELP = `
|
|
32142
32198
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
32143
32199
|
|
package/dist/http-server.js
CHANGED
|
@@ -1400,7 +1400,7 @@ searched. Render the delivery table and judge fit honestly: are these the
|
|
|
1400
1400
|
kind of companies I asked for?
|
|
1401
1401
|
|
|
1402
1402
|
- **\`still_running: true\`** \u2192 the job is ALIVE. Do not judge the seed and do
|
|
1403
|
-
not relaunch \u2014 poll \`leadbay_lead_job_status\` (\`wait_seconds:
|
|
1403
|
+
not relaunch \u2014 poll \`leadbay_lead_job_status\` (\`wait_seconds: 45\`) until
|
|
1404
1404
|
it goes terminal, reporting progress. Relaunching now burns an active-job
|
|
1405
1405
|
slot and rate-limit budget on a search that may be about to deliver.
|
|
1406
1406
|
- **On-profile** (terminal) \u2192 offer Phase 4.
|
|
@@ -1458,7 +1458,7 @@ When I want qualification evidence and/or reachable contacts:
|
|
|
1458
1458
|
\`exploration_cap\` can examine more than that, so send the most recent 500
|
|
1459
1459
|
rejects rather than an over-long list the tool refuses outright.
|
|
1460
1460
|
3. While the job runs, poll with \`leadbay_lead_job_status\`
|
|
1461
|
-
(\`wait_seconds:
|
|
1461
|
+
(\`wait_seconds: 45\`); report progress, not silence.
|
|
1462
1462
|
|
|
1463
1463
|
# PHASE 5 \u2014 DELIVER
|
|
1464
1464
|
|
|
@@ -4932,7 +4932,7 @@ WHEN TO USE: the user asks for a clickable / interactive artifact, dashboard, or
|
|
|
4932
4932
|
|
|
4933
4933
|
WHEN NOT TO USE: the user wants a plain data answer (route to leadbay_pull_leads / leadbay_pull_followups) or to log a single real outreach you just did (leadbay_report_outreach).
|
|
4934
4934
|
`;
|
|
4935
|
-
var leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Pass the \`notification_id
|
|
4935
|
+
var leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Pass the \`notification_id\`, and/or the \`lead_ids\` + \`titles\` + \`email\` / \`phone\` the launch returned. Either one alone is a valid call: with \`notification_id\` the tool reads the job's lead set back from the backend, so an id kept from an earlier conversation still answers with per-lead progress. Pass \`lead_ids\` as well whenever you still have them \u2014 the job lookup is a scan of your recent notifications, so an archived job may not be found, but the leads always answer. \`titles\` / \`email\` / \`phone\` scope the count to the roles and channel THIS run asked for. When \`include_contacts=true\` (opt-in), includes each contact's email/phone_number/job_title/enrichment.done.
|
|
4936
4936
|
|
|
4937
4937
|
WHEN TO USE: poll this REPEATEDLY after leadbay_enrich_titles returns a \`notification_id\`, staying active until the job is done \u2014 don't stop after one check, and don't hand the turn back to the user while progress is still climbing. "Done" = \`all_done:true\`, OR \`overall_progress.done\` has held steady across several SPACED polls (~15\u201330s apart) over at least ~90s\u20132 min of elapsed time (a reservation the provider never answers stays \`done:false\`, so \`all_done\` can stay false forever \u2014 don't spin indefinitely; a contact with nothing found does flip, to \`done:true\` with \`credits_used:0\`). Do NOT declare a plateau from the first few back-to-back reads: right after launch, \`overall_progress.done\` can sit flat while the backend is still spinning the job up, so space your polls out and give it real elapsed time before treating a flat count as terminal. Also do NOT declare a plateau while the result carries \`partial_failures\` \u2014 a flat \`done\` there means a transient per-lead fetch error (e.g. a 429), NOT an unresolvable contact; keep polling (respecting any \`retry_after\`) or surface it as a temporary status failure, rather than reporting those leads as permanently unresolved. Default \`include_contacts=false\` for the cheap interim polls; set \`include_contacts=true\` on the read you report from to pull each lead's enriched contacts for the completion report.
|
|
4938
4938
|
|
|
@@ -5946,7 +5946,7 @@ Pick the 2-3 options that match what actually happened \u2014 never all seven:
|
|
|
5946
5946
|
|
|
5947
5947
|
| Observation | Suggest | Calls |
|
|
5948
5948
|
|---|---|---|
|
|
5949
|
-
| Job still running (\`still_running: true\`) | "Check on it in ~1 min" | leadbay_lead_job_status(job_id, wait_seconds:
|
|
5949
|
+
| Job still running (\`still_running: true\`) | "Check on it in ~1 min" | leadbay_lead_job_status(job_id, wait_seconds: 45) |
|
|
5950
5950
|
| Free run delivered on-profile leads | "Qualify these N against your criteria (uses quota \u2014 \`dry_run\` first)" | leadbay_qualify_leads(prior_deliveries: {job_id}) |
|
|
5951
5951
|
| Delivered leads look right | "Draft outreach for the top ones" | leadbay_prepare_outreach |
|
|
5952
5952
|
| Delivered 0 or off-profile | "Reshape the example and retry" (name the fix from funnel + scope_notes) | leadbay_find_new_leads (NEW request_id) |
|
|
@@ -6942,7 +6942,7 @@ Trigger phrases: "is the lead search done", "any results yet on that job", "chec
|
|
|
6942
6942
|
|
|
6943
6943
|
Do NOT use for: "is the enrichment done" \u2192 \`leadbay_bulk_enrich_status\`; "is my import done" \u2192 \`leadbay_import_status\`; "is the top-N qualification done" \u2192 \`leadbay_qualify_status\`.
|
|
6944
6944
|
|
|
6945
|
-
Prefer when: a find_new_leads / qualify_leads result carried next_poll \u2014 pass its job_id; use wait_seconds
|
|
6945
|
+
Prefer when: a find_new_leads / qualify_leads result carried next_poll \u2014 pass its job_id; use wait_seconds 45 when the user asked to wait for results.
|
|
6946
6946
|
|
|
6947
6947
|
Examples that SHOULD invoke this tool:
|
|
6948
6948
|
- "Any leads yet from that search you started?"
|
|
@@ -6969,7 +6969,7 @@ usage counters (never render them), and the \`explain\` block
|
|
|
6969
6969
|
(basis, seed strategy, scope notes). Items are immutable once emitted \u2014
|
|
6970
6970
|
polling never re-reads live data, so numbers only ever grow.
|
|
6971
6971
|
|
|
6972
|
-
\`wait_seconds: 0\` (default) answers instantly; set
|
|
6972
|
+
\`wait_seconds: 0\` (default) answers instantly; set 45 (the maximum) to block-wait for
|
|
6973
6973
|
completion when the user asked for results "in this reply". \`since\` (from a
|
|
6974
6974
|
prior poll's \`next_since\`) pages only the new items. Jobs terminalize
|
|
6975
6975
|
server-side: past the 30-min wall clock a job reads \`completed_partial\`
|
|
@@ -7151,7 +7151,7 @@ is a status tool, keep it terse:
|
|
|
7151
7151
|
|
|
7152
7152
|
| Observation | Suggest | Calls |
|
|
7153
7153
|
|---|---|---|
|
|
7154
|
-
| Still running | "Keep waiting (~1 min) or leave it \u2014 results are kept 30 days" | leadbay_lead_job_status(job_id, wait_seconds:
|
|
7154
|
+
| Still running | "Keep waiting (~1 min) or leave it \u2014 results are kept 30 days" | leadbay_lead_job_status(job_id, wait_seconds: 45) |
|
|
7155
7155
|
| Terminal (completed / partial / failed) | Render the delivery per the RENDERING block, then offer the matching find_new_leads / qualify_leads NEXT STEPS | \u2014 |
|
|
7156
7156
|
| \`expired\` (past the 30-day window) | "Re-read the delivered leads from your delivery ledger" \u2014 there is nothing left to render: the job terminalized and its items are no longer listed, so do NOT present an empty delivery as a result | leadbay_qualify_leads(prior_deliveries: {job_id}) |
|
|
7157
7157
|
`;
|
|
@@ -8491,7 +8491,7 @@ Pick the 2-3 options that match what actually happened:
|
|
|
8491
8491
|
|
|
8492
8492
|
| Observation | Suggest | Calls |
|
|
8493
8493
|
|---|---|---|
|
|
8494
|
-
| Job still running | "Check on it in ~1 min" | leadbay_lead_job_status(job_id, wait_seconds:
|
|
8494
|
+
| Job still running | "Check on it in ~1 min" | leadbay_lead_job_status(job_id, wait_seconds: 45) |
|
|
8495
8495
|
| Fit leads with contacts delivered | "Draft outreach for the qualified ones" | leadbay_prepare_outreach |
|
|
8496
8496
|
| Items skipped \`not_in_universe\` | "Import those companies first, then re-qualify" | leadbay_import_leads \u2192 leadbay_qualify_leads |
|
|
8497
8497
|
| Items skipped \`low_confidence_identity\` | "Pick the right match" (show \`resolution.alternatives\`) | leadbay_qualify_leads with the chosen lead_id |
|
|
@@ -10744,7 +10744,7 @@ Pick the 2-3 options that match what actually happened \u2014 never all seven:
|
|
|
10744
10744
|
|
|
10745
10745
|
| Observation | Suggest | Calls |
|
|
10746
10746
|
|---|---|---|
|
|
10747
|
-
| Job still running (\`still_running: true\`) | "Check on it in ~1 min" | leadbay_lead_job_status(job_id, wait_seconds:
|
|
10747
|
+
| Job still running (\`still_running: true\`) | "Check on it in ~1 min" | leadbay_lead_job_status(job_id, wait_seconds: 45) |
|
|
10748
10748
|
| Free run delivered on-profile leads | "Qualify these N against your criteria (uses quota \u2014 \`dry_run\` first)" | leadbay_qualify_leads(prior_deliveries: {job_id}) |
|
|
10749
10749
|
| Delivered leads look right | "Draft outreach for the top ones" | leadbay_prepare_outreach |
|
|
10750
10750
|
| Delivered 0 or off-profile | "Reshape the example and retry" (name the fix from funnel + scope_notes) | leadbay_find_new_leads (NEW request_id) |
|
|
@@ -23290,6 +23290,27 @@ async function pMap(items, fn, concurrency) {
|
|
|
23290
23290
|
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
23291
23291
|
return out;
|
|
23292
23292
|
}
|
|
23293
|
+
var BULK_LEADS_PAGE_SIZE = 100;
|
|
23294
|
+
var BULK_LEADS_MAX_PAGES = 3;
|
|
23295
|
+
async function readBulkLeadIds(client, bulkId) {
|
|
23296
|
+
const ids = [];
|
|
23297
|
+
for (let page = 0; page < BULK_LEADS_MAX_PAGES; page += 1) {
|
|
23298
|
+
let res;
|
|
23299
|
+
try {
|
|
23300
|
+
res = await client.request("GET", `/leads/bulk_enrichments/${encodeURIComponent(bulkId)}/leads?contacts=false&count=${BULK_LEADS_PAGE_SIZE}&page=${page}`);
|
|
23301
|
+
} catch {
|
|
23302
|
+
return null;
|
|
23303
|
+
}
|
|
23304
|
+
for (const item of res?.items ?? []) {
|
|
23305
|
+
if (item?.id)
|
|
23306
|
+
ids.push(item.id);
|
|
23307
|
+
}
|
|
23308
|
+
const pages = res?.pagination?.pages ?? 1;
|
|
23309
|
+
if (page + 1 >= pages)
|
|
23310
|
+
return ids;
|
|
23311
|
+
}
|
|
23312
|
+
return null;
|
|
23313
|
+
}
|
|
23293
23314
|
var bulkEnrichStatus = {
|
|
23294
23315
|
name: "leadbay_bulk_enrich_status",
|
|
23295
23316
|
annotations: {
|
|
@@ -23305,7 +23326,7 @@ var bulkEnrichStatus = {
|
|
|
23305
23326
|
properties: {
|
|
23306
23327
|
notification_id: {
|
|
23307
23328
|
type: "string",
|
|
23308
|
-
description: "The `notification_id` returned by leadbay_enrich_titles.
|
|
23329
|
+
description: "The `notification_id` returned by leadbay_enrich_titles. Answers on its own: the tool reads the job's lead set back from the backend, so an id kept from an earlier conversation still gives per-lead progress."
|
|
23309
23330
|
},
|
|
23310
23331
|
lead_ids: {
|
|
23311
23332
|
type: "array",
|
|
@@ -23369,7 +23390,7 @@ var bulkEnrichStatus = {
|
|
|
23369
23390
|
},
|
|
23370
23391
|
execute: async (client, params, ctx) => {
|
|
23371
23392
|
const includeContacts = params.include_contacts ?? false;
|
|
23372
|
-
|
|
23393
|
+
let leadIds = params.lead_ids ?? [];
|
|
23373
23394
|
const startMs = Date.now();
|
|
23374
23395
|
if (!params.notification_id && leadIds.length === 0) {
|
|
23375
23396
|
return {
|
|
@@ -23382,6 +23403,7 @@ var bulkEnrichStatus = {
|
|
|
23382
23403
|
let bp = null;
|
|
23383
23404
|
let inProgress = null;
|
|
23384
23405
|
let launchedAt = null;
|
|
23406
|
+
let bulkAnchorId = null;
|
|
23385
23407
|
if (params.notification_id) {
|
|
23386
23408
|
const n = await readNotificationById(client, params.notification_id);
|
|
23387
23409
|
if (n && inferKind(n) !== "bulk_enrich") {
|
|
@@ -23397,6 +23419,7 @@ var bulkEnrichStatus = {
|
|
|
23397
23419
|
bp = n.bulk_progress;
|
|
23398
23420
|
inProgress = n.in_progress;
|
|
23399
23421
|
launchedAt = n.created_at;
|
|
23422
|
+
bulkAnchorId = anchorIdFor(n, "bulk_enrich");
|
|
23400
23423
|
} else if (leadIds.length === 0) {
|
|
23401
23424
|
return {
|
|
23402
23425
|
error: true,
|
|
@@ -23406,6 +23429,9 @@ var bulkEnrichStatus = {
|
|
|
23406
23429
|
};
|
|
23407
23430
|
}
|
|
23408
23431
|
}
|
|
23432
|
+
if (!bp && leadIds.length === 0 && bulkAnchorId !== null) {
|
|
23433
|
+
leadIds = await readBulkLeadIds(client, bulkAnchorId) ?? [];
|
|
23434
|
+
}
|
|
23409
23435
|
if (leadIds.length > 0) {
|
|
23410
23436
|
const wantTitles = new Set((params.titles ?? []).map((t) => t.trim().toLowerCase()));
|
|
23411
23437
|
const inThisRun = (c) => {
|
|
@@ -23509,13 +23535,18 @@ var bulkEnrichStatus = {
|
|
|
23509
23535
|
};
|
|
23510
23536
|
}
|
|
23511
23537
|
if (!bp) {
|
|
23538
|
+
const isReportRead2 = inProgress === false;
|
|
23539
|
+
const creditsRemaining2 = isReportRead2 ? await readCreditsRemaining(client, true) : null;
|
|
23512
23540
|
return {
|
|
23513
|
-
|
|
23514
|
-
|
|
23515
|
-
|
|
23516
|
-
|
|
23541
|
+
notification_id: params.notification_id,
|
|
23542
|
+
...launchedAt ? { launched_at: launchedAt } : {},
|
|
23543
|
+
status: inProgress ? "launched" : "complete",
|
|
23544
|
+
leads: [],
|
|
23545
|
+
overall_progress: { done: 0, total: 0, done_ratio: 0 },
|
|
23517
23546
|
...inProgress !== null ? { in_progress: inProgress } : {},
|
|
23518
|
-
|
|
23547
|
+
all_done: isReportRead2,
|
|
23548
|
+
...isReportRead2 ? { credits_remaining: creditsRemaining2 } : {},
|
|
23549
|
+
counts_hint: "This job's notification carries no per-contact counters and its lead set could not be read back, so done/total are 0. Re-call with the `lead_ids` returned by leadbay_enrich_titles (plus titles/email/phone) to count contacts directly."
|
|
23519
23550
|
};
|
|
23520
23551
|
}
|
|
23521
23552
|
const done = bp.success_count + bp.failure_count + bp.quota_hit_count;
|
|
@@ -23742,6 +23773,21 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
|
|
|
23742
23773
|
const items = current?.lens_filter?.items ?? [];
|
|
23743
23774
|
const item = items[0] ?? { criteria: [] };
|
|
23744
23775
|
const criteria = item.criteria ? [...item.criteria] : [];
|
|
23776
|
+
const dropIds = (type, fromExcluded, ids) => {
|
|
23777
|
+
if (ids.length === 0)
|
|
23778
|
+
return;
|
|
23779
|
+
const idx = criteria.findIndex((c) => c.type === type && !!c.is_excluded === fromExcluded);
|
|
23780
|
+
if (idx < 0)
|
|
23781
|
+
return;
|
|
23782
|
+
const cur = criteria[idx];
|
|
23783
|
+
const key = type === "sector_ids" ? "sectors" : "locations";
|
|
23784
|
+
const held = cur[key] ?? [];
|
|
23785
|
+
const kept = held.filter((s) => !ids.includes(s));
|
|
23786
|
+
if (kept.length === 0)
|
|
23787
|
+
criteria.splice(idx, 1);
|
|
23788
|
+
else
|
|
23789
|
+
criteria[idx] = { ...cur, [key]: kept };
|
|
23790
|
+
};
|
|
23745
23791
|
if (toAddSectors.length > 0) {
|
|
23746
23792
|
const idx = criteria.findIndex((c) => c.type === "sector_ids" && !c.is_excluded);
|
|
23747
23793
|
if (idx >= 0) {
|
|
@@ -23756,6 +23802,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
|
|
|
23756
23802
|
});
|
|
23757
23803
|
}
|
|
23758
23804
|
}
|
|
23805
|
+
dropIds("sector_ids", true, toAddSectors);
|
|
23759
23806
|
if (toExcludeSectors.length > 0) {
|
|
23760
23807
|
const idx = criteria.findIndex((c) => c.type === "sector_ids" && c.is_excluded);
|
|
23761
23808
|
if (idx >= 0) {
|
|
@@ -23770,6 +23817,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
|
|
|
23770
23817
|
});
|
|
23771
23818
|
}
|
|
23772
23819
|
}
|
|
23820
|
+
dropIds("sector_ids", false, toExcludeSectors);
|
|
23773
23821
|
if (toAddLocations.length > 0) {
|
|
23774
23822
|
const idx = criteria.findIndex((c) => c.type === "location_ids" && !c.is_excluded);
|
|
23775
23823
|
if (idx >= 0) {
|
|
@@ -23784,6 +23832,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
|
|
|
23784
23832
|
});
|
|
23785
23833
|
}
|
|
23786
23834
|
}
|
|
23835
|
+
dropIds("location_ids", true, toAddLocations);
|
|
23787
23836
|
if (toExcludeLocations.length > 0) {
|
|
23788
23837
|
const idx = criteria.findIndex((c) => c.type === "location_ids" && c.is_excluded);
|
|
23789
23838
|
if (idx >= 0) {
|
|
@@ -23798,6 +23847,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
|
|
|
23798
23847
|
});
|
|
23799
23848
|
}
|
|
23800
23849
|
}
|
|
23850
|
+
dropIds("location_ids", false, toExcludeLocations);
|
|
23801
23851
|
if (sizes && sizes.length > 0) {
|
|
23802
23852
|
const normalizedSizes = sizes.map((s) => ({
|
|
23803
23853
|
min: s.min ?? 0,
|
|
@@ -24993,10 +25043,14 @@ function normalizeSearchFilters(filters) {
|
|
|
24993
25043
|
}
|
|
24994
25044
|
return out;
|
|
24995
25045
|
}
|
|
24996
|
-
|
|
24997
|
-
|
|
24998
|
-
|
|
24999
|
-
|
|
25046
|
+
var MAX_WAIT_SECONDS = 45;
|
|
25047
|
+
function clampWaitSeconds(requested, fallback, startedAtMs) {
|
|
25048
|
+
const asked = requested == null || Number.isNaN(requested) ? fallback : requested;
|
|
25049
|
+
const bounded = Math.min(Math.max(asked, 0), MAX_WAIT_SECONDS);
|
|
25050
|
+
if (startedAtMs == null)
|
|
25051
|
+
return bounded;
|
|
25052
|
+
const spentSeconds = (Date.now() - startedAtMs) / 1e3;
|
|
25053
|
+
return Math.max(0, Math.min(bounded, MAX_WAIT_SECONDS - spentSeconds));
|
|
25000
25054
|
}
|
|
25001
25055
|
|
|
25002
25056
|
// ../core/dist/composite/find-new-leads.js
|
|
@@ -25177,13 +25231,14 @@ var findNewLeads = {
|
|
|
25177
25231
|
},
|
|
25178
25232
|
wait_seconds: {
|
|
25179
25233
|
type: "number",
|
|
25180
|
-
description: "How long to poll before returning (default 45
|
|
25234
|
+
description: "How long to poll before returning (default and maximum 45 \u2014 the whole call is bounded by it, so a slow submit shortens the poll; 0 = submit + one snapshot). Free searches usually finish inside the window; qualified exploration can take minutes \u2014 the result then carries still_running:true and the job_id to check with leadbay_lead_job_status."
|
|
25181
25235
|
}
|
|
25182
25236
|
},
|
|
25183
25237
|
required: ["count", "request_id"],
|
|
25184
25238
|
additionalProperties: false
|
|
25185
25239
|
},
|
|
25186
25240
|
execute: async (client, params, ctx) => {
|
|
25241
|
+
const startedAt = Date.now();
|
|
25187
25242
|
params = coerceArrayParams(params, [
|
|
25188
25243
|
"contact_titles",
|
|
25189
25244
|
"channels",
|
|
@@ -25322,7 +25377,7 @@ var findNewLeads = {
|
|
|
25322
25377
|
const mocked = mockedSubmitPreview(submit, "leadbay_find_new_leads", client.region);
|
|
25323
25378
|
if (mocked)
|
|
25324
25379
|
return mocked;
|
|
25325
|
-
const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS);
|
|
25380
|
+
const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS, startedAt);
|
|
25326
25381
|
const snapshot = await snapshotAfterSubmit(client, submit.job_id, waitSeconds, ctx, params.count);
|
|
25327
25382
|
const done = TERMINAL_JOB_STATES.has(snapshot.job.state);
|
|
25328
25383
|
const { leads, skipped } = splitItems(snapshot);
|
|
@@ -25367,7 +25422,7 @@ var findNewLeads = {
|
|
|
25367
25422
|
// INCREMENTALLY instead of re-reading (and re-rendering) the
|
|
25368
25423
|
// rows already delivered in this response.
|
|
25369
25424
|
since: snapshot.next_since ?? null,
|
|
25370
|
-
suggested_wait_seconds: done ? 0 :
|
|
25425
|
+
suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
|
|
25371
25426
|
},
|
|
25372
25427
|
...sectorNote(attempt.rewritten),
|
|
25373
25428
|
...countryNote,
|
|
@@ -25501,7 +25556,7 @@ function identityAnswer(jobId, snapshot, items, offset) {
|
|
|
25501
25556
|
job_id: jobId,
|
|
25502
25557
|
compact: true,
|
|
25503
25558
|
offset: nextOffset,
|
|
25504
|
-
suggested_wait_seconds: done ? 0 :
|
|
25559
|
+
suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
|
|
25505
25560
|
}
|
|
25506
25561
|
};
|
|
25507
25562
|
}
|
|
@@ -25699,12 +25754,13 @@ var qualifyLeads = {
|
|
|
25699
25754
|
},
|
|
25700
25755
|
wait_seconds: {
|
|
25701
25756
|
type: "number",
|
|
25702
|
-
description: "How long to poll before returning (default 45
|
|
25757
|
+
description: "How long to poll before returning (default and maximum 45 \u2014 the whole call is bounded by it, so a slow submit shortens the poll; 0 = submit + one snapshot). Large or research-heavy batches can take minutes \u2014 the result then carries still_running:true and the job_id for leadbay_lead_job_status."
|
|
25703
25758
|
}
|
|
25704
25759
|
},
|
|
25705
25760
|
additionalProperties: false
|
|
25706
25761
|
},
|
|
25707
25762
|
execute: async (client, params, ctx) => {
|
|
25763
|
+
const startedAt = Date.now();
|
|
25708
25764
|
params = coerceArrayParams(params, [
|
|
25709
25765
|
"lead_refs",
|
|
25710
25766
|
"contact_titles",
|
|
@@ -25764,7 +25820,7 @@ var qualifyLeads = {
|
|
|
25764
25820
|
const mocked = mockedSubmitPreview(submit, "leadbay_qualify_leads", client.region);
|
|
25765
25821
|
if (mocked)
|
|
25766
25822
|
return mocked;
|
|
25767
|
-
const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS2);
|
|
25823
|
+
const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS2, startedAt);
|
|
25768
25824
|
const snapshot = await snapshotAfterSubmit(client, submit.job_id, waitSeconds, ctx, submit.items_requested);
|
|
25769
25825
|
const done = TERMINAL_JOB_STATES.has(snapshot.job.state);
|
|
25770
25826
|
const indexed = submit.duplicate ?? false ? remapInputIndexes(snapshot.items, params.lead_refs) : { items: snapshot.items, remapped: true };
|
|
@@ -25841,7 +25897,7 @@ var qualifyLeads = {
|
|
|
25841
25897
|
// INCREMENTALLY instead of re-reading (and re-rendering) the
|
|
25842
25898
|
// rows already delivered in this response.
|
|
25843
25899
|
since: snapshot.next_since ?? null,
|
|
25844
|
-
suggested_wait_seconds: done ? 0 :
|
|
25900
|
+
suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
|
|
25845
25901
|
},
|
|
25846
25902
|
region: client.region
|
|
25847
25903
|
};
|
|
@@ -25876,7 +25932,7 @@ var leadJobStatus = {
|
|
|
25876
25932
|
},
|
|
25877
25933
|
wait_seconds: {
|
|
25878
25934
|
type: "number",
|
|
25879
|
-
description: "0 (default) = instant snapshot. >0 = keep polling up to this many seconds until the job is terminal \u2014 use
|
|
25935
|
+
description: "0 (default) = instant snapshot. >0 = keep polling up to this many seconds (maximum 45) until the job is terminal \u2014 use 45 when the user asked to wait for results."
|
|
25880
25936
|
},
|
|
25881
25937
|
compact: {
|
|
25882
25938
|
type: "boolean",
|
|
@@ -25928,7 +25984,7 @@ var leadJobStatus = {
|
|
|
25928
25984
|
// Same incremental handoff as the submit tools — following
|
|
25929
25985
|
// next_poll without the cursor re-reads the rows just returned.
|
|
25930
25986
|
since: snapshot.next_since ?? null,
|
|
25931
|
-
suggested_wait_seconds: done ? 0 :
|
|
25987
|
+
suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
|
|
25932
25988
|
},
|
|
25933
25989
|
region: client.region
|
|
25934
25990
|
};
|
|
@@ -29410,7 +29466,7 @@ function parseWriteEnv(env = process.env) {
|
|
|
29410
29466
|
}
|
|
29411
29467
|
|
|
29412
29468
|
// src/http-server.ts
|
|
29413
|
-
var VERSION = true ? "0.39.
|
|
29469
|
+
var VERSION = true ? "0.39.9" : "0.0.0-dev";
|
|
29414
29470
|
var PORT = Number(process.env.PORT ?? 8080);
|
|
29415
29471
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
29416
29472
|
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.39.
|
|
3
|
+
"version": "0.39.9",
|
|
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",
|