@leadbay/mcp 0.39.7 → 0.39.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -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: 60) |
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 ~60 when the user asked to wait for results.
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 ~60 to block-wait for
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: 60) |
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: 60) |
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: 60) |
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) |
@@ -21557,6 +21557,21 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
21557
21557
  const items = current?.lens_filter?.items ?? [];
21558
21558
  const item = items[0] ?? { criteria: [] };
21559
21559
  const criteria = item.criteria ? [...item.criteria] : [];
21560
+ const dropIds = (type, fromExcluded, ids) => {
21561
+ if (ids.length === 0)
21562
+ return;
21563
+ const idx = criteria.findIndex((c) => c.type === type && !!c.is_excluded === fromExcluded);
21564
+ if (idx < 0)
21565
+ return;
21566
+ const cur = criteria[idx];
21567
+ const key = type === "sector_ids" ? "sectors" : "locations";
21568
+ const held = cur[key] ?? [];
21569
+ const kept = held.filter((s) => !ids.includes(s));
21570
+ if (kept.length === 0)
21571
+ criteria.splice(idx, 1);
21572
+ else
21573
+ criteria[idx] = { ...cur, [key]: kept };
21574
+ };
21560
21575
  if (toAddSectors.length > 0) {
21561
21576
  const idx = criteria.findIndex((c) => c.type === "sector_ids" && !c.is_excluded);
21562
21577
  if (idx >= 0) {
@@ -21571,6 +21586,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
21571
21586
  });
21572
21587
  }
21573
21588
  }
21589
+ dropIds("sector_ids", true, toAddSectors);
21574
21590
  if (toExcludeSectors.length > 0) {
21575
21591
  const idx = criteria.findIndex((c) => c.type === "sector_ids" && c.is_excluded);
21576
21592
  if (idx >= 0) {
@@ -21585,6 +21601,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
21585
21601
  });
21586
21602
  }
21587
21603
  }
21604
+ dropIds("sector_ids", false, toExcludeSectors);
21588
21605
  if (toAddLocations.length > 0) {
21589
21606
  const idx = criteria.findIndex((c) => c.type === "location_ids" && !c.is_excluded);
21590
21607
  if (idx >= 0) {
@@ -21599,6 +21616,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
21599
21616
  });
21600
21617
  }
21601
21618
  }
21619
+ dropIds("location_ids", true, toAddLocations);
21602
21620
  if (toExcludeLocations.length > 0) {
21603
21621
  const idx = criteria.findIndex((c) => c.type === "location_ids" && c.is_excluded);
21604
21622
  if (idx >= 0) {
@@ -21613,6 +21631,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
21613
21631
  });
21614
21632
  }
21615
21633
  }
