@leadbay/mcp 0.18.0 → 0.18.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/CHANGELOG.md +5 -0
- package/dist/bin.js +373 -11
- package/dist/http-server.js +355 -10
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog — @leadbay/mcp
|
|
2
2
|
|
|
3
|
+
## 0.18.1 — 2026-06-09
|
|
4
|
+
|
|
5
|
+
- **Quota rendering fix**: `leadbay_account_status` now renders the per-resource Daily / Weekly / Monthly **usage** table the API actually returns, instead of collapsing to "quota: null / no limits". Root cause: `quota_status` returns `count` (amount **used**) per resource per window with no cap field and a possibly-`null` `plan`; the old render hint tried to draw `used / cap` and gave up when there was no cap. The hint is now usage-only and explicitly warns that a missing cap / `null` plan is **not** "unlimited" or "no quota". `get-quota.ts` `outputSchema` corrected to the real `org` / `user.resources[]` shape (`{resource_type, count, window_type, resets_at}`, `count` = used), and a failed quota fetch is now distinguished from an empty quota.
|
|
6
|
+
- **Enrichment credit spend**: `leadbay_enrich_titles` surfaces the credit balance before and the actual spend after a run, reported discreetly rather than as a callout. Dropped the per-run "credits used" figure that conflated prior enrichments.
|
|
7
|
+
|
|
3
8
|
## 0.18.0 — 2026-06-08
|
|
4
9
|
|
|
5
10
|
Backend long-task notifications are now consumed by the MCP. When the user (or agent) initiates a bulk operation — contact enrichment, lead qualification, CSV / CRM import — the MCP listens to the backend WebSocket for the completion event and surfaces it on the agent's next tool call so prior outputs that depended on the now-finished data can be revised.
|
package/dist/bin.js
CHANGED
|
@@ -5103,6 +5103,7 @@ var init_composite_file_names = __esm({
|
|
|
5103
5103
|
"../core/dist/composite/_composite-file-names.js"() {
|
|
5104
5104
|
"use strict";
|
|
5105
5105
|
COMPOSITE_FILE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
5106
|
+
"leadbay_account_history",
|
|
5106
5107
|
"leadbay_account_status",
|
|
5107
5108
|
"leadbay_add_leads_to_campaign",
|
|
5108
5109
|
"leadbay_adjust_audience",
|
|
@@ -5438,10 +5439,156 @@ var init_notifications = __esm({
|
|
|
5438
5439
|
});
|
|
5439
5440
|
|
|
5440
5441
|
// ../core/dist/tool-descriptions.generated.js
|
|
5441
|
-
var leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_leads_to_campaign, leadbay_add_note, leadbay_adjust_audience, leadbay_agent_memory_capture, leadbay_agent_memory_recall, leadbay_agent_memory_review, leadbay_answer_clarification, leadbay_bulk_enrich_status, leadbay_bulk_qualify_leads, leadbay_campaign_call_sheet, leadbay_campaign_progression, leadbay_clear_selection, leadbay_clear_user_prompt, leadbay_create_campaign, leadbay_create_custom_field, leadbay_create_lens, leadbay_create_lens_draft, leadbay_create_topup_link, leadbay_deselect_leads, leadbay_discover_leads, leadbay_dislike_lead, leadbay_dismiss_clarification, leadbay_enrich_contacts, leadbay_enrich_titles, leadbay_extend_lens, leadbay_followups_map, leadbay_get_clarification, leadbay_get_contacts, leadbay_get_enrichment_job_titles, leadbay_get_epilogue_responses, leadbay_get_lead_activities, leadbay_get_lead_notes, leadbay_get_lead_profile, leadbay_get_lens_filter, leadbay_get_lens_scoring, leadbay_get_prospecting_actions, leadbay_get_quota, leadbay_get_selection_ids, leadbay_get_taste_profile, leadbay_get_user_prompt, leadbay_get_web_fetch, leadbay_import_and_qualify, leadbay_import_leads, leadbay_import_status, leadbay_launch_bulk_enrichment, leadbay_like_lead, leadbay_list_campaigns, leadbay_list_lenses, leadbay_list_locations, leadbay_list_mappable_fields, leadbay_list_sectors, leadbay_login, leadbay_my_lenses, leadbay_new_lens, leadbay_open_billing_portal, leadbay_pick_clarification, leadbay_prepare_outreach, leadbay_preview_bulk_enrichment, leadbay_promote_lens, leadbay_pull_followups, leadbay_pull_leads, leadbay_qualify_lead, leadbay_qualify_status, leadbay_recall_ordered_titles, leadbay_refine_prompt, leadbay_remove_epilogue, leadbay_remove_leads_from_campaign, leadbay_remove_pushback, leadbay_report_friction, leadbay_report_outreach, leadbay_research_lead_by_id, leadbay_research_lead_by_name_fuzzy, leadbay_resolve_import_rows, leadbay_seed_candidates, leadbay_select_leads, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_user_prompt, leadbay_tour_plan, leadbay_update_lens, leadbay_update_lens_filter;
|
|
5442
|
+
var leadbay_account_history, leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_leads_to_campaign, leadbay_add_note, leadbay_adjust_audience, leadbay_agent_memory_capture, leadbay_agent_memory_recall, leadbay_agent_memory_review, leadbay_answer_clarification, leadbay_bulk_enrich_status, leadbay_bulk_qualify_leads, leadbay_campaign_call_sheet, leadbay_campaign_progression, leadbay_clear_selection, leadbay_clear_user_prompt, leadbay_create_campaign, leadbay_create_custom_field, leadbay_create_lens, leadbay_create_lens_draft, leadbay_create_topup_link, leadbay_deselect_leads, leadbay_discover_leads, leadbay_dislike_lead, leadbay_dismiss_clarification, leadbay_enrich_contacts, leadbay_enrich_titles, leadbay_extend_lens, leadbay_followups_map, leadbay_get_clarification, leadbay_get_contacts, leadbay_get_enrichment_job_titles, leadbay_get_epilogue_responses, leadbay_get_lead_activities, leadbay_get_lead_notes, leadbay_get_lead_profile, leadbay_get_lens_filter, leadbay_get_lens_scoring, leadbay_get_prospecting_actions, leadbay_get_quota, leadbay_get_selection_ids, leadbay_get_taste_profile, leadbay_get_user_prompt, leadbay_get_web_fetch, leadbay_import_and_qualify, leadbay_import_leads, leadbay_import_status, leadbay_launch_bulk_enrichment, leadbay_like_lead, leadbay_list_campaigns, leadbay_list_lenses, leadbay_list_locations, leadbay_list_mappable_fields, leadbay_list_sectors, leadbay_login, leadbay_my_lenses, leadbay_new_lens, leadbay_open_billing_portal, leadbay_pick_clarification, leadbay_prepare_outreach, leadbay_preview_bulk_enrichment, leadbay_promote_lens, leadbay_pull_followups, leadbay_pull_leads, leadbay_qualify_lead, leadbay_qualify_status, leadbay_recall_ordered_titles, leadbay_refine_prompt, leadbay_remove_epilogue, leadbay_remove_leads_from_campaign, leadbay_remove_pushback, leadbay_report_friction, leadbay_report_outreach, leadbay_research_lead_by_id, leadbay_research_lead_by_name_fuzzy, leadbay_resolve_import_rows, leadbay_seed_candidates, leadbay_select_leads, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_user_prompt, leadbay_tour_plan, leadbay_update_lens, leadbay_update_lens_filter;
|
|
5442
5443
|
var init_tool_descriptions_generated = __esm({
|
|
5443
5444
|
"../core/dist/tool-descriptions.generated.js"() {
|
|
5444
5445
|
"use strict";
|
|
5446
|
+
leadbay_account_history = `## WHEN TO USE
|
|
5447
|
+
|
|
5448
|
+
Trigger phrases: "what's the history on this account", "why should I revisit this account", "summarize everything we've done with <Company>", "has this account gone cold", "give me the back-story on lead <UUID>".
|
|
5449
|
+
|
|
5450
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
5451
|
+
|
|
5452
|
+
Do NOT use for: "live signals only, no history" \u2192 \`leadbay_research_lead_by_id\`; "which accounts should I follow up with" \u2192 \`leadbay_pull_followups\`.
|
|
5453
|
+
|
|
5454
|
+
Prefer when: user wants ONE account's full back-story \u2014 notes + past activity + current signals together; pass \`leadId\`
|
|
5455
|
+
|
|
5456
|
+
Examples that SHOULD invoke this tool:
|
|
5457
|
+
- "What's the full history on this account \u2014 why did it resurface?"
|
|
5458
|
+
- "Summarize everything we've logged on that lead and whether it's worth another visit."
|
|
5459
|
+
|
|
5460
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
5461
|
+
- "Tell me the current AI take on this lead."
|
|
5462
|
+
- "Which accounts should I follow up with this week?"
|
|
5463
|
+
|
|
5464
|
+
## RENDER (quick)
|
|
5465
|
+
|
|
5466
|
+
Single resurfaced-account card. Lead the card with the current signal/trigger
|
|
5467
|
+
line (from \`signals\`), then a "History" section: notes digest
|
|
5468
|
+
(chronological) + the activity timeline. Close with a one-line "why revisit
|
|
5469
|
+
now" synthesis and a suggested outreach angle tied to the freshest signal.
|
|
5470
|
+
|
|
5471
|
+
---
|
|
5472
|
+
|
|
5473
|
+
Give me one account's full back-story in a single call. This is the tool for
|
|
5474
|
+
the **reprioritize-a-neglected-account** workflow: the user has an account
|
|
5475
|
+
that was contacted or quoted long ago and wants to know, in one shot, whether
|
|
5476
|
+
a fresh signal makes it worth another visit.
|
|
5477
|
+
|
|
5478
|
+
It bundles three reads on one \`leadId\`:
|
|
5479
|
+
|
|
5480
|
+
1. **Current state** \u2014 passed through verbatim from \`leadbay_research_lead_by_id\`:
|
|
5481
|
+
\`signals\` (web-research signals with hot flags + sources), \`firmographics\`,
|
|
5482
|
+
\`qualification\` answers, \`contacts\`, and \`engagement\` counts. This is the
|
|
5483
|
+
"why is this account hot NOW" layer.
|
|
5484
|
+
2. **\`notes\`** \u2014 the FULL list of notes logged on the record (note body +
|
|
5485
|
+
\`created_at\`), chronological. \`leadbay_research_lead_by_id\` only returns a
|
|
5486
|
+
\`notes_count\`; this tool returns the bodies so you can summarize the
|
|
5487
|
+
historical context.
|
|
5488
|
+
3. **\`activities\`** \u2014 the interaction timeline (\`{type, date}\` entries, newest
|
|
5489
|
+
first) plus the total count. Drives the "no contact in N months" judgement.
|
|
5490
|
+
|
|
5491
|
+
\`notes\` and \`activities\` **degrade gracefully**: if either read fails the card
|
|
5492
|
+
still returns with that section empty (\`notes: []\` /
|
|
5493
|
+
\`activities.total: 0\`). The current-state block is load-bearing \u2014 if research
|
|
5494
|
+
itself errors the whole call fails, because there is nothing to narrate.
|
|
5495
|
+
|
|
5496
|
+
Params: \`leadId\` (required UUID) and \`activityCount\` (optional, default 50,
|
|
5497
|
+
max 100).
|
|
5498
|
+
|
|
5499
|
+
Companion tools: **leadbay_research_lead_by_id** when the user only wants the
|
|
5500
|
+
live AI take with no history; **leadbay_pull_followups** when the user wants a
|
|
5501
|
+
LIST of accounts to act on rather than one account's deep history.
|
|
5502
|
+
|
|
5503
|
+
## RENDERING \u2014 single-record research card, mode-adaptive
|
|
5504
|
+
|
|
5505
|
+
Present as a single-record card, not a table. This tool gets invoked in two distinct user contexts \u2014 detect which and adapt the body density accordingly.
|
|
5506
|
+
|
|
5507
|
+
**MODE A \u2014 Discovery.** The user is evaluating whether to pursue this company as a target. Signals: "tell me about", "what do they do", "is this a fit", "research [company]", arrival via a click-through from \`leadbay_pull_leads\`, no prior outreach context in the conversation. Next step is usually qualify, deep-dive via \`leadbay_research_lead_by_id\`, or decide whether to start outreach.
|
|
5508
|
+
|
|
5509
|
+
**MODE B \u2014 Contact preparation.** The user is about to call or email someone at this company and needs the talking points. Signals: "I'm calling them", "draft an email", "before my call", "outreach prep", "what should I say", or the conversation has already touched on a specific contact. Next step is usually \`leadbay_prepare_outreach\`.
|
|
5510
|
+
|
|
5511
|
+
Default to MODE A when uncertain. Always offer the cross-mode pivot at the end so the user can redirect if you guessed wrong.
|
|
5512
|
+
|
|
5513
|
+
### Common structure (both modes)
|
|
5514
|
+
|
|
5515
|
+
- **Header** (H4 or H5): \`<10-segment score bar>\` \`[Company name](website)\`. Use the score-bar algorithm; the bar lives in a single inline-code span. Prefix \`https://\` to website if it's a bare hostname.
|
|
5516
|
+
- **Pill row** (immediately below the header): short location \xB7 compact size \xB7 social pill chips iterated over \`social_urls\` (each non-null platform becomes \`[<platform-label>](<url>)\`) \xB7 \`[website-domain](website)\` \xB7 \`\u260E phone\` when \`phone_numbers[]\` is non-empty (use the first number). All \` \xB7 \`-separated.
|
|
5517
|
+
- **Blurb**: render \`description\` (preferred) or \`short_description\` as a single blockquoted paragraph.
|
|
5518
|
+
- **Staleness line**: italic, \`"Researched <relative time>"\` from \`web_insights_fetched_at\`. Use \`"today"\` / \`"yesterday"\` / \`"N days ago"\` up to 30 days, then absolute date. Prefix with \`\u26A0\` if older than 30 days.
|
|
5519
|
+
- **Contacts table** (always at the bottom):
|
|
5520
|
+
\`\`\`
|
|
5521
|
+
| | Name | Title | LinkedIn |
|
|
5522
|
+
\`\`\`
|
|
5523
|
+
Markers in column 1:
|
|
5524
|
+
- \`\u2605\` \u2014 \`recommended_contact\` match.
|
|
5525
|
+
- \`\u{1F48E}\` \u2014 name fuzzy-matches a \`hot: true\` entry in \`web_insights\` key_people. (Use \`\u{1F48E}\`, not \`\u{1F525}\`, to avoid glyph collision with the follow-up status badge.)
|
|
5526
|
+
Sort \`\u2605\` first, then \`\u{1F48E}\`-only rows, then API order. Link the name via \`linkedin_page\` first; fall back to LinkedIn people-search with \`<First>+<Last>+<Company>\`. Append \`\xB0\` only when the fallback is in use AND \`social_presence.linkedin == false\`. Cap to 6 rows; if \`contacts_count > shown\`, end with \`"+N more \u2014 ask to see the full list"\`.
|
|
5527
|
+
|
|
5528
|
+
### MODE A body (Discovery, fuller, scannable)
|
|
5529
|
+
|
|
5530
|
+
Render each non-empty \`web_insights\` section as H5 with the emoji + label intact. Section order: \`\u{1F3E2} company profile\` \u2192 \`\u{1F4C8} business signals\` \u2192 \`\u{1F4A1} prospecting clues\` \u2192 \`\u{1F9E9} strategic positioning\` \u2192 \`\u{1F50E} technologies & innovation\`. Inside each, bullet 3\u20135 items. Sort \`hot: true\` items first. **Bold** the description text of hot items; leave cold items plain. Render \`source\` as \`[source](url)\` at the end; include \`date\` when present. Omit empty sections. Skip \`\u{1F517} social links\` (already in the pill row) and \`\u{1F464} key people\` (already in the contacts table).
|
|
5531
|
+
|
|
5532
|
+
### MODE B body (Contact preparation, tighter)
|
|
5533
|
+
|
|
5534
|
+
Render exactly two H5 sections:
|
|
5535
|
+
|
|
5536
|
+
##### \u{1F3AF} Conversation hooks
|
|
5537
|
+
|
|
5538
|
+
Distill the 3 most recent / most hot signals from \`\u{1F4C8} business signals\` and \`\u{1F4A1} prospecting clues\` into one-sentence talking points in salesperson voice. Strip the academic framing. Cite the source inline.
|
|
5539
|
+
|
|
5540
|
+
##### \u{1F464} About the person *(only when recommended_contact is non-empty)*
|
|
5541
|
+
|
|
5542
|
+
2-line summary: their title + any context from \`web_insights\` key_people. If they appear in a hot signal ("X appointed CEO"), surface that prominently.
|
|
5543
|
+
|
|
5544
|
+
Skip \u{1F3E2} profile, \u{1F9E9} strategic positioning, \u{1F50E} technologies in MODE B \u2014 context the user doesn't need for the next 30 seconds.
|
|
5545
|
+
|
|
5546
|
+
If \`qualification[]\` is non-empty, append one collapsed line: \`"Qualification: N questions answered, avg boost X"\` and offer to expand in NEXT STEPS.
|
|
5547
|
+
|
|
5548
|
+
**Hide:** \`id\`, \`lead.id\`, \`contact.id\`, \`lead.location.pos\`, \`web_fetch_in_progress\`, \`enrichment_in_progress\`, \`recommended_contact_title\` (duplicates \`recommended_contact.job_title\`), empty arrays, fields whose value is the string \`"null"\`, \`contact.source\` (internal), insights whose \`source\` is empty.
|
|
5549
|
+
|
|
5550
|
+
**Legend (print once below the card):** \`\` \`\u25B0\` firmographic \xB7 \`\u2756\` AI booster \xB7 \`\u25B1\` unfilled \xB7 \u2605 recommended \xB7 \u{1F48E} hot in web_insights \xB7 \xB0 = no company LinkedIn (fallback link only) \`\`
|
|
5551
|
+
|
|
5552
|
+
## Linking a contact's name
|
|
5553
|
+
|
|
5554
|
+
**MANDATORY: every contact name in your output \u2014 table cells, prose, headers, "Reach <Name>" callouts \u2014 MUST be wrapped in markdown link syntax \`[Name](URL)\`. Never render a contact name as bare text. A plain-text name is a broken contact card; the underlined name is the user's primary affordance for "take me to this person's profile". No "no URL available" exception \u2014 the search URL below is always constructable from name + company.**
|
|
5555
|
+
|
|
5556
|
+
URL priority (first applicable wins):
|
|
5557
|
+
|
|
5558
|
+
1. **Real profile** \u2014 \`contact.linkedin_page\` when it's a string starting with \`https://\` (the MCP coerces the legacy literal \`"null"\` string to real null before you see it).
|
|
5559
|
+
2. **Constructed people-search** \u2014 \`https://www.linkedin.com/search/results/people/?keywords=<First>+<Last>+<Company>\`. URL-encode params. Strip Inc / LLC / Corp / Ltd / GmbH / Co / S.A. / S.L. / PLC / AG / SAS / SARL suffixes from the company. Append a trailing \` \xB0\` to the rendered name ONLY when this fallback is in use AND \`social_presence.linkedin == false\`. Never append \`\xB0\` when a real \`linkedin_page\` was used.
|
|
5560
|
+
|
|
5561
|
+
Never link a person's name to the company's LinkedIn page (and vice versa) \u2014 the two surfaces are different and conflating them quietly degrades the workflow.
|
|
5562
|
+
|
|
5563
|
+
## Linking the company
|
|
5564
|
+
|
|
5565
|
+
Use the lead's \`website\` as the company-name link target \u2014 prefix \`https://\` if the value is a bare hostname. (The MCP does NOT synthesize a Leadbay-app deep-link URL; the team has not standardized one. Linking to \`website\` is always real data.)
|
|
5566
|
+
|
|
5567
|
+
When the response carries \`social_urls\` (the post-fix multi-platform URL block on rich-lead responses), render every non-null platform as a pill chip in the company-info row. Iterate over \`social_urls\`'s keys \u2014 never hardcode a fixed list \u2014 and emit each as \`[<platform-label>](<url>)\`. Skip platforms whose URL is null.
|
|
5568
|
+
|
|
5569
|
+
\`social_presence\` carries booleans for the same 6 platforms (crunchbase, facebook, instagram, linkedin, tiktok, twitter) \u2014 useful when you only care that the company has a profile somewhere. Use it as the \xB0-flag signal in the contact people-search fallback (see linking/contact-linkedin).
|
|
5570
|
+
|
|
5571
|
+
|
|
5572
|
+
|
|
5573
|
+
### RENDERING \u2014 the history layer (on top of the card above)
|
|
5574
|
+
|
|
5575
|
+
After the research card, add a **History** section so the user sees why this
|
|
5576
|
+
account resurfaced:
|
|
5577
|
+
|
|
5578
|
+
- **##### \u{1F5D2} Notes** \u2014 render \`notes\` chronologically (oldest \u2192 newest). Each
|
|
5579
|
+
as a bullet: \`**<relative date from created_at>** \u2014 <note body>\`. Cap at 8;
|
|
5580
|
+
if \`_meta.notes_count > shown\`, end with \`"+N more notes"\`. Omit the section
|
|
5581
|
+
entirely when \`notes\` is empty.
|
|
5582
|
+
- **##### \u{1F553} Timeline** \u2014 render \`activities.activities\` newest-first as a
|
|
5583
|
+
compact bullet list: \`<relative date> \xB7 <type>\`. Cap at 10; if
|
|
5584
|
+
\`activities.total > shown\`, end with \`"+N earlier"\`. Omit when empty.
|
|
5585
|
+
- **##### \u21BB Why revisit now** \u2014 one or two sentences synthesizing the freshest
|
|
5586
|
+
HOT signal from \`signals\` against the gap in \`activities\` (e.g. "Won a public
|
|
5587
|
+
tender last month; no logged contact since the 2024 quote \u2014 strong re-open
|
|
5588
|
+
angle"). Then one suggested outreach angle tied to that signal. This
|
|
5589
|
+
synthesis is the payload of the whole tool \u2014 always include it when there is
|
|
5590
|
+
at least one hot signal.
|
|
5591
|
+
`;
|
|
5445
5592
|
leadbay_account_status = `## WHEN TO USE
|
|
5446
5593
|
|
|
5447
5594
|
Trigger phrases: "what's my account status", "how much quota do I have", "what lens am I on", "I topped up / I bought credits / I added credits".
|
|
@@ -5462,10 +5609,13 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
|
5462
5609
|
|
|
5463
5610
|
## RENDER (quick)
|
|
5464
5611
|
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5612
|
+
If \`quota_error\` is set the call FAILED \u2014 quota unreadable; on 401/403 tell
|
|
5613
|
+
the user to reconnect. NEVER report zero usage or "no limits". Else render
|
|
5614
|
+
\`quota.org.resources\` (usage lives there, NOT at quota.resources) as a
|
|
5615
|
+
table, never prose: rows = resources (llm_completion \xB7 ai_rescore \xB7
|
|
5616
|
+
web_fetch + others), cols = Daily/Weekly/Monthly used \`count\` (= amount
|
|
5617
|
+
USED; no cap field, \`plan\` may be null \u2014 never invent a denominator).
|
|
5618
|
+
Empty = a 0 table, not "unlimited". Above: org + admin, lens.
|
|
5469
5619
|
|
|
5470
5620
|
---
|
|
5471
5621
|
|
|
@@ -5652,6 +5802,10 @@ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible
|
|
|
5652
5802
|
WHEN TO USE: poll this after leadbay_enrich_titles returns a \`bulk_id\`. Default \`include_contacts=false\` for cheap status polls; set \`include_contacts=true\` once \`all_done\` flips for the final read.
|
|
5653
5803
|
|
|
5654
5804
|
WHEN NOT TO USE: as a substitute for leadbay_research_lead_by_id \u2014 that already includes enriched contacts for a single lead.
|
|
5805
|
+
|
|
5806
|
+
## CREDIT COST \u2014 show the balance, discreetly
|
|
5807
|
+
|
|
5808
|
+
Once \`all_done\` is true the result carries \`credits_remaining\` (the post-spend AI-credit balance). Don't make a fuss \u2014 no sentence, no callout. Just append ONE small italic line in parentheses at the very END of your reply: \`_(N credits remaining)_\`. If \`credits_remaining\` is null (billing unavailable), omit the line \u2014 don't print 0. Do NOT report a "credits used" figure for this run: the per-contact cost can't be scoped to this specific enrichment (a lead's contact list mixes in earlier runs), so any "X used" number would be misleading.
|
|
5655
5809
|
`;
|
|
5656
5810
|
leadbay_bulk_qualify_leads = `Pick the next N unqualified leads in the active lens and qualify them (run AI rescore + web fetch). Pass \`wait_for_completion:false\` to return quickly with \`{status:'running', qualify_id}\`; poll leadbay_qualify_status with that id. With \`wait_for_completion\` omitted/true, the legacy behavior polls until the answers are populated or a budget is exhausted. Already-qualified leads (those with a non-null \`ai_agent_lead_score\`) are silently no-ops on the backend, so this composite paginates past them to find fresh candidates. On 429 mid-fanout, stops launching but keeps polling already-launched leads.
|
|
5657
5811
|
|
|
@@ -6116,6 +6270,10 @@ WHEN TO USE: when you have a specific \`contact_id\` (from leadbay_get_contacts)
|
|
|
6116
6270
|
|
|
6117
6271
|
WHEN NOT TO USE: for bulk enrichment by job title across many leads \u2014 use leadbay_enrich_titles, which handles the selection lifecycle and returns a clean preview/launch flow.
|
|
6118
6272
|
|
|
6273
|
+
## CREDIT COST \u2014 discreet
|
|
6274
|
+
|
|
6275
|
+
This is a paid call. The result returns \`credits_remaining\` (billing.ai_credits, read before the spend). Don't make a fuss about credits: only flag the balance if it's low (e.g. \u2264 a few credits) so the user can decide. Otherwise append it quietly as a small italic parenthetical at the END of your reply \u2014 \`_(N credits remaining)_\`. Don't quote an exact per-contact cost (the rate is backend-only). The actual per-contact cost (enrichment.credits_used) appears on the contact via leadbay_get_contacts after enrichment. If \`credits_remaining\` is null, omit the line \u2014 don't assume zero.
|
|
6276
|
+
|
|
6119
6277
|
This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
|
|
6120
6278
|
`;
|
|
6121
6279
|
leadbay_enrich_titles = `Order contact enrichments by job title across many leads. Contacts are NOT returned by default with a lead (Leadbay keeps enrichment out-of-band to control cost); the agent requests them on demand via this tool when it's ready to actually reach out. Two modes: (A) NO \`titles\` param \u2014 returns the available titles + Leadbay's \`title_suggestions\` + \`auto_included_titles\` + a count of enrichable contacts, so the agent can ask the user which titles to enrich. (B) \`titles\` given \u2014 calls preview, then launches if there's anything enrichable. On 429 returns \`{status:'quota_exceeded'}\` cleanly. Selection lifecycle is wrapped in a try/finally so the user's selection is left clean even on error.
|
|
@@ -6124,6 +6282,35 @@ WHEN TO USE: as the agent's go-to enrichment entry point, immediately before pro
|
|
|
6124
6282
|
|
|
6125
6283
|
WHEN NOT TO USE: to enrich a single contact \u2014 that's leadbay_enrich_contacts (granular). Speculatively, before the user has committed to outreaching \u2014 enrichment spends credits.
|
|
6126
6284
|
|
|
6285
|
+
## CREDIT COST \u2014 make spend visible
|
|
6286
|
+
|
|
6287
|
+
Enrichment is the main PAID operation. Surface cost both before and after.
|
|
6288
|
+
|
|
6289
|
+
**BEFORE (confirm before launching).** The discover / preview_only / dry_run modes return \`credits_remaining\` (the balance) and \`enrichable_contacts\` (the volume that would be enriched). Tell the user plainly: **"You have {credits_remaining} credits. This will enrich {enrichable_contacts} contacts."** then ask them to confirm before you launch the paid run. Route that confirmation through \`ask_user_input_v0\` ("Enrich {enrichable_contacts} contacts now?" \u2192 ["Yes, enrich", "No, cancel"]). Do NOT state an exact estimated cost \u2014 the per-contact credit rate lives backend-side and is not in the preview; show the balance and the count, never a fabricated "will cost N credits". If \`credits_remaining\` is null, billing is unavailable \u2014 say the balance is unknown, don't assume zero or unlimited.
|
|
6290
|
+
|
|
6291
|
+
**AFTER (show the balance, discreetly).** Once the job finishes \u2014 poll \`leadbay_bulk_enrich_status\`, which returns \`credits_remaining\` (the post-spend balance). Don't make a fuss: append ONE small italic line in parentheses at the very END of your reply \u2014 \`_(N credits remaining)_\`. Omit it if \`credits_remaining\` is null. Do NOT report a "credits used" figure: per-run cost can't be scoped reliably (a lead's contacts mix earlier enrichments), so only the balance is shown.
|
|
6292
|
+
|
|
6293
|
+
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
6294
|
+
|
|
6295
|
+
Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can route into. These ALWAYS produce a better UX than markdown tables / inline prose for the data shapes they support \u2014 they're tappable on mobile, persistent across turns, and integrate with the host's quick-actions.
|
|
6296
|
+
|
|
6297
|
+
**The Big Three** \u2014 when a tool result fits, route there:
|
|
6298
|
+
|
|
6299
|
+
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
6300
|
+
|---|---|---|
|
|
6301
|
+
| \`places_map_display_v0\` (Claude) | Result has \u22652 leads with \`location.city\` set, and the user's intent is geographic / "in person" / travel | \`{name: lead.company_name, address: "<city>, <country>", place_id: lead.location.place_id ?? omit, notes: <one-sentence pitch>}\` per location |
|
|
6302
|
+
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
6303
|
+
| \`ask_user_input_v0\` (Claude) | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | \`{questions: [{question: "What next?", type: "single_select", options: [<2-4 short button labels>]}]}\`; max 3 questions per call |
|
|
6304
|
+
|
|
6305
|
+
ChatGPT exposes the same routing pattern via \`_meta.openai/outputTemplate\`. We don't ship any custom widgets ourselves \u2014 this gate is exclusively about routing into the host's first-party widgets when the data shape fits.
|
|
6306
|
+
|
|
6307
|
+
**Rules:**
|
|
6308
|
+
- The widget IS the visual. Do NOT emit a markdown table or prose list of the same data alongside \u2014 that produces two competing UIs.
|
|
6309
|
+
- Pass identifiers (place_id, lead.id, contact_id) verbatim. Don't rewrite.
|
|
6310
|
+
- When the host doesn't expose the named widget, the agent falls back to the prose/table rendering the per-tool description already specifies. The directive is host-conditional; the fallback is automatic.
|
|
6311
|
+
- One short intro sentence in chat is enough \u2014 "Here are your 5 NYC follow-ups." Then route into the widget.
|
|
6312
|
+
|
|
6313
|
+
|
|
6127
6314
|
This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
|
|
6128
6315
|
`;
|
|
6129
6316
|
leadbay_extend_lens = `## WHEN TO USE
|
|
@@ -8934,10 +9121,36 @@ var init_get_quota = __esm({
|
|
|
8934
9121
|
outputSchema: {
|
|
8935
9122
|
type: "object",
|
|
8936
9123
|
properties: {
|
|
8937
|
-
plan: {
|
|
9124
|
+
plan: {
|
|
9125
|
+
type: ["string", "null"],
|
|
9126
|
+
description: "Org plan tier (e.g., FREE, TIER1, TIER2). May be null."
|
|
9127
|
+
},
|
|
9128
|
+
org: {
|
|
9129
|
+
type: "object",
|
|
9130
|
+
description: "Org-level quota state.",
|
|
9131
|
+
properties: {
|
|
9132
|
+
spend: { type: "array", description: "Reserved; empty in practice.", items: { type: "object" } },
|
|
9133
|
+
resources: {
|
|
9134
|
+
type: "array",
|
|
9135
|
+
description: "Per-resource per-window USAGE. Each: {resource_type, count, window_type, resets_at}. `count` is the amount USED in that window (not remaining, not a cap). No cap field is returned by the API.",
|
|
9136
|
+
items: { type: "object" }
|
|
9137
|
+
}
|
|
9138
|
+
}
|
|
9139
|
+
},
|
|
9140
|
+
user: {
|
|
9141
|
+
type: "object",
|
|
9142
|
+
description: "User-level quota state, same shape as `org`. May be absent.",
|
|
9143
|
+
properties: {
|
|
9144
|
+
spend: { type: "array", items: { type: "object" } },
|
|
9145
|
+
resources: { type: "array", items: { type: "object" } }
|
|
9146
|
+
}
|
|
9147
|
+
},
|
|
9148
|
+
// Legacy/compat: the live API does NOT return a top-level `windows`
|
|
9149
|
+
// array — usage lives in org/user.resources[]. Declared only so older
|
|
9150
|
+
// recorded fixtures still conform; do not rely on it.
|
|
8938
9151
|
windows: {
|
|
8939
9152
|
type: "array",
|
|
8940
|
-
description: "
|
|
9153
|
+
description: "Deprecated \u2014 not returned by the live API. Use org/user.resources[].",
|
|
8941
9154
|
items: { type: "object" }
|
|
8942
9155
|
}
|
|
8943
9156
|
}
|
|
@@ -15028,6 +15241,86 @@ var init_research_lead_by_name_fuzzy = __esm({
|
|
|
15028
15241
|
}
|
|
15029
15242
|
});
|
|
15030
15243
|
|
|
15244
|
+
// ../core/dist/composite/account-history.js
|
|
15245
|
+
var accountHistory;
|
|
15246
|
+
var init_account_history = __esm({
|
|
15247
|
+
"../core/dist/composite/account-history.js"() {
|
|
15248
|
+
"use strict";
|
|
15249
|
+
init_research_lead_by_id();
|
|
15250
|
+
init_tool_descriptions_generated();
|
|
15251
|
+
accountHistory = {
|
|
15252
|
+
name: "leadbay_account_history",
|
|
15253
|
+
annotations: {
|
|
15254
|
+
title: "One account's full back-story",
|
|
15255
|
+
readOnlyHint: true,
|
|
15256
|
+
destructiveHint: false,
|
|
15257
|
+
idempotentHint: true,
|
|
15258
|
+
openWorldHint: true
|
|
15259
|
+
},
|
|
15260
|
+
description: leadbay_account_history,
|
|
15261
|
+
inputSchema: {
|
|
15262
|
+
type: "object",
|
|
15263
|
+
properties: {
|
|
15264
|
+
leadId: { type: "string", description: "Lead UUID (required)" },
|
|
15265
|
+
activityCount: {
|
|
15266
|
+
type: "number",
|
|
15267
|
+
description: "Number of activity-timeline entries to return, max 100 (default: 50)."
|
|
15268
|
+
},
|
|
15269
|
+
lensId: {
|
|
15270
|
+
type: "number",
|
|
15271
|
+
description: "Lens id the lead came from (escape hatch \u2014 normally omit; defaults to the active lens). Pass it when researching a lead from a lens other than the current default, so the underlying /lenses/{lensId}/leads/{leadId} fetch doesn't 404 after the active lens changed."
|
|
15272
|
+
}
|
|
15273
|
+
},
|
|
15274
|
+
required: ["leadId"],
|
|
15275
|
+
additionalProperties: false
|
|
15276
|
+
},
|
|
15277
|
+
execute: async (client, params, ctx) => {
|
|
15278
|
+
const leadId = params.leadId;
|
|
15279
|
+
const count = Math.max(1, Math.min(Math.floor(params.activityCount ?? 50), 100));
|
|
15280
|
+
const [research, notes, activities] = await Promise.all([
|
|
15281
|
+
researchLeadById.execute(client, { leadId, lensId: params.lensId, response_format: "json" }, ctx),
|
|
15282
|
+
client.request("GET", `/leads/${leadId}/notes`).catch(() => []),
|
|
15283
|
+
client.request("GET", `/leads/${leadId}/activities?count=${count}`).catch(() => ({ items: [], pagination: { total: 0 } }))
|
|
15284
|
+
]);
|
|
15285
|
+
const r = research;
|
|
15286
|
+
const noteList = Array.isArray(notes) ? notes : [];
|
|
15287
|
+
const activityItems = Array.isArray(activities?.items) ? activities.items : [];
|
|
15288
|
+
return {
|
|
15289
|
+
lead: {
|
|
15290
|
+
id: r.firmographics?.id ?? leadId,
|
|
15291
|
+
name: r.firmographics?.name ?? null
|
|
15292
|
+
},
|
|
15293
|
+
// Current state — signals, firmographics, qualification, contacts,
|
|
15294
|
+
// engagement: passed through verbatim from research_lead_by_id so the
|
|
15295
|
+
// agent gets the live "why is this account hot NOW" picture.
|
|
15296
|
+
signals: r.signals ?? null,
|
|
15297
|
+
firmographics: r.firmographics ?? null,
|
|
15298
|
+
qualification: r.qualification ?? [],
|
|
15299
|
+
contacts: r.contacts ?? null,
|
|
15300
|
+
engagement: r.engagement ?? null,
|
|
15301
|
+
// Historical context — the part research only counts/summarizes.
|
|
15302
|
+
notes: noteList,
|
|
15303
|
+
activities: {
|
|
15304
|
+
activities: activityItems.map((a) => ({ type: a.type, date: a.date })),
|
|
15305
|
+
total: activities?.pagination?.total ?? 0
|
|
15306
|
+
},
|
|
15307
|
+
// Preserve research's pass-through metadata (agent_memory summary,
|
|
15308
|
+
// lens_id, web_fetch_in_progress, has_reachable_contact, …) — the
|
|
15309
|
+
// generated description advertises the memory protocol, so dropping
|
|
15310
|
+
// _meta.agent_memory would make history narratives miss stored
|
|
15311
|
+
// preferences. Spread research _meta first, then layer our counts.
|
|
15312
|
+
_meta: {
|
|
15313
|
+
...r._meta ?? {},
|
|
15314
|
+
region: client.region,
|
|
15315
|
+
notes_count: noteList.length,
|
|
15316
|
+
activities_returned: activityItems.length
|
|
15317
|
+
}
|
|
15318
|
+
};
|
|
15319
|
+
}
|
|
15320
|
+
};
|
|
15321
|
+
}
|
|
15322
|
+
});
|
|
15323
|
+
|
|
15031
15324
|
// ../core/dist/composite/recall-ordered-titles.js
|
|
15032
15325
|
var recallOrderedTitles;
|
|
15033
15326
|
var init_recall_ordered_titles = __esm({
|
|
@@ -15208,7 +15501,16 @@ var init_account_status = __esm({
|
|
|
15208
15501
|
},
|
|
15209
15502
|
quota: {
|
|
15210
15503
|
type: ["object", "null"],
|
|
15211
|
-
description: "Per-resource quota state (llm_completion, ai_rescore, web_fetch, LENS_EXTRA_REFILL) across daily/weekly/monthly windows. Null if /quota_status failed (
|
|
15504
|
+
description: "Per-resource quota state (llm_completion, ai_rescore, web_fetch, LENS_EXTRA_REFILL) across daily/weekly/monthly windows. Null if /quota_status failed (see quota_error) or genuinely returned nothing. Pre-check the LENS_EXTRA_REFILL entry before calling leadbay_extend_lens."
|
|
15505
|
+
},
|
|
15506
|
+
quota_error: {
|
|
15507
|
+
type: ["object", "null"],
|
|
15508
|
+
description: "Non-null ONLY when the quota_status call FAILED \u2014 {code, http_status, message}. A 401/403 means the token lacks quota scope: tell the user to reconnect / re-run OAuth. Treat as 'quota unreadable', NEVER as zero usage or 'no limits'.",
|
|
15509
|
+
properties: {
|
|
15510
|
+
code: { type: "string" },
|
|
15511
|
+
http_status: { type: ["number", "null"] },
|
|
15512
|
+
message: { type: "string" }
|
|
15513
|
+
}
|
|
15212
15514
|
},
|
|
15213
15515
|
notifications: {
|
|
15214
15516
|
type: "array",
|
|
@@ -15249,9 +15551,15 @@ var init_account_status = __esm({
|
|
|
15249
15551
|
execute: async (client, _params, ctx) => {
|
|
15250
15552
|
const me = await client.resolveMe();
|
|
15251
15553
|
let quota = null;
|
|
15554
|
+
let quota_error = null;
|
|
15252
15555
|
try {
|
|
15253
15556
|
quota = await client.request("GET", `/organizations/${me.organization.id}/quota_status`);
|
|
15254
15557
|
} catch (err) {
|
|
15558
|
+
quota_error = {
|
|
15559
|
+
code: err?.code ?? "QUOTA_STATUS_FAILED",
|
|
15560
|
+
http_status: err?._meta?.http_status ?? null,
|
|
15561
|
+
message: err?.message ?? "quota_status request failed"
|
|
15562
|
+
};
|
|
15255
15563
|
ctx?.logger?.warn?.(`account_status: quota_status failed: ${err?.message ?? err?.code ?? err}`);
|
|
15256
15564
|
}
|
|
15257
15565
|
return withAgentMemoryMeta(client, {
|
|
@@ -15281,6 +15589,9 @@ var init_account_status = __esm({
|
|
|
15281
15589
|
// Empty array when the WS listener isn't wired (OpenClaw, tests) OR
|
|
15282
15590
|
// when nothing has completed since the last ack.
|
|
15283
15591
|
notifications: ctx?.notificationsInbox?.list() ?? [],
|
|
15592
|
+
// Non-null ONLY when the quota_status call failed. The agent must treat
|
|
15593
|
+
// this as "could not read quota" (reauth on 401/403) — NOT as zero usage.
|
|
15594
|
+
quota_error,
|
|
15284
15595
|
_meta: {
|
|
15285
15596
|
region: client.region
|
|
15286
15597
|
}
|
|
@@ -17661,11 +17972,27 @@ var init_qualify_status = __esm({
|
|
|
17661
17972
|
}
|
|
17662
17973
|
});
|
|
17663
17974
|
|
|
17975
|
+
// ../core/dist/composite/_credits-helpers.js
|
|
17976
|
+
async function readCreditsRemaining(client, force = false) {
|
|
17977
|
+
try {
|
|
17978
|
+
const me = await client.resolveMe(force);
|
|
17979
|
+
return me.organization.billing?.ai_credits ?? null;
|
|
17980
|
+
} catch {
|
|
17981
|
+
return null;
|
|
17982
|
+
}
|
|
17983
|
+
}
|
|
17984
|
+
var init_credits_helpers = __esm({
|
|
17985
|
+
"../core/dist/composite/_credits-helpers.js"() {
|
|
17986
|
+
"use strict";
|
|
17987
|
+
}
|
|
17988
|
+
});
|
|
17989
|
+
|
|
17664
17990
|
// ../core/dist/composite/enrich-titles.js
|
|
17665
17991
|
var DEFAULT_CANDIDATE_COUNT, enrichTitles;
|
|
17666
17992
|
var init_enrich_titles = __esm({
|
|
17667
17993
|
"../core/dist/composite/enrich-titles.js"() {
|
|
17668
17994
|
"use strict";
|
|
17995
|
+
init_credits_helpers();
|
|
17669
17996
|
init_tool_descriptions_generated();
|
|
17670
17997
|
DEFAULT_CANDIDATE_COUNT = 25;
|
|
17671
17998
|
enrichTitles = {
|
|
@@ -17749,6 +18076,10 @@ var init_enrich_titles = __esm({
|
|
|
17749
18076
|
type: "number",
|
|
17750
18077
|
description: "Count of enrichable contacts at preview time."
|
|
17751
18078
|
},
|
|
18079
|
+
credits_remaining: {
|
|
18080
|
+
type: ["number", "null"],
|
|
18081
|
+
description: "AI-credit balance BEFORE launching (billing.ai_credits). Present in discover / preview_only / dry_run modes. Pair with enrichable_contacts to tell the user 'you have N credits, this will enrich M contacts' \u2014 do NOT estimate an exact cost (the per-contact rate is backend-only). Null = billing unavailable."
|
|
18082
|
+
},
|
|
17752
18083
|
selected_lead_count: {
|
|
17753
18084
|
type: "number",
|
|
17754
18085
|
description: "How many leads the selection covers."
|
|
@@ -17872,6 +18203,10 @@ var init_enrich_titles = __esm({
|
|
|
17872
18203
|
previously_enriched: previouslyEnriched,
|
|
17873
18204
|
enrichable_contacts: enrichableContacts,
|
|
17874
18205
|
selected_lead_count: leadIds.length,
|
|
18206
|
+
// BEFORE: show balance + volume. We can't estimate exact cost
|
|
18207
|
+
// (the per-contact rate is backend-only), so surface the balance
|
|
18208
|
+
// and the count, not a fabricated "will cost N".
|
|
18209
|
+
credits_remaining: await readCreditsRemaining(client),
|
|
17875
18210
|
next_action: "Pick titles to enrich and call leadbay_enrich_titles again with titles=[...]"
|
|
17876
18211
|
};
|
|
17877
18212
|
}
|
|
@@ -17894,7 +18229,8 @@ var init_enrich_titles = __esm({
|
|
|
17894
18229
|
preview,
|
|
17895
18230
|
launched: false,
|
|
17896
18231
|
message: "No enrichable contacts for the chosen titles. Try other titles from available_titles or recommendations.",
|
|
17897
|
-
available_titles: availableTitles
|
|
18232
|
+
available_titles: availableTitles,
|
|
18233
|
+
credits_remaining: await readCreditsRemaining(client)
|
|
17898
18234
|
};
|
|
17899
18235
|
}
|
|
17900
18236
|
if (params.dry_run) {
|
|
@@ -17902,7 +18238,12 @@ var init_enrich_titles = __esm({
|
|
|
17902
18238
|
mode: "dry_run",
|
|
17903
18239
|
preview,
|
|
17904
18240
|
launched: false,
|
|
17905
|
-
would_launch: { titles: params.titles, email, phone }
|
|
18241
|
+
would_launch: { titles: params.titles, email, phone },
|
|
18242
|
+
// BEFORE confirmation gate: balance + how many contacts WOULD be
|
|
18243
|
+
// enriched. enrichable_contacts is the volume; credits_remaining
|
|
18244
|
+
// the balance. No estimated cost — that rate is backend-only.
|
|
18245
|
+
enrichable_contacts: preview.enrichable_contacts,
|
|
18246
|
+
credits_remaining: await readCreditsRemaining(client)
|
|
17906
18247
|
};
|
|
17907
18248
|
}
|
|
17908
18249
|
const tracker = ctx?.bulkTracker;
|
|
@@ -18053,6 +18394,7 @@ var init_bulk_enrich_status = __esm({
|
|
|
18053
18394
|
"use strict";
|
|
18054
18395
|
init_get_contacts();
|
|
18055
18396
|
init_bulk_store();
|
|
18397
|
+
init_credits_helpers();
|
|
18056
18398
|
init_tool_descriptions_generated();
|
|
18057
18399
|
STATUS_FETCH_CONCURRENCY = 5;
|
|
18058
18400
|
bulkEnrichStatus = {
|
|
@@ -18119,6 +18461,10 @@ var init_bulk_enrich_status = __esm({
|
|
|
18119
18461
|
type: "boolean",
|
|
18120
18462
|
description: "True when overall_progress.done === total AND no partial_failures."
|
|
18121
18463
|
},
|
|
18464
|
+
credits_remaining: {
|
|
18465
|
+
type: ["number", "null"],
|
|
18466
|
+
description: "AI-credit balance re-read after the spend (force-refreshed /users/me \u2192 billing.ai_credits). Present only when all_done. Null = billing unavailable (don't read as zero). NOTE: a per-run 'credits used' figure is intentionally NOT returned \u2014 getContacts can't scope cost to this bulk, so any sum would conflate historical enrichments."
|
|
18467
|
+
},
|
|
18122
18468
|
partial_failures: {
|
|
18123
18469
|
type: "array",
|
|
18124
18470
|
description: "Per-lead errors observed during contacts fan-out (omitted when no failures).",
|
|
@@ -18225,6 +18571,7 @@ var init_bulk_enrich_status = __esm({
|
|
|
18225
18571
|
leads2 = record.lead_ids.map((id) => ({ lead_id: id }));
|
|
18226
18572
|
}
|
|
18227
18573
|
ctx?.logger?.info?.(`bulk.status_checked_via_notification bulk_id=${record.bulk_id} notification_id=${notifId} done=${bp.success_count}/${bp.total_count} in_progress=${inProgress} wall_ms=${Date.now() - startMs}`);
|
|
18574
|
+
const creditsRemaining2 = !inProgress ? await readCreditsRemaining(client, true) : null;
|
|
18228
18575
|
return {
|
|
18229
18576
|
bulk_id: record.bulk_id,
|
|
18230
18577
|
notification_id: notifId,
|
|
@@ -18244,6 +18591,7 @@ var init_bulk_enrich_status = __esm({
|
|
|
18244
18591
|
bulk_progress: bp,
|
|
18245
18592
|
in_progress: inProgress,
|
|
18246
18593
|
all_done: !inProgress,
|
|
18594
|
+
...!inProgress ? { credits_remaining: creditsRemaining2 } : {},
|
|
18247
18595
|
...bp.quota_hit_count > 0 ? {
|
|
18248
18596
|
quota_hit_hint: "Some contacts could not be enriched because the AI-credits quota was hit. Top up via leadbay_create_topup_link or wait for the window reset."
|
|
18249
18597
|
} : {}
|
|
@@ -18316,6 +18664,10 @@ var init_bulk_enrich_status = __esm({
|
|
|
18316
18664
|
};
|
|
18317
18665
|
const allDone = totalAll > 0 && totalDone === totalAll && partialFailures.length === 0;
|
|
18318
18666
|
ctx?.logger?.info?.(`bulk.status_checked bulk_id=${record.bulk_id} done=${totalDone} total=${totalAll} wall_ms=${Date.now() - startMs}`);
|
|
18667
|
+
let creditsRemaining = null;
|
|
18668
|
+
if (allDone) {
|
|
18669
|
+
creditsRemaining = await readCreditsRemaining(client, true);
|
|
18670
|
+
}
|
|
18319
18671
|
return {
|
|
18320
18672
|
bulk_id: record.bulk_id,
|
|
18321
18673
|
launched_at: record.launched_at,
|
|
@@ -18328,6 +18680,7 @@ var init_bulk_enrich_status = __esm({
|
|
|
18328
18680
|
leads,
|
|
18329
18681
|
overall_progress: overallProgress,
|
|
18330
18682
|
all_done: allDone,
|
|
18683
|
+
...allDone ? { credits_remaining: creditsRemaining } : {},
|
|
18331
18684
|
...partialFailures.length > 0 ? { partial_failures: partialFailures } : {}
|
|
18332
18685
|
};
|
|
18333
18686
|
}
|
|
@@ -20022,6 +20375,7 @@ __export(dist_exports, {
|
|
|
20022
20375
|
NotificationsInbox: () => NotificationsInbox,
|
|
20023
20376
|
NotificationsWsClient: () => NotificationsWsClient,
|
|
20024
20377
|
REGIONS: () => REGIONS,
|
|
20378
|
+
accountHistory: () => accountHistory,
|
|
20025
20379
|
accountStatus: () => accountStatus,
|
|
20026
20380
|
acknowledgeNotification: () => acknowledgeNotification,
|
|
20027
20381
|
addLeadsToCampaign: () => addLeadsToCampaign,
|
|
@@ -20219,6 +20573,7 @@ var init_dist = __esm({
|
|
|
20219
20573
|
init_campaign_call_sheet();
|
|
20220
20574
|
init_research_lead_by_id();
|
|
20221
20575
|
init_research_lead_by_name_fuzzy();
|
|
20576
|
+
init_account_history();
|
|
20222
20577
|
init_recall_ordered_titles();
|
|
20223
20578
|
init_account_status();
|
|
20224
20579
|
init_bulk_qualify_leads();
|
|
@@ -20311,6 +20666,13 @@ var init_dist = __esm({
|
|
|
20311
20666
|
campaignCallSheet,
|
|
20312
20667
|
researchLeadById,
|
|
20313
20668
|
researchLeadByNameFuzzy,
|
|
20669
|
+
// accountHistory layers FULL notes + activity timeline on top of research
|
|
20670
|
+
// so the agent can write the US4 "why has this dormant account resurfaced"
|
|
20671
|
+
// narrative in ONE call. ALWAYS exposed (compositeReadTools) — the underlying
|
|
20672
|
+
// get_lead_notes / get_lead_activities are ADVANCED-gated, but the
|
|
20673
|
+
// reprioritize-a-neglected-account workflow (#3630 GAP C) must work in a
|
|
20674
|
+
// default deployment without LEADBAY_MCP_ADVANCED=1.
|
|
20675
|
+
accountHistory,
|
|
20314
20676
|
recallOrderedTitles,
|
|
20315
20677
|
accountStatus,
|
|
20316
20678
|
bulkEnrichStatus,
|
|
@@ -24276,7 +24638,7 @@ var OAUTH_BASE_URLS = {
|
|
|
24276
24638
|
fr: "https://staging.api.leadbay.app"
|
|
24277
24639
|
}
|
|
24278
24640
|
};
|
|
24279
|
-
var VERSION = "0.18.
|
|
24641
|
+
var VERSION = "0.18.1";
|
|
24280
24642
|
var HELP = `
|
|
24281
24643
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
24282
24644
|
|
package/dist/http-server.js
CHANGED
|
@@ -6188,6 +6188,7 @@ function makeAgentMemoryTombstone(input) {
|
|
|
6188
6188
|
|
|
6189
6189
|
// ../core/dist/composite/_composite-file-names.js
|
|
6190
6190
|
var COMPOSITE_FILE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
6191
|
+
"leadbay_account_history",
|
|
6191
6192
|
"leadbay_account_status",
|
|
6192
6193
|
"leadbay_add_leads_to_campaign",
|
|
6193
6194
|
"leadbay_adjust_audience",
|
|
@@ -6226,6 +6227,152 @@ var COMPOSITE_FILE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
6226
6227
|
var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
6227
6228
|
|
|
6228
6229
|
// ../core/dist/tool-descriptions.generated.js
|
|
6230
|
+
var leadbay_account_history = `## WHEN TO USE
|
|
6231
|
+
|
|
6232
|
+
Trigger phrases: "what's the history on this account", "why should I revisit this account", "summarize everything we've done with <Company>", "has this account gone cold", "give me the back-story on lead <UUID>".
|
|
6233
|
+
|
|
6234
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
6235
|
+
|
|
6236
|
+
Do NOT use for: "live signals only, no history" \u2192 \`leadbay_research_lead_by_id\`; "which accounts should I follow up with" \u2192 \`leadbay_pull_followups\`.
|
|
6237
|
+
|
|
6238
|
+
Prefer when: user wants ONE account's full back-story \u2014 notes + past activity + current signals together; pass \`leadId\`
|
|
6239
|
+
|
|
6240
|
+
Examples that SHOULD invoke this tool:
|
|
6241
|
+
- "What's the full history on this account \u2014 why did it resurface?"
|
|
6242
|
+
- "Summarize everything we've logged on that lead and whether it's worth another visit."
|
|
6243
|
+
|
|
6244
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
6245
|
+
- "Tell me the current AI take on this lead."
|
|
6246
|
+
- "Which accounts should I follow up with this week?"
|
|
6247
|
+
|
|
6248
|
+
## RENDER (quick)
|
|
6249
|
+
|
|
6250
|
+
Single resurfaced-account card. Lead the card with the current signal/trigger
|
|
6251
|
+
line (from \`signals\`), then a "History" section: notes digest
|
|
6252
|
+
(chronological) + the activity timeline. Close with a one-line "why revisit
|
|
6253
|
+
now" synthesis and a suggested outreach angle tied to the freshest signal.
|
|
6254
|
+
|
|
6255
|
+
---
|
|
6256
|
+
|
|
6257
|
+
Give me one account's full back-story in a single call. This is the tool for
|
|
6258
|
+
the **reprioritize-a-neglected-account** workflow: the user has an account
|
|
6259
|
+
that was contacted or quoted long ago and wants to know, in one shot, whether
|
|
6260
|
+
a fresh signal makes it worth another visit.
|
|
6261
|
+
|
|
6262
|
+
It bundles three reads on one \`leadId\`:
|
|
6263
|
+
|
|
6264
|
+
1. **Current state** \u2014 passed through verbatim from \`leadbay_research_lead_by_id\`:
|
|
6265
|
+
\`signals\` (web-research signals with hot flags + sources), \`firmographics\`,
|
|
6266
|
+
\`qualification\` answers, \`contacts\`, and \`engagement\` counts. This is the
|
|
6267
|
+
"why is this account hot NOW" layer.
|
|
6268
|
+
2. **\`notes\`** \u2014 the FULL list of notes logged on the record (note body +
|
|
6269
|
+
\`created_at\`), chronological. \`leadbay_research_lead_by_id\` only returns a
|
|
6270
|
+
\`notes_count\`; this tool returns the bodies so you can summarize the
|
|
6271
|
+
historical context.
|
|
6272
|
+
3. **\`activities\`** \u2014 the interaction timeline (\`{type, date}\` entries, newest
|
|
6273
|
+
first) plus the total count. Drives the "no contact in N months" judgement.
|
|
6274
|
+
|
|
6275
|
+
\`notes\` and \`activities\` **degrade gracefully**: if either read fails the card
|
|
6276
|
+
still returns with that section empty (\`notes: []\` /
|
|
6277
|
+
\`activities.total: 0\`). The current-state block is load-bearing \u2014 if research
|
|
6278
|
+
itself errors the whole call fails, because there is nothing to narrate.
|
|
6279
|
+
|
|
6280
|
+
Params: \`leadId\` (required UUID) and \`activityCount\` (optional, default 50,
|
|
6281
|
+
max 100).
|
|
6282
|
+
|
|
6283
|
+
Companion tools: **leadbay_research_lead_by_id** when the user only wants the
|
|
6284
|
+
live AI take with no history; **leadbay_pull_followups** when the user wants a
|
|
6285
|
+
LIST of accounts to act on rather than one account's deep history.
|
|
6286
|
+
|
|
6287
|
+
## RENDERING \u2014 single-record research card, mode-adaptive
|
|
6288
|
+
|
|
6289
|
+
Present as a single-record card, not a table. This tool gets invoked in two distinct user contexts \u2014 detect which and adapt the body density accordingly.
|
|
6290
|
+
|
|
6291
|
+
**MODE A \u2014 Discovery.** The user is evaluating whether to pursue this company as a target. Signals: "tell me about", "what do they do", "is this a fit", "research [company]", arrival via a click-through from \`leadbay_pull_leads\`, no prior outreach context in the conversation. Next step is usually qualify, deep-dive via \`leadbay_research_lead_by_id\`, or decide whether to start outreach.
|
|
6292
|
+
|
|
6293
|
+
**MODE B \u2014 Contact preparation.** The user is about to call or email someone at this company and needs the talking points. Signals: "I'm calling them", "draft an email", "before my call", "outreach prep", "what should I say", or the conversation has already touched on a specific contact. Next step is usually \`leadbay_prepare_outreach\`.
|
|
6294
|
+
|
|
6295
|
+
Default to MODE A when uncertain. Always offer the cross-mode pivot at the end so the user can redirect if you guessed wrong.
|
|
6296
|
+
|
|
6297
|
+
### Common structure (both modes)
|
|
6298
|
+
|
|
6299
|
+
- **Header** (H4 or H5): \`<10-segment score bar>\` \`[Company name](website)\`. Use the score-bar algorithm; the bar lives in a single inline-code span. Prefix \`https://\` to website if it's a bare hostname.
|
|
6300
|
+
- **Pill row** (immediately below the header): short location \xB7 compact size \xB7 social pill chips iterated over \`social_urls\` (each non-null platform becomes \`[<platform-label>](<url>)\`) \xB7 \`[website-domain](website)\` \xB7 \`\u260E phone\` when \`phone_numbers[]\` is non-empty (use the first number). All \` \xB7 \`-separated.
|
|
6301
|
+
- **Blurb**: render \`description\` (preferred) or \`short_description\` as a single blockquoted paragraph.
|
|
6302
|
+
- **Staleness line**: italic, \`"Researched <relative time>"\` from \`web_insights_fetched_at\`. Use \`"today"\` / \`"yesterday"\` / \`"N days ago"\` up to 30 days, then absolute date. Prefix with \`\u26A0\` if older than 30 days.
|
|
6303
|
+
- **Contacts table** (always at the bottom):
|
|
6304
|
+
\`\`\`
|
|
6305
|
+
| | Name | Title | LinkedIn |
|
|
6306
|
+
\`\`\`
|
|
6307
|
+
Markers in column 1:
|
|
6308
|
+
- \`\u2605\` \u2014 \`recommended_contact\` match.
|
|
6309
|
+
- \`\u{1F48E}\` \u2014 name fuzzy-matches a \`hot: true\` entry in \`web_insights\` key_people. (Use \`\u{1F48E}\`, not \`\u{1F525}\`, to avoid glyph collision with the follow-up status badge.)
|
|
6310
|
+
Sort \`\u2605\` first, then \`\u{1F48E}\`-only rows, then API order. Link the name via \`linkedin_page\` first; fall back to LinkedIn people-search with \`<First>+<Last>+<Company>\`. Append \`\xB0\` only when the fallback is in use AND \`social_presence.linkedin == false\`. Cap to 6 rows; if \`contacts_count > shown\`, end with \`"+N more \u2014 ask to see the full list"\`.
|
|
6311
|
+
|
|
6312
|
+
### MODE A body (Discovery, fuller, scannable)
|
|
6313
|
+
|
|
6314
|
+
Render each non-empty \`web_insights\` section as H5 with the emoji + label intact. Section order: \`\u{1F3E2} company profile\` \u2192 \`\u{1F4C8} business signals\` \u2192 \`\u{1F4A1} prospecting clues\` \u2192 \`\u{1F9E9} strategic positioning\` \u2192 \`\u{1F50E} technologies & innovation\`. Inside each, bullet 3\u20135 items. Sort \`hot: true\` items first. **Bold** the description text of hot items; leave cold items plain. Render \`source\` as \`[source](url)\` at the end; include \`date\` when present. Omit empty sections. Skip \`\u{1F517} social links\` (already in the pill row) and \`\u{1F464} key people\` (already in the contacts table).
|
|
6315
|
+
|
|
6316
|
+
### MODE B body (Contact preparation, tighter)
|
|
6317
|
+
|
|
6318
|
+
Render exactly two H5 sections:
|
|
6319
|
+
|
|
6320
|
+
##### \u{1F3AF} Conversation hooks
|
|
6321
|
+
|
|
6322
|
+
Distill the 3 most recent / most hot signals from \`\u{1F4C8} business signals\` and \`\u{1F4A1} prospecting clues\` into one-sentence talking points in salesperson voice. Strip the academic framing. Cite the source inline.
|
|
6323
|
+
|
|
6324
|
+
##### \u{1F464} About the person *(only when recommended_contact is non-empty)*
|
|
6325
|
+
|
|
6326
|
+
2-line summary: their title + any context from \`web_insights\` key_people. If they appear in a hot signal ("X appointed CEO"), surface that prominently.
|
|
6327
|
+
|
|
6328
|
+
Skip \u{1F3E2} profile, \u{1F9E9} strategic positioning, \u{1F50E} technologies in MODE B \u2014 context the user doesn't need for the next 30 seconds.
|
|
6329
|
+
|
|
6330
|
+
If \`qualification[]\` is non-empty, append one collapsed line: \`"Qualification: N questions answered, avg boost X"\` and offer to expand in NEXT STEPS.
|
|
6331
|
+
|
|
6332
|
+
**Hide:** \`id\`, \`lead.id\`, \`contact.id\`, \`lead.location.pos\`, \`web_fetch_in_progress\`, \`enrichment_in_progress\`, \`recommended_contact_title\` (duplicates \`recommended_contact.job_title\`), empty arrays, fields whose value is the string \`"null"\`, \`contact.source\` (internal), insights whose \`source\` is empty.
|
|
6333
|
+
|
|
6334
|
+
**Legend (print once below the card):** \`\` \`\u25B0\` firmographic \xB7 \`\u2756\` AI booster \xB7 \`\u25B1\` unfilled \xB7 \u2605 recommended \xB7 \u{1F48E} hot in web_insights \xB7 \xB0 = no company LinkedIn (fallback link only) \`\`
|
|
6335
|
+
|
|
6336
|
+
## Linking a contact's name
|
|
6337
|
+
|
|
6338
|
+
**MANDATORY: every contact name in your output \u2014 table cells, prose, headers, "Reach <Name>" callouts \u2014 MUST be wrapped in markdown link syntax \`[Name](URL)\`. Never render a contact name as bare text. A plain-text name is a broken contact card; the underlined name is the user's primary affordance for "take me to this person's profile". No "no URL available" exception \u2014 the search URL below is always constructable from name + company.**
|
|
6339
|
+
|
|
6340
|
+
URL priority (first applicable wins):
|
|
6341
|
+
|
|
6342
|
+
1. **Real profile** \u2014 \`contact.linkedin_page\` when it's a string starting with \`https://\` (the MCP coerces the legacy literal \`"null"\` string to real null before you see it).
|
|
6343
|
+
2. **Constructed people-search** \u2014 \`https://www.linkedin.com/search/results/people/?keywords=<First>+<Last>+<Company>\`. URL-encode params. Strip Inc / LLC / Corp / Ltd / GmbH / Co / S.A. / S.L. / PLC / AG / SAS / SARL suffixes from the company. Append a trailing \` \xB0\` to the rendered name ONLY when this fallback is in use AND \`social_presence.linkedin == false\`. Never append \`\xB0\` when a real \`linkedin_page\` was used.
|
|
6344
|
+
|
|
6345
|
+
Never link a person's name to the company's LinkedIn page (and vice versa) \u2014 the two surfaces are different and conflating them quietly degrades the workflow.
|
|
6346
|
+
|
|
6347
|
+
## Linking the company
|
|
6348
|
+
|
|
6349
|
+
Use the lead's \`website\` as the company-name link target \u2014 prefix \`https://\` if the value is a bare hostname. (The MCP does NOT synthesize a Leadbay-app deep-link URL; the team has not standardized one. Linking to \`website\` is always real data.)
|
|
6350
|
+
|
|
6351
|
+
When the response carries \`social_urls\` (the post-fix multi-platform URL block on rich-lead responses), render every non-null platform as a pill chip in the company-info row. Iterate over \`social_urls\`'s keys \u2014 never hardcode a fixed list \u2014 and emit each as \`[<platform-label>](<url>)\`. Skip platforms whose URL is null.
|
|
6352
|
+
|
|
6353
|
+
\`social_presence\` carries booleans for the same 6 platforms (crunchbase, facebook, instagram, linkedin, tiktok, twitter) \u2014 useful when you only care that the company has a profile somewhere. Use it as the \xB0-flag signal in the contact people-search fallback (see linking/contact-linkedin).
|
|
6354
|
+
|
|
6355
|
+
|
|
6356
|
+
|
|
6357
|
+
### RENDERING \u2014 the history layer (on top of the card above)
|
|
6358
|
+
|
|
6359
|
+
After the research card, add a **History** section so the user sees why this
|
|
6360
|
+
account resurfaced:
|
|
6361
|
+
|
|
6362
|
+
- **##### \u{1F5D2} Notes** \u2014 render \`notes\` chronologically (oldest \u2192 newest). Each
|
|
6363
|
+
as a bullet: \`**<relative date from created_at>** \u2014 <note body>\`. Cap at 8;
|
|
6364
|
+
if \`_meta.notes_count > shown\`, end with \`"+N more notes"\`. Omit the section
|
|
6365
|
+
entirely when \`notes\` is empty.
|
|
6366
|
+
- **##### \u{1F553} Timeline** \u2014 render \`activities.activities\` newest-first as a
|
|
6367
|
+
compact bullet list: \`<relative date> \xB7 <type>\`. Cap at 10; if
|
|
6368
|
+
\`activities.total > shown\`, end with \`"+N earlier"\`. Omit when empty.
|
|
6369
|
+
- **##### \u21BB Why revisit now** \u2014 one or two sentences synthesizing the freshest
|
|
6370
|
+
HOT signal from \`signals\` against the gap in \`activities\` (e.g. "Won a public
|
|
6371
|
+
tender last month; no logged contact since the 2024 quote \u2014 strong re-open
|
|
6372
|
+
angle"). Then one suggested outreach angle tied to that signal. This
|
|
6373
|
+
synthesis is the payload of the whole tool \u2014 always include it when there is
|
|
6374
|
+
at least one hot signal.
|
|
6375
|
+
`;
|
|
6229
6376
|
var leadbay_account_status = `## WHEN TO USE
|
|
6230
6377
|
|
|
6231
6378
|
Trigger phrases: "what's my account status", "how much quota do I have", "what lens am I on", "I topped up / I bought credits / I added credits".
|
|
@@ -6246,10 +6393,13 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
|
6246
6393
|
|
|
6247
6394
|
## RENDER (quick)
|
|
6248
6395
|
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6396
|
+
If \`quota_error\` is set the call FAILED \u2014 quota unreadable; on 401/403 tell
|
|
6397
|
+
the user to reconnect. NEVER report zero usage or "no limits". Else render
|
|
6398
|
+
\`quota.org.resources\` (usage lives there, NOT at quota.resources) as a
|
|
6399
|
+
table, never prose: rows = resources (llm_completion \xB7 ai_rescore \xB7
|
|
6400
|
+
web_fetch + others), cols = Daily/Weekly/Monthly used \`count\` (= amount
|
|
6401
|
+
USED; no cap field, \`plan\` may be null \u2014 never invent a denominator).
|
|
6402
|
+
Empty = a 0 table, not "unlimited". Above: org + admin, lens.
|
|
6253
6403
|
|
|
6254
6404
|
---
|
|
6255
6405
|
|
|
@@ -6436,6 +6586,10 @@ var leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk en
|
|
|
6436
6586
|
WHEN TO USE: poll this after leadbay_enrich_titles returns a \`bulk_id\`. Default \`include_contacts=false\` for cheap status polls; set \`include_contacts=true\` once \`all_done\` flips for the final read.
|
|
6437
6587
|
|
|
6438
6588
|
WHEN NOT TO USE: as a substitute for leadbay_research_lead_by_id \u2014 that already includes enriched contacts for a single lead.
|
|
6589
|
+
|
|
6590
|
+
## CREDIT COST \u2014 show the balance, discreetly
|
|
6591
|
+
|
|
6592
|
+
Once \`all_done\` is true the result carries \`credits_remaining\` (the post-spend AI-credit balance). Don't make a fuss \u2014 no sentence, no callout. Just append ONE small italic line in parentheses at the very END of your reply: \`_(N credits remaining)_\`. If \`credits_remaining\` is null (billing unavailable), omit the line \u2014 don't print 0. Do NOT report a "credits used" figure for this run: the per-contact cost can't be scoped to this specific enrichment (a lead's contact list mixes in earlier runs), so any "X used" number would be misleading.
|
|
6439
6593
|
`;
|
|
6440
6594
|
var leadbay_bulk_qualify_leads = `Pick the next N unqualified leads in the active lens and qualify them (run AI rescore + web fetch). Pass \`wait_for_completion:false\` to return quickly with \`{status:'running', qualify_id}\`; poll leadbay_qualify_status with that id. With \`wait_for_completion\` omitted/true, the legacy behavior polls until the answers are populated or a budget is exhausted. Already-qualified leads (those with a non-null \`ai_agent_lead_score\`) are silently no-ops on the backend, so this composite paginates past them to find fresh candidates. On 429 mid-fanout, stops launching but keeps polling already-launched leads.
|
|
6441
6595
|
|
|
@@ -6900,6 +7054,10 @@ WHEN TO USE: when you have a specific \`contact_id\` (from leadbay_get_contacts)
|
|
|
6900
7054
|
|
|
6901
7055
|
WHEN NOT TO USE: for bulk enrichment by job title across many leads \u2014 use leadbay_enrich_titles, which handles the selection lifecycle and returns a clean preview/launch flow.
|
|
6902
7056
|
|
|
7057
|
+
## CREDIT COST \u2014 discreet
|
|
7058
|
+
|
|
7059
|
+
This is a paid call. The result returns \`credits_remaining\` (billing.ai_credits, read before the spend). Don't make a fuss about credits: only flag the balance if it's low (e.g. \u2264 a few credits) so the user can decide. Otherwise append it quietly as a small italic parenthetical at the END of your reply \u2014 \`_(N credits remaining)_\`. Don't quote an exact per-contact cost (the rate is backend-only). The actual per-contact cost (enrichment.credits_used) appears on the contact via leadbay_get_contacts after enrichment. If \`credits_remaining\` is null, omit the line \u2014 don't assume zero.
|
|
7060
|
+
|
|
6903
7061
|
This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
|
|
6904
7062
|
`;
|
|
6905
7063
|
var leadbay_enrich_titles = `Order contact enrichments by job title across many leads. Contacts are NOT returned by default with a lead (Leadbay keeps enrichment out-of-band to control cost); the agent requests them on demand via this tool when it's ready to actually reach out. Two modes: (A) NO \`titles\` param \u2014 returns the available titles + Leadbay's \`title_suggestions\` + \`auto_included_titles\` + a count of enrichable contacts, so the agent can ask the user which titles to enrich. (B) \`titles\` given \u2014 calls preview, then launches if there's anything enrichable. On 429 returns \`{status:'quota_exceeded'}\` cleanly. Selection lifecycle is wrapped in a try/finally so the user's selection is left clean even on error.
|
|
@@ -6908,6 +7066,35 @@ WHEN TO USE: as the agent's go-to enrichment entry point, immediately before pro
|
|
|
6908
7066
|
|
|
6909
7067
|
WHEN NOT TO USE: to enrich a single contact \u2014 that's leadbay_enrich_contacts (granular). Speculatively, before the user has committed to outreaching \u2014 enrichment spends credits.
|
|
6910
7068
|
|
|
7069
|
+
## CREDIT COST \u2014 make spend visible
|
|
7070
|
+
|
|
7071
|
+
Enrichment is the main PAID operation. Surface cost both before and after.
|
|
7072
|
+
|
|
7073
|
+
**BEFORE (confirm before launching).** The discover / preview_only / dry_run modes return \`credits_remaining\` (the balance) and \`enrichable_contacts\` (the volume that would be enriched). Tell the user plainly: **"You have {credits_remaining} credits. This will enrich {enrichable_contacts} contacts."** then ask them to confirm before you launch the paid run. Route that confirmation through \`ask_user_input_v0\` ("Enrich {enrichable_contacts} contacts now?" \u2192 ["Yes, enrich", "No, cancel"]). Do NOT state an exact estimated cost \u2014 the per-contact credit rate lives backend-side and is not in the preview; show the balance and the count, never a fabricated "will cost N credits". If \`credits_remaining\` is null, billing is unavailable \u2014 say the balance is unknown, don't assume zero or unlimited.
|
|
7074
|
+
|
|
7075
|
+
**AFTER (show the balance, discreetly).** Once the job finishes \u2014 poll \`leadbay_bulk_enrich_status\`, which returns \`credits_remaining\` (the post-spend balance). Don't make a fuss: append ONE small italic line in parentheses at the very END of your reply \u2014 \`_(N credits remaining)_\`. Omit it if \`credits_remaining\` is null. Do NOT report a "credits used" figure: per-run cost can't be scoped reliably (a lead's contacts mix earlier enrichments), so only the balance is shown.
|
|
7076
|
+
|
|
7077
|
+
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
7078
|
+
|
|
7079
|
+
Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can route into. These ALWAYS produce a better UX than markdown tables / inline prose for the data shapes they support \u2014 they're tappable on mobile, persistent across turns, and integrate with the host's quick-actions.
|
|
7080
|
+
|
|
7081
|
+
**The Big Three** \u2014 when a tool result fits, route there:
|
|
7082
|
+
|
|
7083
|
+
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7084
|
+
|---|---|---|
|
|
7085
|
+
| \`places_map_display_v0\` (Claude) | Result has \u22652 leads with \`location.city\` set, and the user's intent is geographic / "in person" / travel | \`{name: lead.company_name, address: "<city>, <country>", place_id: lead.location.place_id ?? omit, notes: <one-sentence pitch>}\` per location |
|
|
7086
|
+
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
7087
|
+
| \`ask_user_input_v0\` (Claude) | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | \`{questions: [{question: "What next?", type: "single_select", options: [<2-4 short button labels>]}]}\`; max 3 questions per call |
|
|
7088
|
+
|
|
7089
|
+
ChatGPT exposes the same routing pattern via \`_meta.openai/outputTemplate\`. We don't ship any custom widgets ourselves \u2014 this gate is exclusively about routing into the host's first-party widgets when the data shape fits.
|
|
7090
|
+
|
|
7091
|
+
**Rules:**
|
|
7092
|
+
- The widget IS the visual. Do NOT emit a markdown table or prose list of the same data alongside \u2014 that produces two competing UIs.
|
|
7093
|
+
- Pass identifiers (place_id, lead.id, contact_id) verbatim. Don't rewrite.
|
|
7094
|
+
- When the host doesn't expose the named widget, the agent falls back to the prose/table rendering the per-tool description already specifies. The directive is host-conditional; the fallback is automatic.
|
|
7095
|
+
- One short intro sentence in chat is enough \u2014 "Here are your 5 NYC follow-ups." Then route into the widget.
|
|
7096
|
+
|
|
7097
|
+
|
|
6911
7098
|
This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
|
|
6912
7099
|
`;
|
|
6913
7100
|
var leadbay_extend_lens = `## WHEN TO USE
|
|
@@ -9675,10 +9862,36 @@ var getQuota = {
|
|
|
9675
9862
|
outputSchema: {
|
|
9676
9863
|
type: "object",
|
|
9677
9864
|
properties: {
|
|
9678
|
-
plan: {
|
|
9865
|
+
plan: {
|
|
9866
|
+
type: ["string", "null"],
|
|
9867
|
+
description: "Org plan tier (e.g., FREE, TIER1, TIER2). May be null."
|
|
9868
|
+
},
|
|
9869
|
+
org: {
|
|
9870
|
+
type: "object",
|
|
9871
|
+
description: "Org-level quota state.",
|
|
9872
|
+
properties: {
|
|
9873
|
+
spend: { type: "array", description: "Reserved; empty in practice.", items: { type: "object" } },
|
|
9874
|
+
resources: {
|
|
9875
|
+
type: "array",
|
|
9876
|
+
description: "Per-resource per-window USAGE. Each: {resource_type, count, window_type, resets_at}. `count` is the amount USED in that window (not remaining, not a cap). No cap field is returned by the API.",
|
|
9877
|
+
items: { type: "object" }
|
|
9878
|
+
}
|
|
9879
|
+
}
|
|
9880
|
+
},
|
|
9881
|
+
user: {
|
|
9882
|
+
type: "object",
|
|
9883
|
+
description: "User-level quota state, same shape as `org`. May be absent.",
|
|
9884
|
+
properties: {
|
|
9885
|
+
spend: { type: "array", items: { type: "object" } },
|
|
9886
|
+
resources: { type: "array", items: { type: "object" } }
|
|
9887
|
+
}
|
|
9888
|
+
},
|
|
9889
|
+
// Legacy/compat: the live API does NOT return a top-level `windows`
|
|
9890
|
+
// array — usage lives in org/user.resources[]. Declared only so older
|
|
9891
|
+
// recorded fixtures still conform; do not rely on it.
|
|
9679
9892
|
windows: {
|
|
9680
9893
|
type: "array",
|
|
9681
|
-
description: "
|
|
9894
|
+
description: "Deprecated \u2014 not returned by the live API. Use org/user.resources[].",
|
|
9682
9895
|
items: { type: "object" }
|
|
9683
9896
|
}
|
|
9684
9897
|
}
|
|
@@ -15317,6 +15530,78 @@ var researchLeadByNameFuzzy = {
|
|
|
15317
15530
|
}
|
|
15318
15531
|
};
|
|
15319
15532
|
|
|
15533
|
+
// ../core/dist/composite/account-history.js
|
|
15534
|
+
var accountHistory = {
|
|
15535
|
+
name: "leadbay_account_history",
|
|
15536
|
+
annotations: {
|
|
15537
|
+
title: "One account's full back-story",
|
|
15538
|
+
readOnlyHint: true,
|
|
15539
|
+
destructiveHint: false,
|
|
15540
|
+
idempotentHint: true,
|
|
15541
|
+
openWorldHint: true
|
|
15542
|
+
},
|
|
15543
|
+
description: leadbay_account_history,
|
|
15544
|
+
inputSchema: {
|
|
15545
|
+
type: "object",
|
|
15546
|
+
properties: {
|
|
15547
|
+
leadId: { type: "string", description: "Lead UUID (required)" },
|
|
15548
|
+
activityCount: {
|
|
15549
|
+
type: "number",
|
|
15550
|
+
description: "Number of activity-timeline entries to return, max 100 (default: 50)."
|
|
15551
|
+
},
|
|
15552
|
+
lensId: {
|
|
15553
|
+
type: "number",
|
|
15554
|
+
description: "Lens id the lead came from (escape hatch \u2014 normally omit; defaults to the active lens). Pass it when researching a lead from a lens other than the current default, so the underlying /lenses/{lensId}/leads/{leadId} fetch doesn't 404 after the active lens changed."
|
|
15555
|
+
}
|
|
15556
|
+
},
|
|
15557
|
+
required: ["leadId"],
|
|
15558
|
+
additionalProperties: false
|
|
15559
|
+
},
|
|
15560
|
+
execute: async (client, params, ctx) => {
|
|
15561
|
+
const leadId = params.leadId;
|
|
15562
|
+
const count = Math.max(1, Math.min(Math.floor(params.activityCount ?? 50), 100));
|
|
15563
|
+
const [research, notes, activities] = await Promise.all([
|
|
15564
|
+
researchLeadById.execute(client, { leadId, lensId: params.lensId, response_format: "json" }, ctx),
|
|
15565
|
+
client.request("GET", `/leads/${leadId}/notes`).catch(() => []),
|
|
15566
|
+
client.request("GET", `/leads/${leadId}/activities?count=${count}`).catch(() => ({ items: [], pagination: { total: 0 } }))
|
|
15567
|
+
]);
|
|
15568
|
+
const r = research;
|
|
15569
|
+
const noteList = Array.isArray(notes) ? notes : [];
|
|
15570
|
+
const activityItems = Array.isArray(activities?.items) ? activities.items : [];
|
|
15571
|
+
return {
|
|
15572
|
+
lead: {
|
|
15573
|
+
id: r.firmographics?.id ?? leadId,
|
|
15574
|
+
name: r.firmographics?.name ?? null
|
|
15575
|
+
},
|
|
15576
|
+
// Current state — signals, firmographics, qualification, contacts,
|
|
15577
|
+
// engagement: passed through verbatim from research_lead_by_id so the
|
|
15578
|
+
// agent gets the live "why is this account hot NOW" picture.
|
|
15579
|
+
signals: r.signals ?? null,
|
|
15580
|
+
firmographics: r.firmographics ?? null,
|
|
15581
|
+
qualification: r.qualification ?? [],
|
|
15582
|
+
contacts: r.contacts ?? null,
|
|
15583
|
+
engagement: r.engagement ?? null,
|
|
15584
|
+
// Historical context — the part research only counts/summarizes.
|
|
15585
|
+
notes: noteList,
|
|
15586
|
+
activities: {
|
|
15587
|
+
activities: activityItems.map((a) => ({ type: a.type, date: a.date })),
|
|
15588
|
+
total: activities?.pagination?.total ?? 0
|
|
15589
|
+
},
|
|
15590
|
+
// Preserve research's pass-through metadata (agent_memory summary,
|
|
15591
|
+
// lens_id, web_fetch_in_progress, has_reachable_contact, …) — the
|
|
15592
|
+
// generated description advertises the memory protocol, so dropping
|
|
15593
|
+
// _meta.agent_memory would make history narratives miss stored
|
|
15594
|
+
// preferences. Spread research _meta first, then layer our counts.
|
|
15595
|
+
_meta: {
|
|
15596
|
+
...r._meta ?? {},
|
|
15597
|
+
region: client.region,
|
|
15598
|
+
notes_count: noteList.length,
|
|
15599
|
+
activities_returned: activityItems.length
|
|
15600
|
+
}
|
|
15601
|
+
};
|
|
15602
|
+
}
|
|
15603
|
+
};
|
|
15604
|
+
|
|
15320
15605
|
// ../core/dist/composite/recall-ordered-titles.js
|
|
15321
15606
|
var recallOrderedTitles = {
|
|
15322
15607
|
name: "leadbay_recall_ordered_titles",
|
|
@@ -15484,7 +15769,16 @@ var accountStatus = {
|
|
|
15484
15769
|
},
|
|
15485
15770
|
quota: {
|
|
15486
15771
|
type: ["object", "null"],
|
|
15487
|
-
description: "Per-resource quota state (llm_completion, ai_rescore, web_fetch, LENS_EXTRA_REFILL) across daily/weekly/monthly windows. Null if /quota_status failed (
|
|
15772
|
+
description: "Per-resource quota state (llm_completion, ai_rescore, web_fetch, LENS_EXTRA_REFILL) across daily/weekly/monthly windows. Null if /quota_status failed (see quota_error) or genuinely returned nothing. Pre-check the LENS_EXTRA_REFILL entry before calling leadbay_extend_lens."
|
|
15773
|
+
},
|
|
15774
|
+
quota_error: {
|
|
15775
|
+
type: ["object", "null"],
|
|
15776
|
+
description: "Non-null ONLY when the quota_status call FAILED \u2014 {code, http_status, message}. A 401/403 means the token lacks quota scope: tell the user to reconnect / re-run OAuth. Treat as 'quota unreadable', NEVER as zero usage or 'no limits'.",
|
|
15777
|
+
properties: {
|
|
15778
|
+
code: { type: "string" },
|
|
15779
|
+
http_status: { type: ["number", "null"] },
|
|
15780
|
+
message: { type: "string" }
|
|
15781
|
+
}
|
|
15488
15782
|
},
|
|
15489
15783
|
notifications: {
|
|
15490
15784
|
type: "array",
|
|
@@ -15525,9 +15819,15 @@ var accountStatus = {
|
|
|
15525
15819
|
execute: async (client, _params, ctx) => {
|
|
15526
15820
|
const me = await client.resolveMe();
|
|
15527
15821
|
let quota = null;
|
|
15822
|
+
let quota_error = null;
|
|
15528
15823
|
try {
|
|
15529
15824
|
quota = await client.request("GET", `/organizations/${me.organization.id}/quota_status`);
|
|
15530
15825
|
} catch (err) {
|
|
15826
|
+
quota_error = {
|
|
15827
|
+
code: err?.code ?? "QUOTA_STATUS_FAILED",
|
|
15828
|
+
http_status: err?._meta?.http_status ?? null,
|
|
15829
|
+
message: err?.message ?? "quota_status request failed"
|
|
15830
|
+
};
|
|
15531
15831
|
ctx?.logger?.warn?.(`account_status: quota_status failed: ${err?.message ?? err?.code ?? err}`);
|
|
15532
15832
|
}
|
|
15533
15833
|
return withAgentMemoryMeta(client, {
|
|
@@ -15557,6 +15857,9 @@ var accountStatus = {
|
|
|
15557
15857
|
// Empty array when the WS listener isn't wired (OpenClaw, tests) OR
|
|
15558
15858
|
// when nothing has completed since the last ack.
|
|
15559
15859
|
notifications: ctx?.notificationsInbox?.list() ?? [],
|
|
15860
|
+
// Non-null ONLY when the quota_status call failed. The agent must treat
|
|
15861
|
+
// this as "could not read quota" (reauth on 401/403) — NOT as zero usage.
|
|
15862
|
+
quota_error,
|
|
15560
15863
|
_meta: {
|
|
15561
15864
|
region: client.region
|
|
15562
15865
|
}
|
|
@@ -17279,6 +17582,16 @@ var qualifyStatus = {
|
|
|
17279
17582
|
}
|
|
17280
17583
|
};
|
|
17281
17584
|
|
|
17585
|
+
// ../core/dist/composite/_credits-helpers.js
|
|
17586
|
+
async function readCreditsRemaining(client, force = false) {
|
|
17587
|
+
try {
|
|
17588
|
+
const me = await client.resolveMe(force);
|
|
17589
|
+
return me.organization.billing?.ai_credits ?? null;
|
|
17590
|
+
} catch {
|
|
17591
|
+
return null;
|
|
17592
|
+
}
|
|
17593
|
+
}
|
|
17594
|
+
|
|
17282
17595
|
// ../core/dist/composite/enrich-titles.js
|
|
17283
17596
|
var DEFAULT_CANDIDATE_COUNT = 25;
|
|
17284
17597
|
var enrichTitles = {
|
|
@@ -17362,6 +17675,10 @@ var enrichTitles = {
|
|
|
17362
17675
|
type: "number",
|
|
17363
17676
|
description: "Count of enrichable contacts at preview time."
|
|
17364
17677
|
},
|
|
17678
|
+
credits_remaining: {
|
|
17679
|
+
type: ["number", "null"],
|
|
17680
|
+
description: "AI-credit balance BEFORE launching (billing.ai_credits). Present in discover / preview_only / dry_run modes. Pair with enrichable_contacts to tell the user 'you have N credits, this will enrich M contacts' \u2014 do NOT estimate an exact cost (the per-contact rate is backend-only). Null = billing unavailable."
|
|
17681
|
+
},
|
|
17365
17682
|
selected_lead_count: {
|
|
17366
17683
|
type: "number",
|
|
17367
17684
|
description: "How many leads the selection covers."
|
|
@@ -17485,6 +17802,10 @@ var enrichTitles = {
|
|
|
17485
17802
|
previously_enriched: previouslyEnriched,
|
|
17486
17803
|
enrichable_contacts: enrichableContacts,
|
|
17487
17804
|
selected_lead_count: leadIds.length,
|
|
17805
|
+
// BEFORE: show balance + volume. We can't estimate exact cost
|
|
17806
|
+
// (the per-contact rate is backend-only), so surface the balance
|
|
17807
|
+
// and the count, not a fabricated "will cost N".
|
|
17808
|
+
credits_remaining: await readCreditsRemaining(client),
|
|
17488
17809
|
next_action: "Pick titles to enrich and call leadbay_enrich_titles again with titles=[...]"
|
|
17489
17810
|
};
|
|
17490
17811
|
}
|
|
@@ -17507,7 +17828,8 @@ var enrichTitles = {
|
|
|
17507
17828
|
preview,
|
|
17508
17829
|
launched: false,
|
|
17509
17830
|
message: "No enrichable contacts for the chosen titles. Try other titles from available_titles or recommendations.",
|
|
17510
|
-
available_titles: availableTitles
|
|
17831
|
+
available_titles: availableTitles,
|
|
17832
|
+
credits_remaining: await readCreditsRemaining(client)
|
|
17511
17833
|
};
|
|
17512
17834
|
}
|
|
17513
17835
|
if (params.dry_run) {
|
|
@@ -17515,7 +17837,12 @@ var enrichTitles = {
|
|
|
17515
17837
|
mode: "dry_run",
|
|
17516
17838
|
preview,
|
|
17517
17839
|
launched: false,
|
|
17518
|
-
would_launch: { titles: params.titles, email, phone }
|
|
17840
|
+
would_launch: { titles: params.titles, email, phone },
|
|
17841
|
+
// BEFORE confirmation gate: balance + how many contacts WOULD be
|
|
17842
|
+
// enriched. enrichable_contacts is the volume; credits_remaining
|
|
17843
|
+
// the balance. No estimated cost — that rate is backend-only.
|
|
17844
|
+
enrichable_contacts: preview.enrichable_contacts,
|
|
17845
|
+
credits_remaining: await readCreditsRemaining(client)
|
|
17519
17846
|
};
|
|
17520
17847
|
}
|
|
17521
17848
|
const tracker = ctx?.bulkTracker;
|
|
@@ -17723,6 +18050,10 @@ var bulkEnrichStatus = {
|
|
|
17723
18050
|
type: "boolean",
|
|
17724
18051
|
description: "True when overall_progress.done === total AND no partial_failures."
|
|
17725
18052
|
},
|
|
18053
|
+
credits_remaining: {
|
|
18054
|
+
type: ["number", "null"],
|
|
18055
|
+
description: "AI-credit balance re-read after the spend (force-refreshed /users/me \u2192 billing.ai_credits). Present only when all_done. Null = billing unavailable (don't read as zero). NOTE: a per-run 'credits used' figure is intentionally NOT returned \u2014 getContacts can't scope cost to this bulk, so any sum would conflate historical enrichments."
|
|
18056
|
+
},
|
|
17726
18057
|
partial_failures: {
|
|
17727
18058
|
type: "array",
|
|
17728
18059
|
description: "Per-lead errors observed during contacts fan-out (omitted when no failures).",
|
|
@@ -17829,6 +18160,7 @@ var bulkEnrichStatus = {
|
|
|
17829
18160
|
leads2 = record.lead_ids.map((id) => ({ lead_id: id }));
|
|
17830
18161
|
}
|
|
17831
18162
|
ctx?.logger?.info?.(`bulk.status_checked_via_notification bulk_id=${record.bulk_id} notification_id=${notifId} done=${bp.success_count}/${bp.total_count} in_progress=${inProgress} wall_ms=${Date.now() - startMs}`);
|
|
18163
|
+
const creditsRemaining2 = !inProgress ? await readCreditsRemaining(client, true) : null;
|
|
17832
18164
|
return {
|
|
17833
18165
|
bulk_id: record.bulk_id,
|
|
17834
18166
|
notification_id: notifId,
|
|
@@ -17848,6 +18180,7 @@ var bulkEnrichStatus = {
|
|
|
17848
18180
|
bulk_progress: bp,
|
|
17849
18181
|
in_progress: inProgress,
|
|
17850
18182
|
all_done: !inProgress,
|
|
18183
|
+
...!inProgress ? { credits_remaining: creditsRemaining2 } : {},
|
|
17851
18184
|
...bp.quota_hit_count > 0 ? {
|
|
17852
18185
|
quota_hit_hint: "Some contacts could not be enriched because the AI-credits quota was hit. Top up via leadbay_create_topup_link or wait for the window reset."
|
|
17853
18186
|
} : {}
|
|
@@ -17920,6 +18253,10 @@ var bulkEnrichStatus = {
|
|
|
17920
18253
|
};
|
|
17921
18254
|
const allDone = totalAll > 0 && totalDone === totalAll && partialFailures.length === 0;
|
|
17922
18255
|
ctx?.logger?.info?.(`bulk.status_checked bulk_id=${record.bulk_id} done=${totalDone} total=${totalAll} wall_ms=${Date.now() - startMs}`);
|
|
18256
|
+
let creditsRemaining = null;
|
|
18257
|
+
if (allDone) {
|
|
18258
|
+
creditsRemaining = await readCreditsRemaining(client, true);
|
|
18259
|
+
}
|
|
17923
18260
|
return {
|
|
17924
18261
|
bulk_id: record.bulk_id,
|
|
17925
18262
|
launched_at: record.launched_at,
|
|
@@ -17932,6 +18269,7 @@ var bulkEnrichStatus = {
|
|
|
17932
18269
|
leads,
|
|
17933
18270
|
overall_progress: overallProgress,
|
|
17934
18271
|
all_done: allDone,
|
|
18272
|
+
...allDone ? { credits_remaining: creditsRemaining } : {},
|
|
17935
18273
|
...partialFailures.length > 0 ? { partial_failures: partialFailures } : {}
|
|
17936
18274
|
};
|
|
17937
18275
|
}
|
|
@@ -19616,6 +19954,13 @@ var compositeReadTools = [
|
|
|
19616
19954
|
campaignCallSheet,
|
|
19617
19955
|
researchLeadById,
|
|
19618
19956
|
researchLeadByNameFuzzy,
|
|
19957
|
+
// accountHistory layers FULL notes + activity timeline on top of research
|
|
19958
|
+
// so the agent can write the US4 "why has this dormant account resurfaced"
|
|
19959
|
+
// narrative in ONE call. ALWAYS exposed (compositeReadTools) — the underlying
|
|
19960
|
+
// get_lead_notes / get_lead_activities are ADVANCED-gated, but the
|
|
19961
|
+
// reprioritize-a-neglected-account workflow (#3630 GAP C) must work in a
|
|
19962
|
+
// default deployment without LEADBAY_MCP_ADVANCED=1.
|
|
19963
|
+
accountHistory,
|
|
19619
19964
|
recallOrderedTitles,
|
|
19620
19965
|
accountStatus,
|
|
19621
19966
|
bulkEnrichStatus,
|
|
@@ -20909,7 +21254,7 @@ function parseWriteEnv(env = process.env) {
|
|
|
20909
21254
|
}
|
|
20910
21255
|
|
|
20911
21256
|
// src/http-server.ts
|
|
20912
|
-
var VERSION = true ? "0.18.
|
|
21257
|
+
var VERSION = true ? "0.18.1" : "0.0.0-dev";
|
|
20913
21258
|
var PORT = Number(process.env.PORT ?? 8080);
|
|
20914
21259
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
20915
21260
|
var sseSessions = /* @__PURE__ */ new Map();
|
|
@@ -1466,7 +1466,7 @@ var init_installer_gui = __esm({
|
|
|
1466
1466
|
init_install_dxt();
|
|
1467
1467
|
init_install_shared();
|
|
1468
1468
|
init_oauth();
|
|
1469
|
-
VERSION = "0.18.
|
|
1469
|
+
VERSION = "0.18.1";
|
|
1470
1470
|
PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
|
|
1471
1471
|
sessions = /* @__PURE__ */ new Map();
|
|
1472
1472
|
OAUTH_BASE_URLS = {
|
package/dist/installer-gui.js
CHANGED
|
@@ -873,7 +873,7 @@ async function oauthLogin(opts) {
|
|
|
873
873
|
}
|
|
874
874
|
|
|
875
875
|
// installer/installer-gui.ts
|
|
876
|
-
var VERSION = "0.18.
|
|
876
|
+
var VERSION = "0.18.1";
|
|
877
877
|
var PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
|
|
878
878
|
var sessions = /* @__PURE__ */ new Map();
|
|
879
879
|
var OAUTH_BASE_URLS = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leadbay/mcp",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.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",
|