@leadbay/mcp 0.17.2 → 0.17.3
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 +10 -0
- package/README.md +38 -1
- package/dist/bin.js +840 -31
- package/dist/http-server.js +822 -30
- package/dist/installer-electron.js +145 -200
- package/dist/installer-gui.js +77 -109
- package/package.json +3 -4
package/dist/bin.js
CHANGED
|
@@ -5102,6 +5102,8 @@ var init_composite_file_names = __esm({
|
|
|
5102
5102
|
"leadbay_import_leads",
|
|
5103
5103
|
"leadbay_import_status",
|
|
5104
5104
|
"leadbay_list_campaigns",
|
|
5105
|
+
"leadbay_my_lenses",
|
|
5106
|
+
"leadbay_new_lens",
|
|
5105
5107
|
"leadbay_prepare_outreach",
|
|
5106
5108
|
"leadbay_pull_followups",
|
|
5107
5109
|
"leadbay_pull_leads",
|
|
@@ -5121,7 +5123,7 @@ var init_composite_file_names = __esm({
|
|
|
5121
5123
|
});
|
|
5122
5124
|
|
|
5123
5125
|
// ../core/dist/tool-descriptions.generated.js
|
|
5124
|
-
var leadbay_account_status, 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_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;
|
|
5126
|
+
var leadbay_account_status, 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;
|
|
5125
5127
|
var init_tool_descriptions_generated = __esm({
|
|
5126
5128
|
"../core/dist/tool-descriptions.generated.js"() {
|
|
5127
5129
|
"use strict";
|
|
@@ -5218,7 +5220,37 @@ WHEN NOT TO USE: to log an outreach action \u2014 use leadbay_report_outreach, w
|
|
|
5218
5220
|
|
|
5219
5221
|
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\`.
|
|
5220
5222
|
`;
|
|
5221
|
-
leadbay_adjust_audience =
|
|
5223
|
+
leadbay_adjust_audience = `## WHEN TO USE
|
|
5224
|
+
|
|
5225
|
+
Trigger phrases: "narrow the audience to <sector>", "add <sector> to my <name> lens", "remove <sector> from this lens", "only show me companies of <size>", "stop including <sector>", "broaden this lens to also include <sector>".
|
|
5226
|
+
|
|
5227
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
5228
|
+
|
|
5229
|
+
Do NOT use for: "create a new lens called X" \u2192 \`leadbay_new_lens\`; "make a new audience for Y" \u2192 \`leadbay_new_lens\`; "show me / list / switch my lenses" \u2192 \`leadbay_my_lenses\`; "focus on a kind of company beyond sector/size (e.g. 'hospitals running their own IT')" \u2192 \`leadbay_refine_prompt\`.
|
|
5230
|
+
|
|
5231
|
+
Prefer when: user wants to change an EXISTING lens's sectors/sizes. If the user NAMES a lens ('my Joinery lens'), you MUST pass lensName with that name \u2014 do NOT edit the active lens. To create a brand-new lens use leadbay_new_lens instead.
|
|
5232
|
+
|
|
5233
|
+
Examples that SHOULD invoke this tool:
|
|
5234
|
+
- "Add fintech to my Joinery lens."
|
|
5235
|
+
- "Narrow my audience to manufacturing companies, 50\u2013500 people."
|
|
5236
|
+
- "Stop including retail in this lens."
|
|
5237
|
+
|
|
5238
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
5239
|
+
- "Create a lens called Joinery for fintech."
|
|
5240
|
+
- "Show me my lenses."
|
|
5241
|
+
- "Focus on hospitals that run their own IT."
|
|
5242
|
+
|
|
5243
|
+
## RENDER (quick)
|
|
5244
|
+
|
|
5245
|
+
On \`applied\`: confirm the lens edited (name) + the sectors/sizes added as
|
|
5246
|
+
chips. On \`ambiguous_sectors\` / \`ambiguous_lens\` / \`lens_not_found\`: surface
|
|
5247
|
+
the candidates and ask the user to pick, then re-call with the id/exact name.
|
|
5248
|
+
|
|
5249
|
+
---
|
|
5250
|
+
|
|
5251
|
+
Restrict (or expand) the lens audience by sector / size. Free-text sectors are auto-resolved against the sector taxonomy; ambiguous matches are surfaced to the agent rather than guessed silently. Permission routing is hidden: the default lens auto-clones to a new user lens; an org-level lens defaults to a per-user draft (admins can override with \`save_for_org:true\`). Filter MERGES with existing criteria (unrelated criteria are not dropped).
|
|
5252
|
+
|
|
5253
|
+
**Targeting a lens \u2014 READ THIS.** By default this edits the user's ACTIVE lens. **If the user names a lens** ("add fintech to my **Joinery** lens", "in my Nordics lens, exclude retail"), you MUST pass \`lensName\` with that name (\`lensName:"Joinery"\`). Do NOT silently edit the active lens when a different one was named \u2014 that corrupts the wrong audience and is a top friction source. The name resolves against the user's lenses (case-insensitive, exact then unique-substring); it is edit-only and does NOT change which lens is active. An unmatched name returns \`status:"lens_not_found"\` with the lens list, and a name matching several returns \`status:"ambiguous_lens"\` with the candidates \u2014 surface them and re-call with the exact \`lensName\` or a \`lensId\`. Use \`leadbay_my_lenses\` if the user first wants to SEE or SWITCH lenses. To CREATE a brand-new lens, use \`leadbay_new_lens\` \u2014 not this tool.
|
|
5222
5254
|
|
|
5223
5255
|
WHEN TO USE: when the user wants to see different kinds of leads (sector / size / etc.).
|
|
5224
5256
|
|
|
@@ -6431,6 +6463,258 @@ WHEN TO USE: at the start of a session if no token is preconfigured (cfg.token /
|
|
|
6431
6463
|
WHEN NOT TO USE: if a token is already preconfigured \u2014 you'll just overwrite it. The user needs a Leadbay account first; they can register at https://wow.leadbay.ai/?register=true.
|
|
6432
6464
|
|
|
6433
6465
|
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\`.
|
|
6466
|
+
`;
|
|
6467
|
+
leadbay_my_lenses = `## WHEN TO USE
|
|
6468
|
+
|
|
6469
|
+
Trigger phrases: "show me my lenses", "list my lenses", "which audiences do I have", "switch to my <name> lens", "change lens", "rename my <name> lens to <X>", "set the description of my <name> lens", "delete my <name> lens", "remove this lens".
|
|
6470
|
+
|
|
6471
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
6472
|
+
|
|
6473
|
+
Do NOT use for: "narrow the audience" \u2192 \`leadbay_adjust_audience\`; "stop showing me <sector>" \u2192 \`leadbay_refine_prompt\`; "more leads on this lens" \u2192 \`leadbay_extend_lens\`; "show me today's leads" \u2192 \`leadbay_pull_leads\`.
|
|
6474
|
+
|
|
6475
|
+
Prefer when: user wants to SEE lenses, CHANGE which is active, RENAME one, or DELETE one \u2014 not edit a lens's sector/size criteria
|
|
6476
|
+
|
|
6477
|
+
Examples that SHOULD invoke this tool:
|
|
6478
|
+
- "Show me my lenses."
|
|
6479
|
+
- "Rename my Auto lens to Automotive and add a description."
|
|
6480
|
+
- "Delete my old Auto lens."
|
|
6481
|
+
|
|
6482
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
6483
|
+
- "Narrow the audience to fintech only."
|
|
6484
|
+
- "I want more leads on this lens."
|
|
6485
|
+
- "Show me today's leads."
|
|
6486
|
+
|
|
6487
|
+
## RENDER (quick)
|
|
6488
|
+
|
|
6489
|
+
Small markdown table, active lens first: col 1 = \u2B50 prefix when active +
|
|
6490
|
+
lens name; col 2 = description (or \`\u2014\`). After a switch lead with
|
|
6491
|
+
"Now showing **<name>**."; after a rename lead with the rename confirmation.
|
|
6492
|
+
Full algorithm below.
|
|
6493
|
+
|
|
6494
|
+
---
|
|
6495
|
+
|
|
6496
|
+
List the user's lenses (saved audiences) and, when asked, switch which one is active. A lens shapes the kind of leads delivered each day; this tool is how the user sees their audiences and moves between them \u2014 it does NOT edit a lens's criteria (that's \`leadbay_adjust_audience\`).
|
|
6497
|
+
|
|
6498
|
+
**Three modes, one tool:**
|
|
6499
|
+
|
|
6500
|
+
- **List (no args)** \u2014 pure read. Returns \`{status:"listed", lenses:[{id, name, description, is_active}], active_lens_id}\`. The active lens is resolved from the user's last-requested lens, so \`is_active\` is authoritative even if a row's flag is stale.
|
|
6501
|
+
- **Switch (\`switchToLensId\`)** \u2014 changes the active lens to that id and returns the REFRESHED list. The id MUST be one of the user's lenses; an unknown id returns \`{status:"not_found"}\` with the current list \u2014 surface it and ask the user to pick, do NOT invent an id. Switching to the already-active lens is a harmless no-op.
|
|
6502
|
+
- **Edit (\`editLensId\` + \`newName\` and/or \`newDescription\`)** \u2014 rename and/or set the description of a lens in one call, returns the REFRESHED list. Provide either or both; pass \`newDescription:""\` to clear a description. Same not_found handling. Use the \`id\` from the list for the lens the user named.
|
|
6503
|
+
- **Delete (\`deleteLensId\`)** \u2014 DESTRUCTIVE and confirm-gated. Without \`confirm:true\` it returns \`status:"delete_preview"\` with \`will_delete\` and removes NOTHING \u2014 show it, get the user's explicit yes, then re-call with \`confirm:true\`. The DEFAULT lens cannot be deleted (\`status:"cannot_delete_default"\`). Deleting the active lens leaves no active lens until the next switch/pull resolves one.
|
|
6504
|
+
|
|
6505
|
+
**Lens ids are strings** (e.g. \`"40005"\`) \u2014 pass the \`id\` value straight from the list when switching/renaming/deleting; it is fine to pass it as the string it came as.
|
|
6506
|
+
|
|
6507
|
+
**When the user is vague** ("switch lens" with no target), list first, then offer the lenses as a quick choice via \`ask_user_input_v0\` rather than guessing.
|
|
6508
|
+
|
|
6509
|
+
WHEN TO USE: when the user wants to see their lenses or switch the active one. Canonical phrasings: "show me my lenses", "which audiences do I have", "switch to my <name> lens".
|
|
6510
|
+
|
|
6511
|
+
WHEN NOT TO USE: to change a lens's audience criteria \u2014 that's \`leadbay_adjust_audience\`. Not for refining beyond firmographics (\`leadbay_refine_prompt\`), not for topping up the same lens (\`leadbay_extend_lens\`), not for the daily pull (\`leadbay_pull_leads\`).
|
|
6512
|
+
|
|
6513
|
+
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\`.
|
|
6514
|
+
|
|
6515
|
+
|
|
6516
|
+
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
6517
|
+
|
|
6518
|
+
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.
|
|
6519
|
+
|
|
6520
|
+
**The Big Three** \u2014 when a tool result fits, route there:
|
|
6521
|
+
|
|
6522
|
+
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
6523
|
+
|---|---|---|
|
|
6524
|
+
| \`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 |
|
|
6525
|
+
| \`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") |
|
|
6526
|
+
| \`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 |
|
|
6527
|
+
|
|
6528
|
+
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.
|
|
6529
|
+
|
|
6530
|
+
**Rules:**
|
|
6531
|
+
- 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.
|
|
6532
|
+
- Pass identifiers (place_id, lead.id, contact_id) verbatim. Don't rewrite.
|
|
6533
|
+
- 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.
|
|
6534
|
+
- One short intro sentence in chat is enough \u2014 "Here are your 5 NYC follow-ups." Then route into the widget.
|
|
6535
|
+
|
|
6536
|
+
|
|
6537
|
+
---
|
|
6538
|
+
|
|
6539
|
+
## RENDERING \u2014 lenses table, active-first
|
|
6540
|
+
|
|
6541
|
+
Markdown table with TWO columns. Sort **active lens first**, then by \`name\`
|
|
6542
|
+
ascending. **No score bar** \u2014 the \`\u25B0\u2756\u25B1\` glyph identity belongs to lead
|
|
6543
|
+
discovery, not lenses.
|
|
6544
|
+
|
|
6545
|
+
**Column 1 \u2014 Lens**
|
|
6546
|
+
- Prefix \`\u2B50 \` when \`is_active\` is true; otherwise no prefix.
|
|
6547
|
+
- The lens name in **bold**. (Lenses have no public URL \u2014 do not fabricate a link.)
|
|
6548
|
+
|
|
6549
|
+
**Column 2 \u2014 Description**
|
|
6550
|
+
- \`description\` verbatim, clipped to \u2264 18 words.
|
|
6551
|
+
- When null/empty: render \`\u2014\`.
|
|
6552
|
+
|
|
6553
|
+
**After a \`switched: true\` response**, open with a single confirmation line
|
|
6554
|
+
ABOVE the table: \`Now showing **<name>**.\` For \`status: "not_found"\`, lead with
|
|
6555
|
+
the \`message\` (the bad id) and render the list so the user can pick a real one.
|
|
6556
|
+
|
|
6557
|
+
**Empty list** (\`lenses: []\`): render \`*You don't have any lenses yet.*\` \u2014 do not
|
|
6558
|
+
render an empty table.
|
|
6559
|
+
|
|
6560
|
+
**Legend:** \u2B50 active lens.
|
|
6561
|
+
|
|
6562
|
+
|
|
6563
|
+
## NEXT STEPS \u2014 after \`leadbay_my_lenses\`
|
|
6564
|
+
|
|
6565
|
+
**RENDER NEXT STEPS via \`ask_user_input_v0\` when the host exposes it.**
|
|
6566
|
+
|
|
6567
|
+
The (Observation, Suggest, Calls) table below is the source of truth for which moves are valid. Pick the 2\u20134 most relevant rows based on what the response actually contains, then surface them as a \`single_select\` quick-select widget:
|
|
6568
|
+
|
|
6569
|
+
\`\`\`
|
|
6570
|
+
ask_user_input_v0({
|
|
6571
|
+
questions: [{
|
|
6572
|
+
question: "What next?",
|
|
6573
|
+
type: "single_select",
|
|
6574
|
+
options: [
|
|
6575
|
+
"<Suggest column from row 1>",
|
|
6576
|
+
"<Suggest column from row 2>",
|
|
6577
|
+
"<Suggest column from row 3>"
|
|
6578
|
+
]
|
|
6579
|
+
}]
|
|
6580
|
+
})
|
|
6581
|
+
\`\`\`
|
|
6582
|
+
|
|
6583
|
+
When the user picks an option, you call the matching tool from the \`Calls\` column. Constraints carried over from the widget contract: 2\u20134 mutually-exclusive options per question, button-sized labels (\u22646 words), max 3 questions per call.
|
|
6584
|
+
|
|
6585
|
+
**Fallback prose mode** \u2014 when the host doesn't expose \`ask_user_input_v0\` (or it returned an error): surface the same 2\u20133 picks as a short bulleted list of "Suggest" phrasings. The table itself stays internal; never recite the whole table to the user.
|
|
6586
|
+
|
|
6587
|
+
---
|
|
6588
|
+
|
|
6589
|
+
|
|
6590
|
+
|
|
6591
|
+
Pick the 2\u20133 rows that fit what the user is likely to want next. When the user
|
|
6592
|
+
named no target but wants to switch, offer the lenses themselves as the
|
|
6593
|
+
quick-select options (each option = a lens name \u2192 \`leadbay_my_lenses(switchToLensId=<id>)\`).
|
|
6594
|
+
|
|
6595
|
+
| Observation | Suggest | Calls |
|
|
6596
|
+
|--------------------------------------|------------------------------------------|------------------------------------------------------|
|
|
6597
|
+
| User wants a different lens | "Switch to <lens name>" | \`leadbay_my_lenses(switchToLensId=<id>)\` |
|
|
6598
|
+
| User wants to rename / describe a lens| "Rename or describe <lens>" | \`leadbay_my_lenses(editLensId=<id>, newName?=<X>, newDescription?=<Y>)\` |
|
|
6599
|
+
| User wants to delete a lens | "Delete <lens>" | \`leadbay_my_lenses(deleteLensId=<id>)\` \u2192 confirm \u2192 \`confirm=true\` |
|
|
6600
|
+
| \`delete_preview\` (not yet deleted) | "Yes, delete it" | \`leadbay_my_lenses(deleteLensId=<id>, confirm=true)\` |
|
|
6601
|
+
| User wants leads on the active lens | "Pull today's leads" | \`leadbay_pull_leads()\` |
|
|
6602
|
+
| User wants to change the audience | "Adjust this lens's audience" | \`leadbay_adjust_audience(...)\` |
|
|
6603
|
+
| User wants more of the same | "Get a bigger batch on this lens" | \`leadbay_extend_lens(...)\` |
|
|
6604
|
+
|
|
6605
|
+
If nothing fits, default to "pull today's leads on the active lens" \u2014 never
|
|
6606
|
+
invent a tool that doesn't exist.
|
|
6607
|
+
`;
|
|
6608
|
+
leadbay_new_lens = `## WHEN TO USE
|
|
6609
|
+
|
|
6610
|
+
Trigger phrases: "create a lens", "create a new lens called <name>", "create a lens specialized in/into <X>", "make me a new audience for <X>", "set up a lens for <sector>", "new lens named <name>", "I want a lens just for <X>".
|
|
6611
|
+
|
|
6612
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
6613
|
+
|
|
6614
|
+
Do NOT use for: "narrow the audience / add or remove a sector on an EXISTING lens" \u2192 \`leadbay_adjust_audience\`; "add <sector> to my <name> lens" \u2192 \`leadbay_adjust_audience\`; "focus on a qualitative trait beyond sector/size" \u2192 \`leadbay_refine_prompt\`; "show me / list / switch my lenses" \u2192 \`leadbay_my_lenses\`; "more leads on this lens" \u2192 \`leadbay_extend_lens\`.
|
|
6615
|
+
|
|
6616
|
+
Prefer when: user wants a brand-new lens (create/make/set up, often 'specialized in <X>'). Editing an existing lens \u2192 leadbay_adjust_audience (use lensName). Qualitative refinement \u2192 refine_prompt (admin-only).
|
|
6617
|
+
|
|
6618
|
+
Examples that SHOULD invoke this tool:
|
|
6619
|
+
- "Create a lens called Joinery for the fintech sector."
|
|
6620
|
+
- "Make me a new audience for healthcare companies, 30\u2013300 people."
|
|
6621
|
+
- "Set up a new lens named Nordics SaaS."
|
|
6622
|
+
|
|
6623
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
6624
|
+
- "Add fintech to my Joinery lens."
|
|
6625
|
+
- "Show me my lenses."
|
|
6626
|
+
- "I want more leads on this lens."
|
|
6627
|
+
|
|
6628
|
+
## RENDER (quick)
|
|
6629
|
+
|
|
6630
|
+
On \`preview\` (default \u2014 NOTHING created yet): show the lens that WILL be
|
|
6631
|
+
created (name + resolved sectors/sizes as chips) and ASK the user to confirm
|
|
6632
|
+
via ask_user_input_v0 ("Create this lens?" / "Change something"). Only on
|
|
6633
|
+
"yes" re-call with confirm:true. On \`created\`: confirm "Created **<name>**."
|
|
6634
|
+
On \`ambiguous_sectors\`: surface the candidate sectors to pick from.
|
|
6635
|
+
|
|
6636
|
+
---
|
|
6637
|
+
|
|
6638
|
+
Create a brand-new lens (saved audience) and apply its sector/size criteria. Clones a base lens (the user's active/default lens unless \`base\` is given), names it, and applies the filter.
|
|
6639
|
+
|
|
6640
|
+
**Confirm before creating \u2014 two-step by default.** A call WITHOUT \`confirm:true\` is a dry run: it resolves the sectors/sizes and returns \`status:"preview"\` with \`will_create\` (what it WOULD build) \u2014 **nothing is created**. Show that to the user, get an explicit yes (ask via \`ask_user_input_v0\`), then re-call the SAME args with \`confirm:true\` to actually create. Never pass \`confirm:true\` on the first call \u2014 the user must see the preview first. (Sector ambiguity is still surfaced in the preview step, so they pick before confirming.)
|
|
6641
|
+
|
|
6642
|
+
**Sectors resolve first.** Free-text \`sectors\`/\`exclude_sectors\` are auto-resolved against the taxonomy. If any don't resolve, the tool returns \`status:"ambiguous_sectors"\` with the candidates and **does NOT create the lens** \u2014 so re-calling after picking the right sector won't leave orphan half-built lenses. To discover valid sector labels up front, use \`leadbay_list_sectors\`.
|
|
6643
|
+
|
|
6644
|
+
**Does not switch the active lens.** The new lens is created but the user stays on their current one. Offer \`leadbay_my_lenses(switchToLensId=<new id>)\` as a next step if they want to start pulling from it.
|
|
6645
|
+
|
|
6646
|
+
WHEN TO USE: when the user wants a NEW lens. Canonical phrasings: "create a lens called X", "make a new audience for Y", "set up a lens for <sector>".
|
|
6647
|
+
|
|
6648
|
+
WHEN NOT TO USE: to EDIT an existing lens \u2014 use \`leadbay_adjust_audience\` (pass \`lensName\` to target one by name). Not for listing/switching (\`leadbay_my_lenses\`) or topping up (\`leadbay_extend_lens\`).
|
|
6649
|
+
|
|
6650
|
+
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\`.
|
|
6651
|
+
|
|
6652
|
+
|
|
6653
|
+
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
6654
|
+
|
|
6655
|
+
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.
|
|
6656
|
+
|
|
6657
|
+
**The Big Three** \u2014 when a tool result fits, route there:
|
|
6658
|
+
|
|
6659
|
+
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
6660
|
+
|---|---|---|
|
|
6661
|
+
| \`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 |
|
|
6662
|
+
| \`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") |
|
|
6663
|
+
| \`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 |
|
|
6664
|
+
|
|
6665
|
+
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.
|
|
6666
|
+
|
|
6667
|
+
**Rules:**
|
|
6668
|
+
- 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.
|
|
6669
|
+
- Pass identifiers (place_id, lead.id, contact_id) verbatim. Don't rewrite.
|
|
6670
|
+
- 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.
|
|
6671
|
+
- One short intro sentence in chat is enough \u2014 "Here are your 5 NYC follow-ups." Then route into the widget.
|
|
6672
|
+
|
|
6673
|
+
|
|
6674
|
+
---
|
|
6675
|
+
|
|
6676
|
+
## NEXT STEPS \u2014 after \`leadbay_new_lens\`
|
|
6677
|
+
|
|
6678
|
+
**RENDER NEXT STEPS via \`ask_user_input_v0\` when the host exposes it.**
|
|
6679
|
+
|
|
6680
|
+
The (Observation, Suggest, Calls) table below is the source of truth for which moves are valid. Pick the 2\u20134 most relevant rows based on what the response actually contains, then surface them as a \`single_select\` quick-select widget:
|
|
6681
|
+
|
|
6682
|
+
\`\`\`
|
|
6683
|
+
ask_user_input_v0({
|
|
6684
|
+
questions: [{
|
|
6685
|
+
question: "What next?",
|
|
6686
|
+
type: "single_select",
|
|
6687
|
+
options: [
|
|
6688
|
+
"<Suggest column from row 1>",
|
|
6689
|
+
"<Suggest column from row 2>",
|
|
6690
|
+
"<Suggest column from row 3>"
|
|
6691
|
+
]
|
|
6692
|
+
}]
|
|
6693
|
+
})
|
|
6694
|
+
\`\`\`
|
|
6695
|
+
|
|
6696
|
+
When the user picks an option, you call the matching tool from the \`Calls\` column. Constraints carried over from the widget contract: 2\u20134 mutually-exclusive options per question, button-sized labels (\u22646 words), max 3 questions per call.
|
|
6697
|
+
|
|
6698
|
+
**Fallback prose mode** \u2014 when the host doesn't expose \`ask_user_input_v0\` (or it returned an error): surface the same 2\u20133 picks as a short bulleted list of "Suggest" phrasings. The table itself stays internal; never recite the whole table to the user.
|
|
6699
|
+
|
|
6700
|
+
---
|
|
6701
|
+
|
|
6702
|
+
|
|
6703
|
+
|
|
6704
|
+
Pick the rows that fit. On \`created\`, the switch + pull rows are the natural
|
|
6705
|
+
follow-ups. On \`ambiguous_sectors\`, the only move is to pick a sector and re-call.
|
|
6706
|
+
|
|
6707
|
+
| Observation | Suggest | Calls |
|
|
6708
|
+
|-----------------------------------|------------------------------------------|--------------------------------------------------------|
|
|
6709
|
+
| \`preview\` (not yet created) | "Yes, create this lens" | \`leadbay_new_lens(...same args..., confirm=true)\` |
|
|
6710
|
+
| \`preview\` (not yet created) | "Change the sectors/size first" | (re-ask the user, then \`leadbay_new_lens\` with new args) |
|
|
6711
|
+
| Lens created | "Switch to it and pull leads" | \`leadbay_my_lenses(switchToLensId=<new id>)\` then \`leadbay_pull_leads()\` |
|
|
6712
|
+
| Lens created | "Refine the audience further" | \`leadbay_adjust_audience(lensName=<new name>, ...)\` |
|
|
6713
|
+
| Lens created | "Leave it; keep my current lens active" | (no call) |
|
|
6714
|
+
| \`ambiguous_sectors\` | "Pick the right sector and create" | \`leadbay_new_lens(name=..., sectors=[<chosen id>])\` |
|
|
6715
|
+
|
|
6716
|
+
If nothing fits, default to "switch to the new lens and pull leads" \u2014 never
|
|
6717
|
+
invent a tool that doesn't exist.
|
|
6434
6718
|
`;
|
|
6435
6719
|
leadbay_open_billing_portal = `Generate a one-shot Stripe customer-portal URL. Wraps \`GET /1.5/stripe/portal\` \u2192 \`{url}\`. Returns a fresh Stripe-hosted URL the user can open to manage their existing Leadbay subscription: change plan tier, swap payment method, view invoices. The agent does NOT make subscription changes itself \u2014 it surfaces the URL and lets the user act.
|
|
6436
6720
|
|
|
@@ -7064,7 +7348,33 @@ WHEN TO USE: before leadbay_enrich_titles, to plan which titles to order.
|
|
|
7064
7348
|
|
|
7065
7349
|
WHEN NOT TO USE: when you already know the exact titles you want to enrich.
|
|
7066
7350
|
`;
|
|
7067
|
-
leadbay_refine_prompt =
|
|
7351
|
+
leadbay_refine_prompt = `## WHEN TO USE
|
|
7352
|
+
|
|
7353
|
+
Trigger phrases: "focus on companies that <qualitative trait>", "I prefer leads that <behavior/characteristic>", "prioritize companies running their own IT", "deprioritize companies that just raised".
|
|
7354
|
+
|
|
7355
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
7356
|
+
|
|
7357
|
+
Do NOT use for: "create a new lens / a lens specialized into <X>" \u2192 \`leadbay_new_lens\`; "add/remove <sector> to/from my <name> lens" \u2192 \`leadbay_adjust_audience\`; "narrow the audience to <sector> / <size>" \u2192 \`leadbay_adjust_audience\`; "show me / list / switch my lenses" \u2192 \`leadbay_my_lenses\`.
|
|
7358
|
+
|
|
7359
|
+
Prefer when: ADMIN-ONLY. Qualitative refinement of the active lens that sector/size can't express. Creating/naming/listing/switching/sector-editing a lens routes elsewhere. Non-admin user \u2192 do NOT pick this.
|
|
7360
|
+
|
|
7361
|
+
Examples that SHOULD invoke this tool:
|
|
7362
|
+
- "Focus on hospitals that run their own IT in-house."
|
|
7363
|
+
- "Prioritize companies that have recently expanded headcount."
|
|
7364
|
+
|
|
7365
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
7366
|
+
- "Create a lens specialized in automobile."
|
|
7367
|
+
- "Add fintech to my Joinery lens."
|
|
7368
|
+
- "Show me my lenses."
|
|
7369
|
+
|
|
7370
|
+
## RENDER (quick)
|
|
7371
|
+
|
|
7372
|
+
On success: confirm the refinement applied to the active lens. If a
|
|
7373
|
+
clarification was raised, surface its question (route via ask_user_input_v0).
|
|
7374
|
+
|
|
7375
|
+
---
|
|
7376
|
+
|
|
7377
|
+
Refine the kind of leads Leadbay surfaces, beyond firmographics. Free-text instruction (e.g. "focus on hospitals running their own IT"). Sets the org's \`user_prompt\`; if the new prompt produces ambiguous criteria, Leadbay raises a clarification question, which this composite polls for and surfaces. Admin-only on the backend (will return 403 for non-admins).
|
|
7068
7378
|
|
|
7069
7379
|
WHEN TO USE: when audience filters (leadbay_adjust_audience) aren't enough.
|
|
7070
7380
|
|
|
@@ -11296,7 +11606,7 @@ var init_create_lens = __esm({
|
|
|
11296
11606
|
},
|
|
11297
11607
|
execute: async (client, params) => {
|
|
11298
11608
|
const lens = await client.request("POST", "/lenses", {
|
|
11299
|
-
base: params.base,
|
|
11609
|
+
base: String(params.base),
|
|
11300
11610
|
name: params.name,
|
|
11301
11611
|
description: params.description
|
|
11302
11612
|
});
|
|
@@ -17465,6 +17775,8 @@ var init_bulk_enrich_status = __esm({
|
|
|
17465
17775
|
|
|
17466
17776
|
// ../core/dist/composite/adjust-audience.js
|
|
17467
17777
|
function tokens(s) {
|
|
17778
|
+
if (!s)
|
|
17779
|
+
return [];
|
|
17468
17780
|
return s.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
17469
17781
|
}
|
|
17470
17782
|
function bestMatches(text, taxonomy) {
|
|
@@ -17478,11 +17790,11 @@ function bestMatches(text, taxonomy) {
|
|
|
17478
17790
|
if (have.has(t))
|
|
17479
17791
|
overlap += 1;
|
|
17480
17792
|
const score = overlap / Math.max(want.size, 1);
|
|
17481
|
-
return { id: s.id, name: s.name, score };
|
|
17793
|
+
return { id: s.id, name: s.name ?? "", score };
|
|
17482
17794
|
}).filter((m) => m.score > 0).sort((a, b) => b.score - a.score);
|
|
17483
17795
|
return ranked.slice(0, 5);
|
|
17484
17796
|
}
|
|
17485
|
-
async function resolveSectors(client, texts) {
|
|
17797
|
+
async function resolveSectors(client, texts, ctx) {
|
|
17486
17798
|
const looksLikeId2 = (s) => /^\d+$/.test(s);
|
|
17487
17799
|
const direct = texts.filter(looksLikeId2);
|
|
17488
17800
|
const free = texts.filter((s) => !looksLikeId2(s));
|
|
@@ -17491,6 +17803,10 @@ async function resolveSectors(client, texts) {
|
|
|
17491
17803
|
const me = await client.resolveMe().catch(() => null);
|
|
17492
17804
|
const lang = me?.language ?? "en";
|
|
17493
17805
|
const taxonomy = await client.request("GET", `/sectors/all?lang=${encodeURIComponent(lang)}&includeInvisible=false`);
|
|
17806
|
+
const nullNames = taxonomy.filter((s) => !s.name).length;
|
|
17807
|
+
if (nullNames > 0) {
|
|
17808
|
+
ctx?.logger?.warn?.(`adjust_audience: /sectors/all returned ${nullNames}/${taxonomy.length} sector(s) with a null/missing name`);
|
|
17809
|
+
}
|
|
17494
17810
|
const resolved = [...direct];
|
|
17495
17811
|
const ambiguities = [];
|
|
17496
17812
|
for (const text of free) {
|
|
@@ -17503,6 +17819,22 @@ async function resolveSectors(client, texts) {
|
|
|
17503
17819
|
}
|
|
17504
17820
|
return { resolved, ambiguities };
|
|
17505
17821
|
}
|
|
17822
|
+
async function resolveLensByName(client, name) {
|
|
17823
|
+
const lenses = await client.request("GET", "/lenses");
|
|
17824
|
+
const all = lenses.map((l) => ({ id: l.id, name: l.name }));
|
|
17825
|
+
const needle = name.trim().toLowerCase();
|
|
17826
|
+
const exact = all.filter((l) => (l.name ?? "").trim().toLowerCase() === needle);
|
|
17827
|
+
if (exact.length === 1)
|
|
17828
|
+
return { ok: true, id: exact[0].id };
|
|
17829
|
+
if (exact.length > 1)
|
|
17830
|
+
return { ok: false, reason: "ambiguous", matches: exact };
|
|
17831
|
+
const partial = all.filter((l) => (l.name ?? "").toLowerCase().includes(needle));
|
|
17832
|
+
if (partial.length === 1)
|
|
17833
|
+
return { ok: true, id: partial[0].id };
|
|
17834
|
+
if (partial.length > 1)
|
|
17835
|
+
return { ok: false, reason: "ambiguous", matches: partial };
|
|
17836
|
+
return { ok: false, reason: "not_found", lenses: all };
|
|
17837
|
+
}
|
|
17506
17838
|
function mergeFilter(current, toAddSectors, toExcludeSectors, sizes) {
|
|
17507
17839
|
const items = current?.lens_filter?.items ?? [];
|
|
17508
17840
|
const item = items[0] ?? { criteria: [] };
|
|
@@ -17536,11 +17868,15 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes) {
|
|
|
17536
17868
|
}
|
|
17537
17869
|
}
|
|
17538
17870
|
if (sizes && sizes.length > 0) {
|
|
17871
|
+
const normalizedSizes = sizes.map((s) => ({
|
|
17872
|
+
min: s.min ?? 0,
|
|
17873
|
+
max: s.max ?? 1e6
|
|
17874
|
+
}));
|
|
17539
17875
|
const idx = criteria.findIndex((c) => c.type === "size");
|
|
17540
17876
|
if (idx >= 0) {
|
|
17541
|
-
criteria[idx] = { type: "size", is_excluded: false, sizes };
|
|
17877
|
+
criteria[idx] = { type: "size", is_excluded: false, sizes: normalizedSizes };
|
|
17542
17878
|
} else {
|
|
17543
|
-
criteria.push({ type: "size", is_excluded: false, sizes });
|
|
17879
|
+
criteria.push({ type: "size", is_excluded: false, sizes: normalizedSizes });
|
|
17544
17880
|
}
|
|
17545
17881
|
}
|
|
17546
17882
|
return {
|
|
@@ -17548,6 +17884,9 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes) {
|
|
|
17548
17884
|
locations: current.locations ?? { results: [], parents: [] }
|
|
17549
17885
|
};
|
|
17550
17886
|
}
|
|
17887
|
+
function filterWriteBody(filter) {
|
|
17888
|
+
return { items: filter.lens_filter.items };
|
|
17889
|
+
}
|
|
17551
17890
|
var adjustAudience;
|
|
17552
17891
|
var init_adjust_audience = __esm({
|
|
17553
17892
|
"../core/dist/composite/adjust-audience.js"() {
|
|
@@ -17594,6 +17933,10 @@ var init_adjust_audience = __esm({
|
|
|
17594
17933
|
description: "Company size buckets, e.g. [{min:30,max:300}]"
|
|
17595
17934
|
},
|
|
17596
17935
|
lensId: { type: "number", description: "Lens id (escape hatch)" },
|
|
17936
|
+
lensName: {
|
|
17937
|
+
type: "string",
|
|
17938
|
+
description: "Target a lens BY NAME (e.g. 'Joinery') instead of the active one. Resolved against your lenses \u2014 edit-only, does NOT switch your active lens. Unknown/ambiguous names are surfaced to pick from. Takes effect only when lensId is not given."
|
|
17939
|
+
},
|
|
17597
17940
|
save_for_org: {
|
|
17598
17941
|
type: "boolean",
|
|
17599
17942
|
description: "Admin only \u2014 propagate the change to the org-level lens for everyone (default false: per-user draft)"
|
|
@@ -17607,21 +17950,35 @@ var init_adjust_audience = __esm({
|
|
|
17607
17950
|
},
|
|
17608
17951
|
outputSchema: {
|
|
17609
17952
|
type: "object",
|
|
17610
|
-
description: "
|
|
17953
|
+
description: "Return shapes: 'applied' on success; 'ambiguous_sectors' when free-text sectors matched multiple candidates (re-call with sector_ids); 'lens_not_found' / 'ambiguous_lens' when a lensName didn't resolve to exactly one lens (re-call with lensId or an exact lensName).",
|
|
17611
17954
|
properties: {
|
|
17612
17955
|
status: {
|
|
17613
17956
|
type: "string",
|
|
17614
|
-
description: "'ambiguous_sectors' or '
|
|
17957
|
+
description: "'applied', 'ambiguous_sectors', 'lens_not_found', or 'ambiguous_lens'."
|
|
17615
17958
|
},
|
|
17616
17959
|
sector_ambiguities: {
|
|
17617
17960
|
type: "array",
|
|
17618
17961
|
description: "Per ambiguous text: {sector_text, matches:[{id, name, score}]}. Agent picks an id and re-calls.",
|
|
17619
17962
|
items: { type: "object" }
|
|
17620
17963
|
},
|
|
17964
|
+
lenses: {
|
|
17965
|
+
type: "array",
|
|
17966
|
+
description: "On 'lens_not_found': the user's lenses [{id, name}] to pick from.",
|
|
17967
|
+
items: { type: "object" }
|
|
17968
|
+
},
|
|
17969
|
+
matches: {
|
|
17970
|
+
type: "array",
|
|
17971
|
+
description: "On 'ambiguous_lens': the lenses [{id, name}] the name matched.",
|
|
17972
|
+
items: { type: "object" }
|
|
17973
|
+
},
|
|
17974
|
+
lens_query: {
|
|
17975
|
+
type: "string",
|
|
17976
|
+
description: "On 'lens_not_found' / 'ambiguous_lens': the lensName the user asked for."
|
|
17977
|
+
},
|
|
17621
17978
|
message: { type: "string" },
|
|
17622
17979
|
lens_used: {
|
|
17623
17980
|
type: "object",
|
|
17624
|
-
description: "Resolved lens metadata: {id, name, was_draft, was_new, save_for_org}."
|
|
17981
|
+
description: "Resolved lens metadata: {id, name, was_draft, was_new, active_lens_changed, save_for_org}."
|
|
17625
17982
|
},
|
|
17626
17983
|
filter_applied: {
|
|
17627
17984
|
type: "object",
|
|
@@ -17634,28 +17991,63 @@ var init_adjust_audience = __esm({
|
|
|
17634
17991
|
execute: async (client, params, ctx) => {
|
|
17635
17992
|
const me = await client.resolveMe();
|
|
17636
17993
|
const isAdmin = me.admin === true;
|
|
17637
|
-
|
|
17994
|
+
let namedLensId;
|
|
17995
|
+
if (params.lensId == null && params.lensName != null && params.lensName.trim() !== "") {
|
|
17996
|
+
const res = await resolveLensByName(client, params.lensName);
|
|
17997
|
+
if (!res.ok && res.reason === "not_found") {
|
|
17998
|
+
return {
|
|
17999
|
+
status: "lens_not_found",
|
|
18000
|
+
lens_query: params.lensName,
|
|
18001
|
+
lenses: res.lenses,
|
|
18002
|
+
message: `No lens named "${params.lensName}". Pick one of the listed lenses (pass lensId or an exact lensName), or create it first.`
|
|
18003
|
+
};
|
|
18004
|
+
}
|
|
18005
|
+
if (!res.ok && res.reason === "ambiguous") {
|
|
18006
|
+
return {
|
|
18007
|
+
status: "ambiguous_lens",
|
|
18008
|
+
lens_query: params.lensName,
|
|
18009
|
+
matches: res.matches,
|
|
18010
|
+
message: `"${params.lensName}" matched multiple lenses. Re-call with the exact lensName or the lensId of the one you mean.`
|
|
18011
|
+
};
|
|
18012
|
+
}
|
|
18013
|
+
if (res.ok)
|
|
18014
|
+
namedLensId = res.id;
|
|
18015
|
+
}
|
|
18016
|
+
const startingLensId = params.lensId ?? namedLensId ?? me.last_requested_lens ?? await client.resolveDefaultLens();
|
|
18017
|
+
const isNamedEdit = namedLensId != null && params.lensId == null;
|
|
17638
18018
|
const includeTexts = [
|
|
17639
18019
|
...params.sectors ?? [],
|
|
17640
18020
|
...params.sector_ids ?? []
|
|
17641
18021
|
];
|
|
17642
18022
|
const excludeTexts = params.exclude_sectors ?? [];
|
|
17643
|
-
const includeRes = await resolveSectors(client, includeTexts);
|
|
17644
|
-
const excludeRes = await resolveSectors(client, excludeTexts);
|
|
18023
|
+
const includeRes = await resolveSectors(client, includeTexts, ctx);
|
|
18024
|
+
const excludeRes = await resolveSectors(client, excludeTexts, ctx);
|
|
17645
18025
|
const ambiguities = [
|
|
17646
18026
|
...includeRes.ambiguities,
|
|
17647
18027
|
...excludeRes.ambiguities
|
|
17648
18028
|
];
|
|
17649
18029
|
if (ambiguities.length > 0) {
|
|
18030
|
+
const noMatch = ambiguities.filter((a) => a.matches.length === 0);
|
|
18031
|
+
const multi = ambiguities.filter((a) => a.matches.length > 0);
|
|
18032
|
+
const parts = [];
|
|
18033
|
+
if (noMatch.length > 0) {
|
|
18034
|
+
const names = noMatch.map((a) => `"${a.sector_text}"`).join(", ");
|
|
18035
|
+
parts.push(`Couldn't find a sector matching ${names}. Ask the user to rephrase or pick a known sector, then re-call with sector_ids=...`);
|
|
18036
|
+
}
|
|
18037
|
+
if (multi.length > 0) {
|
|
18038
|
+
const names = multi.map((a) => `"${a.sector_text}"`).join(", ");
|
|
18039
|
+
parts.push(`${names} matched multiple sectors. Pick from the matches and re-call with sector_ids=...`);
|
|
18040
|
+
}
|
|
17650
18041
|
return {
|
|
17651
18042
|
status: "ambiguous_sectors",
|
|
17652
18043
|
sector_ambiguities: ambiguities,
|
|
17653
|
-
message: "
|
|
18044
|
+
message: parts.join(" ")
|
|
17654
18045
|
};
|
|
17655
18046
|
}
|
|
17656
18047
|
const lens = await client.request("GET", `/lenses/${startingLensId}`);
|
|
17657
18048
|
const currentFilter = await client.request("GET", `/lenses/${startingLensId}/filter`);
|
|
17658
18049
|
const merged = mergeFilter(currentFilter, includeRes.resolved, excludeRes.resolved, params.sizes);
|
|
18050
|
+
const mergedBody = filterWriteBody(merged);
|
|
17659
18051
|
const isDefault = lens.is_default || lens.default;
|
|
17660
18052
|
const isUserLevel = lens.user_id != null;
|
|
17661
18053
|
const isOrgLevel = !isUserLevel && !isDefault;
|
|
@@ -17664,24 +18056,28 @@ var init_adjust_audience = __esm({
|
|
|
17664
18056
|
let wasNew = false;
|
|
17665
18057
|
if (isDefault) {
|
|
17666
18058
|
const name = params.newLensName ?? `Custom audience \u2014 ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
17667
|
-
const
|
|
17668
|
-
base: startingLensId,
|
|
18059
|
+
const newLens2 = await client.request("POST", "/lenses", {
|
|
18060
|
+
base: String(startingLensId),
|
|
17669
18061
|
name
|
|
17670
18062
|
});
|
|
17671
|
-
targetLensId =
|
|
18063
|
+
targetLensId = newLens2.id;
|
|
17672
18064
|
wasNew = true;
|
|
17673
|
-
await client.requestVoid("POST", `/lenses/${targetLensId}/filter`,
|
|
17674
|
-
|
|
18065
|
+
await client.requestVoid("POST", `/lenses/${targetLensId}/filter`, mergedBody);
|
|
18066
|
+
if (!isNamedEdit) {
|
|
18067
|
+
await client.requestVoid("POST", `/lenses/${targetLensId}/update_last_requested`);
|
|
18068
|
+
}
|
|
17675
18069
|
} else if (isUserLevel) {
|
|
17676
18070
|
try {
|
|
17677
|
-
await client.requestVoid("POST", `/lenses/${startingLensId}/filter`,
|
|
18071
|
+
await client.requestVoid("POST", `/lenses/${startingLensId}/filter`, mergedBody);
|
|
17678
18072
|
} catch (err) {
|
|
17679
18073
|
if (err?.code === "FORBIDDEN") {
|
|
17680
18074
|
wasDraft = true;
|
|
17681
18075
|
const draft = await client.request("POST", `/lenses/${startingLensId}/draft`);
|
|
17682
18076
|
targetLensId = draft.id;
|
|
17683
|
-
await client.requestVoid("POST", `/lenses/${targetLensId}/filter`,
|
|
17684
|
-
|
|
18077
|
+
await client.requestVoid("POST", `/lenses/${targetLensId}/filter`, mergedBody);
|
|
18078
|
+
if (!isNamedEdit) {
|
|
18079
|
+
await client.requestVoid("POST", `/lenses/${targetLensId}/update_last_requested`);
|
|
18080
|
+
}
|
|
17685
18081
|
} else {
|
|
17686
18082
|
throw err;
|
|
17687
18083
|
}
|
|
@@ -17693,7 +18089,7 @@ var init_adjust_audience = __esm({
|
|
|
17693
18089
|
const draft = await client.request("POST", `/lenses/${startingLensId}/draft`);
|
|
17694
18090
|
targetLensId = draft.id;
|
|
17695
18091
|
try {
|
|
17696
|
-
await client.requestVoid("POST", `/lenses/${targetLensId}/filter`,
|
|
18092
|
+
await client.requestVoid("POST", `/lenses/${targetLensId}/filter`, mergedBody);
|
|
17697
18093
|
} catch (err) {
|
|
17698
18094
|
ctx?.logger?.warn?.(`adjust_audience: filter on draft ${targetLensId} failed: ${err?.message}`);
|
|
17699
18095
|
try {
|
|
@@ -17709,17 +18105,21 @@ var init_adjust_audience = __esm({
|
|
|
17709
18105
|
}
|
|
17710
18106
|
throw err;
|
|
17711
18107
|
}
|
|
17712
|
-
|
|
18108
|
+
if (!isNamedEdit) {
|
|
18109
|
+
await client.requestVoid("POST", `/lenses/${targetLensId}/update_last_requested`);
|
|
18110
|
+
}
|
|
17713
18111
|
} else {
|
|
17714
18112
|
try {
|
|
17715
|
-
await client.requestVoid("POST", `/lenses/${startingLensId}/filter`,
|
|
18113
|
+
await client.requestVoid("POST", `/lenses/${startingLensId}/filter`, mergedBody);
|
|
17716
18114
|
} catch (err) {
|
|
17717
18115
|
throw err;
|
|
17718
18116
|
}
|
|
17719
18117
|
}
|
|
17720
18118
|
}
|
|
17721
|
-
|
|
18119
|
+
if (!isNamedEdit)
|
|
18120
|
+
client.invalidateMe();
|
|
17722
18121
|
client.invalidateDefaultLens();
|
|
18122
|
+
const namedEditForkedMessage = isNamedEdit && (wasNew || wasDraft) ? ` Note: "${lens.name}" can't be edited in place, so the change was applied to a ${wasDraft ? "personal draft" : "new copy"} (id ${targetLensId}); your active lens is unchanged.` : "";
|
|
17723
18123
|
return {
|
|
17724
18124
|
status: "applied",
|
|
17725
18125
|
lens_used: {
|
|
@@ -17727,10 +18127,11 @@ var init_adjust_audience = __esm({
|
|
|
17727
18127
|
name: lens.name,
|
|
17728
18128
|
was_draft: wasDraft,
|
|
17729
18129
|
was_new: wasNew,
|
|
18130
|
+
active_lens_changed: !isNamedEdit && (wasNew || wasDraft),
|
|
17730
18131
|
save_for_org: params.save_for_org === true && isAdmin && isOrgLevel
|
|
17731
18132
|
},
|
|
17732
18133
|
filter_applied: merged,
|
|
17733
|
-
message: wasDraft ? "Applied to your personal draft of the org lens (your view only)." : wasNew ? `Created a new user-level lens "${lens.name}" with the filter (you can rename via leadbay_update_lens).` : "Applied directly to the lens.",
|
|
18134
|
+
message: (wasDraft ? "Applied to your personal draft of the org lens (your view only)." : wasNew ? `Created a new user-level lens "${lens.name}" with the filter (you can rename via leadbay_update_lens).` : "Applied directly to the lens.") + namedEditForkedMessage,
|
|
17734
18135
|
_meta: { region: client.region }
|
|
17735
18136
|
};
|
|
17736
18137
|
}
|
|
@@ -18125,6 +18526,399 @@ var init_extend_lens = __esm({
|
|
|
18125
18526
|
}
|
|
18126
18527
|
});
|
|
18127
18528
|
|
|
18529
|
+
// ../core/dist/composite/my-lenses.js
|
|
18530
|
+
async function listWithActive(client) {
|
|
18531
|
+
const lenses = await client.request("GET", "/lenses");
|
|
18532
|
+
const me = await client.resolveMe().catch(() => null);
|
|
18533
|
+
const activeFromMe = sid(me?.last_requested_lens);
|
|
18534
|
+
const active_lens_id = activeFromMe ?? sid(lenses.find((l) => l.is_last_active)?.id) ?? null;
|
|
18535
|
+
return {
|
|
18536
|
+
active_lens_id,
|
|
18537
|
+
lenses: lenses.map((l) => ({
|
|
18538
|
+
id: sid(l.id),
|
|
18539
|
+
name: l.name,
|
|
18540
|
+
description: l.description ?? null,
|
|
18541
|
+
is_active: sid(l.id) === active_lens_id,
|
|
18542
|
+
is_default: l.is_default === true || l.default === true
|
|
18543
|
+
}))
|
|
18544
|
+
};
|
|
18545
|
+
}
|
|
18546
|
+
var sid, myLenses;
|
|
18547
|
+
var init_my_lenses = __esm({
|
|
18548
|
+
"../core/dist/composite/my-lenses.js"() {
|
|
18549
|
+
"use strict";
|
|
18550
|
+
init_tool_descriptions_generated();
|
|
18551
|
+
sid = (v) => v == null ? null : String(v);
|
|
18552
|
+
myLenses = {
|
|
18553
|
+
name: "leadbay_my_lenses",
|
|
18554
|
+
annotations: {
|
|
18555
|
+
title: "List, switch, edit, or delete your lenses",
|
|
18556
|
+
// No args → pure read. The delete mode issues DELETE /lenses/:id (an
|
|
18557
|
+
// irreversible side effect), so the tool is destructive — clients must
|
|
18558
|
+
// treat it as approval-required, not auto-run. The delete path is itself
|
|
18559
|
+
// confirm-gated (preview unless confirm:true). switch/edit are not
|
|
18560
|
+
// idempotent across modes either, so don't claim idempotency.
|
|
18561
|
+
readOnlyHint: false,
|
|
18562
|
+
destructiveHint: true,
|
|
18563
|
+
idempotentHint: false,
|
|
18564
|
+
openWorldHint: true
|
|
18565
|
+
},
|
|
18566
|
+
description: leadbay_my_lenses,
|
|
18567
|
+
inputSchema: {
|
|
18568
|
+
type: "object",
|
|
18569
|
+
properties: {
|
|
18570
|
+
switchToLensId: {
|
|
18571
|
+
type: ["string", "number"],
|
|
18572
|
+
description: "When set, switch the active lens to this id (must be one of the user's lenses), then return the refreshed list."
|
|
18573
|
+
},
|
|
18574
|
+
editLensId: {
|
|
18575
|
+
type: ["string", "number"],
|
|
18576
|
+
description: "When set, edit this lens's metadata \u2014 provide newName and/or newDescription. Must be one of the user's lenses."
|
|
18577
|
+
},
|
|
18578
|
+
newName: {
|
|
18579
|
+
type: "string",
|
|
18580
|
+
description: "New lens name (used with editLensId)."
|
|
18581
|
+
},
|
|
18582
|
+
newDescription: {
|
|
18583
|
+
type: "string",
|
|
18584
|
+
description: "New lens description (used with editLensId). Pass an empty string to clear it."
|
|
18585
|
+
},
|
|
18586
|
+
deleteLensId: {
|
|
18587
|
+
type: ["string", "number"],
|
|
18588
|
+
description: "When set, delete this lens. DESTRUCTIVE \u2014 returns a delete_preview unless confirm:true. Cannot delete the default lens."
|
|
18589
|
+
},
|
|
18590
|
+
confirm: {
|
|
18591
|
+
type: "boolean",
|
|
18592
|
+
description: "Required (=true) to actually delete. Without it, deleteLensId returns a preview to confirm with the user first."
|
|
18593
|
+
}
|
|
18594
|
+
},
|
|
18595
|
+
additionalProperties: false
|
|
18596
|
+
},
|
|
18597
|
+
outputSchema: {
|
|
18598
|
+
type: "object",
|
|
18599
|
+
properties: {
|
|
18600
|
+
status: {
|
|
18601
|
+
type: "string",
|
|
18602
|
+
description: "'listed', 'switched', 'already_active', 'edited', 'deleted', 'delete_preview' (confirm to proceed), 'cannot_delete_default', or 'not_found'."
|
|
18603
|
+
},
|
|
18604
|
+
switched: { type: "boolean", description: "True when this call changed the active lens." },
|
|
18605
|
+
edited: { type: "boolean", description: "True when this call renamed/re-described a lens." },
|
|
18606
|
+
deleted: { type: "boolean", description: "True when this call deleted a lens." },
|
|
18607
|
+
will_delete: {
|
|
18608
|
+
type: "object",
|
|
18609
|
+
description: "On 'delete_preview': the lens that WILL be deleted {id, name}. Nothing removed yet."
|
|
18610
|
+
},
|
|
18611
|
+
active_lens_id: { type: ["string", "null"] },
|
|
18612
|
+
lenses: {
|
|
18613
|
+
type: "array",
|
|
18614
|
+
description: "The user's lenses. Each: {id, name, description, is_active}.",
|
|
18615
|
+
items: { type: "object" }
|
|
18616
|
+
},
|
|
18617
|
+
message: { type: "string" }
|
|
18618
|
+
},
|
|
18619
|
+
required: ["status", "lenses", "active_lens_id"]
|
|
18620
|
+
},
|
|
18621
|
+
execute: async (client, params) => {
|
|
18622
|
+
if (params.deleteLensId != null) {
|
|
18623
|
+
const targetId = sid(params.deleteLensId);
|
|
18624
|
+
const before = await listWithActive(client);
|
|
18625
|
+
const target = before.lenses.find((l) => l.id === targetId);
|
|
18626
|
+
if (!target) {
|
|
18627
|
+
return {
|
|
18628
|
+
status: "not_found",
|
|
18629
|
+
switched: false,
|
|
18630
|
+
edited: false,
|
|
18631
|
+
deleted: false,
|
|
18632
|
+
active_lens_id: before.active_lens_id,
|
|
18633
|
+
lenses: before.lenses,
|
|
18634
|
+
message: `No lens with id ${targetId}. Pick one from the list.`
|
|
18635
|
+
};
|
|
18636
|
+
}
|
|
18637
|
+
if (target.is_default) {
|
|
18638
|
+
return {
|
|
18639
|
+
status: "cannot_delete_default",
|
|
18640
|
+
switched: false,
|
|
18641
|
+
edited: false,
|
|
18642
|
+
deleted: false,
|
|
18643
|
+
active_lens_id: before.active_lens_id,
|
|
18644
|
+
lenses: before.lenses,
|
|
18645
|
+
message: `"${target.name}" is the default lens and can't be deleted.`
|
|
18646
|
+
};
|
|
18647
|
+
}
|
|
18648
|
+
if (params.confirm !== true) {
|
|
18649
|
+
return {
|
|
18650
|
+
status: "delete_preview",
|
|
18651
|
+
switched: false,
|
|
18652
|
+
edited: false,
|
|
18653
|
+
deleted: false,
|
|
18654
|
+
active_lens_id: before.active_lens_id,
|
|
18655
|
+
lenses: before.lenses,
|
|
18656
|
+
will_delete: { id: target.id, name: target.name },
|
|
18657
|
+
message: `About to delete "${target.name}". This can't be undone. Confirm with the user, then re-call with confirm:true.`
|
|
18658
|
+
};
|
|
18659
|
+
}
|
|
18660
|
+
await client.requestVoid("DELETE", `/lenses/${targetId}`);
|
|
18661
|
+
client.invalidateMe();
|
|
18662
|
+
client.invalidateDefaultLens();
|
|
18663
|
+
const after = await listWithActive(client);
|
|
18664
|
+
return {
|
|
18665
|
+
status: "deleted",
|
|
18666
|
+
switched: false,
|
|
18667
|
+
edited: false,
|
|
18668
|
+
deleted: true,
|
|
18669
|
+
active_lens_id: after.active_lens_id,
|
|
18670
|
+
lenses: after.lenses,
|
|
18671
|
+
message: `Deleted "${target.name}".`
|
|
18672
|
+
};
|
|
18673
|
+
}
|
|
18674
|
+
if (params.editLensId != null) {
|
|
18675
|
+
const targetId = sid(params.editLensId);
|
|
18676
|
+
const before = await listWithActive(client);
|
|
18677
|
+
const target = before.lenses.find((l) => l.id === targetId);
|
|
18678
|
+
if (!target) {
|
|
18679
|
+
return {
|
|
18680
|
+
status: "not_found",
|
|
18681
|
+
switched: false,
|
|
18682
|
+
edited: false,
|
|
18683
|
+
active_lens_id: before.active_lens_id,
|
|
18684
|
+
lenses: before.lenses,
|
|
18685
|
+
message: `No lens with id ${targetId}. Pick one from the list.`
|
|
18686
|
+
};
|
|
18687
|
+
}
|
|
18688
|
+
const body = {};
|
|
18689
|
+
const newName = params.newName?.trim();
|
|
18690
|
+
if (newName)
|
|
18691
|
+
body.name = newName;
|
|
18692
|
+
if (params.newDescription !== void 0)
|
|
18693
|
+
body.description = params.newDescription;
|
|
18694
|
+
if (Object.keys(body).length === 0) {
|
|
18695
|
+
return {
|
|
18696
|
+
status: "not_found",
|
|
18697
|
+
switched: false,
|
|
18698
|
+
edited: false,
|
|
18699
|
+
active_lens_id: before.active_lens_id,
|
|
18700
|
+
lenses: before.lenses,
|
|
18701
|
+
message: `Nothing to change on "${target.name}" \u2014 provide newName and/or newDescription.`
|
|
18702
|
+
};
|
|
18703
|
+
}
|
|
18704
|
+
await client.requestVoid("POST", `/lenses/${targetId}`, body);
|
|
18705
|
+
client.invalidateDefaultLens();
|
|
18706
|
+
const changed = [
|
|
18707
|
+
body.name != null ? `renamed to "${body.name}"` : null,
|
|
18708
|
+
body.description !== void 0 ? "description updated" : null
|
|
18709
|
+
].filter(Boolean).join(", ");
|
|
18710
|
+
const after = await listWithActive(client);
|
|
18711
|
+
return {
|
|
18712
|
+
status: "edited",
|
|
18713
|
+
switched: false,
|
|
18714
|
+
edited: true,
|
|
18715
|
+
active_lens_id: after.active_lens_id,
|
|
18716
|
+
lenses: after.lenses,
|
|
18717
|
+
message: `"${target.name}" \u2014 ${changed}.`
|
|
18718
|
+
};
|
|
18719
|
+
}
|
|
18720
|
+
if (params.switchToLensId != null) {
|
|
18721
|
+
const targetId = sid(params.switchToLensId);
|
|
18722
|
+
const before = await listWithActive(client);
|
|
18723
|
+
const target = before.lenses.find((l) => l.id === targetId);
|
|
18724
|
+
if (!target) {
|
|
18725
|
+
return {
|
|
18726
|
+
status: "not_found",
|
|
18727
|
+
switched: false,
|
|
18728
|
+
edited: false,
|
|
18729
|
+
active_lens_id: before.active_lens_id,
|
|
18730
|
+
lenses: before.lenses,
|
|
18731
|
+
message: `No lens with id ${targetId}. Pick an id from the list.`
|
|
18732
|
+
};
|
|
18733
|
+
}
|
|
18734
|
+
if (target.is_active) {
|
|
18735
|
+
return {
|
|
18736
|
+
status: "already_active",
|
|
18737
|
+
switched: false,
|
|
18738
|
+
edited: false,
|
|
18739
|
+
active_lens_id: before.active_lens_id,
|
|
18740
|
+
lenses: before.lenses,
|
|
18741
|
+
message: `"${target.name}" is already your active lens.`
|
|
18742
|
+
};
|
|
18743
|
+
}
|
|
18744
|
+
await client.requestVoid("POST", `/lenses/${targetId}/update_last_requested`);
|
|
18745
|
+
client.invalidateMe();
|
|
18746
|
+
client.invalidateDefaultLens();
|
|
18747
|
+
const after = await listWithActive(client);
|
|
18748
|
+
return {
|
|
18749
|
+
status: "switched",
|
|
18750
|
+
switched: true,
|
|
18751
|
+
edited: false,
|
|
18752
|
+
active_lens_id: after.active_lens_id,
|
|
18753
|
+
lenses: after.lenses,
|
|
18754
|
+
message: `Now showing "${target.name}".`
|
|
18755
|
+
};
|
|
18756
|
+
}
|
|
18757
|
+
const { lenses, active_lens_id } = await listWithActive(client);
|
|
18758
|
+
return { status: "listed", switched: false, edited: false, active_lens_id, lenses };
|
|
18759
|
+
}
|
|
18760
|
+
};
|
|
18761
|
+
}
|
|
18762
|
+
});
|
|
18763
|
+
|
|
18764
|
+
// ../core/dist/composite/new-lens.js
|
|
18765
|
+
var EMPTY_FILTER, newLens;
|
|
18766
|
+
var init_new_lens = __esm({
|
|
18767
|
+
"../core/dist/composite/new-lens.js"() {
|
|
18768
|
+
"use strict";
|
|
18769
|
+
init_adjust_audience();
|
|
18770
|
+
init_tool_descriptions_generated();
|
|
18771
|
+
EMPTY_FILTER = {
|
|
18772
|
+
lens_filter: { items: [{ criteria: [] }] },
|
|
18773
|
+
locations: { results: [], parents: [] }
|
|
18774
|
+
};
|
|
18775
|
+
newLens = {
|
|
18776
|
+
name: "leadbay_new_lens",
|
|
18777
|
+
annotations: {
|
|
18778
|
+
title: "Create a new named lens",
|
|
18779
|
+
readOnlyHint: false,
|
|
18780
|
+
destructiveHint: false,
|
|
18781
|
+
idempotentHint: false,
|
|
18782
|
+
// each call creates a distinct lens
|
|
18783
|
+
openWorldHint: true
|
|
18784
|
+
},
|
|
18785
|
+
description: leadbay_new_lens,
|
|
18786
|
+
inputSchema: {
|
|
18787
|
+
type: "object",
|
|
18788
|
+
properties: {
|
|
18789
|
+
name: { type: "string", description: "Display name for the new lens (required)." },
|
|
18790
|
+
sectors: {
|
|
18791
|
+
type: "array",
|
|
18792
|
+
items: { type: "string" },
|
|
18793
|
+
description: "Sectors to include \u2014 free text (auto-resolved) or ids."
|
|
18794
|
+
},
|
|
18795
|
+
exclude_sectors: {
|
|
18796
|
+
type: "array",
|
|
18797
|
+
items: { type: "string" },
|
|
18798
|
+
description: "Sectors to exclude \u2014 free text or ids."
|
|
18799
|
+
},
|
|
18800
|
+
sizes: {
|
|
18801
|
+
type: "array",
|
|
18802
|
+
items: {
|
|
18803
|
+
type: "object",
|
|
18804
|
+
properties: { min: { type: "number" }, max: { type: "number" } }
|
|
18805
|
+
},
|
|
18806
|
+
description: "Company size buckets, e.g. [{min:30,max:300}]."
|
|
18807
|
+
},
|
|
18808
|
+
base: {
|
|
18809
|
+
type: "number",
|
|
18810
|
+
description: "Lens id to clone from. Defaults to the active/default lens."
|
|
18811
|
+
},
|
|
18812
|
+
description: { type: "string", description: "Optional lens description." },
|
|
18813
|
+
confirm: {
|
|
18814
|
+
type: "boolean",
|
|
18815
|
+
description: "Safety gate. Defaults to false \u2192 the tool returns a PREVIEW and creates nothing. Show the preview to the user, get their explicit go-ahead, then re-call the SAME args with confirm:true to actually create the lens."
|
|
18816
|
+
}
|
|
18817
|
+
},
|
|
18818
|
+
required: ["name"],
|
|
18819
|
+
additionalProperties: false
|
|
18820
|
+
},
|
|
18821
|
+
outputSchema: {
|
|
18822
|
+
type: "object",
|
|
18823
|
+
description: "'preview' (default, NOTHING created \u2014 confirm with the user then re-call with confirm:true); 'created' on success; 'ambiguous_sectors' when free-text sectors didn't resolve (re-call with sector ids \u2014 the lens was NOT created).",
|
|
18824
|
+
properties: {
|
|
18825
|
+
status: { type: "string", description: "'preview', 'created', 'ambiguous_sectors', or 'orphan_created' (filter write failed + cleanup failed)." },
|
|
18826
|
+
will_create: {
|
|
18827
|
+
type: "object",
|
|
18828
|
+
description: "On 'preview': what WILL be created \u2014 {name, description, sectors, exclude_sectors, sizes}. Nothing has been written yet."
|
|
18829
|
+
},
|
|
18830
|
+
filter_preview: { type: "object", description: "On 'preview': the FilterPayload that would be applied." },
|
|
18831
|
+
lens: {
|
|
18832
|
+
type: "object",
|
|
18833
|
+
description: "On 'created': the created lens {id, name}."
|
|
18834
|
+
},
|
|
18835
|
+
sector_ambiguities: {
|
|
18836
|
+
type: "array",
|
|
18837
|
+
description: "On 'ambiguous_sectors': per text {sector_text, matches:[{id,name,score}]}.",
|
|
18838
|
+
items: { type: "object" }
|
|
18839
|
+
},
|
|
18840
|
+
filter_applied: { type: "object", description: "On 'created': the FilterPayload POSTed to the new lens." },
|
|
18841
|
+
message: { type: "string" },
|
|
18842
|
+
_meta: { type: "object" }
|
|
18843
|
+
},
|
|
18844
|
+
required: ["status"]
|
|
18845
|
+
},
|
|
18846
|
+
execute: async (client, params, ctx) => {
|
|
18847
|
+
const includeRes = await resolveSectors(client, params.sectors ?? [], ctx);
|
|
18848
|
+
const excludeRes = await resolveSectors(client, params.exclude_sectors ?? [], ctx);
|
|
18849
|
+
const ambiguities = [...includeRes.ambiguities, ...excludeRes.ambiguities];
|
|
18850
|
+
if (ambiguities.length > 0) {
|
|
18851
|
+
const noMatch = ambiguities.filter((a) => a.matches.length === 0);
|
|
18852
|
+
const multi = ambiguities.filter((a) => a.matches.length > 0);
|
|
18853
|
+
const parts = [];
|
|
18854
|
+
if (noMatch.length > 0) {
|
|
18855
|
+
parts.push(`Couldn't find a sector matching ${noMatch.map((a) => `"${a.sector_text}"`).join(", ")}. Pick a known sector and re-call (lens not yet created).`);
|
|
18856
|
+
}
|
|
18857
|
+
if (multi.length > 0) {
|
|
18858
|
+
parts.push(`${multi.map((a) => `"${a.sector_text}"`).join(", ")} matched multiple sectors. Pick from the matches and re-call with the sector id.`);
|
|
18859
|
+
}
|
|
18860
|
+
return {
|
|
18861
|
+
status: "ambiguous_sectors",
|
|
18862
|
+
sector_ambiguities: ambiguities,
|
|
18863
|
+
message: parts.join(" ")
|
|
18864
|
+
};
|
|
18865
|
+
}
|
|
18866
|
+
const merged = mergeFilter(EMPTY_FILTER, includeRes.resolved, excludeRes.resolved, params.sizes);
|
|
18867
|
+
if (params.confirm !== true) {
|
|
18868
|
+
return {
|
|
18869
|
+
status: "preview",
|
|
18870
|
+
will_create: {
|
|
18871
|
+
name: params.name,
|
|
18872
|
+
description: params.description ?? null,
|
|
18873
|
+
sectors: includeRes.resolved,
|
|
18874
|
+
exclude_sectors: excludeRes.resolved,
|
|
18875
|
+
sizes: merged.lens_filter.items[0].criteria.find((c) => c.type === "size") ?? null
|
|
18876
|
+
},
|
|
18877
|
+
filter_preview: merged,
|
|
18878
|
+
message: `About to create "${params.name}". Confirm with the user, then re-call with confirm:true.`,
|
|
18879
|
+
_meta: { region: client.region }
|
|
18880
|
+
};
|
|
18881
|
+
}
|
|
18882
|
+
const base = params.base ?? await client.resolveDefaultLens();
|
|
18883
|
+
const created = await client.request("POST", "/lenses", {
|
|
18884
|
+
base: String(base),
|
|
18885
|
+
name: params.name,
|
|
18886
|
+
description: params.description
|
|
18887
|
+
});
|
|
18888
|
+
const hasCriteria = merged.lens_filter.items[0].criteria.length > 0;
|
|
18889
|
+
if (hasCriteria) {
|
|
18890
|
+
try {
|
|
18891
|
+
await client.requestVoid("POST", `/lenses/${created.id}/filter`, filterWriteBody(merged));
|
|
18892
|
+
} catch (err) {
|
|
18893
|
+
ctx?.logger?.warn?.(`new_lens: filter write on new lens ${created.id} failed: ${err?.message} \u2014 rolling back`);
|
|
18894
|
+
try {
|
|
18895
|
+
await client.requestVoid("DELETE", `/lenses/${created.id}`);
|
|
18896
|
+
} catch {
|
|
18897
|
+
client.invalidateDefaultLens();
|
|
18898
|
+
return {
|
|
18899
|
+
status: "orphan_created",
|
|
18900
|
+
lens: { id: created.id, name: created.name },
|
|
18901
|
+
message: `Created "${created.name}" but applying its filter failed, and cleanup also failed. The lens exists with no criteria \u2014 delete it via leadbay_my_lenses(deleteLensId:"${created.id}", confirm:true) or set its audience with leadbay_adjust_audience.`,
|
|
18902
|
+
_meta: { region: client.region }
|
|
18903
|
+
};
|
|
18904
|
+
}
|
|
18905
|
+
client.invalidateDefaultLens();
|
|
18906
|
+
throw err;
|
|
18907
|
+
}
|
|
18908
|
+
}
|
|
18909
|
+
client.invalidateDefaultLens();
|
|
18910
|
+
return {
|
|
18911
|
+
status: "created",
|
|
18912
|
+
lens: { id: created.id, name: created.name },
|
|
18913
|
+
filter_applied: merged,
|
|
18914
|
+
message: `Created "${created.name}".`,
|
|
18915
|
+
_meta: { region: client.region }
|
|
18916
|
+
};
|
|
18917
|
+
}
|
|
18918
|
+
};
|
|
18919
|
+
}
|
|
18920
|
+
});
|
|
18921
|
+
|
|
18128
18922
|
// ../core/dist/composite/answer-clarification.js
|
|
18129
18923
|
var answerClarification;
|
|
18130
18924
|
var init_answer_clarification = __esm({
|
|
@@ -18867,6 +19661,8 @@ var init_dist = __esm({
|
|
|
18867
19661
|
init_refine_prompt();
|
|
18868
19662
|
init_seed_candidates();
|
|
18869
19663
|
init_extend_lens();
|
|
19664
|
+
init_my_lenses();
|
|
19665
|
+
init_new_lens();
|
|
18870
19666
|
init_answer_clarification();
|
|
18871
19667
|
init_report_outreach();
|
|
18872
19668
|
init_report_friction();
|
|
@@ -18886,7 +19682,6 @@ var init_dist = __esm({
|
|
|
18886
19682
|
getQuota,
|
|
18887
19683
|
getLensFilter,
|
|
18888
19684
|
getLensScoring,
|
|
18889
|
-
listSectors,
|
|
18890
19685
|
listLocations,
|
|
18891
19686
|
getUserPrompt,
|
|
18892
19687
|
getClarification,
|
|
@@ -18957,6 +19752,11 @@ var init_dist = __esm({
|
|
|
18957
19752
|
// it for discoverability; expose it always-on so agents can find custom fields
|
|
18958
19753
|
// without needing LEADBAY_MCP_ADVANCED=1.
|
|
18959
19754
|
listMappableFields,
|
|
19755
|
+
// listSectors is granular-shaped but ALWAYS exposed: it's the sector taxonomy
|
|
19756
|
+
// lookup the agent needs to STOP guessing sector names (and to feed
|
|
19757
|
+
// leadbay_new_lens / leadbay_adjust_audience). Without it the agent can only
|
|
19758
|
+
// probe sectors by trial-and-error or ask the user to read the web UI.
|
|
19759
|
+
listSectors,
|
|
18960
19760
|
// Billing / top-up tools — granular-shaped but ALWAYS exposed because
|
|
18961
19761
|
// they're the canonical recovery path from a QUOTA_EXCEEDED wall. If
|
|
18962
19762
|
// they were gated behind LEADBAY_MCP_ADVANCED=1 the agent would
|
|
@@ -18999,7 +19799,16 @@ var init_dist = __esm({
|
|
|
18999
19799
|
removeLeadsFromCampaign,
|
|
19000
19800
|
// Lens extend — agent-driven on-demand fill (additive). Gated behind
|
|
19001
19801
|
// LEADBAY_MCP_WRITE=1. Subject to per-org daily LENS_EXTRA_REFILL quota.
|
|
19002
|
-
extendLens
|
|
19802
|
+
extendLens,
|
|
19803
|
+
// Lens list/switch — read-first (no args = pure list); a switchToLensId
|
|
19804
|
+
// changes the active lens. In compositeWriteTools because the switch path
|
|
19805
|
+
// mutates last_requested_lens, but it stays on the default surface
|
|
19806
|
+
// (write is on by default since 0.3.0).
|
|
19807
|
+
myLenses,
|
|
19808
|
+
// Lens creation — make a brand-new named lens with sectors/sizes in one
|
|
19809
|
+
// call. Default-surface so "create a lens called X for Y" works without
|
|
19810
|
+
// the advanced gate.
|
|
19811
|
+
newLens
|
|
19003
19812
|
];
|
|
19004
19813
|
compositeTools = [
|
|
19005
19814
|
...compositeReadTools,
|
|
@@ -22871,7 +23680,7 @@ var OAUTH_BASE_URLS = {
|
|
|
22871
23680
|
fr: "https://staging.api.leadbay.app"
|
|
22872
23681
|
}
|
|
22873
23682
|
};
|
|
22874
|
-
var VERSION = "0.17.
|
|
23683
|
+
var VERSION = "0.17.3";
|
|
22875
23684
|
var HELP = `
|
|
22876
23685
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
22877
23686
|
|