21634
+ dropIds("location_ids", false, toExcludeLocations);
21616
21635
  if (sizes && sizes.length > 0) {
21617
21636
  const normalizedSizes = sizes.map((s) => ({
21618
21637
  min: s.min ?? 0,
@@ -22773,12 +22792,15 @@ function normalizeSearchFilters(filters) {
22773
22792
  }
22774
22793
  return out;
22775
22794
  }
22776
- function clampWaitSeconds(requested, fallback) {
22777
- if (requested == null || Number.isNaN(requested))
22778
- return fallback;
22779
- return Math.min(Math.max(requested, 0), 180);
22795
+ function clampWaitSeconds(requested, fallback, startedAtMs) {
22796
+ const asked = requested == null || Number.isNaN(requested) ? fallback : requested;
22797
+ const bounded = Math.min(Math.max(asked, 0), MAX_WAIT_SECONDS);
22798
+ if (startedAtMs == null)
22799
+ return bounded;
22800
+ const spentSeconds = (Date.now() - startedAtMs) / 1e3;
22801
+ return Math.max(0, Math.min(bounded, MAX_WAIT_SECONDS - spentSeconds));
22780
22802
  }
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;
22803
+ 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
22804
  var init_mcp_job_helpers = __esm({
22783
22805
  "../core/dist/composite/_mcp-job-helpers.js"() {
22784
22806
  "use strict";
@@ -22842,6 +22864,7 @@ var init_mcp_job_helpers = __esm({
22842
22864
  COUNTRY_LOCATION_VALUES = buildCountryLocationValues();
22843
22865
  MAX_EXCLUDE_LEAD_IDS = 500;
22844
22866
  MAX_LEAD_REFS = 500;
22867
+ MAX_WAIT_SECONDS = 45;
22845
22868
  }
22846
22869
  });
22847
22870
 
@@ -23031,13 +23054,14 @@ var init_find_new_leads = __esm({
23031
23054
  },
23032
23055
  wait_seconds: {
23033
23056
  type: "number",
23034
- description: "How long to poll before returning (default 45, max 180, 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."
23057
+ 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
23058
  }
23036
23059
  },
23037
23060
  required: ["count", "request_id"],
23038
23061
  additionalProperties: false
23039
23062
  },
23040
23063
  execute: async (client, params, ctx) => {
23064
+ const startedAt = Date.now();
23041
23065
  params = coerceArrayParams(params, [
23042
23066
  "contact_titles",
23043
23067
  "channels",
@@ -23176,7 +23200,7 @@ var init_find_new_leads = __esm({
23176
23200
  const mocked = mockedSubmitPreview(submit, "leadbay_find_new_leads", client.region);
23177
23201
  if (mocked)
23178
23202
  return mocked;
23179
- const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS);
23203
+ const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS, startedAt);
23180
23204
  const snapshot = await snapshotAfterSubmit(client, submit.job_id, waitSeconds, ctx, params.count);
23181
23205
  const done = TERMINAL_JOB_STATES.has(snapshot.job.state);
23182
23206
  const { leads, skipped } = splitItems(snapshot);
@@ -23221,7 +23245,7 @@ var init_find_new_leads = __esm({
23221
23245
  // INCREMENTALLY instead of re-reading (and re-rendering) the
23222
23246
  // rows already delivered in this response.
23223
23247
  since: snapshot.next_since ?? null,
23224
- suggested_wait_seconds: done ? 0 : 60
23248
+ suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
23225
23249
  },
23226
23250
  ...sectorNote(attempt.rewritten),
23227
23251
  ...countryNote,
@@ -23346,7 +23370,7 @@ function identityAnswer(jobId, snapshot, items, offset) {
23346
23370
  job_id: jobId,
23347
23371
  compact: true,
23348
23372
  offset: nextOffset,
23349
- suggested_wait_seconds: done ? 0 : 60
23373
+ suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
23350
23374
  }
23351
23375
  };
23352
23376
  }
@@ -23571,12 +23595,13 @@ var init_qualify_leads = __esm({
23571
23595
  },
23572
23596
  wait_seconds: {
23573
23597
  type: "number",
23574
- description: "How long to poll before returning (default 45, max 180, 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."
23598
+ 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
23599
  }
23576
23600
  },
23577
23601
  additionalProperties: false
23578
23602
  },
23579
23603
  execute: async (client, params, ctx) => {
23604
+ const startedAt = Date.now();
23580
23605
  params = coerceArrayParams(params, [
23581
23606
  "lead_refs",
23582
23607
  "contact_titles",
@@ -23636,7 +23661,7 @@ var init_qualify_leads = __esm({
23636
23661
  const mocked = mockedSubmitPreview(submit, "leadbay_qualify_leads", client.region);
23637
23662
  if (mocked)
23638
23663
  return mocked;
23639
- const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS2);
23664
+ const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS2, startedAt);
23640
23665
  const snapshot = await snapshotAfterSubmit(client, submit.job_id, waitSeconds, ctx, submit.items_requested);
23641
23666
  const done = TERMINAL_JOB_STATES.has(snapshot.job.state);
23642
23667
  const indexed = submit.duplicate ?? false ? remapInputIndexes(snapshot.items, params.lead_refs) : { items: snapshot.items, remapped: true };
@@ -23713,7 +23738,7 @@ var init_qualify_leads = __esm({
23713
23738
  // INCREMENTALLY instead of re-reading (and re-rendering) the
23714
23739
  // rows already delivered in this response.
23715
23740
  since: snapshot.next_since ?? null,
23716
- suggested_wait_seconds: done ? 0 : 60
23741
+ suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
23717
23742
  },
23718
23743
  region: client.region
23719
23744
  };
@@ -23757,7 +23782,7 @@ var init_lead_job_status = __esm({
23757
23782
  },
23758
23783
  wait_seconds: {
23759
23784
  type: "number",
23760
- description: "0 (default) = instant snapshot. >0 = keep polling up to this many seconds until the job is terminal \u2014 use ~60 when the user asked to wait for results."
23785
+ 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
23786
  },
23762
23787
  compact: {
23763
23788
  type: "boolean",
@@ -23809,7 +23834,7 @@ var init_lead_job_status = __esm({
23809
23834
  // Same incremental handoff as the submit tools — following
23810
23835
  // next_poll without the cursor re-reads the rows just returned.
23811
23836
  since: snapshot.next_since ?? null,
23812
- suggested_wait_seconds: done ? 0 : 60
23837
+ suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
23813
23838
  },
23814
23839
  region: client.region
23815
23840
  };
@@ -27229,7 +27254,7 @@ searched. Render the delivery table and judge fit honestly: are these the
27229
27254
  kind of companies I asked for?
27230
27255
 
27231
27256
  - **\`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: 60\`) until
27257
+ not relaunch \u2014 poll \`leadbay_lead_job_status\` (\`wait_seconds: 45\`) until
27233
27258
  it goes terminal, reporting progress. Relaunching now burns an active-job
27234
27259
  slot and rate-limit budget on a search that may be about to deliver.
27235
27260
  - **On-profile** (terminal) \u2192 offer Phase 4.
@@ -27287,7 +27312,7 @@ When I want qualification evidence and/or reachable contacts:
27287
27312
  \`exploration_cap\` can examine more than that, so send the most recent 500
27288
27313
  rejects rather than an over-long list the tool refuses outright.
27289
27314
  3. While the job runs, poll with \`leadbay_lead_job_status\`
27290
- (\`wait_seconds: 60\`); report progress, not silence.
27315
+ (\`wait_seconds: 45\`); report progress, not silence.
27291
27316
 
27292
27317
  # PHASE 5 \u2014 DELIVER
27293
27318
 
@@ -32137,7 +32162,7 @@ var OAUTH_BASE_URLS = {
32137
32162
  fr: "https://staging.api.leadbay.app"
32138
32163
  }
32139
32164
  };
32140
- var VERSION = "0.39.7";
32165
+ var VERSION = "0.39.8";
32141
32166
  var HELP = `
32142
32167
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
32143
32168
 
@@ -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: 60\`) until
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: 60\`); report progress, not silence.
1461
+ (\`wait_seconds: 45\`); report progress, not silence.
1462
1462
 
1463
1463
  # PHASE 5 \u2014 DELIVER
1464
1464
 
@@ -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: 60) |
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 ~60 when the user asked to wait for results.
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 ~60 to block-wait for
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: 60) |
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: 60) |
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: 60) |
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) |
@@ -23742,6 +23742,21 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
23742
23742
  const items = current?.lens_filter?.items ?? [];
23743
23743
  const item = items[0] ?? { criteria: [] };
23744
23744
  const criteria = item.criteria ? [...item.criteria] : [];
23745
+ const dropIds = (type, fromExcluded, ids) => {
23746
+ if (ids.length === 0)
23747
+ return;
23748
+ const idx = criteria.findIndex((c) => c.type === type && !!c.is_excluded === fromExcluded);
23749
+ if (idx < 0)
23750
+ return;
23751
+ const cur = criteria[idx];
23752
+ const key = type === "sector_ids" ? "sectors" : "locations";
23753
+ const held = cur[key] ?? [];
23754
+ const kept = held.filter((s) => !ids.includes(s));
23755
+ if (kept.length === 0)
23756
+ criteria.splice(idx, 1);
23757
+ else
23758
+ criteria[idx] = { ...cur, [key]: kept };
23759
+ };
23745
23760
  if (toAddSectors.length > 0) {
23746
23761
  const idx = criteria.findIndex((c) => c.type === "sector_ids" && !c.is_excluded);
23747
23762
  if (idx >= 0) {
@@ -23756,6 +23771,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
23756
23771
  });
23757
23772
  }
23758
23773
  }
23774
+ dropIds("sector_ids", true, toAddSectors);
23759
23775
  if (toExcludeSectors.length > 0) {
23760
23776
  const idx = criteria.findIndex((c) => c.type === "sector_ids" && c.is_excluded);
23761
23777
  if (idx >= 0) {
@@ -23770,6 +23786,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
23770
23786
  });
23771
23787
  }
23772
23788
  }
23789
+ dropIds("sector_ids", false, toExcludeSectors);
23773
23790
  if (toAddLocations.length > 0) {
23774
23791
  const idx = criteria.findIndex((c) => c.type === "location_ids" && !c.is_excluded);
23775
23792
  if (idx >= 0) {
@@ -23784,6 +23801,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
23784
23801
  });
23785
23802
  }
23786
23803
  }
23804
+ dropIds("location_ids", true, toAddLocations);
23787
23805
  if (toExcludeLocations.length > 0) {
23788
23806
  const idx = criteria.findIndex((c) => c.type === "location_ids" && c.is_excluded);
23789
23807
  if (idx >= 0) {
@@ -23798,6 +23816,7 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocati
23798
23816
  });
23799
23817
  }
23800
23818
  }
23819
+ dropIds("location_ids", false, toExcludeLocations);
23801
23820
  if (sizes && sizes.length > 0) {
23802
23821
  const normalizedSizes = sizes.map((s) => ({
23803
23822
  min: s.min ?? 0,
@@ -24993,10 +25012,14 @@ function normalizeSearchFilters(filters) {
24993
25012
  }
24994
25013
  return out;
24995
25014
  }
24996
- function clampWaitSeconds(requested, fallback) {
24997
- if (requested == null || Number.isNaN(requested))
24998
- return fallback;
24999
- return Math.min(Math.max(requested, 0), 180);
25015
+ var MAX_WAIT_SECONDS = 45;
25016
+ function clampWaitSeconds(requested, fallback, startedAtMs) {
25017
+ const asked = requested == null || Number.isNaN(requested) ? fallback : requested;
25018
+ const bounded = Math.min(Math.max(asked, 0), MAX_WAIT_SECONDS);
25019
+ if (startedAtMs == null)
25020
+ return bounded;
25021
+ const spentSeconds = (Date.now() - startedAtMs) / 1e3;
25022
+ return Math.max(0, Math.min(bounded, MAX_WAIT_SECONDS - spentSeconds));
25000
25023
  }
25001
25024
 
25002
25025
  // ../core/dist/composite/find-new-leads.js
@@ -25177,13 +25200,14 @@ var findNewLeads = {
25177
25200
  },
25178
25201
  wait_seconds: {
25179
25202
  type: "number",
25180
- description: "How long to poll before returning (default 45, max 180, 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."
25203
+ 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
25204
  }
25182
25205
  },
25183
25206
  required: ["count", "request_id"],
25184
25207
  additionalProperties: false
25185
25208
  },
25186
25209
  execute: async (client, params, ctx) => {
25210
+ const startedAt = Date.now();
25187
25211
  params = coerceArrayParams(params, [
25188
25212
  "contact_titles",
25189
25213
  "channels",
@@ -25322,7 +25346,7 @@ var findNewLeads = {
25322
25346
  const mocked = mockedSubmitPreview(submit, "leadbay_find_new_leads", client.region);
25323
25347
  if (mocked)
25324
25348
  return mocked;
25325
- const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS);
25349
+ const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS, startedAt);
25326
25350
  const snapshot = await snapshotAfterSubmit(client, submit.job_id, waitSeconds, ctx, params.count);
25327
25351
  const done = TERMINAL_JOB_STATES.has(snapshot.job.state);
25328
25352
  const { leads, skipped } = splitItems(snapshot);
@@ -25367,7 +25391,7 @@ var findNewLeads = {
25367
25391
  // INCREMENTALLY instead of re-reading (and re-rendering) the
25368
25392
  // rows already delivered in this response.
25369
25393
  since: snapshot.next_since ?? null,
25370
- suggested_wait_seconds: done ? 0 : 60
25394
+ suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
25371
25395
  },
25372
25396
  ...sectorNote(attempt.rewritten),
25373
25397
  ...countryNote,
@@ -25501,7 +25525,7 @@ function identityAnswer(jobId, snapshot, items, offset) {
25501
25525
  job_id: jobId,
25502
25526
  compact: true,
25503
25527
  offset: nextOffset,
25504
- suggested_wait_seconds: done ? 0 : 60
25528
+ suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
25505
25529
  }
25506
25530
  };
25507
25531
  }
@@ -25699,12 +25723,13 @@ var qualifyLeads = {
25699
25723
  },
25700
25724
  wait_seconds: {
25701
25725
  type: "number",
25702
- description: "How long to poll before returning (default 45, max 180, 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."
25726
+ 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
25727
  }
25704
25728
  },
25705
25729
  additionalProperties: false
25706
25730
  },
25707
25731
  execute: async (client, params, ctx) => {
25732
+ const startedAt = Date.now();
25708
25733
  params = coerceArrayParams(params, [
25709
25734
  "lead_refs",
25710
25735
  "contact_titles",
@@ -25764,7 +25789,7 @@ var qualifyLeads = {
25764
25789
  const mocked = mockedSubmitPreview(submit, "leadbay_qualify_leads", client.region);
25765
25790
  if (mocked)
25766
25791
  return mocked;
25767
- const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS2);
25792
+ const waitSeconds = clampWaitSeconds(params.wait_seconds, DEFAULT_WAIT_SECONDS2, startedAt);
25768
25793
  const snapshot = await snapshotAfterSubmit(client, submit.job_id, waitSeconds, ctx, submit.items_requested);
25769
25794
  const done = TERMINAL_JOB_STATES.has(snapshot.job.state);
25770
25795
  const indexed = submit.duplicate ?? false ? remapInputIndexes(snapshot.items, params.lead_refs) : { items: snapshot.items, remapped: true };
@@ -25841,7 +25866,7 @@ var qualifyLeads = {
25841
25866
  // INCREMENTALLY instead of re-reading (and re-rendering) the
25842
25867
  // rows already delivered in this response.
25843
25868
  since: snapshot.next_since ?? null,
25844
- suggested_wait_seconds: done ? 0 : 60
25869
+ suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
25845
25870
  },
25846
25871
  region: client.region
25847
25872
  };
@@ -25876,7 +25901,7 @@ var leadJobStatus = {
25876
25901
  },
25877
25902
  wait_seconds: {
25878
25903
  type: "number",
25879
- description: "0 (default) = instant snapshot. >0 = keep polling up to this many seconds until the job is terminal \u2014 use ~60 when the user asked to wait for results."
25904
+ 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
25905
  },
25881
25906
  compact: {
25882
25907
  type: "boolean",
@@ -25928,7 +25953,7 @@ var leadJobStatus = {
25928
25953
  // Same incremental handoff as the submit tools — following
25929
25954
  // next_poll without the cursor re-reads the rows just returned.
25930
25955
  since: snapshot.next_since ?? null,
25931
- suggested_wait_seconds: done ? 0 : 60
25956
+ suggested_wait_seconds: done ? 0 : MAX_WAIT_SECONDS
25932
25957
  },
25933
25958
  region: client.region
25934
25959
  };
@@ -29410,7 +29435,7 @@ function parseWriteEnv(env = process.env) {
29410
29435
  }
29411
29436
 
29412
29437
  // src/http-server.ts
29413
- var VERSION = true ? "0.39.7" : "0.0.0-dev";
29438
+ var VERSION = true ? "0.39.8" : "0.0.0-dev";
29414
29439
  var PORT = Number(process.env.PORT ?? 8080);
29415
29440
  var HOST = process.env.HOST ?? "0.0.0.0";
29416
29441
  var logger = {
@@ -1805,7 +1805,7 @@ var init_installer_gui = __esm({
1805
1805
  init_install_dxt();
1806
1806
  init_install_shared();
1807
1807
  init_oauth();
1808
- VERSION = true ? "0.39.7" : "0.0.0-dev";
1808
+ VERSION = true ? "0.39.8" : "0.0.0-dev";
1809
1809
  MESSAGES = {
1810
1810
  en: {
1811
1811
  installer: {
@@ -1068,7 +1068,7 @@ async function oauthLogin(opts) {
1068
1068
  }
1069
1069
 
1070
1070
  // installer/installer-gui.ts
1071
- var VERSION = true ? "0.39.7" : "0.0.0-dev";
1071
+ var VERSION = true ? "0.39.8" : "0.0.0-dev";
1072
1072
  var MESSAGES = {
1073
1073
  en: {
1074
1074
  installer: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.39.7",
3
+ "version": "0.39.8",
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",