@leadbay/mcp 0.21.3 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -5163,6 +5163,7 @@ var init_composite_file_names = __esm({
5163
5163
  "leadbay_resolve_import_rows",
5164
5164
  "leadbay_scan_portfolio_signals",
5165
5165
  "leadbay_seed_candidates",
5166
+ "leadbay_team_activity",
5166
5167
  "leadbay_tour_plan"
5167
5168
  ]);
5168
5169
  }
@@ -5467,7 +5468,7 @@ var init_notifications = __esm({
5467
5468
  });
5468
5469
 
5469
5470
  // ../core/dist/tool-descriptions.generated.js
5470
- var leadbay_account_history, leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_contact, 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_pin_contact, 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_contact, 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_scan_portfolio_signals, leadbay_seed_candidates, leadbay_select_leads, leadbay_send_feedback, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_user_prompt, leadbay_tour_plan, leadbay_unpin_contact, leadbay_update_contact, leadbay_update_lens, leadbay_update_lens_filter;
5471
+ var leadbay_account_history, leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_contact, 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_artifact_kit, 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_pin_contact, 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_contact, 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_scan_portfolio_signals, leadbay_seed_candidates, leadbay_select_leads, leadbay_send_feedback, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_user_prompt, leadbay_team_activity, leadbay_tour_plan, leadbay_unpin_contact, leadbay_update_contact, leadbay_update_lens, leadbay_update_lens_filter;
5471
5472
  var init_tool_descriptions_generated = __esm({
5472
5473
  "../core/dist/tool-descriptions.generated.js"() {
5473
5474
  "use strict";
@@ -5863,6 +5864,53 @@ WHEN TO USE: after leadbay_refine_prompt returns \`status='clarification_pending
5863
5864
  WHEN NOT TO USE: to set a brand-new prompt \u2014 use leadbay_refine_prompt.
5864
5865
 
5865
5866
  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\`.
5867
+ `;
5868
+ leadbay_artifact_kit = `## WHEN TO USE
5869
+
5870
+ Trigger phrases: "build me a dashboard", "build a call sheet", "interactive artifact", "make a page with buttons", "build an artifact to work my leads", "interactive lead triage board", "a page with buttons that log my calls".
5871
+
5872
+ **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
5873
+
5874
+ Do NOT use for: "show me today's leads" \u2192 \`leadbay_pull_leads\`; "leads I should follow up with" \u2192 \`leadbay_pull_followups\`; "log that I emailed" \u2192 \`leadbay_report_outreach\`.
5875
+
5876
+ Prefer when: the user wants a clickable/interactive HTML artifact whose controls call Leadbay tools \u2014 not a one-off data answer
5877
+
5878
+ Examples that SHOULD invoke this tool:
5879
+ - "Build me an interactive call sheet for these leads."
5880
+ - "Make an artifact with buttons to log call outcomes per lead."
5881
+ - "I want a clickable lead-triage board I can work down."
5882
+
5883
+ Examples that should NOT invoke this tool (sound similar, route elsewhere):
5884
+ - "Show me today's leads."
5885
+ - "Which leads should I follow up with this week?"
5886
+ - "Log that I emailed Acme's CTO."
5887
+
5888
+ ## RENDER (quick)
5889
+
5890
+ Do NOT render this tool's result as prose or a table. It returns
5891
+ \`runtime\` (a JS string) + \`usage_guide\` (the recipe) + \`version\`. Inline
5892
+ \`runtime\` as ONE \`<script>\` (it self-attaches \`window.LeadbayArtifacts\`),
5893
+ build view-models with \`lb.field/action/resource/list\` + the domain helpers,
5894
+ bind them to your own HTML, and pass the tools you call as the artifact's
5895
+ \`mcp_tools\`. READ \`usage_guide\` first. The artifact is the answer.
5896
+
5897
+ ---
5898
+
5899
+ Hands you everything to build an interactive HTML **artifact** the user runs inside their own Claude (cowork): headless **domain view-models** + a markdown usage guide. A view-model owns a control's whole data lifecycle \u2014 populate a dropdown's options from a Leadbay call, hold the value, expose loading/error, validate, encapsulate the API call + business rules. You own 100% of the markup, layout, and style. The library renders nothing.
5900
+
5901
+ Returns \`{ runtime, usage_guide, version }\`:
5902
+ - \`runtime\` \u2014 a minified, zero-dependency vanilla IIFE that self-attaches \`window.LeadbayArtifacts\` (call it \`lb\`). Inline once as \`<script>\`.
5903
+ - \`usage_guide\` \u2014 the full recipe: \`lb.field\` / \`lb.action\` / the \`lb.bind*\` sugar, a copy-paste cold-call call sheet, and the write-call rules. READ IT before building.
5904
+
5905
+ The model \u2014 two layers. Primitives: \`lb.field\` (value + API-populated options), \`lb.action\` (write/submit), \`lb.resource\` (load-on-click / poll-until-done / \`.refresh()\`), \`lb.list\` (paginated rows); each exposes \`.loading/.error/.subscribe\`, and you bind them to YOUR native elements with \`lb.bindSelect/bindValue/bindAction\` (no style imposed). Domain components (pre-wired, footguns baked in): \`lb.campaigns\`, \`lb.outreach\`, \`lb.note\`, \`lb.like/dislike\`, \`lb.leadHistory\`, \`lb.leadProfile\`, \`lb.callList\`, \`lb.enrichment\` (launch + poll), \`lb.teamActivity\` (manager leaderboard + trend). TanStack-Query's headless-view-model separation, vanilla (cowork is inline-only \u2014 no React).
5906
+
5907
+ Canonical uses: a cold-call sheet (\`lb.callList\` + per-row \`lb.outreach\`/\`lb.leadHistory\`); a manager dashboard (\`lb.teamActivity\` \u2192 leaderboard table + Chart.js trend); a live enrichment view (\`lb.enrichment\` \u2192 progress + refresh). Live auto-poll is host-dependent \u2014 always wire a Refresh.
5908
+
5909
+ Write-call footguns (in the guide, repeated because they bite): for \`leadbay_report_outreach\` (status/disposition) the \`args\` MUST include \`verification:{source:"user_confirmed",ref:"\u2026"}\` AND \`_triggered_by:"<the user's request>"\`, or the call is rejected. \`leadbay_add_leads_to_campaign\` needs \`_triggered_by\` too. \`leadbay_add_note\`/\`leadbay_like_lead\`/\`leadbay_dislike_lead\` need only their own args. Snoozing (pushback) and standalone status are advanced-gated \u2014 not callable from a default artifact; use \`report_outreach\`'s \`epilogue_status\` for outcomes.
5910
+
5911
+ WHEN TO USE: the user asks for a clickable / interactive artifact, dashboard, or call sheet that DOES things (not just displays data).
5912
+
5913
+ WHEN NOT TO USE: the user wants a plain data answer (route to leadbay_pull_leads / leadbay_pull_followups) or to log a single real outreach you just did (leadbay_report_outreach).
5866
5914
  `;
5867
5915
  leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Returns the \`bulk_id\`, progress per lead (done/total enrichable contacts), and overall progress. When \`include_contacts=true\` (opt-in), includes each contact's email/phone/job_title/enrichment.done.
5868
5916
 
@@ -8867,6 +8915,50 @@ WHEN TO USE: low-level.
8867
8915
  WHEN NOT TO USE: from agent flow \u2014 use leadbay_refine_prompt, which polls for follow-up clarifications.
8868
8916
 
8869
8917
  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\`.
8918
+ `;
8919
+ leadbay_team_activity = `## WHEN TO USE
8920
+
8921
+ Trigger phrases: "how is my team doing", "team activity", "top performers", "rep leaderboard", "manager dashboard", "who's most active this week", "activity by rep".
8922
+
8923
+ **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
8924
+
8925
+ Do NOT use for: "show me leads" \u2192 \`leadbay_pull_leads\`; "leads I should follow up with" \u2192 \`leadbay_pull_followups\`; "how is this campaign progressing" \u2192 \`leadbay_campaign_progression\`.
8926
+
8927
+ Prefer when: a manager wants team-wide / per-rep activity aggregates, not a lead list
8928
+
8929
+ Examples that SHOULD invoke this tool:
8930
+ - "How is my team doing this month?"
8931
+ - "Who are my top performers in the last two weeks?"
8932
+ - "Show me activity by rep."
8933
+
8934
+ Examples that should NOT invoke this tool (sound similar, route elsewhere):
8935
+ - "Show me today's leads."
8936
+ - "How is the Q3 Outbound campaign progressing?"
8937
+ - "Which leads should I follow up with?"
8938
+
8939
+ ## RENDER (quick)
8940
+
8941
+ Per-rep leaderboard table (sort by total_activities desc) + an activity
8942
+ time-series. Columns worth showing: name, total_activities, notes,
8943
+ contacts_added, meetings_or_interest, lost. Feeds a manager dashboard
8944
+ artifact (table + Chart.js line of \`trend\`).
8945
+
8946
+ ---
8947
+
8948
+ Manager / team KPIs \u2014 the data behind the web app's Dashboard-Manager screen, exposed so an agent (or an artifact) can render a team dashboard.
8949
+
8950
+ Returns \`{ range, reps, trend }\`:
8951
+ - \`range\` \u2014 \`{ from, to, periodicity }\` (the resolved look-back window).
8952
+ - \`reps\` \u2014 per-rep activity, sorted by \`total_activities\` desc (the leaderboard). Each: \`{ user_id, name, email, total_activities, likes, saves, website_clicks, exported, profile_views, contacts_added, contacts_purchased, notes, meetings_or_interest, could_not_reach, lost, still_chasing }\`. The \`epilogue_*\`-derived fields (\`meetings_or_interest\`, \`lost\`, \u2026) are the outcome signal.
8953
+ - \`trend\` \u2014 \`[{ date, count }]\` activity over time for the window (feed a chart).
8954
+
8955
+ Params: \`weeks\` (default 4) OR explicit \`from\`/\`to\` (YYYY-MM-DD); \`periodicity\` (DAILY|WEEKLY, default WEEKLY); \`user_ids\` (omit for the whole team). Admins get the whole org; non-admins are scoped to themselves by the backend.
8956
+
8957
+ Wraps \`GET /kpi/users\` + \`GET /kpi/trends\`. Quota / remaining capacity for the org lives on leadbay_account_status (don't duplicate it here).
8958
+
8959
+ WHEN TO USE: a manager wants to see team-wide or per-rep activity (leaderboard, who's active, activity trend).
8960
+
8961
+ WHEN NOT TO USE: the user wants a lead list (leadbay_pull_leads / leadbay_pull_followups) or one campaign's funnel (leadbay_campaign_progression).
8870
8962
  `;
8871
8963
  leadbay_tour_plan = `## WHEN TO USE
8872
8964
 
@@ -15652,7 +15744,13 @@ var init_research_lead_by_id = __esm({
15652
15744
  },
15653
15745
  agent_memory: { type: "object" }
15654
15746
  },
15655
- additionalProperties: false
15747
+ // _meta is an open envelope: the MCP server layer injects
15748
+ // additional keys (latency_ms, notifications, update_available)
15749
+ // after the composite returns. Mirror the sibling tools
15750
+ // (leadbay_pull_leads et al.) and leave _meta open so those
15751
+ // server-side additions don't fail outputSchema validation
15752
+ // with -32602 (data/_meta must NOT have additional properties).
15753
+ additionalProperties: true
15656
15754
  }
15657
15755
  },
15658
15756
  required: [
@@ -21405,6 +21503,84 @@ var init_report_friction = __esm({
21405
21503
  }
21406
21504
  });
21407
21505
 
21506
+ // ../core/dist/composite/team-activity.js
21507
+ function mapRep(r) {
21508
+ const n = (v) => v ?? 0;
21509
+ return {
21510
+ user_id: r.user?.id,
21511
+ name: r.user?.name ?? null,
21512
+ email: r.user?.email ?? null,
21513
+ total_activities: n(r.total_activities),
21514
+ likes: n(r.likes),
21515
+ saves: n(r.saves),
21516
+ website_clicks: n(r.website),
21517
+ exported: n(r.exported),
21518
+ profile_views: n(r.lead_profile_views),
21519
+ contacts_added: n(r.create_lead_contact),
21520
+ contacts_purchased: n(r.purchase_lead_contact),
21521
+ notes: n(r.create_lead_note),
21522
+ // Epilogue outcomes logged in the window — the "deals" signal.
21523
+ meetings_or_interest: n(r.epilogue_interest_validated_or_meeting_planed),
21524
+ could_not_reach: n(r.epilogue_could_not_reach_still_trying),
21525
+ lost: n(r.epilogue_not_interested_lost),
21526
+ still_chasing: n(r.epilogue_still_chasing)
21527
+ };
21528
+ }
21529
+ var DAY_MS, ymd, teamActivity;
21530
+ var init_team_activity = __esm({
21531
+ "../core/dist/composite/team-activity.js"() {
21532
+ "use strict";
21533
+ init_tool_descriptions_generated();
21534
+ DAY_MS = 864e5;
21535
+ ymd = (d) => d.toISOString().slice(0, 10);
21536
+ teamActivity = {
21537
+ name: "leadbay_team_activity",
21538
+ annotations: {
21539
+ title: "Team activity + per-rep KPIs",
21540
+ readOnlyHint: true,
21541
+ destructiveHint: false,
21542
+ idempotentHint: true,
21543
+ openWorldHint: true
21544
+ },
21545
+ description: leadbay_team_activity,
21546
+ write: false,
21547
+ inputSchema: {
21548
+ type: "object",
21549
+ properties: {
21550
+ weeks: { type: "number", description: "Look-back window in weeks (default 4). Ignored if from/to given." },
21551
+ from: { type: "string", description: "ISO date YYYY-MM-DD (start). Overrides weeks." },
21552
+ to: { type: "string", description: "ISO date YYYY-MM-DD (end). Overrides weeks." },
21553
+ periodicity: { type: "string", enum: ["DAILY", "WEEKLY"], description: "Trend bucketing (default WEEKLY)." },
21554
+ user_ids: {
21555
+ type: "array",
21556
+ items: { type: "string" },
21557
+ description: "Specific rep user-ids; omit for the whole team."
21558
+ }
21559
+ },
21560
+ additionalProperties: false
21561
+ },
21562
+ execute: async (client, params, _ctx) => {
21563
+ const periodicity = params.periodicity ?? "WEEKLY";
21564
+ const to = params.to ?? ymd(/* @__PURE__ */ new Date());
21565
+ const from = params.from ?? ymd(new Date(Date.parse(to) - Math.max(1, params.weeks ?? 4) * 7 * DAY_MS));
21566
+ const ids = params.user_ids && params.user_ids.length > 0 ? params.user_ids.join(",") : "ALL";
21567
+ const qs = `from=${from}&to=${to}&periodicity=${periodicity}&user_ids=${encodeURIComponent(ids)}`;
21568
+ const [usersRaw, trendRaw] = await Promise.all([
21569
+ client.request("GET", `/kpi/users?${qs}`),
21570
+ client.request("GET", `/kpi/trends?${qs}`)
21571
+ ]);
21572
+ const reps = (usersRaw ?? []).map(mapRep).sort((a, b) => b.total_activities - a.total_activities);
21573
+ return {
21574
+ range: { from, to, periodicity },
21575
+ reps,
21576
+ trend: (trendRaw ?? []).map((t) => ({ date: t.date, count: t.count ?? 0 })),
21577
+ _meta: { region: client.region }
21578
+ };
21579
+ }
21580
+ };
21581
+ }
21582
+ });
21583
+
21408
21584
  // ../core/dist/tools/send-feedback.js
21409
21585
  var MESSAGE_MAX, sendFeedback;
21410
21586
  var init_send_feedback = __esm({
@@ -21489,6 +21665,55 @@ var init_send_feedback = __esm({
21489
21665
  }
21490
21666
  });
21491
21667
 
21668
+ // ../core/dist/artifact-runtime.generated.js
21669
+ var ARTIFACT_KIT_VERSION, ARTIFACT_RUNTIME, ARTIFACT_USAGE_GUIDE;
21670
+ var init_artifact_runtime_generated = __esm({
21671
+ "../core/dist/artifact-runtime.generated.js"() {
21672
+ "use strict";
21673
+ ARTIFACT_KIT_VERSION = "0.3.1";
21674
+ ARTIFACT_RUNTIME = '"use strict";(()=>{var _=Object.defineProperty;var k=(e,t,n)=>t in e?_(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var o=(e,t,n)=>k(e,typeof t!="symbol"?t+"":t,n);var T="0.3.1",c=class extends Error{constructor(n,i={}){super(n);o(this,"code");o(this,"raw");this.name="LbError",this.code=i.code,this.raw=i.raw}},p=null,f=3e4;function E(){let e=globalThis.cowork;return e&&typeof e.callMcpTool=="function"?(t,n)=>e.callMcpTool(t,n):null}function v(e){if(e&&typeof e=="object"&&"content"in e){let t=e.content;if(Array.isArray(t)&&t[0]&&typeof t[0].text=="string")return t[0].text}return null}function y(e){if(!e||typeof e!="object")return e;let t=e;if(t.isError)throw new c(v(e)??"tool call failed",{raw:e});if("structuredContent"in t&&t.structuredContent!=null)return t.structuredContent;let n=v(e);if(n!=null)try{return JSON.parse(n)}catch{return n}return e}function L(e){return e instanceof Error?e.message:String(e)}function m(e){let t=e instanceof c?e.code:void 0;return{message:L(e),unavailable:t==="unavailable",code:t}}function S(e={}){p=e.call??null,f=e.timeoutMs??3e4}async function w(e,t){if(!f||f<=0)return e;let n,i=new Promise((r,s)=>{n=setTimeout(()=>s(new c(`"${t}" timed out after ${f}ms`,{code:"timeout"})),f)});try{return await Promise.race([e,i])}finally{n&&clearTimeout(n)}}async function a(e,t={}){if(p)return y(await w(Promise.resolve(p(e,t)),e));let n=E();if(!n)throw new c("Leadbay bridge unavailable (window.cowork absent)",{code:"unavailable"});return y(await w(Promise.resolve(n(e,t)),e))}var d=class{constructor(){o(this,"subs",new Set)}subscribe(t){return this.subs.add(t),t(this),()=>this.subs.delete(t)}emit(){for(let t of this.subs)t(this)}};function A(e){return Array.isArray(e)?e.map(t=>t&&typeof t=="object"?t:{value:t,label:String(t)}):[]}var h=class extends d{constructor(n={}){super();o(this,"kind");o(this,"value");o(this,"options",[]);o(this,"loading",!1);o(this,"error",null);o(this,"ready",!1);o(this,"cfg");o(this,"depUnsubs",[]);o(this,"seq",0);this.cfg=n,this.kind=n.kind,this.value=n.value??"";for(let i of n.dependsOn??[]){let r=i.value;this.depUnsubs.push(i.subscribe(()=>{i.value!==r&&(r=i.value,this.cfg.load&&this.load())}))}n.load&&(n.autoLoad??!0)&&this.load()}async load(){if(!this.cfg.load)return;let n=++this.seq;this.loading=!0,this.error=null,this.emit();try{let i=await this.cfg.load();if(n!==this.seq)return;this.options=this.cfg.options?this.cfg.options(i):A(i),this.ready=!0;let r=this.value==null?"":String(this.value);this.options.length&&(r===""||!this.options.some(s=>String(s.value)===r))&&(this.value=this.options[0].value)}catch(i){if(n!==this.seq)return;this.options=[],this.error=m(i)}finally{n===this.seq&&(this.loading=!1,this.emit())}}setValue(n){this.value=n;let i=this.validate();this.error=i?{message:i,unavailable:!1}:null,this.emit()}validate(){return this.cfg.validate?this.cfg.validate(this.value):null}get valid(){return this.validate()==null}reset(){this.value=this.cfg.value??"",this.error=null,this.emit()}dispose(){for(let n of this.depUnsubs)n();this.depUnsubs=[]}},l=class extends d{constructor(n){super();o(this,"loading",!1);o(this,"error",null);o(this,"lastResult",null);o(this,"cfg");this.cfg=n}async run(){if(this.loading)return;for(let i of this.cfg.fields??[]){let r=i.validate();if(r!=null){this.error={message:r,unavailable:!1},this.emit();return}}if(this.cfg.confirm&&typeof globalThis.confirm=="function"&&!globalThis.confirm(this.cfg.confirm))return;this.loading=!0,this.error=null,this.emit();let n;try{let i=typeof this.cfg.args=="function"?this.cfg.args():this.cfg.args??{};n=await a(this.cfg.tool,i)}catch(i){this.error=m(i),this.loading=!1,this.emit(),this.cfg.onError?.(this.error);return}return this.lastResult=n,this.loading=!1,this.emit(),this.cfg.onSuccess?.(n),n}reset(){this.error=null,this.lastResult=null,this.emit()}},u=class extends d{constructor(n){super();o(this,"data",null);o(this,"loading",!1);o(this,"refreshing",!1);o(this,"error",null);o(this,"done",!1);o(this,"cfg");o(this,"timer",null);o(this,"seq",0);this.cfg=n,(n.autoLoad??!0)&&this.load()}async load(){this.clearTimer();let n=++this.seq;this.data==null?this.loading=!0:this.refreshing=!0,this.error=null,this.emit();try{let r=await this.cfg.load();if(n!==this.seq)return;this.data=r,this.done=this.cfg.until?this.cfg.until(r):!0,this.cfg.pollEvery&&!this.done&&(this.timer=setTimeout(()=>void this.load(),this.cfg.pollEvery))}catch(r){if(n!==this.seq)return;this.error=m(r)}finally{n===this.seq&&(this.loading=!1,this.refreshing=!1,this.emit())}}refresh(){return this.load()}stop(){this.clearTimer()}clearTimer(){this.timer&&(clearTimeout(this.timer),this.timer=null)}},b=class extends d{constructor(n){super();o(this,"items",[]);o(this,"page",0);o(this,"pageSize");o(this,"total",0);o(this,"loading",!1);o(this,"error",null);o(this,"cfg");o(this,"seq",0);this.cfg=n,this.pageSize=n.pageSize??20,(n.autoLoad??!0)&&this.loadPage(0)}async loadPage(n){let i=++this.seq;this.loading=!0,this.error=null,this.emit();try{let r=await this.cfg.load({page:n,pageSize:this.pageSize});if(i!==this.seq)return;this.items=r.items??[],this.total=r.total??this.items.length,this.page=n}catch(r){if(i!==this.seq)return;this.error=m(r)}finally{i===this.seq&&(this.loading=!1,this.emit())}}next(){return this.loadPage(this.page+1)}prev(){return this.loadPage(Math.max(0,this.page-1))}get hasMore(){return(this.page+1)*this.pageSize<this.total}};function x(e,t){let n=t.error?.unavailable?"unavailable":t.loading?"loading":t.error?"error":"ready";e.setAttribute("data-lb-state",n),t.error?e.setAttribute("data-lb-error",t.error.message):e.removeAttribute("data-lb-error")}function C(e,t){let n=()=>t.setValue(e.value);e.addEventListener("change",n);let i=t.subscribe(()=>{x(e,t),e.disabled=t.loading,e.innerHTML="";for(let r of t.options){let s=document.createElement("option");s.value=String(r.value),s.textContent=r.label,e.appendChild(s)}e.value=t.value==null?"":String(t.value)});return()=>{e.removeEventListener("change",n),i()}}function I(e,t){let n=e.type==="checkbox",i=e.tagName==="SELECT"?"change":"input",r=()=>t.setValue(n?e.checked:e.value);e.addEventListener(i,r);let s=t.subscribe(()=>{if(n)e.checked=!!t.value;else{let g=t.value==null?"":String(t.value);e.value!==g&&(e.value=g)}e.setAttribute("data-lb-state",t.error?"error":"ready"),t.error?e.setAttribute("data-lb-error",t.error.message):e.removeAttribute("data-lb-error")});return()=>{e.removeEventListener(i,r),s()}}function R(e,t){let n=r=>{r.preventDefault(),t.run()};e.addEventListener("click",n);let i=t.subscribe(()=>{let r=t.error?.unavailable?"unavailable":t.loading?"loading":t.error?"error":t.lastResult!=null?"success":"idle";e.setAttribute("data-lb-state",r),"disabled"in e&&(e.disabled=t.loading),t.error?e.setAttribute("data-lb-error",t.error.message):e.removeAttribute("data-lb-error")});return()=>{e.removeEventListener("click",n),i()}}var O=["STILL_CHASING","COULD_NOT_REACH_STILL_TRYING","INTEREST_VALIDATED_OR_MEETING_PLANED","NOT_INTERESTED_LOST"];function P(e){return new h({kind:"select",load:()=>a("leadbay_list_campaigns",{_triggered_by:e}),options:t=>(t?.campaigns??[]).map(i=>{let r=i?.campaign??i;return r?.id?{value:r.id,label:r.name??r.ai_generated_name??String(r.id)}:null}).filter(i=>i!=null)})}function M(e){return new l({tool:"leadbay_report_outreach",fields:e.note?[e.note]:[],args:()=>({lead_id:e.leadId,...e.status?{epilogue_status:e.status.value}:{},note:e.note?e.note.value:"",verification:{source:"user_confirmed",ref:e.ref??"logged from artifact"},_triggered_by:e.ask})})}function F(e){return new l({tool:"leadbay_add_note",fields:[e.note],args:()=>({leadId:e.leadId,note:e.note.value})})}function q(e){return new l({tool:"leadbay_like_lead",args:{lead_id:e}})}function H(e){return new l({tool:"leadbay_dislike_lead",args:{lead_id:e}})}function z(e,t){return new u({autoLoad:!1,load:()=>a("leadbay_account_history",{leadId:e,_triggered_by:t})})}function N(e,t){return new u({autoLoad:!1,load:()=>a("leadbay_research_lead_by_id",{leadId:e,_triggered_by:t})})}function U(e){let t=null;return new u({pollEvery:e.pollEvery??4e3,until:n=>!!n?.all_done,load:async()=>{if(!t){let n=await a("leadbay_enrich_titles",{...e.leadIds?{leadIds:e.leadIds}:{},titles:e.titles,email:e.email??!0,phone:e.phone??!1,_triggered_by:e.ask});if(t=n?.bulk_id??null,!t)return{all_done:!0,no_job:!0,mode:n?.mode,preview:n?.preview}}return a("leadbay_bulk_enrich_status",{bulk_id:t,_triggered_by:e.ask})}})}function D(e){let t=e.source??"followups";return new b({pageSize:e.pageSize??20,load:async({page:n,pageSize:i})=>{let s=t==="campaign"?await a("leadbay_campaign_call_sheet",{campaign_id:e.campaignId,page:n,count:i,_triggered_by:e.ask}):await a("leadbay_pull_followups",{page:n,count:i,...e.city?{city:e.city}:{},_triggered_by:e.ask}),g=s.leads??s.items??[];return{items:g,total:s.total_leads??s.pagination?.total??g.length}}})}function V(e){return new u({load:()=>a("leadbay_team_activity",{weeks:e.weeks??4,_triggered_by:e.ask})})}var j={VERSION:T,configure:S,call:a,field:e=>new h(e),action:e=>new l(e),resource:e=>new u(e),list:e=>new b(e),bindSelect:C,bindValue:I,bindAction:R,campaigns:P,outreach:M,note:F,like:q,dislike:H,leadHistory:z,leadProfile:N,enrichment:U,callList:D,teamActivity:V,EPILOGUE_STATUSES:O};typeof globalThis<"u"&&(globalThis.LeadbayArtifacts=j);})();';
21675
+ ARTIFACT_USAGE_GUIDE = '# Leadbay Artifact Kit \u2014 headless domain components\n\nYou are building a single-file HTML **artifact** the user runs inside cowork. This\nkit gives you **headless view-models** that own a control\'s whole data lifecycle \u2014\nload/populate from a Leadbay call, hold value/state, poll, validate, and\nencapsulate the API call + business rules. **You own 100% of markup/layout/style.**\nThe library renders nothing. Inline the runtime once as a `<script>`; it exposes\none global `window.LeadbayArtifacts` (call it `lb`). Vanilla, no React, no build.\n\nPass every tool you use as the artifact\'s `mcp_tools` so the host permits it.\n\n## Two layers\n\n**Primitives** (generic):\n- `lb.field({ load, options, value, validate, dependsOn })` \u2014 a value + optionally\n API-populated options. `.value/.setValue/.options/.loading/.error/.valid/.subscribe`.\n- `lb.action({ tool, args, fields, confirm, onSuccess, onError })` \u2014 a write/submit.\n `.run()/.loading/.error/.lastResult/.subscribe`.\n- `lb.resource({ load, pollEvery?, until?, autoLoad? })` \u2014 one read that may change:\n load-on-click or poll-until-`until`. `.data/.loading/.refreshing/.error/.done/.load()/.refresh()/.stop()/.subscribe`.\n- `lb.list({ load, pageSize })` \u2014 paginated rows. `.items/.page/.total/.loading/.loadPage(n)/.next()/.prev()/.hasMore/.subscribe`.\n\n`.error` is `{ message, unavailable } | null`. `subscribe(cb)` fires immediately\nthen on every change \u2014 render your own DOM from it.\n\n**Domain components** (pre-wired \u2014 bake in the tool name, arg shape, and footguns):\n\n| Call | Returns | For |\n|---|---|---|\n| `lb.campaigns(ask)` | field | a campaign `<select>`, options from `leadbay_list_campaigns` |\n| `lb.outreach({leadId, ask, status?, note?})` | action | log a call \u2192 `report_outreach` (verification + `_triggered_by` baked in) |\n| `lb.note({leadId, note})` | action | add a note \u2192 `add_note` |\n| `lb.like(leadId)` / `lb.dislike(leadId)` | action | taste signal |\n| `lb.leadHistory(leadId, ask)` | resource (lazy) | notes + activities + engagement \u2192 `account_history` |\n| `lb.leadProfile(leadId, ask)` | resource (lazy) | full lead profile \u2192 `research_lead_by_id` |\n| `lb.callList({source:\'followups\'\\|\'campaign\', campaignId?, city?, ask})` | list | a cold-call list (Monitor or a campaign) |\n| `lb.enrichment({leadIds, titles, ask, pollEvery?})` | resource (polling) | launch + watch contact enrichment |\n| `lb.teamActivity({weeks, ask})` | resource | manager leaderboard + activity trend \u2192 `leadbay_team_activity` |\n\n`lb.EPILOGUE_STATUSES` = the 4 disposition values\n(`STILL_CHASING`, `COULD_NOT_REACH_STILL_TRYING`, `INTEREST_VALIDATED_OR_MEETING_PLANED`, `NOT_INTERESTED_LOST`).\n\n**Binding sugar** (optional; binds a view-model to YOUR native element, no style):\n`lb.bindSelect(selectEl, field)` (populates options + value), `lb.bindValue(inputEl, field)`,\n`lb.bindAction(buttonEl, action)`. They set `data-lb-state`\n(`ready|loading|error|success|unavailable`) + `data-lb-error` on your element as\nstyling hooks. For lists/resources, use `.subscribe()` and render yourself.\n\n`ask` is the user\'s request this artifact serves \u2014 it becomes `_triggered_by`.\n\n## Recipe: cold-call sheet (one row per lead)\n\n```js\nconst lb = window.LeadbayArtifacts; lb.configure();\nconst ASK = "<the user\'s request>";\n\nconst list = lb.callList({ source: "campaign", campaignId: CID, ask: ASK });\nlist.subscribe((l) => renderRows(l.items, l.loading)); // your render\n\n// per lead row (call when you build a row):\nfunction wireRow(lead, els) {\n const status = lb.field({ value: "STILL_CHASING" }); // static-enum <select>\n const note = lb.field({ validate: (v) => (v && v.trim() ? null : "Add a note") });\n lb.bindValue(els.status, status);\n lb.bindValue(els.note, note);\n lb.bindAction(els.log, lb.outreach({ leadId: lead.id, ask: ASK, status, note }));\n lb.bindAction(els.like, lb.like(lead.id));\n\n const history = lb.leadHistory(lead.id, ASK); // lazy\n history.subscribe((h) => renderHistory(els.history, h));\n els.expand.onclick = () => history.load(); // load on click\n}\n```\n\n## Recipe: manager dashboard\n\n```js\nconst team = lb.teamActivity({ weeks: 4, ask: ASK });\nteam.subscribe((t) => {\n if (t.loading) showSpinner();\n if (t.data) {\n renderLeaderboard(t.data.reps); // sorted by total_activities; cols: name, notes, meetings_or_interest, lost\u2026\n renderTrendChart(t.data.trend); // [{date,count}] \u2192 Chart.js (allowed from CDN)\n }\n});\nrefreshBtn.onclick = () => team.refresh();\n```\n\n## Recipe: live enrichment\n\n```js\nconst job = lb.enrichment({ leadIds: [LEAD], titles: ["CEO", "VP Sales"], ask: ASK });\njob.subscribe((j) => {\n const p = j.data && j.data.overall_progress; // {done,total,done_ratio}\n renderBar(p);\n if (j.done) renderContacts(j.data.leads); // enriched contacts\n});\nrefreshBtn.onclick = () => job.refresh();\n```\n\n## Write-call rules\n\nThe domain factories handle these for you. If you hand-roll an action:\n`leadbay_report_outreach` args MUST include `verification:{source:"user_confirmed", ref}`\nAND `_triggered_by`; `leadbay_add_leads_to_campaign` needs `_triggered_by`;\n`add_note`/`like_lead`/`dislike_lead` take only their own args. `epilogue_status` is\none of `lb.EPILOGUE_STATUSES`. Snoozing (pushback) and org WON/LOST status are\nadvanced-gated \u2014 not callable from a default artifact; use the epilogue values.\n\n## Degradation + live updates\n\nIf the host bridge is absent, a view-model\'s `.error` is set with `.error.unavailable\n=== true` (bind helpers set `data-lb-state="unavailable"`) \u2014 nothing throws. Every\ncall also has a **30s timeout** (configurable via `lb.configure({ timeoutMs })`): a\nhost call that never settles becomes `.error` with `code:"timeout"`, so a control is\nnever stuck loading forever \u2014 always render the `.error` branch so the user can retry.\nAuto-poll (`pollEvery`) depends on the cowork host serving FRESH reads; `.refresh()`\nis the guaranteed manual path \u2014 always wire a Refresh control for polling resources.';
21676
+ }
21677
+ });
21678
+
21679
+ // ../core/dist/tools/artifact-kit.js
21680
+ var artifactKit;
21681
+ var init_artifact_kit = __esm({
21682
+ "../core/dist/tools/artifact-kit.js"() {
21683
+ "use strict";
21684
+ init_tool_descriptions_generated();
21685
+ init_artifact_runtime_generated();
21686
+ artifactKit = {
21687
+ name: "leadbay_artifact_kit",
21688
+ annotations: {
21689
+ title: "Artifact component kit",
21690
+ readOnlyHint: true,
21691
+ destructiveHint: false,
21692
+ idempotentHint: true,
21693
+ openWorldHint: false
21694
+ },
21695
+ description: leadbay_artifact_kit,
21696
+ write: false,
21697
+ inputSchema: {
21698
+ type: "object",
21699
+ properties: {},
21700
+ additionalProperties: false
21701
+ },
21702
+ // No outputSchema by design: the return is three opaque strings. Declaring a
21703
+ // schema would enroll the tool in the output-schema-conformance drift-catcher
21704
+ // (an existing test file we don't modify). The server still emits the
21705
+ // plain-object return as structuredContent: { runtime, usage_guide, version }.
21706
+ execute: async (_client, _params, _ctx) => {
21707
+ return {
21708
+ version: ARTIFACT_KIT_VERSION,
21709
+ runtime: ARTIFACT_RUNTIME,
21710
+ usage_guide: ARTIFACT_USAGE_GUIDE
21711
+ };
21712
+ }
21713
+ };
21714
+ }
21715
+ });
21716
+
21492
21717
  // ../core/dist/index.js
21493
21718
  var dist_exports = {};
21494
21719
  __export(dist_exports, {
@@ -21519,6 +21744,7 @@ __export(dist_exports, {
21519
21744
  answerClarification: () => answerClarification,
21520
21745
  appendEntry: () => appendEntry,
21521
21746
  appendTombstone: () => appendTombstone,
21747
+ artifactKit: () => artifactKit,
21522
21748
  assertSafeInsight: () => assertSafeInsight,
21523
21749
  bulkEnrichStatus: () => bulkEnrichStatus,
21524
21750
  bulkQualifyLeads: () => bulkQualifyLeads,
@@ -21624,6 +21850,7 @@ __export(dist_exports, {
21624
21850
  setEpilogueStatus: () => setEpilogueStatus,
21625
21851
  setPushback: () => setPushback,
21626
21852
  setUserPrompt: () => setUserPrompt,
21853
+ teamActivity: () => teamActivity,
21627
21854
  toInboxEntry: () => toInboxEntry,
21628
21855
  tools: () => tools,
21629
21856
  tourPlan: () => tourPlan,
@@ -21731,7 +21958,9 @@ var init_dist = __esm({
21731
21958
  init_answer_clarification();
21732
21959
  init_report_outreach();
21733
21960
  init_report_friction();
21961
+ init_team_activity();
21734
21962
  init_send_feedback();
21963
+ init_artifact_kit();
21735
21964
  init_bulk_store();
21736
21965
  agentMemoryTools = [
21737
21966
  agentMemoryRecall,
@@ -21816,6 +22045,11 @@ var init_dist = __esm({
21816
22045
  // answer to "which of my leads have signal X" that previously forced a
21817
22046
  // per-lead research_lead_by_id loop (issue #3704).
21818
22047
  scanPortfolioSignals,
22048
+ // Team activity — manager-facing per-rep leaderboard + activity trend (wraps
22049
+ // /kpi/users + /kpi/trends, the web Dashboard-Manager surface). Read-only;
22050
+ // always exposed so a manager artifact can render team KPIs. Non-admins are
22051
+ // scoped to themselves by the backend.
22052
+ teamActivity,
21819
22053
  recallOrderedTitles,
21820
22054
  accountStatus,
21821
22055
  bulkEnrichStatus,
@@ -21854,7 +22088,13 @@ var init_dist = __esm({
21854
22088
  // every tool response regardless of write gating; without ack the agent
21855
22089
  // sees the same entries on every call forever. Pairing the surfacing
21856
22090
  // channel with the clearing tool is non-optional.
21857
- acknowledgeNotification
22091
+ acknowledgeNotification,
22092
+ // Artifact kit — ALWAYS exposed, read-only. Returns the headless
22093
+ // @leadbay/components runtime + usage guide the agent uses to BUILD an
22094
+ // interactive HTML artifact (cold-call sheet, lead-triage board) whose
22095
+ // buttons call Leadbay writes. No backend call; granular-shaped (lives in
22096
+ // tools/) so it carries no _triggered_by mandate for a kit fetch.
22097
+ artifactKit
21858
22098
  ];
21859
22099
  compositeWriteTools = [
21860
22100
  bulkQualifyLeads,
@@ -21944,6 +22184,204 @@ import {
21944
22184
  } from "@modelcontextprotocol/sdk/types.js";
21945
22185
 
21946
22186
  // src/prompts.generated.ts
22187
+ var leadbay_build_campaign = `
22188
+ ## MEMORY
22189
+
22190
+ Before responding, glance at any \`_meta.agent_memory.summary\` returned by tool calls earlier in this session and reflect its top signals in your reasoning ("Filtering by your stated preference for healthcare"). After any material new signal from the user this conversation (sector, region, deal size, communication style, qualification rule, explicit retraction, or recurrence / scheduling preference such as "I do this every day" or "remind me every morning"), call \`leadbay_agent_memory_capture\` to persist it: \`source:"user_stated"\` if literal, \`source:"inferred"\` with confidence <=6 if inferred.
22191
+
22192
+
22193
+ Build me a Leadbay campaign from scratch{{arg:campaign_name_paren}}. {{arg:audience_block}}
22194
+
22195
+ GATE \u2014 DEFER TO TOOL RENDERING. When you call a Leadbay composite that ships its own RENDERING block (every composite in 0.9.0+ does), render the response using that block's recipe verbatim \u2014 score bars, glyph palette, column order, hide-list, link priorities, all of it. Do NOT substitute prose, a numbered list, or a different column structure even when an orchestrating prompt's body suggests alternate framing. Prompt-specific commentary (motivational nudges, summaries, next-action recommendations) belongs ABOVE or BELOW the canonical table, never in place of it.
22196
+
22197
+ If the prompt's body and the tool's RENDERING appear to conflict, the tool's RENDERING wins for the structural layout; the prompt's voice wins for the commentary that surrounds it.
22198
+
22199
+
22200
+ # Resilience rules for Leadbay long-running tools
22201
+
22202
+ These four rules apply to every Leadbay workflow that calls \`leadbay_pull_leads\`, \`leadbay_bulk_qualify_leads\`, \`leadbay_research_lead_by_id\`, \`leadbay_import_and_qualify\`, or \`leadbay_enrich_titles\`. **Treat timeouts and stream-closed errors as transient, not as signals to replan.**
22203
+
22204
+ ## Rule 1 \u2014 Pin the lens
22205
+
22206
+ After your first \`leadbay_pull_leads\` call, capture \`response.lens.id\` into your working memory and **pass it explicitly as the \`lensId\` argument to every subsequent call** in this session \u2014 including any re-pulls, bulk qualifies, or research calls that accept it. (Field-name caveat: the response nests it as \`lens.id\`; the parameter on subsequent calls is \`lensId\`.) The active lens can shift between calls (5-minute client cache + backend \`last_requested_lens\` can change if the user touches the web UI). A lens shift mid-workflow throws away your top-10 work.
22207
+
22208
+ ## Rule 2 \u2014 Prefer async for bulk operations
22209
+
22210
+ \`leadbay_bulk_qualify_leads\` and \`leadbay_import_and_qualify\` accept \`wait_for_completion:false\`, which returns \`{status:'running', qualify_id}\` immediately. Then poll \`leadbay_qualify_status\` (or \`leadbay_import_status\`) every ~10s until the job completes. **Use the async pattern by default** \u2014 the blocking default can exceed the MCP client's per-call timeout on large batches and produce a misleading \`"Request timed out"\` even though the server is still working.
22211
+
22212
+ ## Rule 3 \u2014 Serialize \`leadbay_research_lead_by_id\` fan-out
22213
+
22214
+ \`leadbay_research_lead_by_id\` is composite and reads many sub-resources. Calling it on 10 leads in parallel can saturate the transport and produce \`"Tool permission stream closed"\` errors that look like permission failures but are really backpressure. **Call it sequentially**, or at most 3 in parallel. If one call fails with a stream/timeout error, retry that one call once before moving on; on a second failure, note the lead and continue \u2014 do not abandon the remaining leads.
22215
+
22216
+ ## Rule 4 \u2014 Retry, don't replan
22217
+
22218
+ If a Leadbay tool returns \`"Request timed out"\`, \`"stream closed"\`, or any other transport-level error (distinct from a Leadbay-issued error payload), the work may still be running server-side. Do this in order:
22219
+
22220
+ 1. For bulk tools \u2014 retry with \`wait_for_completion:false\` and poll the status tool with the returned id. Don't re-pull leads; that can shift the lens.
22221
+ 2. For single-lead tools \u2014 retry the same call once. If it still fails, record the lead id and continue with the rest of the workflow.
22222
+ 3. **Do not** switch strategies (e.g. "the endpoint is broken, let me re-pull from scratch"). The earlier work is still valid; the timeout was the wire.
22223
+
22224
+ If \`pull_leads\` itself fails and you have no prior batch, then yes \u2014 retry it, explicitly pass the lensId you captured (if any), and continue.
22225
+
22226
+
22227
+ # PHASE 0 \u2014 STATE + AUDIENCE
22228
+
22229
+ Call \`leadbay_account_status\` to see my remaining quota, my **enrichment credits**, and my **active lens**. Note \`web_fetch_remaining\` (qualification budget) and the enrichment credit balance \u2014 Phase 3 spends credits.
22230
+
22231
+ Resolve the audience:
22232
+
22233
+ - **Default \u2014 use my active lens.** If I didn't name a fresh audience, the active lens IS the audience. Do NOT create a new lens.
22234
+ - **Fresh-audience fork.** Only if I described a NEW audience the active lens doesn't already cover, set it up first: \`leadbay_adjust_audience\` for sector/size tweaks, or \`leadbay_new_lens\` to create a brand-new named lens \u2014 then continue on that lens. Do NOT silently overwrite my existing lens; confirm once before switching.
22235
+
22236
+ # PHASE 1 \u2014 DISCOVER
22237
+
22238
+ Call \`leadbay_pull_leads\` on the resolved lens. **Capture \`response.lens.id\` and pass it as an explicit \`lensId\` on every later call this session** \u2014 a mid-session lens shift would discard the cohort I'm about to pick. Render the batch with the canonical layout:
22239
+
22240
+ ## RENDERING \u2014 markdown table, three columns, score-bar driven
22241
+
22242
+ Present the response as a markdown table sorted by \`score\` descending, with exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
22243
+
22244
+ ## Score-bar (10-segment, inline-code wrapped)
22245
+
22246
+ Wrap a 10-glyph bar in a SINGLE inline-code span (backticks). The inline-code styling is what gives the bar contrast in most chat renderers \u2014 HTML \`<span>\` is stripped inside table cells.
22247
+
22248
+ Glyphs (use these exact characters; do not substitute):
22249
+
22250
+ - \`\u25B0\` \u2014 firmographic-only fill
22251
+ - \`\u2756\` \u2014 AI-booster cap (placed at the RIGHT END of the filled run, never the front)
22252
+ - \`\u25B1\` \u2014 empty
22253
+
22254
+ Computation:
22255
+
22256
+ \`\`\`
22257
+ total_filled = round(score / 10), clamped to 0..10
22258
+ ai_segments = round(qualification_summary.avg_qualification_boost / 3.3),
22259
+ clamped to [0, total_filled]
22260
+ normal_filled = total_filled \u2212 ai_segments
22261
+ bar = "\u25B0" \xD7 normal_filled
22262
+ + "\u2756" \xD7 ai_segments
22263
+ + "\u25B1" \xD7 (10 \u2212 total_filled)
22264
+ \`\`\`
22265
+
22266
+ If \`qualification_summary.answered == 0\` or \`avg_qualification_boost\` is null, set \`ai_segments = 0\` (no \u2756). Always wrap the bar in backticks. Print the legend \`\` \`\u25B0\` firmographic \xB7 \`\u2756\` AI booster cap \xB7 \`\u25B1\` unfilled \`\` once below the table.
22267
+
22268
+
22269
+ **Column 1 \u2014 Company**
22270
+
22271
+ - Line 1: the 10-segment score bar in inline-code backticks (see the score-bar snippet above for the algorithm).
22272
+ - Insert \`<br>\` between lines.
22273
+ - Line 2: linked company name + \` \xB7 \` + short location + \` \xB7 \` + compact size.
22274
+ - Link target: \`website\` (prefix \`https://\` if it's a bare hostname). Don't synthesize an app deep-link.
22275
+ - Location: shorten "City of New York" \u2192 "NYC"; otherwise "City ST"; state alone only when city missing.
22276
+ - Size: \`"Xk+"\` when \`size.min >= 1000\`, \`"min\u2013max"\` otherwise.
22277
+
22278
+ **Column 2 \u2014 Why it fits**
22279
+
22280
+ - One sentence, \u2264 20 words.
22281
+ - Synthesize from (in priority order, whichever is present) the lead's \`short_description\`, top 2 \`tags[].display_name\`, and the gist of \`qualification_summary.best_response_excerpt\`. The trim payload does NOT carry the longer \`description\` field \u2014 for that, agent must call \`leadbay_research_lead_by_id\` or \`leadbay_research_lead_by_name_fuzzy\`.
22282
+ - Do NOT append \`(boost N)\` \u2014 the \u2756 cap in column 1 already carries that signal.
22283
+ - No bullet lists, no line breaks inside the cell.
22284
+
22285
+ **Column 3 \u2014 Contact**
22286
+
22287
+ \`[Contact name](LINK) \xB7 short job title\`. The \`[Contact name](LINK)\` markdown link wrapping is mandatory \u2014 never render the name as plain text. See linking/contact-linkedin for the URL priority (real profile \u2192 constructed people-search) and the \xB0-flag fallback.
22288
+
22289
+ **Hide from the user (never include in any cell):** \`id\`, \`location.pos\`, \`location.country\` (unless city/state both missing), \`sector_id\`, \`is_hq\`, \`web_fetch_in_progress\`, \`enrichment_in_progress\`, \`highlighted_fields\`, \`custom_fields\`, \`contacts_count\` when 0, \`notes_count\` / \`epilogue_actions_count\` / \`prospecting_actions_count\` when 0, \`stale_at\`, \`deal_insights\`, \`social_presence\` booleans (except as the \xB0-flag signal), \`need_attention\` flags, any field whose value is the string \`"null"\`.
22290
+
22291
+ ## Linking a contact's name
22292
+
22293
+ **MANDATORY: every contact name in your output \u2014 table cells, prose, headers, "Reach <Name>" callouts \u2014 MUST be wrapped in markdown link syntax \`[Name](URL)\`. Never render a contact name as bare text. A plain-text name is a broken contact card; the underlined name is the user's primary affordance for "take me to this person's profile". No "no URL available" exception \u2014 the search URL below is always constructable from name + company.**
22294
+
22295
+ URL priority (first applicable wins):
22296
+
22297
+ 1. **Real profile** \u2014 \`contact.linkedin_page\` when it's a string starting with \`https://\` (the MCP coerces the legacy literal \`"null"\` string to real null before you see it).
22298
+ 2. **Constructed people-search** \u2014 \`https://www.linkedin.com/search/results/people/?keywords=<First>+<Last>+<Company>\`. URL-encode params. Strip Inc / LLC / Corp / Ltd / GmbH / Co / S.A. / S.L. / PLC / AG / SAS / SARL suffixes from the company. Append a trailing \` \xB0\` to the rendered name ONLY when this fallback is in use AND \`social_presence.linkedin == false\`. Never append \`\xB0\` when a real \`linkedin_page\` was used.
22299
+
22300
+ Never link a person's name to the company's LinkedIn page (and vice versa) \u2014 the two surfaces are different and conflating them quietly degrades the workflow.
22301
+
22302
+ ## Linking the company
22303
+
22304
+ Use the lead's \`website\` as the company-name link target \u2014 prefix \`https://\` if the value is a bare hostname. (The MCP does NOT synthesize a Leadbay-app deep-link URL; the team has not standardized one. Linking to \`website\` is always real data.)
22305
+
22306
+ When the response carries \`social_urls\` (the post-fix multi-platform URL block on rich-lead responses), render every non-null platform as a pill chip in the company-info row. Iterate over \`social_urls\`'s keys \u2014 never hardcode a fixed list \u2014 and emit each as \`[<platform-label>](<url>)\`. Skip platforms whose URL is null.
22307
+
22308
+ \`social_presence\` carries booleans for the same 6 platforms (crunchbase, facebook, instagram, linkedin, tiktok, twitter) \u2014 useful when you only care that the company has a profile somewhere. Use it as the \xB0-flag signal in the contact people-search fallback (see linking/contact-linkedin).
22309
+
22310
+
22311
+
22312
+ If the batch is thin (fewer than ~10 workable leads) or I ask for more depth, top it up: call \`leadbay_bulk_qualify_leads({lensId:<captured>, count:<deficit, max 25>, wait_for_completion:false})\`, poll \`leadbay_qualify_status\` until done, then re-pull with the same \`lensId\`. Never re-pull without \`lensId\`.
22313
+
22314
+ # PHASE 2 \u2014 PICK AN ICP CANDIDATE POOL
22315
+
22316
+ A campaign is only as good as the leads in it \u2014 AND only as good as whether each lead has a reachable BUYER (see Phase 3). So pick a **generous candidate pool** now, not the final cohort: aim for ~1.5\xD7 the target size of in-ICP leads (highest \`ai_agent_lead_score\`), so Phase 3 can drop any lead that turns out to have no buyer-persona contact and still hit the target. If the batch is short, top up via \`leadbay_bulk_qualify_leads\` / \`leadbay_extend_lens\`.
22317
+
22318
+ If I named specific leads, use those (but still apply the Phase 3 buyer-coverage check and tell me which lack a buyer). Otherwise recommend the pool and show it. Capture the candidate \`leadIds\`. Confirm the count ("12 candidates \u2014 I'll lock the final ~8 after checking each has a buyer"). Do NOT create the campaign yet \u2014 the final cohort is decided after Phase 3's coverage check.
22319
+
22320
+ # PHASE 3 \u2014 ENRICH THE RIGHT CONTACTS (load-bearing)
22321
+
22322
+ This is the phase that decides whether the campaign is worth a salesperson's time. Contacts aren't attached by default and enrichment is paid \u2014 so spend it ONLY on the people who would actually **buy what I sell**, not on whoever is most senior.
22323
+
22324
+ **Step A \u2014 work out MY buyer persona (do this before touching titles).**
22325
+ Figure out what *I* sell and therefore who, inside the target company, owns the decision to buy it:
22326
+
22327
+ - Infer my product / value-prop from my context: my org and account (\`leadbay_account_status\`), and especially my lens's qualification criteria \u2014 the \`qualification_summary\` on the leads tells you *why* these companies are good targets for me, which implies what I'm offering them.
22328
+ - Map value-prop \u2192 the **buying department/persona**, NOT seniority:
22329
+ - A sales / prospecting / lead-gen / outbound / marketing / GTM / revenue tool \u2192 the **revenue org**: VP / Head / Director of Sales, Business Development, Account/Carrier Sales, CRO, CMO / VP Marketing, Head of Growth / Demand Gen, RevOps. (This is Leadbay's own persona.)
22330
+ - An operations / logistics tool \u2192 operations leaders. A finance tool \u2192 finance. A dev tool \u2192 engineering. Etc.
22331
+ - **Company size caveat:** Founder / CEO / Owner is a real buyer at small companies (\u2264~50), but at larger ones they are not \u2014 there the functional leader (e.g. VP Sales) is the buyer.
22332
+ - **State the persona in one line and confirm it with me** before spending (see Step C). I can correct it.
22333
+
22334
+ **ANTI-PATTERN \u2014 do NOT do this:** picking the most senior or most "decision-maker-sounding" title regardless of department. A Director of Operations, COO, Mgr of Logistics, CFO, or CTO will **never** buy a sales tool \u2014 enriching them wastes credits and hands me a useless list. Seniority is not the same as being my buyer.
22335
+
22336
+ **Step B \u2014 find the persona-matching, enrichable contacts.**
22337
+ Call \`leadbay_recall_ordered_titles({leadIds, lensId})\` and \`leadbay_enrich_titles({leadIds, lensId})\` in **discovery mode** (no \`titles\`). These return previously-enriched titles, \`title_suggestions\`, \`auto_included_titles\`, \`available_in_selection\`, \`enrichable_contacts\`, and \`credits_remaining\`. Treat them as a **menu to filter against my persona \u2014 not the answer.** If past-enriched titles or suggestions are off-persona (e.g. operations roles for a sales tool), do NOT repeat them. Select the titles that match my buyer persona AND are actually enrichable.
22338
+
22339
+ **Step B.5 \u2014 coverage guarantee (lock the final cohort here).** A campaign where half the leads have no buyer is a failed campaign. So before enriching, determine for each candidate lead whether it actually has an **enrichable buyer-persona contact** \u2014 use the discovery data plus, where it's ambiguous, a quick \`leadbay_research_lead_by_id\` to see that lead's available contact titles. Then:
22340
+
22341
+ - **KEEP** candidates that have \u22651 enrichable buyer-persona contact.
22342
+ - **SWAP OUT** candidates whose only contacts are off-persona (e.g. ops/dispatch/finance only) or who have no enrichable contact at all. Replace each with the **highest-\`ai_agent_lead_score\` in-ICP candidate** from the pool that DOES have a buyer, until the cohort hits the target size (default 20; here capped by the enrichment budget).
22343
+ - **Do NOT trade ICP fit for coverage.** A lead with a buyer but weak ICP fit (low \`ai_agent_lead_score\`, a vertical that doesn't match what I sell) is still the wrong lead \u2014 coverage is a filter applied AFTER ICP, never a reason to admit an off-ICP company. The final cohort must be both high-ICP AND buyer-covered.
22344
+ - If the lens genuinely can't supply enough buyer-ready, in-ICP leads, say so honestly and offer to widen/extend rather than padding the campaign with no-buyer leads OR with off-ICP ones.
22345
+
22346
+ Tell me what you swapped in one line ("dropped Corbett + RBS \u2014 ops-only; swapped in Acme + Globex which have Sales VPs"). The goal is a final cohort where EVERY lead has a real buyer to call.
22347
+
22348
+ **Step C \u2014 show the spend + persona and confirm.** State the persona, the chosen titles, and "You have {credits_remaining} credits; this enriches {enrichable_contacts} contacts." Confirm via \`ask_user_input_v0\` ("Enrich these {enrichable_contacts} <persona> contacts now?" \u2192 ["Yes, enrich", "No, skip", "Change the persona/titles"]). Never launch a paid run without this. Enrich up to the campaign size (default 20) best persona-matching contacts.
22349
+
22350
+ **Step D \u2014 launch + poll.** On yes: \`leadbay_enrich_titles({leadIds, lensId, titles:[...chosen], email:true, phone:true})\` to launch, then poll \`leadbay_bulk_enrich_status\` until done (enrichment can take several minutes \u2014 keep polling, don't render an empty sheet prematurely). Append one small line at the very end of your reply: \`_(N credits remaining)_\`.
22351
+
22352
+ If I skip enrichment, continue \u2014 the campaign can be enriched later from the call sheet.
22353
+
22354
+ # PHASE 4 \u2014 CREATE THE CAMPAIGN
22355
+
22356
+ Derive a name (\`<lens or audience> \u2013 <today's date>\`) or use the one I gave. Call \`leadbay_create_campaign({name, lead_ids:<final buyer-ready cohort from Phase 3>})\` \u2014 this creates AND seeds in one call. Seed the cohort you actually enriched buyers on, not the original raw pool. Confirm the returned campaign name + id in one line. (If I later ask to add more leads to it, that's \`leadbay_add_leads_to_campaign\`.)
22357
+
22358
+ # PHASE 5 \u2014 THE VIEW (call / email ready)
22359
+
22360
+ If you launched enrichment in Phase 3, **poll \`leadbay_bulk_enrich_status\` until it's actually done before rendering** \u2014 do not render a "still enriching" sheet with empty contact cells; the whole point is the landed phones/emails. Enrichment can take several minutes; keep polling.
22361
+
22362
+ Then call \`leadbay_campaign_call_sheet({campaign_id})\` and render it per its RENDERING block \u2014 one card per lead, contacts with \`[phone](tel:)\` + \`[email](mailto:)\` one-tap links, the readiness chip at the top, map optional. This is the view I work from: scan \u2192 tap to call \u2192 tap to email.
22363
+
22364
+ **Flag suspect contacts** so I don't email the wrong person blind: mark with \u26A0 any enriched contact whose email domain doesn't match the company's website, or who shows up on more than one lead in this campaign (a sign of a mis-attributed enrichment). Keep the phone (it's usually still right) but tell me the email looks off.
22365
+
22366
+ # PHASE 6 \u2014 HANDOFF + STOP
22367
+
22368
+ The campaign exists and is call/email ready. End by offering, via \`ask_user_input_v0\`:
22369
+
22370
+ - "Start working it now" \u2192 run \`leadbay_work_campaign\` on this campaign (the calling/email session + outcome-logging loop).
22371
+ - "See the pulse" \u2192 \`leadbay_campaign_progression\` for per-lead status.
22372
+
22373
+ Then STOP. Building a campaign is NOT outreaching \u2014 do not send anything and do not call \`leadbay_report_outreach\`. When I come back later to log calls, see previous statuses, and do follow-ups, that is \`leadbay_work_campaign\`, not this prompt.
22374
+
22375
+ # Iron laws
22376
+
22377
+ - Enrichment targets MY buyer persona \u2014 the people who would actually buy what *I* sell, derived from my product/ICP \u2014 NOT generic seniority. For a sales/prospecting tool that means the revenue org (sales / BD / growth / marketing leaders); a Director of Operations, COO, or logistics manager is useless no matter how senior. Get the persona right or the campaign is worthless.
22378
+ - Selection is DATA-DRIVEN (\`leadbay_recall_ordered_titles\` + \`leadbay_enrich_titles\` discovery) but FILTERED to the persona \u2014 never blindly repeat past-enriched or suggested titles that don't match who buys my product.
22379
+ - The FINAL cohort must be all buyer-ready: drop/swap any lead with no enrichable buyer-persona contact (Phase 3 Step B.5) rather than shipping it empty. A campaign where half the leads have no buyer to call is a failed campaign. Pick a generous pool in Phase 2 so swaps are possible.
22380
+ - NEVER launch paid enrichment without showing \`credits_remaining\` + \`enrichable_contacts\`, naming the persona, and getting a yes.
22381
+ - Qualify / pick BEFORE \`leadbay_create_campaign\` \u2014 never seed a campaign with unvetted leads.
22382
+ - Carry the captured \`lensId\` on every call. A lens shift loses the cohort.
22383
+ - End at the rendered call sheet, then hand off to \`leadbay_work_campaign\`. Do NOT re-implement the calling / follow-up loop here, and do NOT call \`leadbay_report_outreach\`.
22384
+ `;
21947
22385
  var leadbay_daily_check_in = `
21948
22386
  ## MEMORY
21949
22387
 
@@ -22968,6 +23406,7 @@ Optional: offer to review the \`leadbay_campaign_progression\` for the same camp
22968
23406
  - If the user dictates an outcome that doesn't cleanly map to one of the four epilogue values, ASK ONCE before guessing.
22969
23407
  `;
22970
23408
  var PROMPT_META = {
23409
+ leadbay_build_campaign: { "name": "leadbay_build_campaign", "short_description": 'Build a sales campaign from scratch in one guided flow: discover on the\nactive lens, qualify and pick a cohort, enrich the contacts most likely to\nengage, save it via `leadbay_create_campaign`, then show a one-tap call/email\nview via `leadbay_campaign_call_sheet`. Trigger on "build me a campaign",\n"set up a new campaign", "create a campaign from scratch". WORK an existing\ncampaign with `leadbay_work_campaign`; split leads across reps with\n`leadbay_setup_team_prospecting`.\n', "arguments": [{ "name": "audience", "description": "Optional: a fresh audience to target (e.g. 'dental clinics in Texas'). Omit to build from your ACTIVE lens \u2014 the default.", "required": false }, { "name": "campaign_name", "description": "Optional: a name for the campaign. Omit and one is derived from the lens/audience + date (or the backend AI-names it).", "required": false }], "expected_calls": ["leadbay_account_status", "leadbay_pull_leads", "leadbay_bulk_qualify_leads", "leadbay_qualify_status", "leadbay_recall_ordered_titles", "leadbay_enrich_titles", "leadbay_bulk_enrich_status", "leadbay_create_campaign", "leadbay_add_leads_to_campaign", "leadbay_campaign_call_sheet", "leadbay_campaign_progression", "leadbay_new_lens", "leadbay_adjust_audience"], "failure_modes": ["Enriches by seniority instead of by buyer persona \u2014 picks COO / Director of Operations / Mgr of Logistics / CFO / CTO because they sound senior, when the user sells a SALES tool whose buyer is the revenue org (VP/Head/Director of Sales, BD, growth, marketing). Operations people never buy a sales tool; this hands the salesperson a useless list.", "Fails to derive the user's buyer persona from their product/ICP before choosing titles \u2014 jumps straight to titles without working out who buys what THIS user sells, then defaults to generic exec titles.", "Blindly repeats leadbay_recall_ordered_titles / discovery suggestions even when they are off-persona (e.g. operations roles a prior session wrongly enriched) \u2014 recall is a filtered input, not the answer.", "Poor coverage \u2014 leaves many picked leads with no persona-matching contact (or 0 enrichments on some leads) and doesn't flag it, so the salesperson opens the campaign to half-empty rows.", "Launches paid enrichment without first showing credits_remaining + enrichable_contacts, naming the persona, and getting a yes \u2014 the spend-visibility gate is mandatory.", "Creates the campaign before qualifying / picking \u2014 seeds a campaign with unvetted leads. Qualify and let the user pick (or AI-recommend) FIRST, then leadbay_create_campaign.", "Ends at 'campaign created' without rendering the leadbay_campaign_call_sheet view \u2014 the ready-to-work view IS the deliverable; stopping short is purpose drift.", "Re-implements the calling / outcome / follow-up loop instead of handing off to leadbay_work_campaign \u2014 that prompt already owns it. This prompt BUILDS; work_campaign WORKS.", "Auto-sends outreach or calls leadbay_report_outreach \u2014 building a campaign is not outreaching. No send, no log; the user works it afterward.", "Re-pulls leadbay_pull_leads without the captured lensId \u2014 a mid-session lens shift discards the picked cohort.", "Silently overwrites the user's active lens when forking to a fresh audience \u2014 confirm once before switching lenses.", "Renders the picked leads or the call sheet as prose instead of the canonical per-tool RENDERING layout."] },
22971
23410
  leadbay_daily_check_in: { "name": "leadbay_daily_check_in", "short_description": 'Morning DISCOVERY workflow \u2014 new leads from the lens wishlist. Trigger\non "show me leads", "what\'s new today", "let\'s prospect", "run my check-in",\n"my morning check-in", "I do this every day", "every morning". Recurrence\nlanguage always means this prompt. Do NOT trigger on follow-up phrasings\n("follow up", "before my trip") \u2014 those go to `leadbay_followup_check_in`.\n', "arguments": [], "expected_calls": ["leadbay_account_status", "leadbay_pull_leads", "leadbay_research_lead_by_id", "leadbay_bulk_qualify_leads", "leadbay_enrich_contacts"], "failure_modes": ["Calls leadbay_report_outreach without explicit user authorization", "Surfaces fewer than 10 leads when more are available, or fails to top up via leadbay_qualify_top_n when the batch is short", `Replaces the canonical pull_leads table layout with prose per row (the per-tool RENDERING block is the structural contract; "Today's nudges" goes above it, not in place of it)`, "Skips the nudge paragraph entirely \u2014 the table alone is fine but adding the nudge is the value-add", `Skips deep research on promising leads (Phase 4) \u2014 the agent must call leadbay_research_lead_by_id on each when the user's intent is to research specific leads; Phase 4 is intentionally skipped for batch-view requests ("show me today's leads", "run my morning check-in") per the Phase 4 skip gate`, "Triggers contact enrichment without asking the user first (it consumes quota)", "Skips the STOP byproduct and proposes next actions on its own", 'Fires 10 parallel leadbay_research_lead_by_id calls and treats "stream closed" errors as terminal \u2014 must serialize and retry singletons', "Re-pulls leadbay_pull_leads without passing the captured lensId, allowing a backend lens shift to discard the Phase 2 batch", 'Treats a "Request timed out" from leadbay_bulk_qualify_leads as terminal instead of retrying with wait_for_completion:false + qualify_status polling', 'Triggers on a follow-up query (e.g., "leads I should follow up with") that should have routed to `leadbay_followup_check_in` \u2014 the two entry points are different data sources (Discover wishlist vs Monitor view) per \xA71.6'] },
22972
23411
  leadbay_extend_my_lens: { "name": "leadbay_extend_my_lens", "short_description": "Add more leads to the current lens on demand \u2014 for users whose appetite\nexceeds the standard daily fill. The agent picks seeds silently from\nwhat's already on the lens, fires the extra refill, and surfaces the\nqueue confirmation. The user never reviews the seed list.\n", "arguments": [{ "name": "extra_count", "description": "How many extra leads to add. Optional. Omit to use the backend default.", "required": false }], "expected_calls": ["leadbay_account_status", "leadbay_seed_candidates", "leadbay_extend_lens", "leadbay_pull_leads"], "failure_modes": ["Surfaces the seed candidate list to the user instead of picking silently \u2014 the user asked for MORE LEADS, not a candidate review meeting", "Skips the seeded path and calls `leadbay_extend_lens` with no `seed_lead_ids`, losing the bias signal the recommender needs", "On 429, silently retries instead of surfacing the three options (smaller / wait / upgrade) via your host's choice widget (`ask_user_input_v0` or `AskUserQuestion`)", "Forgets to pre-check `LENS_EXTRA_REFILL` quota in `leadbay_account_status` and burns a wasted API call", "Skips the post-queue pull-leads suggestion, so the user doesn't see what just got added"] },
22973
23412
  leadbay_followup_check_in: { "name": "leadbay_followup_check_in", "short_description": 'Follow-up check-in: surface KNOWN leads from the Monitor view needing\nre-engagement. Trigger on "follow up", "already known leads", "what\'s\noverdue", "before my trip", "who should I re-engage". Do NOT trigger on\n"show me today\'s leads", "my morning check-in", "run my check-in",\n"I do this every day", "every morning" \u2014 those go to\n`leadbay_daily_check_in`.\n', "arguments": [], "expected_calls": ["leadbay_pull_followups", "leadbay_research_lead_by_id", "leadbay_prepare_outreach"], "failure_modes": ["Calls leadbay_pull_leads (the Discover entry point) instead of leadbay_pull_followups \u2014 these are different data sources; the Discover queue does NOT contain Monitor's known-but-cold pipeline", 'Iterates pages of leadbay_pull_leads filtering by engagement_count to "fake" a follow-up view (a real bug observed in 0.9.0 \u2014 the right move is to call pull_followups directly)', "Replaces the canonical pull_followups table layout with prose per row (the per-tool RENDERING block is the structural contract; commentary belongs above or below)", 'Skips the cross-mode pivot offer at the end ("Want to see NEW leads from your wishlist instead?" routes to leadbay_pull_leads)'] },
@@ -22989,6 +23428,7 @@ should I follow up on" to "I'll send via lemlist".
22989
23428
  };
22990
23429
  var PROMPT_CATALOG_HEADER = `This server exposes the following workflow prompts via \`prompts/list\` and \`prompts/get\`. Some MCP clients render them as slash commands; if your client does not, you (the agent) should invoke them directly via \`prompts/get\` when the user's request matches one of the triggers described below.`;
22991
23430
  var PROMPT_CATALOG_BULLETS = {
23431
+ leadbay_build_campaign: `- \`leadbay_build_campaign\` (optional args: audience, campaign_name): Build a sales campaign from scratch in one guided flow: discover on the active lens, qualify and pick a cohort, enrich the contacts most likely to engage, save it via \`leadbay_create_campaign\`, then show a one-tap call/email view via \`leadbay_campaign_call_sheet\`. Trigger on "build me a campaign", "set up a new campaign", "create a campaign from scratch". WORK an existing campaign with \`leadbay_work_campaign\`; split leads across reps with \`leadbay_setup_team_prospecting\`.`,
22992
23432
  leadbay_daily_check_in: `- \`leadbay_daily_check_in\`: Morning DISCOVERY workflow \u2014 new leads from the lens wishlist. Trigger on "show me leads", "what's new today", "let's prospect", "run my check-in", "my morning check-in", "I do this every day", "every morning". Recurrence language always means this prompt. Do NOT trigger on follow-up phrasings ("follow up", "before my trip") \u2014 those go to \`leadbay_followup_check_in\`.`,
22993
23433
  leadbay_extend_my_lens: `- \`leadbay_extend_my_lens\` (optional args: extra_count): Add more leads to the current lens on demand \u2014 for users whose appetite exceeds the standard daily fill. The agent picks seeds silently from what's already on the lens, fires the extra refill, and surfaces the queue confirmation. The user never reviews the seed list.`,
22994
23434
  leadbay_followup_check_in: `- \`leadbay_followup_check_in\`: Follow-up check-in: surface KNOWN leads from the Monitor view needing re-engagement. Trigger on "follow up", "already known leads", "what's overdue", "before my trip", "who should I re-engage". Do NOT trigger on "show me today's leads", "my morning check-in", "run my check-in", "I do this every day", "every morning" \u2014 those go to \`leadbay_daily_check_in\`.`,
@@ -23136,6 +23576,30 @@ var CATALOG = [
23136
23576
  )
23137
23577
  ]
23138
23578
  },
23579
+ {
23580
+ name: "leadbay_build_campaign",
23581
+ description: PROMPT_META.leadbay_build_campaign.short_description,
23582
+ arguments: [
23583
+ {
23584
+ name: "audience",
23585
+ description: "Optional: a fresh audience to target (e.g. 'dental clinics in Texas'). Omit to build from your ACTIVE lens \u2014 the default.",
23586
+ required: false
23587
+ },
23588
+ {
23589
+ name: "campaign_name",
23590
+ description: "Optional: a name for the campaign. Omit and one is derived from the lens/audience + date (or the backend AI-names it).",
23591
+ required: false
23592
+ }
23593
+ ],
23594
+ render: (args) => [
23595
+ userMessage(
23596
+ substitutePlaceholders(leadbay_build_campaign, {
23597
+ audience_block: args.audience ? `Target audience: **${args.audience}** \u2014 if my active lens doesn't already cover it, set it up first (confirm before switching lenses).` : "Use my active Leadbay lens as the audience.",
23598
+ campaign_name_paren: args.campaign_name ? ` named **${args.campaign_name}**` : ""
23599
+ })
23600
+ )
23601
+ ]
23602
+ },
23139
23603
  {
23140
23604
  name: "leadbay_setup_team_prospecting",
23141
23605
  description: PROMPT_META.leadbay_setup_team_prospecting.short_description,
@@ -26184,7 +26648,7 @@ var OAUTH_BASE_URLS = {
26184
26648
  fr: "https://staging.api.leadbay.app"
26185
26649
  }
26186
26650
  };
26187
- var VERSION = "0.21.3";
26651
+ var VERSION = "0.23.0";
26188
26652
  var HELP = `
26189
26653
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
26190
26654