@leadbay/mcp 0.17.1 → 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 +16 -0
- package/README.md +38 -1
- package/dist/bin.js +844 -35
- package/dist/http-server.js +822 -30
- package/dist/installer-electron.js +168 -196
- package/dist/installer-gui.js +104 -113
- package/package.json +3 -4
package/dist/http-server.js
CHANGED
|
@@ -6187,6 +6187,8 @@ var COMPOSITE_FILE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
6187
6187
|
"leadbay_import_leads",
|
|
6188
6188
|
"leadbay_import_status",
|
|
6189
6189
|
"leadbay_list_campaigns",
|
|
6190
|
+
"leadbay_my_lenses",
|
|
6191
|
+
"leadbay_new_lens",
|
|
6190
6192
|
"leadbay_prepare_outreach",
|
|
6191
6193
|
"leadbay_pull_followups",
|
|
6192
6194
|
"leadbay_pull_leads",
|
|
@@ -6297,7 +6299,37 @@ WHEN NOT TO USE: to log an outreach action \u2014 use leadbay_report_outreach, w
|
|
|
6297
6299
|
|
|
6298
6300
|
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\`.
|
|
6299
6301
|
`;
|
|
6300
|
-
var leadbay_adjust_audience =
|
|
6302
|
+
var leadbay_adjust_audience = `## WHEN TO USE
|
|
6303
|
+
|
|
6304
|
+
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>".
|
|
6305
|
+
|
|
6306
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
6307
|
+
|
|
6308
|
+
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\`.
|
|
6309
|
+
|
|
6310
|
+
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.
|
|
6311
|
+
|
|
6312
|
+
Examples that SHOULD invoke this tool:
|
|
6313
|
+
- "Add fintech to my Joinery lens."
|
|
6314
|
+
- "Narrow my audience to manufacturing companies, 50\u2013500 people."
|
|
6315
|
+
- "Stop including retail in this lens."
|
|
6316
|
+
|
|
6317
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
6318
|
+
- "Create a lens called Joinery for fintech."
|
|
6319
|
+
- "Show me my lenses."
|
|
6320
|
+
- "Focus on hospitals that run their own IT."
|
|
6321
|
+
|
|
6322
|
+
## RENDER (quick)
|
|
6323
|
+
|
|
6324
|
+
On \`applied\`: confirm the lens edited (name) + the sectors/sizes added as
|
|
6325
|
+
chips. On \`ambiguous_sectors\` / \`ambiguous_lens\` / \`lens_not_found\`: surface
|
|
6326
|
+
the candidates and ask the user to pick, then re-call with the id/exact name.
|
|
6327
|
+
|
|
6328
|
+
---
|
|
6329
|
+
|
|
6330
|
+
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).
|
|
6331
|
+
|
|
6332
|
+
**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.
|
|
6301
6333
|
|
|
6302
6334
|
WHEN TO USE: when the user wants to see different kinds of leads (sector / size / etc.).
|
|
6303
6335
|
|
|
@@ -7511,6 +7543,258 @@ WHEN NOT TO USE: if a token is already preconfigured \u2014 you'll just overwrit
|
|
|
7511
7543
|
|
|
7512
7544
|
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\`.
|
|
7513
7545
|
`;
|
|
7546
|
+
var leadbay_my_lenses = `## WHEN TO USE
|
|
7547
|
+
|
|
7548
|
+
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".
|
|
7549
|
+
|
|
7550
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
7551
|
+
|
|
7552
|
+
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\`.
|
|
7553
|
+
|
|
7554
|
+
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
|
|
7555
|
+
|
|
7556
|
+
Examples that SHOULD invoke this tool:
|
|
7557
|
+
- "Show me my lenses."
|
|
7558
|
+
- "Rename my Auto lens to Automotive and add a description."
|
|
7559
|
+
- "Delete my old Auto lens."
|
|
7560
|
+
|
|
7561
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
7562
|
+
- "Narrow the audience to fintech only."
|
|
7563
|
+
- "I want more leads on this lens."
|
|
7564
|
+
- "Show me today's leads."
|
|
7565
|
+
|
|
7566
|
+
## RENDER (quick)
|
|
7567
|
+
|
|
7568
|
+
Small markdown table, active lens first: col 1 = \u2B50 prefix when active +
|
|
7569
|
+
lens name; col 2 = description (or \`\u2014\`). After a switch lead with
|
|
7570
|
+
"Now showing **<name>**."; after a rename lead with the rename confirmation.
|
|
7571
|
+
Full algorithm below.
|
|
7572
|
+
|
|
7573
|
+
---
|
|
7574
|
+
|
|
7575
|
+
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\`).
|
|
7576
|
+
|
|
7577
|
+
**Three modes, one tool:**
|
|
7578
|
+
|
|
7579
|
+
- **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.
|
|
7580
|
+
- **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.
|
|
7581
|
+
- **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.
|
|
7582
|
+
- **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.
|
|
7583
|
+
|
|
7584
|
+
**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.
|
|
7585
|
+
|
|
7586
|
+
**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.
|
|
7587
|
+
|
|
7588
|
+
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".
|
|
7589
|
+
|
|
7590
|
+
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\`).
|
|
7591
|
+
|
|
7592
|
+
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\`.
|
|
7593
|
+
|
|
7594
|
+
|
|
7595
|
+
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
7596
|
+
|
|
7597
|
+
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.
|
|
7598
|
+
|
|
7599
|
+
**The Big Three** \u2014 when a tool result fits, route there:
|
|
7600
|
+
|
|
7601
|
+
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7602
|
+
|---|---|---|
|
|
7603
|
+
| \`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 |
|
|
7604
|
+
| \`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") |
|
|
7605
|
+
| \`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 |
|
|
7606
|
+
|
|
7607
|
+
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.
|
|
7608
|
+
|
|
7609
|
+
**Rules:**
|
|
7610
|
+
- 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.
|
|
7611
|
+
- Pass identifiers (place_id, lead.id, contact_id) verbatim. Don't rewrite.
|
|
7612
|
+
- 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.
|
|
7613
|
+
- One short intro sentence in chat is enough \u2014 "Here are your 5 NYC follow-ups." Then route into the widget.
|
|
7614
|
+
|
|
7615
|
+
|
|
7616
|
+
---
|
|
7617
|
+
|
|
7618
|
+
## RENDERING \u2014 lenses table, active-first
|
|
7619
|
+
|
|
7620
|
+
Markdown table with TWO columns. Sort **active lens first**, then by \`name\`
|
|
7621
|
+
ascending. **No score bar** \u2014 the \`\u25B0\u2756\u25B1\` glyph identity belongs to lead
|
|
7622
|
+
discovery, not lenses.
|
|
7623
|
+
|
|
7624
|
+
**Column 1 \u2014 Lens**
|
|
7625
|
+
- Prefix \`\u2B50 \` when \`is_active\` is true; otherwise no prefix.
|
|
7626
|
+
- The lens name in **bold**. (Lenses have no public URL \u2014 do not fabricate a link.)
|
|
7627
|
+
|
|
7628
|
+
**Column 2 \u2014 Description**
|
|
7629
|
+
- \`description\` verbatim, clipped to \u2264 18 words.
|
|
7630
|
+
- When null/empty: render \`\u2014\`.
|
|
7631
|
+
|
|
7632
|
+
**After a \`switched: true\` response**, open with a single confirmation line
|
|
7633
|
+
ABOVE the table: \`Now showing **<name>**.\` For \`status: "not_found"\`, lead with
|
|
7634
|
+
the \`message\` (the bad id) and render the list so the user can pick a real one.
|
|
7635
|
+
|
|
7636
|
+
**Empty list** (\`lenses: []\`): render \`*You don't have any lenses yet.*\` \u2014 do not
|
|
7637
|
+
render an empty table.
|
|
7638
|
+
|
|
7639
|
+
**Legend:** \u2B50 active lens.
|
|
7640
|
+
|
|
7641
|
+
|
|
7642
|
+
## NEXT STEPS \u2014 after \`leadbay_my_lenses\`
|
|
7643
|
+
|
|
7644
|
+
**RENDER NEXT STEPS via \`ask_user_input_v0\` when the host exposes it.**
|
|
7645
|
+
|
|
7646
|
+
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:
|
|
7647
|
+
|
|
7648
|
+
\`\`\`
|
|
7649
|
+
ask_user_input_v0({
|
|
7650
|
+
questions: [{
|
|
7651
|
+
question: "What next?",
|
|
7652
|
+
type: "single_select",
|
|
7653
|
+
options: [
|
|
7654
|
+
"<Suggest column from row 1>",
|
|
7655
|
+
"<Suggest column from row 2>",
|
|
7656
|
+
"<Suggest column from row 3>"
|
|
7657
|
+
]
|
|
7658
|
+
}]
|
|
7659
|
+
})
|
|
7660
|
+
\`\`\`
|
|
7661
|
+
|
|
7662
|
+
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.
|
|
7663
|
+
|
|
7664
|
+
**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.
|
|
7665
|
+
|
|
7666
|
+
---
|
|
7667
|
+
|
|
7668
|
+
|
|
7669
|
+
|
|
7670
|
+
Pick the 2\u20133 rows that fit what the user is likely to want next. When the user
|
|
7671
|
+
named no target but wants to switch, offer the lenses themselves as the
|
|
7672
|
+
quick-select options (each option = a lens name \u2192 \`leadbay_my_lenses(switchToLensId=<id>)\`).
|
|
7673
|
+
|
|
7674
|
+
| Observation | Suggest | Calls |
|
|
7675
|
+
|--------------------------------------|------------------------------------------|------------------------------------------------------|
|
|
7676
|
+
| User wants a different lens | "Switch to <lens name>" | \`leadbay_my_lenses(switchToLensId=<id>)\` |
|
|
7677
|
+
| User wants to rename / describe a lens| "Rename or describe <lens>" | \`leadbay_my_lenses(editLensId=<id>, newName?=<X>, newDescription?=<Y>)\` |
|
|
7678
|
+
| User wants to delete a lens | "Delete <lens>" | \`leadbay_my_lenses(deleteLensId=<id>)\` \u2192 confirm \u2192 \`confirm=true\` |
|
|
7679
|
+
| \`delete_preview\` (not yet deleted) | "Yes, delete it" | \`leadbay_my_lenses(deleteLensId=<id>, confirm=true)\` |
|
|
7680
|
+
| User wants leads on the active lens | "Pull today's leads" | \`leadbay_pull_leads()\` |
|
|
7681
|
+
| User wants to change the audience | "Adjust this lens's audience" | \`leadbay_adjust_audience(...)\` |
|
|
7682
|
+
| User wants more of the same | "Get a bigger batch on this lens" | \`leadbay_extend_lens(...)\` |
|
|
7683
|
+
|
|
7684
|
+
If nothing fits, default to "pull today's leads on the active lens" \u2014 never
|
|
7685
|
+
invent a tool that doesn't exist.
|
|
7686
|
+
`;
|
|
7687
|
+
var leadbay_new_lens = `## WHEN TO USE
|
|
7688
|
+
|
|
7689
|
+
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>".
|
|
7690
|
+
|
|
7691
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
7692
|
+
|
|
7693
|
+
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\`.
|
|
7694
|
+
|
|
7695
|
+
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).
|
|
7696
|
+
|
|
7697
|
+
Examples that SHOULD invoke this tool:
|
|
7698
|
+
- "Create a lens called Joinery for the fintech sector."
|
|
7699
|
+
- "Make me a new audience for healthcare companies, 30\u2013300 people."
|
|
7700
|
+
- "Set up a new lens named Nordics SaaS."
|
|
7701
|
+
|
|
7702
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
7703
|
+
- "Add fintech to my Joinery lens."
|
|
7704
|
+
- "Show me my lenses."
|
|
7705
|
+
- "I want more leads on this lens."
|
|
7706
|
+
|
|
7707
|
+
## RENDER (quick)
|
|
7708
|
+
|
|
7709
|
+
On \`preview\` (default \u2014 NOTHING created yet): show the lens that WILL be
|
|
7710
|
+
created (name + resolved sectors/sizes as chips) and ASK the user to confirm
|
|
7711
|
+
via ask_user_input_v0 ("Create this lens?" / "Change something"). Only on
|
|
7712
|
+
"yes" re-call with confirm:true. On \`created\`: confirm "Created **<name>**."
|
|
7713
|
+
On \`ambiguous_sectors\`: surface the candidate sectors to pick from.
|
|
7714
|
+
|
|
7715
|
+
---
|
|
7716
|
+
|
|
7717
|
+
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.
|
|
7718
|
+
|
|
7719
|
+
**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.)
|
|
7720
|
+
|
|
7721
|
+
**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\`.
|
|
7722
|
+
|
|
7723
|
+
**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.
|
|
7724
|
+
|
|
7725
|
+
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>".
|
|
7726
|
+
|
|
7727
|
+
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\`).
|
|
7728
|
+
|
|
7729
|
+
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\`.
|
|
7730
|
+
|
|
7731
|
+
|
|
7732
|
+
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
7733
|
+
|
|
7734
|
+
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.
|
|
7735
|
+
|
|
7736
|
+
**The Big Three** \u2014 when a tool result fits, route there:
|
|
7737
|
+
|
|
7738
|
+
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7739
|
+
|---|---|---|
|
|
7740
|
+
| \`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 |
|
|
7741
|
+
| \`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") |
|
|
7742
|
+
| \`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 |
|
|
7743
|
+
|
|
7744
|
+
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.
|
|
7745
|
+
|
|
7746
|
+
**Rules:**
|
|
7747
|
+
- 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.
|
|
7748
|
+
- Pass identifiers (place_id, lead.id, contact_id) verbatim. Don't rewrite.
|
|
7749
|
+
- 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.
|
|
7750
|
+
- One short intro sentence in chat is enough \u2014 "Here are your 5 NYC follow-ups." Then route into the widget.
|
|
7751
|
+
|
|
7752
|
+
|
|
7753
|
+
---
|
|
7754
|
+
|
|
7755
|
+
## NEXT STEPS \u2014 after \`leadbay_new_lens\`
|
|
7756
|
+
|
|
7757
|
+
**RENDER NEXT STEPS via \`ask_user_input_v0\` when the host exposes it.**
|
|
7758
|
+
|
|
7759
|
+
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:
|
|
7760
|
+
|
|
7761
|
+
\`\`\`
|
|
7762
|
+
ask_user_input_v0({
|
|
7763
|
+
questions: [{
|
|
7764
|
+
question: "What next?",
|
|
7765
|
+
type: "single_select",
|
|
7766
|
+
options: [
|
|
7767
|
+
"<Suggest column from row 1>",
|
|
7768
|
+
"<Suggest column from row 2>",
|
|
7769
|
+
"<Suggest column from row 3>"
|
|
7770
|
+
]
|
|
7771
|
+
}]
|
|
7772
|
+
})
|
|
7773
|
+
\`\`\`
|
|
7774
|
+
|
|
7775
|
+
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.
|
|
7776
|
+
|
|
7777
|
+
**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.
|
|
7778
|
+
|
|
7779
|
+
---
|
|
7780
|
+
|
|
7781
|
+
|
|
7782
|
+
|
|
7783
|
+
Pick the rows that fit. On \`created\`, the switch + pull rows are the natural
|
|
7784
|
+
follow-ups. On \`ambiguous_sectors\`, the only move is to pick a sector and re-call.
|
|
7785
|
+
|
|
7786
|
+
| Observation | Suggest | Calls |
|
|
7787
|
+
|-----------------------------------|------------------------------------------|--------------------------------------------------------|
|
|
7788
|
+
| \`preview\` (not yet created) | "Yes, create this lens" | \`leadbay_new_lens(...same args..., confirm=true)\` |
|
|
7789
|
+
| \`preview\` (not yet created) | "Change the sectors/size first" | (re-ask the user, then \`leadbay_new_lens\` with new args) |
|
|
7790
|
+
| Lens created | "Switch to it and pull leads" | \`leadbay_my_lenses(switchToLensId=<new id>)\` then \`leadbay_pull_leads()\` |
|
|
7791
|
+
| Lens created | "Refine the audience further" | \`leadbay_adjust_audience(lensName=<new name>, ...)\` |
|
|
7792
|
+
| Lens created | "Leave it; keep my current lens active" | (no call) |
|
|
7793
|
+
| \`ambiguous_sectors\` | "Pick the right sector and create" | \`leadbay_new_lens(name=..., sectors=[<chosen id>])\` |
|
|
7794
|
+
|
|
7795
|
+
If nothing fits, default to "switch to the new lens and pull leads" \u2014 never
|
|
7796
|
+
invent a tool that doesn't exist.
|
|
7797
|
+
`;
|
|
7514
7798
|
var 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.
|
|
7515
7799
|
|
|
7516
7800
|
Sibling of \`leadbay_create_topup_link\`. Use cases differ:
|
|
@@ -8143,7 +8427,33 @@ WHEN TO USE: before leadbay_enrich_titles, to plan which titles to order.
|
|
|
8143
8427
|
|
|
8144
8428
|
WHEN NOT TO USE: when you already know the exact titles you want to enrich.
|
|
8145
8429
|
`;
|
|
8146
|
-
var leadbay_refine_prompt =
|
|
8430
|
+
var leadbay_refine_prompt = `## WHEN TO USE
|
|
8431
|
+
|
|
8432
|
+
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".
|
|
8433
|
+
|
|
8434
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
8435
|
+
|
|
8436
|
+
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\`.
|
|
8437
|
+
|
|
8438
|
+
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.
|
|
8439
|
+
|
|
8440
|
+
Examples that SHOULD invoke this tool:
|
|
8441
|
+
- "Focus on hospitals that run their own IT in-house."
|
|
8442
|
+
- "Prioritize companies that have recently expanded headcount."
|
|
8443
|
+
|
|
8444
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
8445
|
+
- "Create a lens specialized in automobile."
|
|
8446
|
+
- "Add fintech to my Joinery lens."
|
|
8447
|
+
- "Show me my lenses."
|
|
8448
|
+
|
|
8449
|
+
## RENDER (quick)
|
|
8450
|
+
|
|
8451
|
+
On success: confirm the refinement applied to the active lens. If a
|
|
8452
|
+
clarification was raised, surface its question (route via ask_user_input_v0).
|
|
8453
|
+
|
|
8454
|
+
---
|
|
8455
|
+
|
|
8456
|
+
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).
|
|
8147
8457
|
|
|
8148
8458
|
WHEN TO USE: when audience filters (leadbay_adjust_audience) aren't enough.
|
|
8149
8459
|
|
|
@@ -12118,7 +12428,7 @@ var createLens = {
|
|
|
12118
12428
|
},
|
|
12119
12429
|
execute: async (client, params) => {
|
|
12120
12430
|
const lens = await client.request("POST", "/lenses", {
|
|
12121
|
-
base: params.base,
|
|
12431
|
+
base: String(params.base),
|
|
12122
12432
|
name: params.name,
|
|
12123
12433
|
description: params.description
|
|
12124
12434
|
});
|
|
@@ -17373,6 +17683,8 @@ var bulkEnrichStatus = {
|
|
|
17373
17683
|
|
|
17374
17684
|
// ../core/dist/composite/adjust-audience.js
|
|
17375
17685
|
function tokens(s) {
|
|
17686
|
+
if (!s)
|
|
17687
|
+
return [];
|
|
17376
17688
|
return s.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
17377
17689
|
}
|
|
17378
17690
|
function bestMatches(text, taxonomy) {
|
|
@@ -17386,11 +17698,11 @@ function bestMatches(text, taxonomy) {
|
|
|
17386
17698
|
if (have.has(t))
|
|
17387
17699
|
overlap += 1;
|
|
17388
17700
|
const score = overlap / Math.max(want.size, 1);
|
|
17389
|
-
return { id: s.id, name: s.name, score };
|
|
17701
|
+
return { id: s.id, name: s.name ?? "", score };
|
|
17390
17702
|
}).filter((m) => m.score > 0).sort((a, b) => b.score - a.score);
|
|
17391
17703
|
return ranked.slice(0, 5);
|
|
17392
17704
|
}
|
|
17393
|
-
async function resolveSectors(client, texts) {
|
|
17705
|
+
async function resolveSectors(client, texts, ctx) {
|
|
17394
17706
|
const looksLikeId2 = (s) => /^\d+$/.test(s);
|
|
17395
17707
|
const direct = texts.filter(looksLikeId2);
|
|
17396
17708
|
const free = texts.filter((s) => !looksLikeId2(s));
|
|
@@ -17399,6 +17711,10 @@ async function resolveSectors(client, texts) {
|
|
|
17399
17711
|
const me = await client.resolveMe().catch(() => null);
|
|
17400
17712
|
const lang = me?.language ?? "en";
|
|
17401
17713
|
const taxonomy = await client.request("GET", `/sectors/all?lang=${encodeURIComponent(lang)}&includeInvisible=false`);
|
|
17714
|
+
const nullNames = taxonomy.filter((s) => !s.name).length;
|
|
17715
|
+
if (nullNames > 0) {
|
|
17716
|
+
ctx?.logger?.warn?.(`adjust_audience: /sectors/all returned ${nullNames}/${taxonomy.length} sector(s) with a null/missing name`);
|
|
17717
|
+
}
|
|
17402
17718
|
const resolved = [...direct];
|
|
17403
17719
|
const ambiguities = [];
|
|
17404
17720
|
for (const text of free) {
|
|
@@ -17411,6 +17727,22 @@ async function resolveSectors(client, texts) {
|
|
|
17411
17727
|
}
|
|
17412
17728
|
return { resolved, ambiguities };
|
|
17413
17729
|
}
|
|
17730
|
+
async function resolveLensByName(client, name) {
|
|
17731
|
+
const lenses = await client.request("GET", "/lenses");
|
|
17732
|
+
const all = lenses.map((l) => ({ id: l.id, name: l.name }));
|
|
17733
|
+
const needle = name.trim().toLowerCase();
|
|
17734
|
+
const exact = all.filter((l) => (l.name ?? "").trim().toLowerCase() === needle);
|
|
17735
|
+
if (exact.length === 1)
|
|
17736
|
+
return { ok: true, id: exact[0].id };
|
|
17737
|
+
if (exact.length > 1)
|
|
17738
|
+
return { ok: false, reason: "ambiguous", matches: exact };
|
|
17739
|
+
const partial = all.filter((l) => (l.name ?? "").toLowerCase().includes(needle));
|
|
17740
|
+
if (partial.length === 1)
|
|
17741
|
+
return { ok: true, id: partial[0].id };
|
|
17742
|
+
if (partial.length > 1)
|
|
17743
|
+
return { ok: false, reason: "ambiguous", matches: partial };
|
|
17744
|
+
return { ok: false, reason: "not_found", lenses: all };
|
|
17745
|
+
}
|
|
17414
17746
|
function mergeFilter(current, toAddSectors, toExcludeSectors, sizes) {
|
|
17415
17747
|
const items = current?.lens_filter?.items ?? [];
|
|
17416
17748
|
const item = items[0] ?? { criteria: [] };
|
|
@@ -17444,11 +17776,15 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes) {
|
|
|
17444
17776
|
}
|
|
17445
17777
|
}
|
|
17446
17778
|
if (sizes && sizes.length > 0) {
|
|
17779
|
+
const normalizedSizes = sizes.map((s) => ({
|
|
17780
|
+
min: s.min ?? 0,
|
|
17781
|
+
max: s.max ?? 1e6
|
|
17782
|
+
}));
|
|
17447
17783
|
const idx = criteria.findIndex((c) => c.type === "size");
|
|
17448
17784
|
if (idx >= 0) {
|
|
17449
|
-
criteria[idx] = { type: "size", is_excluded: false, sizes };
|
|
17785
|
+
criteria[idx] = { type: "size", is_excluded: false, sizes: normalizedSizes };
|
|
17450
17786
|
} else {
|
|
17451
|
-
criteria.push({ type: "size", is_excluded: false, sizes });
|
|
17787
|
+
criteria.push({ type: "size", is_excluded: false, sizes: normalizedSizes });
|
|
17452
17788
|
}
|
|
17453
17789
|
}
|
|
17454
17790
|
return {
|
|
@@ -17456,6 +17792,9 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes) {
|
|
|
17456
17792
|
locations: current.locations ?? { results: [], parents: [] }
|
|
17457
17793
|
};
|
|
17458
17794
|
}
|
|
17795
|
+
function filterWriteBody(filter) {
|
|
17796
|
+
return { items: filter.lens_filter.items };
|
|
17797
|
+
}
|
|
17459
17798
|
var adjustAudience = {
|
|
17460
17799
|
name: "leadbay_adjust_audience",
|
|
17461
17800
|
annotations: {
|
|
@@ -17497,6 +17836,10 @@ var adjustAudience = {
|
|
|
17497
17836
|
description: "Company size buckets, e.g. [{min:30,max:300}]"
|
|
17498
17837
|
},
|
|
17499
17838
|
lensId: { type: "number", description: "Lens id (escape hatch)" },
|
|
17839
|
+
lensName: {
|
|
17840
|
+
type: "string",
|
|
17841
|
+
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."
|
|
17842
|
+
},
|
|
17500
17843
|
save_for_org: {
|
|
17501
17844
|
type: "boolean",
|
|
17502
17845
|
description: "Admin only \u2014 propagate the change to the org-level lens for everyone (default false: per-user draft)"
|
|
@@ -17510,21 +17853,35 @@ var adjustAudience = {
|
|
|
17510
17853
|
},
|
|
17511
17854
|
outputSchema: {
|
|
17512
17855
|
type: "object",
|
|
17513
|
-
description: "
|
|
17856
|
+
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).",
|
|
17514
17857
|
properties: {
|
|
17515
17858
|
status: {
|
|
17516
17859
|
type: "string",
|
|
17517
|
-
description: "'ambiguous_sectors' or '
|
|
17860
|
+
description: "'applied', 'ambiguous_sectors', 'lens_not_found', or 'ambiguous_lens'."
|
|
17518
17861
|
},
|
|
17519
17862
|
sector_ambiguities: {
|
|
17520
17863
|
type: "array",
|
|
17521
17864
|
description: "Per ambiguous text: {sector_text, matches:[{id, name, score}]}. Agent picks an id and re-calls.",
|
|
17522
17865
|
items: { type: "object" }
|
|
17523
17866
|
},
|
|
17867
|
+
lenses: {
|
|
17868
|
+
type: "array",
|
|
17869
|
+
description: "On 'lens_not_found': the user's lenses [{id, name}] to pick from.",
|
|
17870
|
+
items: { type: "object" }
|
|
17871
|
+
},
|
|
17872
|
+
matches: {
|
|
17873
|
+
type: "array",
|
|
17874
|
+
description: "On 'ambiguous_lens': the lenses [{id, name}] the name matched.",
|
|
17875
|
+
items: { type: "object" }
|
|
17876
|
+
},
|
|
17877
|
+
lens_query: {
|
|
17878
|
+
type: "string",
|
|
17879
|
+
description: "On 'lens_not_found' / 'ambiguous_lens': the lensName the user asked for."
|
|
17880
|
+
},
|
|
17524
17881
|
message: { type: "string" },
|
|
17525
17882
|
lens_used: {
|
|
17526
17883
|
type: "object",
|
|
17527
|
-
description: "Resolved lens metadata: {id, name, was_draft, was_new, save_for_org}."
|
|
17884
|
+
description: "Resolved lens metadata: {id, name, was_draft, was_new, active_lens_changed, save_for_org}."
|
|
17528
17885
|
},
|
|
17529
17886
|
filter_applied: {
|
|
17530
17887
|
type: "object",
|
|
@@ -17537,28 +17894,63 @@ var adjustAudience = {
|
|
|
17537
17894
|
execute: async (client, params, ctx) => {
|
|
17538
17895
|
const me = await client.resolveMe();
|
|
17539
17896
|
const isAdmin = me.admin === true;
|
|
17540
|
-
|
|
17897
|
+
let namedLensId;
|
|
17898
|
+
if (params.lensId == null && params.lensName != null && params.lensName.trim() !== "") {
|
|
17899
|
+
const res = await resolveLensByName(client, params.lensName);
|
|
17900
|
+
if (!res.ok && res.reason === "not_found") {
|
|
17901
|
+
return {
|
|
17902
|
+
status: "lens_not_found",
|
|
17903
|
+
lens_query: params.lensName,
|
|
17904
|
+
lenses: res.lenses,
|
|
17905
|
+
message: `No lens named "${params.lensName}". Pick one of the listed lenses (pass lensId or an exact lensName), or create it first.`
|
|
17906
|
+
};
|
|
17907
|
+
}
|
|
17908
|
+
if (!res.ok && res.reason === "ambiguous") {
|
|
17909
|
+
return {
|
|
17910
|
+
status: "ambiguous_lens",
|
|
17911
|
+
lens_query: params.lensName,
|
|
17912
|
+
matches: res.matches,
|
|
17913
|
+
message: `"${params.lensName}" matched multiple lenses. Re-call with the exact lensName or the lensId of the one you mean.`
|
|
17914
|
+
};
|
|
17915
|
+
}
|
|
17916
|
+
if (res.ok)
|
|
17917
|
+
namedLensId = res.id;
|
|
17918
|
+
}
|
|
17919
|
+
const startingLensId = params.lensId ?? namedLensId ?? me.last_requested_lens ?? await client.resolveDefaultLens();
|
|
17920
|
+
const isNamedEdit = namedLensId != null && params.lensId == null;
|
|
17541
17921
|
const includeTexts = [
|
|
17542
17922
|
...params.sectors ?? [],
|
|
17543
17923
|
...params.sector_ids ?? []
|
|
17544
17924
|
];
|
|
17545
17925
|
const excludeTexts = params.exclude_sectors ?? [];
|
|
17546
|
-
const includeRes = await resolveSectors(client, includeTexts);
|
|
17547
|
-
const excludeRes = await resolveSectors(client, excludeTexts);
|
|
17926
|
+
const includeRes = await resolveSectors(client, includeTexts, ctx);
|
|
17927
|
+
const excludeRes = await resolveSectors(client, excludeTexts, ctx);
|
|
17548
17928
|
const ambiguities = [
|
|
17549
17929
|
...includeRes.ambiguities,
|
|
17550
17930
|
...excludeRes.ambiguities
|
|
17551
17931
|
];
|
|
17552
17932
|
if (ambiguities.length > 0) {
|
|
17933
|
+
const noMatch = ambiguities.filter((a) => a.matches.length === 0);
|
|
17934
|
+
const multi = ambiguities.filter((a) => a.matches.length > 0);
|
|
17935
|
+
const parts = [];
|
|
17936
|
+
if (noMatch.length > 0) {
|
|
17937
|
+
const names = noMatch.map((a) => `"${a.sector_text}"`).join(", ");
|
|
17938
|
+
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=...`);
|
|
17939
|
+
}
|
|
17940
|
+
if (multi.length > 0) {
|
|
17941
|
+
const names = multi.map((a) => `"${a.sector_text}"`).join(", ");
|
|
17942
|
+
parts.push(`${names} matched multiple sectors. Pick from the matches and re-call with sector_ids=...`);
|
|
17943
|
+
}
|
|
17553
17944
|
return {
|
|
17554
17945
|
status: "ambiguous_sectors",
|
|
17555
17946
|
sector_ambiguities: ambiguities,
|
|
17556
|
-
message: "
|
|
17947
|
+
message: parts.join(" ")
|
|
17557
17948
|
};
|
|
17558
17949
|
}
|
|
17559
17950
|
const lens = await client.request("GET", `/lenses/${startingLensId}`);
|
|
17560
17951
|
const currentFilter = await client.request("GET", `/lenses/${startingLensId}/filter`);
|
|
17561
17952
|
const merged = mergeFilter(currentFilter, includeRes.resolved, excludeRes.resolved, params.sizes);
|
|
17953
|
+
const mergedBody = filterWriteBody(merged);
|
|
17562
17954
|
const isDefault = lens.is_default || lens.default;
|
|
17563
17955
|
const isUserLevel = lens.user_id != null;
|
|
17564
17956
|
const isOrgLevel = !isUserLevel && !isDefault;
|
|
@@ -17567,24 +17959,28 @@ var adjustAudience = {
|
|
|
17567
17959
|
let wasNew = false;
|
|
17568
17960
|
if (isDefault) {
|
|
17569
17961
|
const name = params.newLensName ?? `Custom audience \u2014 ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
17570
|
-
const
|
|
17571
|
-
base: startingLensId,
|
|
17962
|
+
const newLens2 = await client.request("POST", "/lenses", {
|
|
17963
|
+
base: String(startingLensId),
|
|
17572
17964
|
name
|
|
17573
17965
|
});
|
|
17574
|
-
targetLensId =
|
|
17966
|
+
targetLensId = newLens2.id;
|
|
17575
17967
|
wasNew = true;
|
|
17576
|
-
await client.requestVoid("POST", `/lenses/${targetLensId}/filter`,
|
|
17577
|
-
|
|
17968
|
+
await client.requestVoid("POST", `/lenses/${targetLensId}/filter`, mergedBody);
|
|
17969
|
+
if (!isNamedEdit) {
|
|
17970
|
+
await client.requestVoid("POST", `/lenses/${targetLensId}/update_last_requested`);
|
|
17971
|
+
}
|
|
17578
17972
|
} else if (isUserLevel) {
|
|
17579
17973
|
try {
|
|
17580
|
-
await client.requestVoid("POST", `/lenses/${startingLensId}/filter`,
|
|
17974
|
+
await client.requestVoid("POST", `/lenses/${startingLensId}/filter`, mergedBody);
|
|
17581
17975
|
} catch (err) {
|
|
17582
17976
|
if (err?.code === "FORBIDDEN") {
|
|
17583
17977
|
wasDraft = true;
|
|
17584
17978
|
const draft = await client.request("POST", `/lenses/${startingLensId}/draft`);
|
|
17585
17979
|
targetLensId = draft.id;
|
|
17586
|
-
await client.requestVoid("POST", `/lenses/${targetLensId}/filter`,
|
|
17587
|
-
|
|
17980
|
+
await client.requestVoid("POST", `/lenses/${targetLensId}/filter`, mergedBody);
|
|
17981
|
+
if (!isNamedEdit) {
|
|
17982
|
+
await client.requestVoid("POST", `/lenses/${targetLensId}/update_last_requested`);
|
|
17983
|
+
}
|
|
17588
17984
|
} else {
|
|
17589
17985
|
throw err;
|
|
17590
17986
|
}
|
|
@@ -17596,7 +17992,7 @@ var adjustAudience = {
|
|
|
17596
17992
|
const draft = await client.request("POST", `/lenses/${startingLensId}/draft`);
|
|
17597
17993
|
targetLensId = draft.id;
|
|
17598
17994
|
try {
|
|
17599
|
-
await client.requestVoid("POST", `/lenses/${targetLensId}/filter`,
|
|
17995
|
+
await client.requestVoid("POST", `/lenses/${targetLensId}/filter`, mergedBody);
|
|
17600
17996
|
} catch (err) {
|
|
17601
17997
|
ctx?.logger?.warn?.(`adjust_audience: filter on draft ${targetLensId} failed: ${err?.message}`);
|
|
17602
17998
|
try {
|
|
@@ -17612,17 +18008,21 @@ var adjustAudience = {
|
|
|
17612
18008
|
}
|
|
17613
18009
|
throw err;
|
|
17614
18010
|
}
|
|
17615
|
-
|
|
18011
|
+
if (!isNamedEdit) {
|
|
18012
|
+
await client.requestVoid("POST", `/lenses/${targetLensId}/update_last_requested`);
|
|
18013
|
+
}
|
|
17616
18014
|
} else {
|
|
17617
18015
|
try {
|
|
17618
|
-
await client.requestVoid("POST", `/lenses/${startingLensId}/filter`,
|
|
18016
|
+
await client.requestVoid("POST", `/lenses/${startingLensId}/filter`, mergedBody);
|
|
17619
18017
|
} catch (err) {
|
|
17620
18018
|
throw err;
|
|
17621
18019
|
}
|
|
17622
18020
|
}
|
|
17623
18021
|
}
|
|
17624
|
-
|
|
18022
|
+
if (!isNamedEdit)
|
|
18023
|
+
client.invalidateMe();
|
|
17625
18024
|
client.invalidateDefaultLens();
|
|
18025
|
+
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.` : "";
|
|
17626
18026
|
return {
|
|
17627
18027
|
status: "applied",
|
|
17628
18028
|
lens_used: {
|
|
@@ -17630,10 +18030,11 @@ var adjustAudience = {
|
|
|
17630
18030
|
name: lens.name,
|
|
17631
18031
|
was_draft: wasDraft,
|
|
17632
18032
|
was_new: wasNew,
|
|
18033
|
+
active_lens_changed: !isNamedEdit && (wasNew || wasDraft),
|
|
17633
18034
|
save_for_org: params.save_for_org === true && isAdmin && isOrgLevel
|
|
17634
18035
|
},
|
|
17635
18036
|
filter_applied: merged,
|
|
17636
|
-
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.",
|
|
18037
|
+
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,
|
|
17637
18038
|
_meta: { region: client.region }
|
|
17638
18039
|
};
|
|
17639
18040
|
}
|
|
@@ -18005,6 +18406,384 @@ var extendLens = {
|
|
|
18005
18406
|
}
|
|
18006
18407
|
};
|
|
18007
18408
|
|
|
18409
|
+
// ../core/dist/composite/my-lenses.js
|
|
18410
|
+
var sid = (v) => v == null ? null : String(v);
|
|
18411
|
+
async function listWithActive(client) {
|
|
18412
|
+
const lenses = await client.request("GET", "/lenses");
|
|
18413
|
+
const me = await client.resolveMe().catch(() => null);
|
|
18414
|
+
const activeFromMe = sid(me?.last_requested_lens);
|
|
18415
|
+
const active_lens_id = activeFromMe ?? sid(lenses.find((l) => l.is_last_active)?.id) ?? null;
|
|
18416
|
+
return {
|
|
18417
|
+
active_lens_id,
|
|
18418
|
+
lenses: lenses.map((l) => ({
|
|
18419
|
+
id: sid(l.id),
|
|
18420
|
+
name: l.name,
|
|
18421
|
+
description: l.description ?? null,
|
|
18422
|
+
is_active: sid(l.id) === active_lens_id,
|
|
18423
|
+
is_default: l.is_default === true || l.default === true
|
|
18424
|
+
}))
|
|
18425
|
+
};
|
|
18426
|
+
}
|
|
18427
|
+
var myLenses = {
|
|
18428
|
+
name: "leadbay_my_lenses",
|
|
18429
|
+
annotations: {
|
|
18430
|
+
title: "List, switch, edit, or delete your lenses",
|
|
18431
|
+
// No args → pure read. The delete mode issues DELETE /lenses/:id (an
|
|
18432
|
+
// irreversible side effect), so the tool is destructive — clients must
|
|
18433
|
+
// treat it as approval-required, not auto-run. The delete path is itself
|
|
18434
|
+
// confirm-gated (preview unless confirm:true). switch/edit are not
|
|
18435
|
+
// idempotent across modes either, so don't claim idempotency.
|
|
18436
|
+
readOnlyHint: false,
|
|
18437
|
+
destructiveHint: true,
|
|
18438
|
+
idempotentHint: false,
|
|
18439
|
+
openWorldHint: true
|
|
18440
|
+
},
|
|
18441
|
+
description: leadbay_my_lenses,
|
|
18442
|
+
inputSchema: {
|
|
18443
|
+
type: "object",
|
|
18444
|
+
properties: {
|
|
18445
|
+
switchToLensId: {
|
|
18446
|
+
type: ["string", "number"],
|
|
18447
|
+
description: "When set, switch the active lens to this id (must be one of the user's lenses), then return the refreshed list."
|
|
18448
|
+
},
|
|
18449
|
+
editLensId: {
|
|
18450
|
+
type: ["string", "number"],
|
|
18451
|
+
description: "When set, edit this lens's metadata \u2014 provide newName and/or newDescription. Must be one of the user's lenses."
|
|
18452
|
+
},
|
|
18453
|
+
newName: {
|
|
18454
|
+
type: "string",
|
|
18455
|
+
description: "New lens name (used with editLensId)."
|
|
18456
|
+
},
|
|
18457
|
+
newDescription: {
|
|
18458
|
+
type: "string",
|
|
18459
|
+
description: "New lens description (used with editLensId). Pass an empty string to clear it."
|
|
18460
|
+
},
|
|
18461
|
+
deleteLensId: {
|
|
18462
|
+
type: ["string", "number"],
|
|
18463
|
+
description: "When set, delete this lens. DESTRUCTIVE \u2014 returns a delete_preview unless confirm:true. Cannot delete the default lens."
|
|
18464
|
+
},
|
|
18465
|
+
confirm: {
|
|
18466
|
+
type: "boolean",
|
|
18467
|
+
description: "Required (=true) to actually delete. Without it, deleteLensId returns a preview to confirm with the user first."
|
|
18468
|
+
}
|
|
18469
|
+
},
|
|
18470
|
+
additionalProperties: false
|
|
18471
|
+
},
|
|
18472
|
+
outputSchema: {
|
|
18473
|
+
type: "object",
|
|
18474
|
+
properties: {
|
|
18475
|
+
status: {
|
|
18476
|
+
type: "string",
|
|
18477
|
+
description: "'listed', 'switched', 'already_active', 'edited', 'deleted', 'delete_preview' (confirm to proceed), 'cannot_delete_default', or 'not_found'."
|
|
18478
|
+
},
|
|
18479
|
+
switched: { type: "boolean", description: "True when this call changed the active lens." },
|
|
18480
|
+
edited: { type: "boolean", description: "True when this call renamed/re-described a lens." },
|
|
18481
|
+
deleted: { type: "boolean", description: "True when this call deleted a lens." },
|
|
18482
|
+
will_delete: {
|
|
18483
|
+
type: "object",
|
|
18484
|
+
description: "On 'delete_preview': the lens that WILL be deleted {id, name}. Nothing removed yet."
|
|
18485
|
+
},
|
|
18486
|
+
active_lens_id: { type: ["string", "null"] },
|
|
18487
|
+
lenses: {
|
|
18488
|
+
type: "array",
|
|
18489
|
+
description: "The user's lenses. Each: {id, name, description, is_active}.",
|
|
18490
|
+
items: { type: "object" }
|
|
18491
|
+
},
|
|
18492
|
+
message: { type: "string" }
|
|
18493
|
+
},
|
|
18494
|
+
required: ["status", "lenses", "active_lens_id"]
|
|
18495
|
+
},
|
|
18496
|
+
execute: async (client, params) => {
|
|
18497
|
+
if (params.deleteLensId != null) {
|
|
18498
|
+
const targetId = sid(params.deleteLensId);
|
|
18499
|
+
const before = await listWithActive(client);
|
|
18500
|
+
const target = before.lenses.find((l) => l.id === targetId);
|
|
18501
|
+
if (!target) {
|
|
18502
|
+
return {
|
|
18503
|
+
status: "not_found",
|
|
18504
|
+
switched: false,
|
|
18505
|
+
edited: false,
|
|
18506
|
+
deleted: false,
|
|
18507
|
+
active_lens_id: before.active_lens_id,
|
|
18508
|
+
lenses: before.lenses,
|
|
18509
|
+
message: `No lens with id ${targetId}. Pick one from the list.`
|
|
18510
|
+
};
|
|
18511
|
+
}
|
|
18512
|
+
if (target.is_default) {
|
|
18513
|
+
return {
|
|
18514
|
+
status: "cannot_delete_default",
|
|
18515
|
+
switched: false,
|
|
18516
|
+
edited: false,
|
|
18517
|
+
deleted: false,
|
|
18518
|
+
active_lens_id: before.active_lens_id,
|
|
18519
|
+
lenses: before.lenses,
|
|
18520
|
+
message: `"${target.name}" is the default lens and can't be deleted.`
|
|
18521
|
+
};
|
|
18522
|
+
}
|
|
18523
|
+
if (params.confirm !== true) {
|
|
18524
|
+
return {
|
|
18525
|
+
status: "delete_preview",
|
|
18526
|
+
switched: false,
|
|
18527
|
+
edited: false,
|
|
18528
|
+
deleted: false,
|
|
18529
|
+
active_lens_id: before.active_lens_id,
|
|
18530
|
+
lenses: before.lenses,
|
|
18531
|
+
will_delete: { id: target.id, name: target.name },
|
|
18532
|
+
message: `About to delete "${target.name}". This can't be undone. Confirm with the user, then re-call with confirm:true.`
|
|
18533
|
+
};
|
|
18534
|
+
}
|
|
18535
|
+
await client.requestVoid("DELETE", `/lenses/${targetId}`);
|
|
18536
|
+
client.invalidateMe();
|
|
18537
|
+
client.invalidateDefaultLens();
|
|
18538
|
+
const after = await listWithActive(client);
|
|
18539
|
+
return {
|
|
18540
|
+
status: "deleted",
|
|
18541
|
+
switched: false,
|
|
18542
|
+
edited: false,
|
|
18543
|
+
deleted: true,
|
|
18544
|
+
active_lens_id: after.active_lens_id,
|
|
18545
|
+
lenses: after.lenses,
|
|
18546
|
+
message: `Deleted "${target.name}".`
|
|
18547
|
+
};
|
|
18548
|
+
}
|
|
18549
|
+
if (params.editLensId != null) {
|
|
18550
|
+
const targetId = sid(params.editLensId);
|
|
18551
|
+
const before = await listWithActive(client);
|
|
18552
|
+
const target = before.lenses.find((l) => l.id === targetId);
|
|
18553
|
+
if (!target) {
|
|
18554
|
+
return {
|
|
18555
|
+
status: "not_found",
|
|
18556
|
+
switched: false,
|
|
18557
|
+
edited: false,
|
|
18558
|
+
active_lens_id: before.active_lens_id,
|
|
18559
|
+
lenses: before.lenses,
|
|
18560
|
+
message: `No lens with id ${targetId}. Pick one from the list.`
|
|
18561
|
+
};
|
|
18562
|
+
}
|
|
18563
|
+
const body = {};
|
|
18564
|
+
const newName = params.newName?.trim();
|
|
18565
|
+
if (newName)
|
|
18566
|
+
body.name = newName;
|
|
18567
|
+
if (params.newDescription !== void 0)
|
|
18568
|
+
body.description = params.newDescription;
|
|
18569
|
+
if (Object.keys(body).length === 0) {
|
|
18570
|
+
return {
|
|
18571
|
+
status: "not_found",
|
|
18572
|
+
switched: false,
|
|
18573
|
+
edited: false,
|
|
18574
|
+
active_lens_id: before.active_lens_id,
|
|
18575
|
+
lenses: before.lenses,
|
|
18576
|
+
message: `Nothing to change on "${target.name}" \u2014 provide newName and/or newDescription.`
|
|
18577
|
+
};
|
|
18578
|
+
}
|
|
18579
|
+
await client.requestVoid("POST", `/lenses/${targetId}`, body);
|
|
18580
|
+
client.invalidateDefaultLens();
|
|
18581
|
+
const changed = [
|
|
18582
|
+
body.name != null ? `renamed to "${body.name}"` : null,
|
|
18583
|
+
body.description !== void 0 ? "description updated" : null
|
|
18584
|
+
].filter(Boolean).join(", ");
|
|
18585
|
+
const after = await listWithActive(client);
|
|
18586
|
+
return {
|
|
18587
|
+
status: "edited",
|
|
18588
|
+
switched: false,
|
|
18589
|
+
edited: true,
|
|
18590
|
+
active_lens_id: after.active_lens_id,
|
|
18591
|
+
lenses: after.lenses,
|
|
18592
|
+
message: `"${target.name}" \u2014 ${changed}.`
|
|
18593
|
+
};
|
|
18594
|
+
}
|
|
18595
|
+
if (params.switchToLensId != null) {
|
|
18596
|
+
const targetId = sid(params.switchToLensId);
|
|
18597
|
+
const before = await listWithActive(client);
|
|
18598
|
+
const target = before.lenses.find((l) => l.id === targetId);
|
|
18599
|
+
if (!target) {
|
|
18600
|
+
return {
|
|
18601
|
+
status: "not_found",
|
|
18602
|
+
switched: false,
|
|
18603
|
+
edited: false,
|
|
18604
|
+
active_lens_id: before.active_lens_id,
|
|
18605
|
+
lenses: before.lenses,
|
|
18606
|
+
message: `No lens with id ${targetId}. Pick an id from the list.`
|
|
18607
|
+
};
|
|
18608
|
+
}
|
|
18609
|
+
if (target.is_active) {
|
|
18610
|
+
return {
|
|
18611
|
+
status: "already_active",
|
|
18612
|
+
switched: false,
|
|
18613
|
+
edited: false,
|
|
18614
|
+
active_lens_id: before.active_lens_id,
|
|
18615
|
+
lenses: before.lenses,
|
|
18616
|
+
message: `"${target.name}" is already your active lens.`
|
|
18617
|
+
};
|
|
18618
|
+
}
|
|
18619
|
+
await client.requestVoid("POST", `/lenses/${targetId}/update_last_requested`);
|
|
18620
|
+
client.invalidateMe();
|
|
18621
|
+
client.invalidateDefaultLens();
|
|
18622
|
+
const after = await listWithActive(client);
|
|
18623
|
+
return {
|
|
18624
|
+
status: "switched",
|
|
18625
|
+
switched: true,
|
|
18626
|
+
edited: false,
|
|
18627
|
+
active_lens_id: after.active_lens_id,
|
|
18628
|
+
lenses: after.lenses,
|
|
18629
|
+
message: `Now showing "${target.name}".`
|
|
18630
|
+
};
|
|
18631
|
+
}
|
|
18632
|
+
const { lenses, active_lens_id } = await listWithActive(client);
|
|
18633
|
+
return { status: "listed", switched: false, edited: false, active_lens_id, lenses };
|
|
18634
|
+
}
|
|
18635
|
+
};
|
|
18636
|
+
|
|
18637
|
+
// ../core/dist/composite/new-lens.js
|
|
18638
|
+
var EMPTY_FILTER = {
|
|
18639
|
+
lens_filter: { items: [{ criteria: [] }] },
|
|
18640
|
+
locations: { results: [], parents: [] }
|
|
18641
|
+
};
|
|
18642
|
+
var newLens = {
|
|
18643
|
+
name: "leadbay_new_lens",
|
|
18644
|
+
annotations: {
|
|
18645
|
+
title: "Create a new named lens",
|
|
18646
|
+
readOnlyHint: false,
|
|
18647
|
+
destructiveHint: false,
|
|
18648
|
+
idempotentHint: false,
|
|
18649
|
+
// each call creates a distinct lens
|
|
18650
|
+
openWorldHint: true
|
|
18651
|
+
},
|
|
18652
|
+
description: leadbay_new_lens,
|
|
18653
|
+
inputSchema: {
|
|
18654
|
+
type: "object",
|
|
18655
|
+
properties: {
|
|
18656
|
+
name: { type: "string", description: "Display name for the new lens (required)." },
|
|
18657
|
+
sectors: {
|
|
18658
|
+
type: "array",
|
|
18659
|
+
items: { type: "string" },
|
|
18660
|
+
description: "Sectors to include \u2014 free text (auto-resolved) or ids."
|
|
18661
|
+
},
|
|
18662
|
+
exclude_sectors: {
|
|
18663
|
+
type: "array",
|
|
18664
|
+
items: { type: "string" },
|
|
18665
|
+
description: "Sectors to exclude \u2014 free text or ids."
|
|
18666
|
+
},
|
|
18667
|
+
sizes: {
|
|
18668
|
+
type: "array",
|
|
18669
|
+
items: {
|
|
18670
|
+
type: "object",
|
|
18671
|
+
properties: { min: { type: "number" }, max: { type: "number" } }
|
|
18672
|
+
},
|
|
18673
|
+
description: "Company size buckets, e.g. [{min:30,max:300}]."
|
|
18674
|
+
},
|
|
18675
|
+
base: {
|
|
18676
|
+
type: "number",
|
|
18677
|
+
description: "Lens id to clone from. Defaults to the active/default lens."
|
|
18678
|
+
},
|
|
18679
|
+
description: { type: "string", description: "Optional lens description." },
|
|
18680
|
+
confirm: {
|
|
18681
|
+
type: "boolean",
|
|
18682
|
+
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."
|
|
18683
|
+
}
|
|
18684
|
+
},
|
|
18685
|
+
required: ["name"],
|
|
18686
|
+
additionalProperties: false
|
|
18687
|
+
},
|
|
18688
|
+
outputSchema: {
|
|
18689
|
+
type: "object",
|
|
18690
|
+
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).",
|
|
18691
|
+
properties: {
|
|
18692
|
+
status: { type: "string", description: "'preview', 'created', 'ambiguous_sectors', or 'orphan_created' (filter write failed + cleanup failed)." },
|
|
18693
|
+
will_create: {
|
|
18694
|
+
type: "object",
|
|
18695
|
+
description: "On 'preview': what WILL be created \u2014 {name, description, sectors, exclude_sectors, sizes}. Nothing has been written yet."
|
|
18696
|
+
},
|
|
18697
|
+
filter_preview: { type: "object", description: "On 'preview': the FilterPayload that would be applied." },
|
|
18698
|
+
lens: {
|
|
18699
|
+
type: "object",
|
|
18700
|
+
description: "On 'created': the created lens {id, name}."
|
|
18701
|
+
},
|
|
18702
|
+
sector_ambiguities: {
|
|
18703
|
+
type: "array",
|
|
18704
|
+
description: "On 'ambiguous_sectors': per text {sector_text, matches:[{id,name,score}]}.",
|
|
18705
|
+
items: { type: "object" }
|
|
18706
|
+
},
|
|
18707
|
+
filter_applied: { type: "object", description: "On 'created': the FilterPayload POSTed to the new lens." },
|
|
18708
|
+
message: { type: "string" },
|
|
18709
|
+
_meta: { type: "object" }
|
|
18710
|
+
},
|
|
18711
|
+
required: ["status"]
|
|
18712
|
+
},
|
|
18713
|
+
execute: async (client, params, ctx) => {
|
|
18714
|
+
const includeRes = await resolveSectors(client, params.sectors ?? [], ctx);
|
|
18715
|
+
const excludeRes = await resolveSectors(client, params.exclude_sectors ?? [], ctx);
|
|
18716
|
+
const ambiguities = [...includeRes.ambiguities, ...excludeRes.ambiguities];
|
|
18717
|
+
if (ambiguities.length > 0) {
|
|
18718
|
+
const noMatch = ambiguities.filter((a) => a.matches.length === 0);
|
|
18719
|
+
const multi = ambiguities.filter((a) => a.matches.length > 0);
|
|
18720
|
+
const parts = [];
|
|
18721
|
+
if (noMatch.length > 0) {
|
|
18722
|
+
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).`);
|
|
18723
|
+
}
|
|
18724
|
+
if (multi.length > 0) {
|
|
18725
|
+
parts.push(`${multi.map((a) => `"${a.sector_text}"`).join(", ")} matched multiple sectors. Pick from the matches and re-call with the sector id.`);
|
|
18726
|
+
}
|
|
18727
|
+
return {
|
|
18728
|
+
status: "ambiguous_sectors",
|
|
18729
|
+
sector_ambiguities: ambiguities,
|
|
18730
|
+
message: parts.join(" ")
|
|
18731
|
+
};
|
|
18732
|
+
}
|
|
18733
|
+
const merged = mergeFilter(EMPTY_FILTER, includeRes.resolved, excludeRes.resolved, params.sizes);
|
|
18734
|
+
if (params.confirm !== true) {
|
|
18735
|
+
return {
|
|
18736
|
+
status: "preview",
|
|
18737
|
+
will_create: {
|
|
18738
|
+
name: params.name,
|
|
18739
|
+
description: params.description ?? null,
|
|
18740
|
+
sectors: includeRes.resolved,
|
|
18741
|
+
exclude_sectors: excludeRes.resolved,
|
|
18742
|
+
sizes: merged.lens_filter.items[0].criteria.find((c) => c.type === "size") ?? null
|
|
18743
|
+
},
|
|
18744
|
+
filter_preview: merged,
|
|
18745
|
+
message: `About to create "${params.name}". Confirm with the user, then re-call with confirm:true.`,
|
|
18746
|
+
_meta: { region: client.region }
|
|
18747
|
+
};
|
|
18748
|
+
}
|
|
18749
|
+
const base = params.base ?? await client.resolveDefaultLens();
|
|
18750
|
+
const created = await client.request("POST", "/lenses", {
|
|
18751
|
+
base: String(base),
|
|
18752
|
+
name: params.name,
|
|
18753
|
+
description: params.description
|
|
18754
|
+
});
|
|
18755
|
+
const hasCriteria = merged.lens_filter.items[0].criteria.length > 0;
|
|
18756
|
+
if (hasCriteria) {
|
|
18757
|
+
try {
|
|
18758
|
+
await client.requestVoid("POST", `/lenses/${created.id}/filter`, filterWriteBody(merged));
|
|
18759
|
+
} catch (err) {
|
|
18760
|
+
ctx?.logger?.warn?.(`new_lens: filter write on new lens ${created.id} failed: ${err?.message} \u2014 rolling back`);
|
|
18761
|
+
try {
|
|
18762
|
+
await client.requestVoid("DELETE", `/lenses/${created.id}`);
|
|
18763
|
+
} catch {
|
|
18764
|
+
client.invalidateDefaultLens();
|
|
18765
|
+
return {
|
|
18766
|
+
status: "orphan_created",
|
|
18767
|
+
lens: { id: created.id, name: created.name },
|
|
18768
|
+
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.`,
|
|
18769
|
+
_meta: { region: client.region }
|
|
18770
|
+
};
|
|
18771
|
+
}
|
|
18772
|
+
client.invalidateDefaultLens();
|
|
18773
|
+
throw err;
|
|
18774
|
+
}
|
|
18775
|
+
}
|
|
18776
|
+
client.invalidateDefaultLens();
|
|
18777
|
+
return {
|
|
18778
|
+
status: "created",
|
|
18779
|
+
lens: { id: created.id, name: created.name },
|
|
18780
|
+
filter_applied: merged,
|
|
18781
|
+
message: `Created "${created.name}".`,
|
|
18782
|
+
_meta: { region: client.region }
|
|
18783
|
+
};
|
|
18784
|
+
}
|
|
18785
|
+
};
|
|
18786
|
+
|
|
18008
18787
|
// ../core/dist/composite/answer-clarification.js
|
|
18009
18788
|
var answerClarification = {
|
|
18010
18789
|
name: "leadbay_answer_clarification",
|
|
@@ -18524,7 +19303,6 @@ var granularReadTools = [
|
|
|
18524
19303
|
getQuota,
|
|
18525
19304
|
getLensFilter,
|
|
18526
19305
|
getLensScoring,
|
|
18527
|
-
listSectors,
|
|
18528
19306
|
listLocations,
|
|
18529
19307
|
getUserPrompt,
|
|
18530
19308
|
getClarification,
|
|
@@ -18595,6 +19373,11 @@ var compositeReadTools = [
|
|
|
18595
19373
|
// it for discoverability; expose it always-on so agents can find custom fields
|
|
18596
19374
|
// without needing LEADBAY_MCP_ADVANCED=1.
|
|
18597
19375
|
listMappableFields,
|
|
19376
|
+
// listSectors is granular-shaped but ALWAYS exposed: it's the sector taxonomy
|
|
19377
|
+
// lookup the agent needs to STOP guessing sector names (and to feed
|
|
19378
|
+
// leadbay_new_lens / leadbay_adjust_audience). Without it the agent can only
|
|
19379
|
+
// probe sectors by trial-and-error or ask the user to read the web UI.
|
|
19380
|
+
listSectors,
|
|
18598
19381
|
// Billing / top-up tools — granular-shaped but ALWAYS exposed because
|
|
18599
19382
|
// they're the canonical recovery path from a QUOTA_EXCEEDED wall. If
|
|
18600
19383
|
// they were gated behind LEADBAY_MCP_ADVANCED=1 the agent would
|
|
@@ -18637,7 +19420,16 @@ var compositeWriteTools = [
|
|
|
18637
19420
|
removeLeadsFromCampaign,
|
|
18638
19421
|
// Lens extend — agent-driven on-demand fill (additive). Gated behind
|
|
18639
19422
|
// LEADBAY_MCP_WRITE=1. Subject to per-org daily LENS_EXTRA_REFILL quota.
|
|
18640
|
-
extendLens
|
|
19423
|
+
extendLens,
|
|
19424
|
+
// Lens list/switch — read-first (no args = pure list); a switchToLensId
|
|
19425
|
+
// changes the active lens. In compositeWriteTools because the switch path
|
|
19426
|
+
// mutates last_requested_lens, but it stays on the default surface
|
|
19427
|
+
// (write is on by default since 0.3.0).
|
|
19428
|
+
myLenses,
|
|
19429
|
+
// Lens creation — make a brand-new named lens with sectors/sizes in one
|
|
19430
|
+
// call. Default-surface so "create a lens called X for Y" works without
|
|
19431
|
+
// the advanced gate.
|
|
19432
|
+
newLens
|
|
18641
19433
|
];
|
|
18642
19434
|
var compositeTools = [
|
|
18643
19435
|
...compositeReadTools,
|
|
@@ -19837,7 +20629,7 @@ function parseWriteEnv(env = process.env) {
|
|
|
19837
20629
|
}
|
|
19838
20630
|
|
|
19839
20631
|
// src/http-server.ts
|
|
19840
|
-
var VERSION = true ? "0.17.
|
|
20632
|
+
var VERSION = true ? "0.17.3" : "0.0.0-dev";
|
|
19841
20633
|
var PORT = Number(process.env.PORT ?? 8080);
|
|
19842
20634
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
19843
20635
|
var sseSessions = /* @__PURE__ */ new Map();
|