@leadbay/mcp 0.27.0 → 0.29.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.
@@ -435,6 +435,606 @@ Render this acknowledgment VERBATIM as the last line of your message:
435
435
  STOP \u2014 awaiting user decision. I will not take any further action until you tell me what to do next.
436
436
  \`\`\`
437
437
 
438
+ Do not propose a next action. Do not call any more tools. Hand control back to the user.
439
+ `;
440
+ var leadbay_getting_started = `
441
+ ## MEMORY
442
+
443
+ 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.
444
+
445
+
446
+ Walk me through Leadbay. Treat these the same way: "I'm new here", "how do I
447
+ use this?", "getting started", "show me how Leadbay works", "give me a tour",
448
+ "I just installed this".
449
+
450
+ This is a GUIDED WALKTHROUGH, not an explainer. The user learns by clicking,
451
+ and every click runs a real Leadbay call against their own account. By the end
452
+ they will have actually checked their account, pulled leads, had a first email
453
+ drafted to the best of them, and revealed the person to send it to.
454
+
455
+ If the user wants orientation PROSE without doing anything \u2014 "explain how
456
+ Leadbay works", "what's the difference between discovery and follow-up" \u2014
457
+ this is the wrong prompt. Use \`leadbay_prospecting_overview\` instead.
458
+
459
+ If their problem is **setup** rather than usage \u2014 the connector isn't installed
460
+ yet, they can't sign in, their Leadbay tools aren't appearing, or they're asking
461
+ how to run this on another host \u2014 this walkthrough cannot help them. It assumes
462
+ a working connection, and GATE 1 is what proves it. Point them at the setup
463
+ guide instead of guessing at install steps:
464
+ <https://docs.leadbay.app/doc/leadbay-mcp/quickstart>
465
+
466
+ 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.
467
+
468
+ 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.
469
+
470
+
471
+ # Resilience rules for Leadbay long-running tools
472
+
473
+ 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.**
474
+
475
+ ## Rule 1 \u2014 Pin the lens
476
+
477
+ 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.
478
+
479
+ ## Rule 2 \u2014 Prefer async for bulk operations
480
+
481
+ \`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.
482
+
483
+ ## Rule 3 \u2014 Serialize \`leadbay_research_lead_by_id\` fan-out
484
+
485
+ \`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.
486
+
487
+ ## Rule 4 \u2014 Retry, don't replan
488
+
489
+ 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:
490
+
491
+ 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.
492
+ 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.
493
+ 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.
494
+
495
+ 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.
496
+
497
+
498
+ # THE ONE-FORWARD-OPTION RULE \u2014 the structural contract of this walkthrough
499
+
500
+ Every gate presents **exactly ONE way forward, plus a way out**. Two options,
501
+ never more:
502
+
503
+ 1. **The action** \u2014 the single next step of the tour.
504
+ 2. **The exit** \u2014 \`I'm done for now\`, which ends the walkthrough politely.
505
+
506
+ This is deliberate. A first-run user does not yet know enough to choose between
507
+ *paths* \u2014 a menu of alternatives makes them stall. One forward move makes the
508
+ next step obvious, and the click is what teaches them the tool. The exit exists
509
+ so the tour is never a trap, and because your host's choice widget requires 2\u20134
510
+ options: a lone option is rejected or silently degrades to prose, which kills
511
+ the whole feature.
512
+
513
+ **Never add a third option**, and never turn the exit into an alternative route
514
+ ("show me my lenses instead") \u2014 that reintroduces the choice this rule exists
515
+ to remove.
516
+
517
+ **The gate IS the widget.** Call your host's choice widget with these two
518
+ options. **Never render a gate as a prose question** \u2014 "say the word and I'll
519
+ check it" is a defect, not a gate: the user gets no button and the walkthrough
520
+ becomes a conversation they have to drive themselves.
521
+
522
+ **EVERY GATE IS TWO BEATS \u2014 EXPLAIN, THEN ASK.** This is a tutorial, so the
523
+ user must understand what they're about to do *before* they click:
524
+
525
+ 1. **Explain** \u2014 one or two plain sentences saying what this step does and why
526
+ it matters. Never jargon. This is the teaching half; skipping it turns the
527
+ walkthrough into a series of unexplained buttons.
528
+ 2. **Ask** \u2014 fire the widget. **Then STOP and wait for the click.**
529
+
530
+ **NEVER run a step's tool without firing its widget first and receiving the
531
+ user's click.** Calling \`leadbay_pull_leads\` because the walkthrough "obviously
532
+ goes there next" defeats the entire feature \u2014 the click IS the lesson. The one
533
+ exception is when the user's own message already told you to do it (e.g. "walk
534
+ me through it and just run everything"); then follow what they asked.
535
+
536
+ **Each gate ships its own widget payload \u2014 use it, don't rewrite it.** Every
537
+ step in the manifest carries \`explain\` (what to say) and \`next_steps\`
538
+ (\`{question, options[]}\`, already the widget's shape). Map \`next_steps\` into
539
+ your host's widget VERBATIM \u2014 same question, same two options, same labels and
540
+ descriptions. Do not reword them, do not merge two gates into one widget, and
541
+ do not add a third option.
542
+
543
+ Typing works as an escape hatch too. If the user types
544
+ something off-script ("actually just show me my lenses"), abandon the
545
+ walkthrough and serve what they asked. Never re-fire a gate the user has
546
+ already declined in prose.
547
+
548
+ **ALWAYS render NEXT STEPS via your host's next-step widget.** Use whichever is in your tool set \u2014 the NAME and SCHEMA differ: **\`ask_user_input_v0\`** (Claude chat / ChatGPT) takes plain-string options with \`type:"single_select"\`; **\`AskUserQuestion\`** (Claude cowork / Claude Code) takes object options \`{label, description}\` plus a required short \`header\` (\u226412 chars) and \`multiSelect\`, NO \`type\` field, and never add an "Other" option (the host adds it). Match the schema to the tool you actually have \u2014 the wrong schema fails silently and you fall back to prose. Prose bullets are the fallback ONLY when NEITHER widget exists. Any turn that would end with a choice must be the widget \u2014 the widget IS the question.
549
+
550
+ **If the tool result carries a \`next_steps\` object, that is the source of truth \u2014 use it directly.** Each option has a short \`.label\` (\u22645 words) and a full \`.description\`. Map \`next_steps.options[]\` into your host widget VERBATIM and in order: for \`AskUserQuestion\` (cowork / Claude Code) pass each as \`{label, description}\`; for \`ask_user_input_v0\` (Claude chat / ChatGPT, string options only) pass each option's \`.description\` as the string (it's the full sentence). Do NOT reword, reorder, drop, or prose-ify them \u2014 they're built deterministically by the server so the offer (incl. the artifact option at position 0) fires every time. Fall back to the table below only when there is NO \`next_steps\` field.
551
+
552
+ **One exception \u2014 skip the widget** when the user's original message contained a complete sequential instruction chain ("show me X and then do Y") AND all stated steps have been completed. In that case, end with STOP directly \u2014 the user stated their full plan and does not need a "what next?" prompt.
553
+ - Skip example: "Show me today's leads and then research the top one for me." \u2192 after research completes, emit STOP without the widget.
554
+ - Do NOT skip for: plain requests ("show me today's leads", "run my check-in"), recurring-language requests ("I do this every day"), or requests where only one action was stated.
555
+
556
+ Pick 2\u20134 rows from the (Observation, Suggest, Calls) table below most relevant to the response, then call your host's widget with ITS schema (per the schema rules above \u2014 wrong schema fails silently):
557
+ - \`ask_user_input_v0\`: \`{questions:[{question,type:"single_select",options:["<Suggest 1>","<Suggest 2>"]}]}\`
558
+ - \`AskUserQuestion\`: \`{questions:[{question,header:"Next step",multiSelect:false,options:[{label:"<\u22645 words>",description:"<Suggest 1>"}]}]}\`
559
+
560
+ User picks \u2192 call the matching \`Calls\` tool. Constraints: 2\u20134 mutually-exclusive options, AskUserQuestion labels \u22645 words (full text in \`description\`), max 3 questions. Table stays internal; never recite it.
561
+
562
+ ---
563
+
564
+
565
+
566
+ # THE OPENING \u2014 SHORT, THEN STRAIGHT INTO GATE 1
567
+
568
+ **A short paragraph, then the widget** \u2014 3\u20134 sentences, all in your FIRST
569
+ message. In the user's own language, no jargon, cover:
570
+
571
+ 1. **What Leadbay is** \u2014 it brings you a fresh batch of companies worth
572
+ selling to every day, rather than you going hunting for them.
573
+ 2. **How it knows what to send** \u2014 you describe who you sell to (that
574
+ description is your **lens**), and it goes and finds companies matching it,
575
+ getting sharper as you engage with what it sends.
576
+ 3. **What this walkthrough will do** \u2014 four quick steps, each a real action on
577
+ their own account, ending with leads in hand, a first email already written,
578
+ and the person to send it to.
579
+ 4. **One line handing off to the first step** \u2014 e.g. "First, let's see which
580
+ account you're on."
581
+
582
+ Then **fire GATE 1's widget immediately, in the same message**, and stop.
583
+
584
+ Keep it to a paragraph. Do NOT walk through the four steps one at a time here
585
+ \u2014 each gate explains itself when its turn arrives, and turning the opening
586
+ into a syllabus buries the first button under text nobody reads.
587
+
588
+ Call no tool in the opening. The widget is the whole ask.
589
+
590
+ # GATE 1 \u2014 "Check my account"
591
+
592
+ The opening paragraph above IS this gate's explanation \u2014 don't add another one
593
+ on top of it. Just hand off in a line and fire the widget.
594
+
595
+ **Why it's useful**, if you say anything at all: this is where they can see at
596
+ a glance how much they've used this week and what's left \u2014 so a batch that
597
+ comes back small later has a visible reason rather than feeling broken.
598
+
599
+ **Fire the widget** \u2014 question \`Let's start with your account status.\`, first option labelled \`Check my account\`, description \`Check my Leadbay account status.\` Second option: \`I'm done for now\` / \`Stop the walkthrough here.\` **Wait for the click.**
600
+
601
+ On click: call \`leadbay_account_status\` (it takes no arguments).
602
+
603
+ **Show them their actual account \u2014 this is the payoff of the click.** Lead with
604
+ one line on who they're signed in as and their organization, then render their
605
+ **quota windows in full**, exactly as the web app shows them: Daily / Weekly /
606
+ Monthly, each with a \`\u25B0\u25B1\` gauge, % used, $ spent against the cap, and when it
607
+ resets \u2014 plus the per-resource breakdown underneath. A one-line "you're
608
+ connected as X" is an under-delivery: they clicked a button labelled *check my
609
+ account status*, so show them the status.
610
+
611
+ ## RENDERING \u2014 quota windows (percentage + $, like the frontend)
612
+
613
+ Mirror the Leadbay web quota widget: three windows side by side \u2014 **Daily**,
614
+ **Weekly**, **Monthly** \u2014 each headlined by a **% used** gauge and a **$ spend /
615
+ $ cap** figure, with a per-resource usage breakdown underneath. **Never speak in
616
+ raw "credits"** for quota \u2014 the unit is a percentage and a dollar spend.
617
+
618
+ **Include the quota whenever it is readable** \u2014 as part of the default account
619
+ answer, even when the user only asked "what account am I connected to?". The
620
+ sole reason to omit it is the silence gate below (unreadable quota, or an
621
+ unlimited account); it is NOT gated on the user explicitly asking for quota.
622
+
623
+ **Silence gate (check FIRST).** Render NOTHING about quota when any of these
624
+ holds \u2014 do not mention quota at all, do not say "unreadable", never tell the user
625
+ to reconnect:
626
+ - \`quota\` is null, OR \`quota_error\` is set (a 401/403 backend quirk for plan-less
627
+ orgs \u2014 the same token read user/org fine), OR
628
+ - \`organization.unlimited_credits\` is true (internal/unlimited account \u2014 stay
629
+ silent on quota; never announce "unlimited").
630
+
631
+ **Pick the group (for DISPLAY only).** Prefer \`quota.user\` (present for every
632
+ caller). Use \`quota.org\` only when \`quota.user\` is absent (admins receive both \u2014
633
+ still show the caller's own \`user\` view). Call the chosen group \`<group>\` below.
634
+
635
+ **Exception \u2014 lens-refill pre-checks read the refill row, ORG-first.** This
636
+ user-preference is for the display gauge ONLY. When you pre-check the
637
+ \`LENS_EXTRA_REFILL\` resource before \`leadbay_extend_lens\`, look for the row in
638
+ **\`quota.org.resources[]\` first** (admins get the org group, and the refill
639
+ quota is org-scoped there); when \`quota.org\` is absent \u2014 non-admin callers only
640
+ receive the \`user\` group \u2014 fall back to **\`quota.user.resources[]\`**. Match the
641
+ resource type case-insensitively (\`LENS_EXTRA_REFILL\` / \`lens_extra_refill\`).
642
+ Skipping the \`user\` fallback for non-admins would make the row invisible even
643
+ when the quota data exists, so the agent burns the write and hits the very 429
644
+ this pre-check exists to avoid.
645
+
646
+ **Per window (fixed order: daily \u2192 weekly \u2192 monthly).** Match entries by
647
+ \`window_type\` (\`"daily"\` / \`"weekly"\` / \`"monthly"\`).
648
+
649
+ **Headline \u2014 when \`<group>.spend[]\` has an entry for the window (the % gauge):**
650
+ - \`pct = round(current_units / max_units \xD7 100)\` (both are dollar_cents).
651
+ - \`$used = (current_units / 100).toFixed(2)\`, \`$cap = (max_units / 100).toFixed(2)\`.
652
+ - 10-segment bar in a SINGLE inline-code span (backticks give it contrast):
653
+ \`filled = round(pct / 10)\` clamped 0..10; \`bar = "\u25B0"\xD7filled + "\u25B1"\xD7(10 \u2212 filled)\`.
654
+ Use ONLY \`\u25B0\`/\`\u25B1\` \u2014 do NOT use the \`\u2756\` glyph (that identity belongs to lead
655
+ discovery, not quota).
656
+ - Line: **\`<Window>\`** \`\` \`\u25B0\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\` \`\` \`<pct>% used \xB7 $<used> / $<cap> \xB7 resets <resets_at, relative>\`.
657
+ e.g. \`**Daily** \` + \`\` \`\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\` \`\` + \` 7% used \xB7 $0.84 / $12.00 \xB7 resets in ~7 h\`.
658
+
659
+ **Fallback \u2014 when \`<group>.spend[]\` is empty** (internal / free orgs have no
660
+ OVERALL_SPEND quota): no gauge. Render the per-window resource breakdown as a
661
+ compact table instead \u2014 one row per resource in \`<group>.resources[]\` for that
662
+ window: the friendly label + \`count\` (append \`/ <max_units>\` only when
663
+ \`max_units\` is a number). This is the pre-existing behavior, preserved.
664
+
665
+ **Resource labels (look up case-insensitively \u2014 lower-case \`resource_type\`
666
+ first).** Localize to \`user.language\` (FR canonical shown; English in parens):
667
+ - \`llm_completion\` \u2192 **G\xE9n\xE9rations par IA** (AI generations)
668
+ - \`ai_rescore\` \u2192 **Leads qualifi\xE9s** (qualified leads)
669
+ - \`web_fetch\` \u2192 **Informations web** (web insights)
670
+ - \`contact_enrichment_phone\` \u2192 **T\xE9l\xE9phones enrichis** (phones enriched)
671
+ - \`contact_enrichment_email\` \u2192 **E-mails enrichis** (emails enriched)
672
+
673
+ Skip any resource type not in this map silently \u2014 never dump the raw
674
+ \`resource_type\` string at the user.
675
+
676
+ **\`resets_at\`.** Show as a relative countdown ("resets in ~7 h", "resets in 3
677
+ days"), computed against now \u2014 mirroring the widget's "r\xE9initialis\xE9 dans X". The
678
+ raw value is an ISO-8601 timestamp.
679
+
680
+ **Top-up (optional, subordinate).** When \`quota.topup\` is present, you MAY add one
681
+ small line below the windows: \`Top-up: $<remaining_cents/100> of $<total_credit_cents/100> left\`.
682
+ Keep it secondary \u2014 the three window gauges are the headline. Omit when null.
683
+
684
+ **Legend** (once, below): \`\` \`\u25B0\` used \xB7 \`\u25B1\` remaining \`\`.
685
+
686
+
687
+ **Then explain what they're looking at \u2014 one or two plain lines, no jargon.**
688
+ A first-run user has never seen these numbers and won't know whether they're
689
+ good, bad, or something to worry about. Say, in your own words:
690
+
691
+ - **What it counts** \u2014 the AI work Leadbay does on their behalf: researching
692
+ companies on the web and qualifying leads against their criteria. Not
693
+ "credits", and not something they spend by clicking around.
694
+ - **Why it matters to them** \u2014 it paces how many fresh leads arrive. Heavy use
695
+ now means Leadbay queues up a bigger batch for next time; and if a batch ever
696
+ comes back smaller than expected, this is where they'd see why. Each window
697
+ refills on its own at the reset time already shown.
698
+
699
+ Keep it to a sentence or two, in their language. Do NOT lecture, do NOT explain
700
+ every resource row one by one, and do NOT turn this into a pricing pitch \u2014 if a
701
+ window is genuinely exhausted the tool's own guidance covers wait-vs-top-up.
702
+
703
+ **When the silence gate above applies, skip this explanation too** \u2014 there is
704
+ nothing on screen to explain, and describing an absent gauge just confuses.
705
+
706
+ **Two things this gate must NOT do** (both are pinned regressions):
707
+
708
+ - **Say nothing about quota when the silence gate above applies** \u2014 \`quota\` is
709
+ null, \`quota_error\` is set, or the org has \`unlimited_credits\`. A brand-new
710
+ org often has no billing plan yet, so the quota read fails. That is NOT an
711
+ error worth showing: do not mention quota, do not mention a 401, and above
712
+ all do NOT tell the user to log in again or reconnect \u2014 their token is fine,
713
+ the very same response just read their account. In that case fall back to the
714
+ short user + org line and move on to GATE 2 without comment.
715
+ - **Do not volunteer the lens.** The response deliberately withholds the lens
716
+ unless the user asked about it, so there is nothing to report. Don't reach
717
+ for another tool to find it either. The lens shows up naturally at GATE 2.
718
+
719
+ # GATE 2 \u2014 "Pull today's leads"
720
+
721
+ **Explain first \u2014 this is where you teach the LENS.** Leadbay keeps a *lens*:
722
+ their description of who they sell to. Every day it goes and finds fresh
723
+ companies matching it. This click pulls today's batch.
724
+
725
+ **Why it's useful:** it replaces the hour spent digging through directories and
726
+ LinkedIn looking for someone worth calling \u2014 the list is already waiting, and
727
+ already scored, when they sit down. And it sharpens itself: the leads they
728
+ like, contact or skip teach the lens what a good fit looks like, so tomorrow's
729
+ batch lands closer than today's.
730
+
731
+ **Then fire the widget** \u2014 question \`Now let's see today's leads. Ready?\`, first option labelled \`Pull today's leads\`, description \`Pull today's leads from your lens.\` Second option: \`I'm done for now\` / \`Stop the walkthrough here.\` **Wait for the click.**
732
+
733
+ On click: call \`leadbay_pull_leads\` with **no arguments** (it resolves the
734
+ user's default lens itself).
735
+
736
+ Capture \`lens.id\` from the response and pass it as an explicit \`lensId\` on
737
+ every later call in this walkthrough, so gate 4 enriches the same lens the
738
+ user just looked at. Pin the TOP-SCORING lead's id and name too \u2014 gate 3 drafts
739
+ to it, and gate 4 reveals its contact.
740
+
741
+ Render the batch with the canonical layout:
742
+
743
+ ## RENDERING \u2014 markdown table, three columns, score-bar driven
744
+
745
+ Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
746
+
747
+ ## Score-bar (10-segment, inline-code wrapped)
748
+
749
+ 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.
750
+
751
+ Glyphs (use these exact characters; do not substitute):
752
+
753
+ - \`\u25B0\` \u2014 firmographic-only fill
754
+ - \`\u2756\` \u2014 AI-booster cap (placed at the RIGHT END of the filled run, never the front)
755
+ - \`\u25B1\` \u2014 empty
756
+
757
+ Computation:
758
+
759
+ \`\`\`
760
+ total_filled = round(score / 10), clamped to 0..10
761
+ ai_segments = round(qualification_summary.avg_qualification_boost / 3.3),
762
+ clamped to [0, total_filled]
763
+ normal_filled = total_filled \u2212 ai_segments
764
+ bar = "\u25B0" \xD7 normal_filled
765
+ + "\u2756" \xD7 ai_segments
766
+ + "\u25B1" \xD7 (10 \u2212 total_filled)
767
+ \`\`\`
768
+
769
+ 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.
770
+
771
+
772
+ **Column 1 \u2014 Company**
773
+
774
+ - Line 1: the 10-segment score bar in inline-code backticks (see the score-bar snippet above for the algorithm).
775
+ - Insert \`<br>\` between lines.
776
+ - Line 2: linked company name + \` \xB7 \` + short location + \` \xB7 \` + compact size.
777
+ - Link target: \`website\` (prefix \`https://\` if it's a bare hostname). Don't synthesize an app deep-link.
778
+ - Location: shorten "City of New York" \u2192 "NYC"; otherwise "City ST"; state alone only when city missing.
779
+ - Size: \`"Xk+"\` when \`size.min >= 1000\`, \`"min\u2013max"\` otherwise.
780
+
781
+ **Column 2 \u2014 Why it fits**
782
+
783
+ - One sentence, \u2264 20 words.
784
+ - 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\`.
785
+ - Do NOT append \`(boost N)\` \u2014 the \u2756 cap in column 1 already carries that signal.
786
+ - No bullet lists, no line breaks inside the cell.
787
+
788
+ **Column 3 \u2014 Contact**
789
+
790
+ \`[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.
791
+
792
+ **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"\`.
793
+
794
+ ## Linking a contact's name
795
+
796
+ **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.**
797
+
798
+ URL priority (first applicable wins):
799
+
800
+ 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).
801
+ 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.
802
+
803
+ 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.
804
+
805
+ ## Linking the company
806
+
807
+ 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.)
808
+
809
+ 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.
810
+
811
+ \`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).
812
+
813
+
814
+
815
+ ## Branch \u2014 the batch came back empty
816
+
817
+ A brand-new account often reads empty for the first minute while the backend
818
+ computes the lens wishlist. Check \`computing_wishlist\` / \`computing_scores\`:
819
+
820
+ - **Either is true** \u2192 the lens is still building. Say exactly that, in the
821
+ user's terms: "your lens is still building your first batch \u2014 that's normal
822
+ on a new account, it takes about a minute." The tool's \`next_steps\` payload
823
+ carries a **two-option** warm-up widget ("Re-pull in ~30s" / "Refine
824
+ audience") \u2014 render it VERBATIM. This is the ONE place a gate carries two
825
+ options, because the server built the payload and a re-pull genuinely has a
826
+ real alternative. On "Re-pull in ~30s", wait ~30s and return to GATE 2.
827
+ **NEVER say "no leads found."**
828
+ - **Both false** \u2192 the lens is genuinely empty or too narrow, and \`next_steps\`
829
+ is \`null\`. Say so honestly, offer to widen the audience, and end the
830
+ walkthrough here. There is nothing to enrich.
831
+
832
+ # GATE 3 \u2014 "Draft the first email"
833
+
834
+ **Explain first \u2014 and name the company.** Take the TOP-SCORING lead from
835
+ GATE 2 and say its name out loud, so this is an offer about a real company
836
+ rather than an abstraction. Leadbay already worked out *why* that company fits
837
+ them, so it can write the first email instead of leaving them at a blank page.
838
+
839
+ **Why it's useful:** finding companies was never the hard part. Writing the
840
+ twentieth opener of the day is where prospecting actually dies. This turns a
841
+ row in a table into something they could send in a minute.
842
+
843
+ Say plainly that this only **drafts** \u2014 nothing is sent, and they see it first.
844
+
845
+ **Then fire the widget** \u2014 question \`Want me to draft the first email to your top lead?\`, first option labelled \`Draft the first email\`, description \`Write a first email to the best company in today's batch. Nothing is sent.\` Second option: \`I'm done for now\` / \`Stop the walkthrough here.\` **Wait for the click.**
846
+
847
+ On click: call \`leadbay_prepare_outreach\` with \`leadId\` = the top lead's id,
848
+ **and nothing else**.
849
+
850
+ **This gate spends NOTHING. Never pass \`enrich: true\`** \u2014 that launches a paid
851
+ contact reveal off the back of a *draft* click. They agreed to see an email
852
+ written, not to spend. GATE 4 is where the reveal gets asked for, on its own
853
+ terms.
854
+
855
+ \`recommended_contact\` comes back in its post-enrichment shape with \`email\` and
856
+ \`phone\` still **null**. That is expected, not a failure \u2014 and it's exactly the
857
+ hook for the next gate: an email written, and nobody to send it to yet. Don't
858
+ apologise for it, and don't reach for another tool to fill it in.
859
+
860
+ **Render the draft through \`message_compose_v1\`** \u2014 \`kind: "email"\`, a
861
+ \`summary_title\` naming the company, and 2\u20133 variants whose labels name the
862
+ **strategy** ("Lead with the growth signal", "Ask about their current setup"),
863
+ never the tone. Do NOT also paste the body into chat prose; the composer *is*
864
+ the answer. If the host exposes no composer, fall back to the canonical
865
+ prepare-outreach layout: one context line, then subject + body as a quoted
866
+ block.
867
+
868
+ **Address it to the job TITLE** \u2014 "the Head of Operations at <Company>". You do
869
+ not have a name yet, and inventing one is fabrication.
870
+
871
+ Add one line on *why this company was the pick* \u2014 its score and the fit reason
872
+ from the lead's summary \u2014 so the draft reads as reasoned rather than generated.
873
+
874
+ # GATE 4 \u2014 "Find who to email"
875
+
876
+ **Explain first \u2014 point at the gap the draft just opened.** They have an email
877
+ ready and nobody to send it to: it's addressed to a job title, not a person.
878
+ That's what this step fixes. Leadbay can find *which roles* exist at that
879
+ company, then reveal the actual human and how to reach them.
880
+
881
+ **Why it's useful:** they ask for the operations director by name instead of
882
+ pitching whoever answers the switchboard \u2014 the difference between a
883
+ conversation and a dead end.
884
+
885
+ Say plainly that the first look is **free**, and that revealing the contact
886
+ costs credits and needs their say-so.
887
+
888
+ **First, check \`leadbay_enrich_titles\` is in your tool set.** On a read-only
889
+ deployment it is not registered, and a gate whose tool cannot run is a dead
890
+ end. If it's missing: don't fire this widget, say plainly that revealing
891
+ contacts isn't enabled on this connection, note the draft is still theirs, and
892
+ go straight to the closing. Ending one step early beats offering a button that
893
+ does nothing.
894
+
895
+ **Then fire the widget** \u2014 question \`Want to find out who to send that email to?\`, first option labelled \`Find who to email\`, description \`See the roles at that company. Free \u2014 no contact details revealed yet.\` Second option: \`I'm done for now\` / \`Stop the walkthrough here.\` **Wait for the click.**
896
+
897
+ This gate runs in **TWO BEATS**. Do not collapse them.
898
+
899
+ ## BEAT 1 \u2014 the free look (spends nothing)
900
+
901
+ On click: call \`leadbay_enrich_titles\` with \`leadIds\` = **the one lead you
902
+ drafted for at GATE 3** and \`lensId\` = the pinned lens id.
903
+
904
+ **This call must spend NOTHING.** Omit \`titles\` entirely: that returns
905
+ \`mode:"discover"\`, the free preview of which job titles exist at that company.
906
+ Do NOT pass \`titles\`, \`confirm=true\`, \`email=true\` or \`phone=true\` on this call
907
+ \u2014 any one of them launches the paid reveal before the user has chosen anything.
908
+
909
+ Present the discovered titles and say plainly: "nothing spent yet."
910
+
911
+ ## BEAT 2 \u2014 reveal the person the draft is for (spends credits)
912
+
913
+ Name the title the GATE 3 draft is addressed to, and tell them the cost
914
+ **before** they decide: one credit per contact revealed \u2014 here that's **one
915
+ contact, one credit**. Then ask them to confirm.
916
+
917
+ **Wait for an explicit confirmation.** Silence is not consent, and neither is
918
+ "they clicked the gate earlier" \u2014 the gate click bought the free look, not the
919
+ reveal.
920
+
921
+ Once confirmed, call \`leadbay_enrich_titles\` AGAIN with
922
+ \`leadIds: [<the drafted lead's id>]\` \u2014 **the array, always, even for one lead**
923
+ \u2014 plus the chosen \`titles\`, \`confirm: true\` and \`email: true\`. That's the real,
924
+ paid reveal.
925
+
926
+ \`leadIds\` is the only key this tool reads for scope. A singular \`leadId\` is not
927
+ a parameter: it is silently ignored, and the call then falls back to the
928
+ account's **default wishlist selection** while \`confirm\`/\`email\` are set \u2014 so
929
+ it would reveal and charge for the whole batch instead of the one lead the user
930
+ agreed to.
931
+
932
+ It returns a \`bulk_id\` and runs async \u2014 poll \`leadbay_bulk_enrich_status\`
933
+ with that id (\`include_contacts=true\`) until \`all_done\`, or until the resolved
934
+ count plateaus across a few spaced polls. Then report the contact that actually
935
+ resolved: name, title, and the email/phone that came back. Contacts sometimes
936
+ don't resolve; say so honestly rather than implying success.
937
+
938
+ **Then close the loop** \u2014 one line: one credit per contact revealed, so this
939
+ cost one. And say the thing that makes it land: the draft from GATE 3 now has a
940
+ real person and a real address to go to. This is the moment GATE 1's quota
941
+ numbers stop being abstract, because they just watched them move and got
942
+ something for it. Don't turn it into a pricing pitch.
943
+
944
+ If they decline the reveal, that's fine \u2014 keep the draft and the title, and
945
+ let it go without pushing \u2014 the tour is done either way.
946
+
947
+ # HOW THE TOUR ENDS \u2014 THREE ENDINGS, PICK THE RIGHT ONE
948
+
949
+ This is the ONLY place that says what to do when the walkthrough stops. There
950
+ is no other closing section: work out which of these three happened, then do
951
+ that one in full, in the order written.
952
+
953
+ **The buttons disappear when the walkthrough ends.** If it stops without
954
+ telling the user what to *type*, they learned to click through a tutorial and
955
+ nothing about using Leadbay tomorrow. That is what the cheat-sheet is for.
956
+
957
+ ## ENDING A \u2014 they finished all four gates
958
+
959
+ 1. Render the \`keep_going\` cheat-sheet (below).
960
+ 2. Then the setup-guide link (below).
961
+
962
+ ## ENDING B \u2014 they picked \`I'm done for now\`
963
+
964
+ **All three beats, in this order. The offer is the LAST thing you say.**
965
+
966
+ 1. One short line acknowledging the stop \u2014 "No problem, we'll leave it there."
967
+ 2. The \`keep_going\` cheat-sheet, then the setup-guide link (below).
968
+ 3. **The 1:1 offer \u2014 REQUIRED, and it goes last.** Ending B without it is
969
+ incomplete: they stopped right before the setup work a call actually helps
970
+ with, which makes this the one moment the offer is welcome rather than
971
+ pushy. Say, in your own words, one sentence and the link:
972
+
973
+ > If you want a hand tuning this to your own market, Zoe on our team runs 1:1
974
+ > sessions: <https://calendly.com/zoe-leadbay/demo-leadbay>
975
+
976
+ That length is the rule, not a suggestion \u2014 **one sentence**. Listing
977
+ everything Zoe could help with turns an offer into promotional copy, which
978
+ is exactly what a user who just said "I'm done" doesn't want.
979
+
980
+ Keep it to **one sentence and the link**. Never re-open the walkthrough,
981
+ never re-fire the gate they just declined, and never argue for finishing the
982
+ tour.
983
+
984
+ **On this path the offer is the last PROSE you write.** The STOP block below
985
+ still closes the message \u2014 it is a machine marker, not something the user
986
+ reads as content, so it does not displace the offer. What must never happen
987
+ is the offer being dropped or pushed above the cheat-sheet to make room.
988
+
989
+ ## ENDING C \u2014 they typed something off-script
990
+
991
+ Serve what they actually asked for. **No cheat-sheet, no setup link, no 1:1
992
+ offer** \u2014 they're already off doing what they wanted, and any of it on top of
993
+ their real question is exactly the interruption they were avoiding.
994
+
995
+ ## The cheat-sheet (endings A and B)
996
+
997
+ Render the manifest's \`keep_going\` rows as a compact two-column markdown table,
998
+ titled something like **"Next time, just ask"**. Keep the phrases VERBATIM \u2014
999
+ each one is taken from that tool's own trigger list, so it's a phrase that
1000
+ genuinely routes. Do not invent extra rows, and do not reword the phrases into
1001
+ something that sounds nicer but doesn't match.
1002
+
1003
+ | What you want | Just say |
1004
+ |---|---|
1005
+ | Today's fresh leads | "Show me today's leads" |
1006
+ | Who to follow up with | "What should I follow up on" |
1007
+ | The story on one company | "Research <Company>" |
1008
+ | An email to a contact | "Draft outreach for <Contact>" |
1009
+ | Change who you target | "Narrow the audience to <sector>" |
1010
+ | Switch target audience | "Show me my lenses" |
1011
+
1012
+ Add one closing line in your own words: they don't need to remember exact
1013
+ wording \u2014 plain language works, and this is just a starting point.
1014
+
1015
+ ## The setup guide (endings A and B)
1016
+
1017
+ One plain link, for the things the four gates didn't cover \u2014 installing Leadbay
1018
+ on another machine, adding a teammate, signing back in later:
1019
+ <https://docs.leadbay.app/doc/leadbay-mcp/quickstart>
1020
+
1021
+ **Once, here, and nowhere else.** Never drop that link between gates: a link
1022
+ mid-tour is an invitation to leave the thing they're in the middle of doing.
1023
+
1024
+ # STOP
1025
+
1026
+ IRON LAW \u2014 the walkthrough **drafts** an email at GATE 3 but never **sends**
1027
+ one. The draft stays in the chat for the user to read and judge; nothing
1028
+ leaves. Never send it, never offer to send it on their behalf, and never call
1029
+ \`leadbay_report_outreach\` \u2014 logging an outreach that never happened poisons the
1030
+ human team's pipeline.
1031
+
1032
+ Render this acknowledgment VERBATIM as the last line of your message:
1033
+
1034
+ \`\`\`
1035
+ STOP \u2014 awaiting user decision. I will not take any further action until you tell me what to do next.
1036
+ \`\`\`
1037
+
438
1038
  Do not propose a next action. Do not call any more tools. Hand control back to the user.
439
1039
  `;
440
1040
  var leadbay_import_file = `
@@ -1076,114 +1676,565 @@ Default to MODE A when uncertain. Always offer the cross-mode pivot at the end s
1076
1676
  - \`\u{1F48E}\` \u2014 name fuzzy-matches a \`hot: true\` entry in \`web_insights\` key_people. (Use \`\u{1F48E}\`, not \`\u{1F525}\`, to avoid glyph collision with the follow-up status badge.)
1077
1677
  Sort \`\u2605\` first, then \`\u{1F48E}\`-only rows, then API order. Link the name via \`linkedin_page\` first; fall back to LinkedIn people-search with \`<First>+<Last>+<Company>\`. Append \`\xB0\` only when the fallback is in use AND \`social_presence.linkedin == false\`. Cap to 6 rows; if \`contacts_count > shown\`, end with \`"+N more \u2014 ask to see the full list"\`.
1078
1678
 
1079
- ### MODE A body (Discovery, fuller, scannable)
1679
+ ### MODE A body (Discovery, fuller, scannable)
1680
+
1681
+ Render each non-empty \`web_insights\` section as H5 with the emoji + label intact. Section order: \`\u{1F3E2} company profile\` \u2192 \`\u{1F4C8} business signals\` \u2192 \`\u{1F4A1} prospecting clues\` \u2192 \`\u{1F9E9} strategic positioning\` \u2192 \`\u{1F50E} technologies & innovation\`. Inside each, bullet 3\u20135 items. Sort \`hot: true\` items first. **Bold** the description text of hot items; leave cold items plain. Render \`source\` as \`[source](url)\` at the end; include \`date\` when present. Omit empty sections. Skip \`\u{1F517} social links\` (already in the pill row) and \`\u{1F464} key people\` (already in the contacts table).
1682
+
1683
+ ### MODE B body (Contact preparation, tighter)
1684
+
1685
+ Render exactly two H5 sections:
1686
+
1687
+ ##### \u{1F3AF} Conversation hooks
1688
+
1689
+ Distill the 3 most recent / most hot signals from \`\u{1F4C8} business signals\` and \`\u{1F4A1} prospecting clues\` into one-sentence talking points in salesperson voice. Strip the academic framing. Cite the source inline.
1690
+
1691
+ ##### \u{1F464} About the person *(only when recommended_contact is non-empty)*
1692
+
1693
+ 2-line summary: their title + any context from \`web_insights\` key_people. If they appear in a hot signal ("X appointed CEO"), surface that prominently.
1694
+
1695
+ Skip \u{1F3E2} profile, \u{1F9E9} strategic positioning, \u{1F50E} technologies in MODE B \u2014 context the user doesn't need for the next 30 seconds.
1696
+
1697
+ If \`qualification[]\` is non-empty, append one collapsed line: \`"Qualification: N questions answered, avg boost X"\` and offer to expand in NEXT STEPS.
1698
+
1699
+ **Hide:** \`id\`, \`lead.id\`, \`contact.id\`, \`lead.location.pos\`, \`web_fetch_in_progress\`, \`enrichment_in_progress\`, \`recommended_contact_title\` (duplicates \`recommended_contact.job_title\`), empty arrays, fields whose value is the string \`"null"\`, \`contact.source\` (internal), insights whose \`source\` is empty.
1700
+
1701
+ **Legend (print once below the card):** \`\` \`\u25B0\` firmographic \xB7 \`\u2756\` AI booster \xB7 \`\u25B1\` unfilled \xB7 \u2605 recommended \xB7 \u{1F48E} hot in web_insights \xB7 \xB0 = no company LinkedIn (fallback link only) \`\`
1702
+
1703
+ ## Linking a contact's name
1704
+
1705
+ **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.**
1706
+
1707
+ URL priority (first applicable wins):
1708
+
1709
+ 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).
1710
+ 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.
1711
+
1712
+ 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.
1713
+
1714
+ ## Linking the company
1715
+
1716
+ 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.)
1717
+
1718
+ 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.
1719
+
1720
+ \`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).
1721
+
1722
+
1723
+
1724
+ # PHASE 2 \u2014 NOT FOUND
1725
+ If the resolver returns \`LEAD_NOT_FOUND\`, say that the existing visible corpus
1726
+ was searched. **Do NOT call \`leadbay_import_and_qualify\` automatically.** Offer
1727
+ to import and qualify the company as a separate, explicit next step; only call
1728
+ it after the user agrees.
1729
+
1730
+ # PHASE 3 \u2014 SUMMARY
1731
+ Place a 2\u20133 sentence summary ABOVE the card with:
1732
+ - Who is this company (1 sentence)
1733
+ - Their fit (cite specific qualification answers or signals from the research response)
1734
+ - Which contact would I email first (one short clause \u2014 the card's contacts table carries the rest)
1735
+
1736
+ The card itself handles the signal callouts (\`\u{1F4C8} business signals\`, \`\u{1F4A1} prospecting clues\`). Do NOT re-narrate signals in prose above the card \u2014 that's what the card sections are for. Be honest about uncertainty: if any field is missing from tool responses, say "not surfaced by qualification" rather than guessing.
1737
+ `;
1738
+ var leadbay_setup_team_prospecting = `
1739
+ Set up manager-led prospecting for me: turn the audience into a lens, validate candidates, then persist as named campaigns.
1740
+
1741
+ Audience: **{{arg:audience}}**
1742
+ {{arg:rep_split_block}}
1743
+
1744
+ 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.
1745
+
1746
+ 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.
1747
+
1748
+
1749
+ # PHASE 1 \u2014 INTERPRET INTENT INTO A LENS
1750
+
1751
+ Call \`leadbay_refine_prompt({user_prompt: "{{arg:audience}}"})\`. This handles the clarification protocol natively \u2014 if the system needs more info (e.g. industry disambiguation, geography precision), it returns \`status: "clarification_needed"\` with options. Surface those to me; on my answer, re-call \`leadbay_refine_prompt\` until the prompt converges.
1752
+
1753
+ When the prompt has converged, call \`leadbay_create_lens({user_prompt: <refined>, name: "<short descriptive name>"})\` to create a draft lens, then \`leadbay_promote_lens({lensId})\` to make it the active lens.
1754
+
1755
+ # PHASE 2 \u2014 PULL + VALIDATE CANDIDATES
1756
+
1757
+ Call \`leadbay_pull_leads({count: 20, lensId: <the new lens id>})\` to surface the top 20 candidates from the freshly-created lens. Render with the canonical \`pull_leads\` table layout.
1758
+
1759
+ Ask me ONCE: "Want me to deep-research the top N for validation?" If yes, call \`leadbay_research_lead_by_id\` serialized over the top 3-5 (one at a time, max 3 in parallel per the long-running-tools rule). Surface a research summary per lead.
1760
+
1761
+ Then ask me ONCE: "Which of these should we drop?" If I name leads to drop, exclude them from the working set. The remaining is the validated set.
1762
+
1763
+ # PHASE 3 \u2014 DECIDE THE CAMPAIGN SHAPE
1764
+
1765
+ If I provided a \`rep_split\` ("one campaign per rep: John gets Tulsa, Sarah gets OKC"), partition the validated leads accordingly. If I didn't, ask ONCE: "Create one campaign for the whole batch, or split per rep / region / sector?" \u2014 surface 2-4 options via your host's choice widget (\`ask_user_input_v0\` or \`AskUserQuestion\`) when available, else as a bulleted list.
1766
+
1767
+ For each campaign-shape decision, derive a name. Templates:
1768
+ - Whole batch: \`"<lens-name> \u2013 <YYYY-MM-DD>"\`
1769
+ - Per rep: \`"<lens-name> \u2013 <RepName>"\`
1770
+ - Per region: \`"<lens-name> \u2013 <RegionName>"\`
1771
+
1772
+ # PHASE 4 \u2014 PERSIST
1773
+
1774
+ For each campaign-shape partition, call \`leadbay_create_campaign({lead_ids: [...partition], name: "<derived>"})\`. Surface the returned \`id\` + \`name\` per campaign as a confirmation line.
1775
+
1776
+ # PHASE 5 \u2014 BE HONEST ABOUT SCOPE
1777
+
1778
+ Once the campaigns are created, surface this caveat in plain prose:
1779
+
1780
+ > Campaign visibility is currently scoped to the user who CREATED the campaign \u2014 the reps won't see these in their own MCP \`leadbay_list_campaigns\` calls. They CAN see them in the web UI at app.leadbay.ai \u2192 Campaigns. Cross-user MCP visibility would need backend work; flag this as a #3630 US3 product gap if your reps work primarily through MCP.
1781
+
1782
+ End with a NEXT STEPS chip via your host's choice widget (\`ask_user_input_v0\` or \`AskUserQuestion\`): "View progression on one of these now?" \u2192 routes to \`leadbay_campaign_progression\`.
1783
+
1784
+ # PHASE 6 \u2014 STOP
1785
+
1786
+ Done. The lens is live, the validated cohort is persisted as named campaigns, and the manager knows where the cross-user-visibility gap is.
1787
+ `;
1788
+ var leadbay_top_accounts_to_activate = `
1789
+ ## MEMORY
1790
+
1791
+ 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.
1792
+
1793
+
1794
+ Build me a **top-{{arg:count_or_default}} account-conquest plan** \u2014 the accounts worth activating, ranked, each one carrying a strategic motif, a phone pitch and a three-step checklist. {{arg:territory_block}}
1795
+
1796
+ This deliverable goes in front of a paying client, so **the honesty of the numbers matters more than their completeness**. Deliver the strongest plan the available data actually supports, and be explicit about what it doesn't.
1797
+
1798
+ **DATA PROVENANCE \u2014 every number carries its source.** This deliverable mixes
1799
+ four data sources with very different trust levels, and it is shown to a
1800
+ paying client. A figure whose origin is unstated reads as measured fact. Tag
1801
+ every number you emit with exactly one class:
1802
+
1803
+ | Tag | Meaning | Where it comes from |
1804
+ |---|---|---|
1805
+ | \`[ERP]\` | measured in the client's own invoicing / management extract | the file the user attached, or a \`leadbay_get_lead_custom_fields\` read of a value imported from it |
1806
+ | \`[LB]\` | returned by a \`leadbay_*\` tool THIS session | \`leadbay_pull_leads\`, \`leadbay_pull_followups\`, \`leadbay_bulk_qualify_leads\`, \`leadbay_enrich_titles\`, \`leadbay_scan_portfolio_signals\`, \`leadbay_account_history\`, \`leadbay_research_lead_by_id\` |
1807
+ | \`[SIRENE]\` | the French public company registry | \`recherche-entreprises.api.gouv.fr\` \u2014 **your own web tool, NOT Leadbay.** Leadbay does not proxy the registry |
1808
+ | \`[HYP]\` | a modelled assumption | the \u20AC/employee benchmark, the 35 % objective, the trade purchase mix, any Tier-1 threshold the client hasn't confirmed |
1809
+
1810
+ **Taint propagates.** A derived figure inherits the weakest class of its
1811
+ inputs. \`cash = pot12 \u2212 ca12\` where \`pot12\` is \`[HYP]\` makes **\`cash\` itself
1812
+ \`[HYP]\`** \u2014 say so in the artefact's own caveat block, not only in chat. A
1813
+ client who mistakes a modelled \`cash\` figure for an audited one will build a
1814
+ sales plan on it.
1815
+
1816
+ **Print the PROVENANCE LEDGER before you build anything**, BEFORE writing
1817
+ artifact code or the final table. The block below is a **shape, not a literal**:
1818
+ keep the header, the \`field / class / source\` columns and the closing rule, but
1819
+ **replace every \`<...>\` placeholder with the real field name, class and source**
1820
+ \u2014 one row per field you actually emit. A ledger still showing \`<field name>\` has
1821
+ passed the ordering check while telling the reader nothing, which defeats its
1822
+ entire purpose.
1823
+
1824
+ \`\`\`
1825
+ PROVENANCE LEDGER
1826
+ =================
1827
+ field class source
1828
+ <field name> ERP <file>:col "<column header>"
1829
+ <field name> LB <tool that returned it>
1830
+ <field name> SIRENE recherche-entreprises.api.gouv.fr
1831
+ <field name> HYP <the formula + which input is assumed>
1832
+ <field name> OMITTED <why it cannot be computed>
1833
+ =================
1834
+ \`\`\`
1835
+
1836
+ An \`OMITTED\` row is the point of the ledger: it makes a gap **visible** instead
1837
+ of silently filled with a plausible guess. Never drop a field from the ledger
1838
+ just because you couldn't source it \u2014 render it as \`OMITTED\` with the reason.
1839
+
1840
+ **When a number is unavailable, do NOT model it \u2014 switch modes.** Specifically:
1841
+ if the client's revenue-realized figure is absent, do not estimate it, do not
1842
+ proxy it from headcount / sector / score, and **do not sort by any quantity
1843
+ derived from it.** Say plainly which fields are unavailable, name the exact
1844
+ columns you'd need, and deliver the plan the prompt describes \u2014 ordered by the
1845
+ strongest \`[LB]\` ranking you actually have.
1846
+
1847
+ **Sorting is where fabrication hides.** Asked for a ranking "by cash to go
1848
+ get" with no revenue data, the tempting move is to invent a revenue figure per
1849
+ account purely so the sort produces a plausible-looking order. That is
1850
+ fabrication with a confident shape, and it is the single most likely failure of
1851
+ this workflow. Change the sort and say so; never invent the key.
1852
+
1853
+ **Client-specific parameters are to be CONFIRMED, not assumed as product
1854
+ constants.** The Tier-1 threshold, the \u20AC/employee benchmark and the purchase
1855
+ mix all come from one client's economics. State each as \`[HYP]\` with its value
1856
+ visible and offer to re-run when the client supplies the real figure.
1857
+
1858
+
1859
+ 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.
1860
+
1861
+ 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.
1862
+
1863
+
1864
+ # Resilience rules for Leadbay long-running tools
1865
+
1866
+ 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.**
1867
+
1868
+ ## Rule 1 \u2014 Pin the lens
1869
+
1870
+ 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.
1871
+
1872
+ ## Rule 2 \u2014 Prefer async for bulk operations
1873
+
1874
+ \`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.
1875
+
1876
+ ## Rule 3 \u2014 Serialize \`leadbay_research_lead_by_id\` fan-out
1877
+
1878
+ \`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.
1879
+
1880
+ ## Rule 4 \u2014 Retry, don't replan
1881
+
1882
+ 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:
1883
+
1884
+ 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.
1885
+ 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.
1886
+ 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.
1080
1887
 
1081
- Render each non-empty \`web_insights\` section as H5 with the emoji + label intact. Section order: \`\u{1F3E2} company profile\` \u2192 \`\u{1F4C8} business signals\` \u2192 \`\u{1F4A1} prospecting clues\` \u2192 \`\u{1F9E9} strategic positioning\` \u2192 \`\u{1F50E} technologies & innovation\`. Inside each, bullet 3\u20135 items. Sort \`hot: true\` items first. **Bold** the description text of hot items; leave cold items plain. Render \`source\` as \`[source](url)\` at the end; include \`date\` when present. Omit empty sections. Skip \`\u{1F517} social links\` (already in the pill row) and \`\u{1F464} key people\` (already in the contacts table).
1888
+ 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.
1082
1889
 
1083
- ### MODE B body (Contact preparation, tighter)
1084
1890
 
1085
- Render exactly two H5 sections:
1891
+ # PHASE 0 \u2014 SCOPE + STATE
1086
1892
 
1087
- ##### \u{1F3AF} Conversation hooks
1893
+ Call \`leadbay_account_status\` for my quota and active lens.
1088
1894
 
1089
- Distill the 3 most recent / most hot signals from \`\u{1F4C8} business signals\` and \`\u{1F4A1} prospecting clues\` into one-sentence talking points in salesperson voice. Strip the academic framing. Cite the source inline.
1895
+ **What this plan is, and what it deliberately isn't.** Leadbay knows who a company is, how it scores, what signals it has and who to call there. It does **not** know what any account buys from me \u2014 invoicing lives in my ERP, and no Leadbay tool exposes it. So this is a **conquest plan**: real accounts, real qualification, real signals, real contacts, ranked by the strongest Leadbay signal available. Revenue-realized, per-family revenue, addressable spend and cash-to-capture are **OMITTED \u2014 never estimated, never proxied from headcount, sector or lead score.**
1090
1896
 
1091
- ##### \u{1F464} About the person *(only when recommended_contact is non-empty)*
1897
+ Say that scope in one line up front, so nobody reads the ranking as a money sort. If I ask for a cash-ranked plan, tell me plainly that it needs my invoicing extract and that the MCP has no path to it today \u2014 then deliver this plan anyway rather than stopping.
1092
1898
 
1093
- 2-line summary: their title + any context from \`web_insights\` key_people. If they appear in a hot signal ("X appointed CEO"), surface that prominently.
1899
+ **DELIVER FIRST, ASK ALONGSIDE \u2014 never gate the plan on a missing input.** Only ONE thing can stop you before you have shipped a ranked list of real accounts: not knowing **whose** plan this is (a company-identity mismatch you genuinely cannot resolve). Everything else is a question you carry *next to* the delivered plan, not a reason to withhold it:
1094
1900
 
1095
- Skip \u{1F3E2} profile, \u{1F9E9} strategic positioning, \u{1F50E} technologies in MODE B \u2014 context the user doesn't need for the next 30 seconds.
1901
+ - **No benchmark?** Costs nothing here \u2014 the money column is OMITTED regardless. Pull, qualify, rank by the Leadbay signal, deliver, and mention what a cash-ranked version would need.
1902
+ - **No Tier-1 threshold?** Not a blocker. Deliver, and ask alongside.
1903
+ - **No territory?** Not a blocker \u2014 but do NOT call the result "national". You'll be pulling my ACTIVE lens, which may already be scoped to a city, sector or rep patch. Say the plan covers **my active lens's existing scope** (name the lens), not the whole country, and offer to re-scope. Calling a city-scoped lens a national plan misdescribes the deliverable to a client.
1904
+ - **\`last_requested_lens: null\`?** Not a blocker \u2014 and **do NOT read it as "no lens exists".** \`leadbay_account_status\` deliberately WITHHOLDS the lens id unless the request mentioned the lens/audience, so a plain "top 50 accounts to activate" returns null even when I have a perfectly good active lens. Default to calling \`leadbay_pull_leads\` with **no** \`lensId\` and let it resolve my active lens; capture \`response.lens.id\` from that result and pin it thereafter. Only create or switch a lens when I explicitly asked to scope or change the audience (e.g. a \`territory\` argument) \u2014 inventing a new lens silently changes what I see in the product.
1905
+ - **Only 3 qualification questions instead of 5?** Not a blocker. Use the org's real questions, note the gap, recommend the additions \u2014 do not wait for permission before pulling.
1096
1906
 
1097
- If \`qualification[]\` is non-empty, append one collapsed line: \`"Qualification: N questions answered, avg boost X"\` and offer to expand in NEXT STEPS.
1907
+ Bundling a non-blocking question in with a blocking one turns a justified pause into an over-wide gate, and the user gets a plan-of-a-plan instead of a plan. The test is **"have I shipped a ranked list of real accounts yet?"** \u2014 if you're about to end a turn without one, you are almost certainly over-gating: deliver first, then ask.
1098
1908
 
1099
- **Hide:** \`id\`, \`lead.id\`, \`contact.id\`, \`lead.location.pos\`, \`web_fetch_in_progress\`, \`enrichment_in_progress\`, \`recommended_contact_title\` (duplicates \`recommended_contact.job_title\`), empty arrays, fields whose value is the string \`"null"\`, \`contact.source\` (internal), insights whose \`source\` is empty.
1909
+ If I gave a \`territory\`, scope discovery to it now, and **make sure the scoping actually took effect before you pull** \u2014 a territory request that silently returns out-of-territory accounts is worse than none.
1100
1910
 
1101
- **Legend (print once below the card):** \`\` \`\u25B0\` firmographic \xB7 \`\u2756\` AI booster \xB7 \`\u25B1\` unfilled \xB7 \u2605 recommended \xB7 \u{1F48E} hot in web_insights \xB7 \xB0 = no company LinkedIn (fallback link only) \`\`
1911
+ - **Preferred: \`leadbay_adjust_audience\`** on my active lens, passing the place as \`locations\`. It applies directly, so the lens I already use is now scoped and \`leadbay_pull_leads\` needs no new id.
1102
1912
 
1103
- ## Linking a contact's name
1913
+ \u26A0 **Location criteria MERGE \u2014 they do not replace.** \`adjust_audience\` unions the new \`location_ids\` into any existing include-location criterion (and \`pull_followups\` merges its \`city\` shortcut the same way). So asking for "R\xE9gion Ouest" on a lens already scoped to Paris yields **Paris OR R\xE9gion Ouest** while your header claims R\xE9gion Ouest. Before adding a territory, check the current filter: if it already carries locations you were not asked to keep, clear or replace them (or build a fresh territory-only lens for this one-off plan) rather than stacking a union.
1914
+ - **If a new lens is genuinely warranted: \`leadbay_new_lens\` is a two-step call.** It returns \`status:"preview"\` and creates NOTHING unless you re-call the same args with \`confirm:true\`. So: preview \u2192 confirm \u2192 take \`lens.id\` from the \`created\` response \u2192 pass that id as \`lensId\` on every subsequent pull. Never continue on the previous active lens after previewing a new one; that delivers the old audience under a new heading.
1104
1915
 
1105
- **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.**
1916
+ A place name goes to \`locations\`, never to \`sectors\` or a refine prompt.
1106
1917
 
1107
- URL priority (first applicable wins):
1918
+ # PHASE 1 \u2014 THE FIVE QUALIFICATION QUESTIONS
1108
1919
 
1109
- 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).
1110
- 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.
1920
+ Call \`leadbay_get_qualification_questions\` and use the org's **actual** questions \u2014 they become the qualification row on every card. Do NOT invent them.
1111
1921
 
1112
- 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.
1922
+ If the org has none set, or they don't discriminate for this exercise, recommend this shape and offer to set it via \`leadbay_set_qualification_questions\` (max 5, and ask before replacing anything): **Q1** exercises a core-target trade \xB7 **Q2** big enough to matter \xB7 **Q3** operates in the covered territory \xB7 **Q4** recent activity signals \xB7 **Q5** likely need in the next quarter. Q4 and Q5 are the load-bearing pair \u2014 they separate "fits the profile" from "worth calling this week". Recommend; don't overwrite without my say-so.
1113
1923
 
1114
- ## Linking the company
1924
+ # PHASE 2 \u2014 THE ACCOUNT UNIVERSE
1115
1925
 
1116
- 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.)
1926
+ \u26A0 **Monitor membership is not client status.** Monitor tells you what Leadbay is watching \u2014 lens scoring decides who lands there, not whether the company ever bought anything. Label that pane "Leadbay view membership", never "customer".
1117
1927
 
1118
- 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.
1928
+ **Get the accounts.** \`leadbay_pull_followups\` for the known/identified side, \`leadbay_pull_leads\` for the not-yet-identified side.
1119
1929
 
1120
- \`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).
1930
+ \u26A0 **Monitor's scope must match the scope you put in the header \u2014 never leave it accidental.** \`leadbay_pull_followups\` defaults to applying whatever Monitor filter is persisted server-side from a previous session, and that filter has nothing to do with the lens Discover is using. Two stale-state traps, one rule:
1121
1931
 
1932
+ - **A persisted filter you didn't ask for** silently shrinks the known side, so a rep who once filtered Monitor to a city gets a "whole base" plan missing most of it.
1933
+ - **Blindly passing \`filtered:false\`** does the opposite: Monitor goes org-wide while Discover stays on a scoped lens, so out-of-scope known accounts land in a plan headed with the lens's name.
1122
1934
 
1935
+ \u26A0 **You cannot mirror a geography you haven't read.** \`leadbay_pull_leads\` returns only \`lens: {id}\` \u2014 not the lens's filter \u2014 so capturing the id tells you nothing about which locations it covers. Before scoping Monitor to match a lens, read the **\`lens://<id>/definition\` resource** \u2014 that is where the filter and its \`location_ids\` actually live. \`leadbay_my_lenses\` returns only id / name / description / active flags, so it cannot tell you a lens's geography and must not be used for this. If you cannot determine the lens's geography, do NOT guess: pull Monitor org-wide with \`filtered:false\` and say in the header that the known side is org-wide while Discover follows lens \`<id>\`, whose scope you could not read. An unstated mismatch is the failure; a stated one is honest.
1123
1936
 
1124
- # PHASE 2 \u2014 NOT FOUND
1125
- If the resolver returns \`LEAD_NOT_FOUND\`, say that the existing visible corpus
1126
- was searched. **Do NOT call \`leadbay_import_and_qualify\` automatically.** Offer
1127
- to import and qualify the company as a separate, explicit next step; only call
1128
- it after the user agrees.
1937
+ So: **read the persisted filter first** (the response reports \`active_filters\`), then make it match the plan's declared scope. If the plan is scoped (a \`territory\`, or an active lens with its own geography), apply that same geography to Monitor. If the plan is genuinely org-wide, pass \`filtered:false\`. Either way, state the known side's scope in the header in the same breath as the Discover side \u2014 a plan whose two halves are scoped differently is misleading even when both halves are individually correct.
1129
1938
 
1130
- # PHASE 3 \u2014 SUMMARY
1131
- Place a 2\u20133 sentence summary ABOVE the card with:
1132
- - Who is this company (1 sentence)
1133
- - Their fit (cite specific qualification answers or signals from the research response)
1134
- - Which contact would I email first (one short clause \u2014 the card's contacts table carries the rest)
1939
+ \u26A0 **A territory must scope BOTH sides.** Adjusting or creating a lens only scopes Discover; Monitor is filtered through its own path, so pass the territory to \`leadbay_pull_followups\` as well (its \`city\` free-text shortcut resolves to a \`location_ids\` filter, same resolver as the lens). Otherwise a territory-scoped plan quietly mixes in out-of-territory known accounts \u2014 and a client reading "R\xE9gion Ouest" at the top will not check every row. Unless I named a \`territory\`, call \`leadbay_pull_leads\` with **no \`lensId\`** so it resolves my active lens \u2014 do not create a lens just because \`account_status\` showed a null. Capture \`response.lens.id\` from the first pull and pass it as an explicit \`lensId\` on every later call \u2014 a mid-session lens shift discards the cohort. Keep pulling until you have a pool comfortably deeper than {{arg:count_or_default}}, topping up with \`leadbay_bulk_qualify_leads\` \u2192 \`leadbay_qualify_status\` \u2192 re-pull as needed.
1135
1940
 
1136
- The card itself handles the signal callouts (\`\u{1F4C8} business signals\`, \`\u{1F4A1} prospecting clues\`). Do NOT re-narrate signals in prose above the card \u2014 that's what the card sections are for. Be honest about uncertainty: if any field is missing from tool responses, say "not surfaced by qualification" rather than guessing.
1137
- `;
1138
- var leadbay_setup_team_prospecting = `
1139
- Set up manager-led prospecting for me: turn the audience into a lens, validate candidates, then persist as named campaigns.
1941
+ # PHASE 3 \u2014 QUALIFY, SIGNAL, MOTIF
1140
1942
 
1141
- Audience: **{{arg:audience}}**
1142
- {{arg:rep_split_block}}
1943
+ **Qualify \u2014 the SELECTED cohort, in chunks of 25.** \`leadbay_bulk_qualify_leads\` caps \`count\` at **25**, so a single call cannot cover a 50-account plan. Loop until the whole cohort is qualified, polling \`leadbay_qualify_status\` between chunks.
1143
1944
 
1144
- 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.
1945
+ \u26A0 **Never qualify Monitor rows through a Discover \`lensId\`.** \`leadbay_qualify_status\` re-checks each lead against the lens it was launched on and returns them under **\`not_in_lens\`** \u2014 the backend does not qualify them, so those rows ship with permanently empty pills while the poll reads "still running". So split the cohort: qualify the Discover rows with the pinned \`lensId\`, and for known-side rows use the qualification data \`leadbay_pull_followups\` already returned rather than re-launching them off-lens. If a Monitor row has no qualification data, say so in its cell \u2014 do not leave a pill that will never fill. **Always read \`not_in_lens\` in the poll response** and report anything listed there rather than waiting on it.
1145
1946
 
1146
- 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.
1947
+ \u26A0 **Pass explicit \`leadIds\` whenever the cohort isn't simply "the next N on the lens"** \u2014 e.g. after you've selected a shortlist, or when the plan mixes Monitor and Discover rows. The \`count\`-based path selects the next *unqualified leads from the lens wishlist*, so on any other cohort it qualifies unrelated leads and hands you handles whose pills belong to different companies. Use \`leadbay_bulk_qualify_leads({leadIds:[\u2026\u226425 of the cohort], wait_for_completion:false})\` and chunk through the cohort's own ids. The \`{lensId, count}\` form is only right when the cohort genuinely *is* the lens's top N.
1147
1948
 
1949
+ **Qualify the plan cohort, not the whole base.** Select your ~{{arg:count_or_default}} candidates (plus a modest buffer for drop-outs) BEFORE qualifying \u2014 qualification is async and quota-bearing, so running it across an entire portfolio to produce a top-{{arg:count_or_default}} burns the user's quota for rows that will never appear. **Keep every returned \`qualify_id\`** \u2014 the deck's live qualification layer is wired from those handles, and a deck with none is a dead deck that still looks finished. Never ship a plan whose lower ranks have empty qualification pills because only the first 25 were ever qualified.
1148
1950
 
1149
- # PHASE 1 \u2014 INTERPRET INTENT INTO A LENS
1951
+ **Signals \u2014 scoped to the cohort.** \u26A0 **Always pass the selected \`leadIds\`.** With \`leadIds\` omitted, \`leadbay_scan_portfolio_signals\` builds its own portfolio by paging \`/monitor\` \u2014 so on an imported cohort or a freshly-pulled Discover set it would scan a *different population* and you'd render dashes for accounts whose signals were never read.
1150
1952
 
1151
- Call \`leadbay_refine_prompt({user_prompt: "{{arg:audience}}"})\`. This handles the clarification protocol natively \u2014 if the system needs more info (e.g. industry disambiguation, geography precision), it returns \`status: "clarification_needed"\` with options. Surface those to me; on my answer, re-call \`leadbay_refine_prompt\` until the prompt converges.
1953
+ \`leadbay_scan_portfolio_signals\` is also a **filtered** read: it requires a concrete \`query\` and returns only the accounts whose cached signals match it. It is not a generic "read every signal" call. So run it **once per why-now theme, as SEPARATE calls** \u2014 expansion/new site \xB7 contract or tender won \xB7 funding \xB7 hiring \xB7 acquisition \xB7 new venue \u2014 and union the results.
1152
1954
 
1153
- When the prompt has converged, call \`leadbay_create_lens({user_prompt: <refined>, name: "<short descriptive name>"})\` to create a draft lens, then \`leadbay_promote_lens({lensId})\` to make it the active lens.
1955
+ \u26A0 **One comma-joined omnibus query is NOT six themed scans.** Cramming every keyword into a single string is one match attempt whose recall you cannot inspect: a lead that would have matched "hiring" alone can be missed, and you have no way to tell which themes actually returned anything. Six calls, six result sets, one union. If a theme returns nothing, that is information \u2014 record it rather than hiding it inside a broad string. An account that matched no query has **not** been shown to be signal-free; render it with an explicit \`\u2014\`, never an invented event. For the identified side, take interaction recency from the fields \`leadbay_pull_followups\` already returned. \u26A0 **Do NOT reach for \`leadbay_account_history\` on Monitor rows outside the active lens** \u2014 it calls \`research_lead_by_id\` first, which fetches \`/lenses/{lensId}/leads/{leadId}\` and 404s off-lens, so the very rows that need a SUIVI / R\xC9VEIL-LB decision are the ones it fails on. Use it only for a lead you know is in the pinned lens.
1154
1956
 
1155
- # PHASE 2 \u2014 PULL + VALIDATE CANDIDATES
1957
+ **SIGNAL HONESTY \u2014 never infer signals from freshness.** \`stale_at\`,
1958
+ \`web_fetch_in_progress\`, \`fetch_at\` are freshness markers, not signal
1959
+ indicators \u2014 signal presence is read ONLY from the actual \`signals[]\` /
1960
+ \`web_fetch.content\` entries. For "which of my leads have signal X" across a
1961
+ portfolio, call **\`leadbay_scan_portfolio_signals\`** (bulk-reads cached
1962
+ signals); don't loop \`leadbay_research_lead_by_id\` per lead or guess from
1963
+ freshness. A lead with no cached content is \`not_researched\`, not "no match";
1964
+ never report a signal verdict for a lead you never read.
1156
1965
 
1157
- Call \`leadbay_pull_leads({count: 20, lensId: <the new lens id>})\` to surface the top 20 candidates from the freshly-created lens. Render with the canonical \`pull_leads\` table layout.
1158
1966
 
1159
- Ask me ONCE: "Want me to deep-research the top N for validation?" If yes, call \`leadbay_research_lead_by_id\` serialized over the top 3-5 (one at a time, max 3 in parallel per the long-running-tools rule). Surface a research summary per lead.
1967
+ **Assign the motif.**
1160
1968
 
1161
- Then ask me ONCE: "Which of these should we drop?" If I name leads to drop, exclude them from the working set. The remaining is the validated set.
1969
+ **THE ACTIVATION MOTIFS.** Every account on the plan carries exactly one motif
1970
+ from this closed set of six. The motif is not decoration \u2014 it decides the phone
1971
+ pitch, the checklist, and whether the account belongs to the *Pilotage* engine
1972
+ (already identified) or the *Conqu\xEAte* engine (not yet identified). Assign it
1973
+ from observable data and state the deciding evidence in one line per account.
1162
1974
 
1163
- # PHASE 3 \u2014 DECIDE THE CAMPAIGN SHAPE
1975
+ | Motif | Assign when | Engine |
1976
+ |---|---|---|
1977
+ | **SAUVETAGE** | was buying steadily, has now stopped \u2014 a recent, sharp break (e.g. no order in ~60\u201390 days against a real history) | Pilotage |
1978
+ | **PLAN DE COMPTE** | large, still active, buying broadly \u2014 the risk is complacency, not loss; plan the coming half-year and lock volume terms | Pilotage |
1979
+ | **MONT\xC9E EN GAMME** | active but narrow \u2014 buys one product family while comparable accounts of the same size buy several; the gap is cross-sell | Pilotage |
1980
+ | **R\xC9VEIL** | account exists, essentially dormant \u2014 long-dead history (e.g. 12+ months at zero) but the company is demonstrably still trading | Pilotage |
1981
+ | **CONQU\xCATE** | not present in the Leadbay known pipeline \u2014 in the addressable market, absent from the base. \u26A0 Absence from Monitor is NOT proof they never bought (see below) | Conqu\xEAte |
1982
+ | **SUIVI** | in the known pipeline with recent activity, purchase behaviour unknown \u2014 the honest label for an active Monitor row when no order history is available | Pilotage |
1983
+
1984
+ **Decision order matters.** Test in this order and stop at the first match, or
1985
+ a big lapsed account will be labelled R\xC9VEIL when it is really a SAUVETAGE:
1986
+ recent sharp break \u2192 SAUVETAGE; long-dormant \u2192 R\xC9VEIL; never bought \u2192
1987
+ CONQU\xCATE; buying broadly at scale \u2192 PLAN DE COMPTE; buying narrowly \u2192
1988
+ MONT\xC9E EN GAMME.
1989
+
1990
+ **Without order history the first five tests cannot run at all.** In that case
1991
+ the split is simply: in the Leadbay known pipeline \u2192 **SUIVI**; not in the
1992
+ pipeline \u2192 **CONQU\xCATE**. Never reach for a Pilotage motif you cannot evidence,
1993
+ and never invent a seventh label \u2014 the set is closed at six.
1994
+
1995
+ When a known-pipeline row has long-dormant *Leadbay* activity, it stays
1996
+ **SUIVI** and you say what the dormancy measures in its why-now cell: "no
1997
+ Leadbay-logged action in N months". That is a qualifier on the evidence, not a
1998
+ new motif. It is NOT R\xC9VEIL \u2014 R\xC9VEIL means dormant *purchasing*, which needs
1999
+ order history you do not have.
2000
+
2001
+ **What each motif changes in the output.**
2002
+
2003
+ - The **pitch angle** \u2014 SAUVETAGE opens on the silence itself and offers terms
2004
+ to resume; PLAN DE COMPTE opens on the relationship and plans forward;
2005
+ MONT\xC9E EN GAMME opens on what comparable firms buy that this one doesn't;
2006
+ R\xC9VEIL asks what made them leave and offers a re-entry incentive; CONQU\xCATE
2007
+ introduces the company and asks for a short first meeting \u2014 **without
2008
+ asserting no prior relationship**. Never write "we've never worked together"
2009
+ or "as a new customer" on a Leadbay-only plan: absence from the known
2010
+ pipeline is not proof they never bought, and that line told to an existing
2011
+ customer is the one mistake a rep cannot walk back. Write the pitch in the
2012
+ client's own commercial voice, naming the specific families and figures the
2013
+ account's data actually supports. **SUIVI** picks up the existing thread \u2014
2014
+ a continuation, never an introduction and never a win-back.
2015
+ - The **checklist** \u2014 three concrete, checkable next actions matching the
2016
+ motif's shape: diagnose \u2192 schedule \u2192 send-terms for SAUVETAGE; review \u2192
2017
+ propose \u2192 open-a-family for PLAN DE COMPTE; visit-with-full-tariff \u2192 quote \u2192
2018
+ first-order-in-the-new-family for MONT\xC9E EN GAMME; understand-the-departure \u2192
2019
+ send-offer \u2192 first-order-back for R\xC9VEIL; reach-the-decision-maker \u2192
2020
+ open-the-account \u2192 first-test-order for CONQU\xCATE; confirm-the-state \u2192
2021
+ identify-the-current-need \u2192 agree-a-next-step for SUIVI. When a signal exists,
2022
+ promote "exploit <the signal>" to the top of that account's checklist.
2023
+
2024
+ **Motif assignment depends on order history, which is ERP data.** Without the
2025
+ client's extract, SAUVETAGE / PLAN DE COMPTE / MONT\xC9E EN GAMME / R\xC9VEIL cannot
2026
+ be assigned from purchase behaviour \u2014 do not guess them from a lead score, a
2027
+ sector, or a company's size. Two honest options, in order of preference:
2028
+
2029
+ **The Monitor gap \u2014 read this before assigning anything.** Four of the six
2030
+ motifs (SAUVETAGE / PLAN DE COMPTE / MONT\xC9E EN GAMME / R\xC9VEIL) are purchase-
2031
+ behaviour reads, and CONQU\xCATE means "not in the known pipeline". A Monitor row
2032
+ that is *actively* worked therefore matches none of them: it IS in the pipeline,
2033
+ and without order history you cannot tell whether it buys broadly, narrowly, or
2034
+ at all. Do NOT resolve that by guessing a purchase motif, and do NOT silently
2035
+ drop the row.
2036
+
2037
+ Those rows take **SUIVI** (row 6 of the table above). Say in the plan's legend
2038
+ that SUIVI exists precisely because purchase history is unavailable, and that
2039
+ ERP order data would split those rows into the four Pilotage motifs.
2040
+
2041
+ 1. **CONQU\xCATE is assignable from Leadbay alone \u2014 but say what it actually
2042
+ means.** Discover membership proves a company is **not in the Leadbay known
2043
+ pipeline**; it does NOT prove they never bought. Monitor membership is set by
2044
+ lens scoring, not by purchase history, so an existing customer who was never
2045
+ scored into the known view will appear in Discover. Without order history
2046
+ there is no way to tell the two apart.
2047
+
2048
+ So label the motif for what the data supports \u2014 "fresh / not in the Leadbay
2049
+ pipeline" \u2014 and **write the pitch so it survives being wrong**: an opener
2050
+ that introduces the company works for a genuine prospect and merely sounds
2051
+ uninformed to a customer, whereas "we've never worked together" told to a
2052
+ current customer damages the relationship and the credibility of the whole
2053
+ plan. Only ERP order history can upgrade this to a true never-a-client
2054
+ claim. A Leadbay-only plan is still a legitimate *Conqu\xEAte* plan \u2014 say so in
2055
+ the title rather than implying it covers the whole base.
2056
+ 2. **Leadbay-activity recency is a qualifier, never a motif.** A long-dormant
2057
+ known row stays **SUIVI** with "no Leadbay-logged action in N months" in its
2058
+ why-now cell \u2014 never "no orders in N months", and never a seventh label.
2059
+ Logged activity is not invoicing.
2060
+
2061
+
2062
+ # PHASE 4 \u2014 POTENTIAL AND RANKING
2063
+
2064
+ Rank by \`ai_agent_lead_score\`, then qualification boost, then headcount. **Name that key in the plan's own header** \u2014 a reader who assumes a money-sort misreads the whole order \u2014 and title the deliverable for what it is (a conquest plan), not for what it isn't.
2065
+
2066
+ Cash-to-capture is not available: it needs \`ca12\` from my invoicing system, which no Leadbay tool exposes. Show it as OMITTED in the ledger and say what a cash-ranked version would require (12-month revenue per account, per-family split, last order date, order count, plus a \u20AC/employee benchmark) \u2014 do not model it.
2067
+
2068
+ # PHASE 5 \u2014 CONTACTS (consent-gated)
2069
+
2070
+ Each card needs a reachable decision-maker. \`leadbay_enrich_titles({leadIds, lensId})\` in discovery mode first \u2014 that reveals what's enrichable and spends nothing. Render whatever contact detail is already on the record; many accounts already carry a named contact.
2071
+
2072
+ **Do NOT stop and wait for enrichment consent before delivering.** Asking for a plan is not authorization to spend quota on {{arg:count_or_default}} accounts \u2014 but neither is it a reason to end the turn on a spending question with no plan attached. Ship the ranked plan (Phase 6), then **offer** the paid reveal alongside it. The discovery call returned no \`titles\`, so it only told you what's *available* \u2014 **the offer must therefore carry the titles you propose to enrich AND the channels**, not just a volume: "enrich N contacts at these titles (\`<the titles you picked from available_titles / title_suggestions>\`), email only / email + phone \u2014 reveals consume quota". A bare "yes" to a volume-only question is not a mandate to pick titles yourself, and re-running discovery instead of launching wastes a turn.
2073
+
2074
+ \u26A0 **Do NOT quote a cost or a credits figure.** The per-reveal rate is backend-side and enrichment is gated by quota, not a credit balance; \`credits_remaining\` is advisory context only. A spend number invented to make the offer concrete is the same failure as an invented euro on a card.
2075
+
2076
+ On an explicit yes, launch with the agreed \`titles\` + channels, then poll \`leadbay_bulk_enrich_status\` until done and **keep the \`bulk_id\` handles** for the deck.
2077
+
2078
+ \u26A0 **Render only the channels that actually came back.** The default reveal is email-only unless phone was explicitly requested, so never emit a \`tel:\` link for a contact whose phone was never revealed \u2014 show the channels enrichment returned and mark the rest omitted. A fabricated phone link is the same failure as a fabricated euro.
2079
+
2080
+ # PHASE 6 \u2014 DELIVER
2081
+
2082
+ Render the PROVENANCE LEDGER and its legend FIRST, then the chat answer beneath it \u2014 never the other way round. A ranked money column read before its sourcing has already misled the reader:
2083
+
2084
+ ## RENDERING \u2014 account activation plan
2085
+
2086
+ Two surfaces. The **chat table** is the default answer and must stand alone as
2087
+ useful. The **interactive deck** is offered, not forced (see the widget gate) \u2014
2088
+ build it only once the user accepts.
2089
+
2090
+ ### Order on the page \u2014 ledger FIRST, then the plan
2091
+
2092
+ Print the PROVENANCE LEDGER (and the one-line provenance legend) **before** the
2093
+ chat table, the deck, or any other part of the deliverable. The reader must know
2094
+ which figures are measured and which are modelled *before* they read a ranking
2095
+ built on them \u2014 a cash column read first and sourced second has already done its
2096
+ damage. This ordering is the workflow contract, not a stylistic preference.
2097
+
2098
+ ### The chat table (render immediately after the ledger)
1164
2099
 
1165
- If I provided a \`rep_split\` ("one campaign per rep: John gets Tulsa, Sarah gets OKC"), partition the validated leads accordingly. If I didn't, ask ONCE: "Create one campaign for the whole batch, or split per rep / region / sector?" \u2014 surface 2-4 options via your host's choice widget (\`ask_user_input_v0\` or \`AskUserQuestion\`) when available, else as a bulleted list.
2100
+ **The chat answer must be the whole deliverable the user asked for** \u2014 the deck
2101
+ is optional, so a top-50 request whose chat half stops at 10 rows has delivered
2102
+ a fifth of the plan. Render the **requested count**, with its pitch + checklist
2103
+ block per row (see below).
2104
+
2105
+ If that is genuinely too long for one message, do NOT silently truncate: state
2106
+ the delivered count plainly ("here are 20 of the 50 \u2014 say the word for the
2107
+ rest"), so the user knows what they have. Never present a partial list as
2108
+ though it were the plan. Four columns:
1166
2109
 
1167
- For each campaign-shape decision, derive a name. Templates:
1168
- - Whole batch: \`"<lens-name> \u2013 <YYYY-MM-DD>"\`
1169
- - Per rep: \`"<lens-name> \u2013 <RepName>"\`
1170
- - Per region: \`"<lens-name> \u2013 <RegionName>"\`
2110
+ Col 3's header is **the ranking key you actually used** \u2014 never a cash label,
2111
+ since cash-to-capture cannot be computed from Leadbay data:
1171
2112
 
1172
- # PHASE 4 \u2014 PERSIST
2113
+ \`\`\`
2114
+ | # \xB7 Account | Motif | Fit score | Why now |
2115
+ \`\`\`
1173
2116
 
1174
- For each campaign-shape partition, call \`leadbay_create_campaign({lead_ids: [...partition], name: "<derived>"})\`. Surface the returned \`id\` + \`name\` per campaign as a confirmation line.
2117
+ - **Col 1** \u2014 rank number, then the company name linked to its website when one
2118
+ is known. Follow with a compact \` \xB7 \`-separated pill line: city \xB7 headcount \xB7
2119
+ any account reference you were given. **Every figure in that pill line carries
2120
+ its class too** \u2014 headcount is \`[LB]\` (a Leadbay size band, so render the band
2121
+ rather than a false-precision point value) or \`[SIRENE]\` if you read it from
2122
+ the registry. An untagged employee count is still an untagged number in front
2123
+ of a client; omit it rather than ship it bare.
2124
+ - **Col 2** \u2014 the motif, exactly one of SAUVETAGE / PLAN DE COMPTE / MONT\xC9E EN
2125
+ GAMME / R\xC9VEIL / CONQU\xCATE / SUIVI. Never invent a seventh.
2126
+ - **Col 3** \u2014 the ranking signal with its provenance class, e.g. \`AI 30 [LB]\`.
2127
+ Tagging is not optional; an untagged figure reads as measured fact. **There is
2128
+ no money column** \u2014 cash-to-capture needs invoicing data Leadbay does not
2129
+ hold, so it stays OMITTED in the ledger rather than being modelled. A column
2130
+ of invented euros next to a client's name is the exact failure this
2131
+ deliverable must not ship.
2132
+ - **Col 4** \u2014 the one-line reason to act now: the signal when there is one,
2133
+ otherwise the motif's deciding evidence. Never fill this with a
2134
+ plausible-sounding invented event; an account with nothing read shows \`\u2014\`.
2135
+
2136
+ Sort strictly by the ranking key named in the ledger (which was printed above).
2137
+
2138
+ ### The pitch + checklist block (part of the chat answer, not the deck)
2139
+
2140
+ The table alone is a shortlist, not a plan \u2014 the pitch and the three-step
2141
+ checklist are what make it actionable, and the deck is **optional**, so they
2142
+ cannot live only there. Under the table, render a block for **every account you
2143
+ put in the table** \u2014 if a row is good enough to rank, it is good enough to carry
2144
+ its pitch. Do NOT ship the top 5 or 10 and offer the rest "on request": that
2145
+ puts the actionable half of the deliverable behind another user turn, and the
2146
+ rows you defer are the ones a rep is least likely to chase. If the full plan is
2147
+ genuinely long, shrink the TABLE (fewer rows, stated plainly) rather than
2148
+ shipping ranked rows with no pitch:
1175
2149
 
1176
- # PHASE 5 \u2014 BE HONEST ABOUT SCOPE
2150
+ \`\`\`
2151
+ **<rank> \xB7 <Company>** \u2014 <MOTIF>
2152
+ \u260E <contact name>, <title> \xB7 <only the channels actually revealed>
2153
+ > "<the motif's pitch, in the client's commercial voice>"
2154
+ \u2610 <step 1> \u2610 <step 2> \u2610 <step 3>
2155
+ \`\`\`
1177
2156
 
1178
- Once the campaigns are created, surface this caveat in plain prose:
2157
+ Keep each pitch to one or two sentences a rep can say out loud, and each
2158
+ checklist to three concrete, checkable actions matching that motif's shape.
2159
+ When an account has a signal, lead the pitch with it and promote
2160
+ "exploit &lt;the signal&gt;" to the top of its checklist.
2161
+
2162
+ ### The interactive deck (only after the user accepts)
2163
+
2164
+ One card per account, ordered by the same key. Per card:
2165
+
2166
+ - **Header** \u2014 rank badge, company name, city \xB7 trade \xB7 headcount, and the
2167
+ ranking signal right-aligned with its class tag (e.g. \`AI 30 [LB]\`). No cash
2168
+ figure: the deck and the chat answer must never disagree about which fields
2169
+ exist, and a card carrying a euro the table omitted means one was modelled.
2170
+ - **Motif badge** \u2014 the motif, visually distinct per motif so the deck can be
2171
+ scanned by strategy.
2172
+ - **Qualification row** \u2014 the org's **actual** questions as returned by
2173
+ \`leadbay_get_qualification_questions\` (there may be fewer than five), each with
2174
+ \u2713 / \u2717 / pending and labelled with its real text. Render exactly as many rows as
2175
+ the org has: never pad to five with invented labels or bogus pending pills, and
2176
+ never substitute invented wording.
2177
+ - **Signal line** \u2014 the event driving urgency, or omitted.
2178
+ - **Action block** \u2014 the named contact with **only the channels enrichment
2179
+ actually returned** as one-tap \`tel:\` / \`mailto:\` links (the default reveal is
2180
+ email-only unless phone was requested \u2014 never emit a \`tel:\` for a phone that
2181
+ was never revealed; mark it omitted instead), the motif's pitch as a quoted
2182
+ line, and the three-item checklist as checkboxes.
2183
+ - **Caveat block** \u2014 closing the deck: which classes fed it, an explicit line
2184
+ that \`[HYP]\` figures are modelled rather than measured, and which fields are
2185
+ OMITTED because Leadbay does not hold them. A missing input means an omitted
2186
+ field, never a stand-in number.
2187
+
2188
+ Header KPIs across the top, each carrying its provenance class: accounts on the
2189
+ plan, count qualified, count with a reachable contact. **No euro totals** \u2014 the
2190
+ same rule as the table, for the same reason.
2191
+
2192
+ \u26A0 **No "activated" KPI at build time.** Nothing in this workflow measures
2193
+ activation \u2014 the deck is built before any outreach happens \u2014 so a count would be
2194
+ fabricated or imply outcome tracking that doesn't exist. If the deck's checklists
2195
+ persist locally, an "activated" tile may count *checked* accounts and must be
2196
+ labelled as local checklist state, not a measured outcome.
2197
+
2198
+
2199
+ Then **offer** the interactive deck \u2014 don't force it:
1179
2200
 
1180
- > Campaign visibility is currently scoped to the user who CREATED the campaign \u2014 the reps won't see these in their own MCP \`leadbay_list_campaigns\` calls. They CAN see them in the web UI at app.leadbay.ai \u2192 Campaigns. Cross-user MCP visibility would need backend work; flag this as a #3630 US3 product gap if your reps work primarily through MCP.
2201
+ ## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
1181
2202
 
1182
- End with a NEXT STEPS chip via your host's choice widget (\`ask_user_input_v0\` or \`AskUserQuestion\`): "View progression on one of these now?" \u2192 routes to \`leadbay_campaign_progression\`.
2203
+ 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.
1183
2204
 
1184
- # PHASE 6 \u2014 STOP
2205
+ **The Big Three** \u2014 when a tool result fits, route there:
1185
2206
 
1186
- Done. The lens is live, the validated cohort is persisted as named campaigns, and the manager knows where the cross-user-visibility gap is.
2207
+ | Host widget | Use when | Field map (from Leadbay payload) |
2208
+ |---|---|---|
2209
+ | \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
2210
+ | \`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") |
2211
+ | \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
2212
+
2213
+ 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.
2214
+
2215
+ **Rules:**
2216
+ - 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.
2217
+ - Pass identifiers (place_id, lead.id, contact_id) verbatim. Don't rewrite.
2218
+ - 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.
2219
+ - One short intro sentence in chat is enough \u2014 "Here are your 5 NYC follow-ups." Then route into the widget.
2220
+
2221
+
2222
+ \u26A0 **The deck's contact layer depends on what actually happened in Phase 5.** Bind a \`leadbay_bulk_enrich_status\` resource ONLY if a paid reveal was launched and you hold a \`bulk_id\`. If the user accepted the deck but not the reveal, render the contacts already on record and carry the paid-reveal offer inside the deck \u2014 never wire a status resource with no handle (it renders permanently empty) and never launch enrichment from the deck to manufacture one.
2223
+
2224
+ On acceptance, call \`leadbay_artifact_kit\`, read its \`usage_guide\` before writing any code, and build a single-file deck. Wire the live layer from the handles you kept: a poll-until-done resource per \`qualify_id\` for the qualification pills, and one over \`leadbay_bulk_enrich_status\` for the contacts. \u26A0 **If enrichment already ran this session, bind the existing \`bulk_id\` \u2014 re-launching enrichment from the deck double-spends my quota.** Per-card notes and outcomes go through the pre-wired note/outreach view-models (they carry the required verification and \`_triggered_by\` fields; hand-rolling those is where it breaks). Keep the checklists in local storage, and always wire a Refresh \u2014 auto-poll is host-dependent. List every tool the deck calls in its \`mcp_tools\`, and render the bridge-unavailable branch, or the pills silently show empty.
2225
+
2226
+ # Iron laws
2227
+
2228
+ - **Never invent a number.** No revenue figure, registry count, signal or lead id that didn't come from a Leadbay response or a real registry query. A modelled figure is fine \u2014 tagged \`[HYP]\` and named as an assumption. An untagged one is not.
2229
+ - **The ledger ships before the deliverable**, with un-sourceable fields shown as OMITTED rather than dropped.
2230
+ - **A conquest plan is the deliverable, not a consolation prize.** Leadbay holds no invoicing data, so the money columns are OMITTED by design. Title it honestly, name what a cash-ranked version would need \u2014 never refuse, and never fill the gap with a guess.
2231
+ - **Deliver first, ask alongside.** Do not end a turn without a ranked list of real accounts. The benchmark, the Tier-1 threshold, the territory, a missing lens, a short question set and an unanswered enrichment offer are all NON-blocking \u2014 carry them next to the plan. Only an unresolvable identity mismatch (whose plan is this?) may stop delivery.
2232
+ - **One motif per account, from the closed set of six**, with its deciding evidence stated.
2233
+ - **The org's real qualification questions**, read from Leadbay \u2014 never invented.
2234
+ - **Consent before any paid enrichment**, and never re-launch a bulk that already exists.
2235
+ - **Offer the deck; don't force it.** The chat answer must stand alone as useful.
2236
+ - Carry the captured \`lensId\` on the calls whose schema **accepts** it (\`leadbay_pull_leads\`, \`leadbay_bulk_qualify_leads\`, \`leadbay_enrich_titles\`). Do NOT add it to \`leadbay_pull_followups\`, \`leadbay_scan_portfolio_signals\`, \`leadbay_qualify_status\` or \`leadbay_bulk_enrich_status\` \u2014 they declare no such argument and reject unknown properties.
2237
+ - Building a plan is not outreaching \u2014 do not send anything and do not call \`leadbay_report_outreach\`.
1187
2238
  `;
1188
2239
  var leadbay_work_campaign = `
1189
2240
  Work my **{{arg:campaign_or_default}}** campaign as an outreach session{{arg:mode_paren}}.
@@ -1294,6 +2345,13 @@ var PROMPT_META = {
1294
2345
  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'] },
1295
2346
  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"] },
1296
2347
  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)'] },
2348
+ leadbay_getting_started: { "name": "leadbay_getting_started", "short_description": `Guided first-run walkthrough \u2014 four clicks that actually use Leadbay: check
2349
+ the account, pull today's leads, draft a first email to the top one, then
2350
+ reveal who to send it to. Use when the user is new or asks to be SHOWN how
2351
+ Leadbay works ("walk me through Leadbay", "I'm new", "how do I use this",
2352
+ "give me a tour"). Don't use it for orientation prose with no clicking \u2014
2353
+ that's leadbay_prospecting_overview.
2354
+ `, "arguments": [], "expected_calls": ["leadbay_account_status", "leadbay_pull_leads", "leadbay_prepare_outreach", "leadbay_enrich_titles", "leadbay_bulk_enrich_status"], "failure_modes": ['Presents a gate as prose ("let me know if you want me to pull your leads") instead of CALLING the host choice widget \u2014 the click IS the lesson, and prose turns the walkthrough into a lecture', "Runs a step's tool WITHOUT firing that step's widget first and waiting for the click \u2014 the walkthrough becomes an automated demo the user only watches, which is the exact opposite of learning by doing", "Fires the widget without the EXPLAIN beat, so the user gets an unexplained button and learns nothing about what a lens or an enrichment actually is", 'Answers gate 1 with a bare "you\'re connected as X at Y" when the quota IS readable \u2014 the user clicked a button labelled `check my account status`, so the quota windows (Daily/Weekly/Monthly gauges, % used, $ spent, resets) ARE the answer, not an optional extra', 'Renders quota as raw "credits" instead of the web app\'s percentage + dollar-spend gauges, or dumps raw `resource_type` strings the user has never seen', "Opens with a wall of text \u2014 previewing all four steps, explaining lenses up front, or writing several paragraphs before the first widget. The opening is TWO lines then the button; a first-run user wants to see it work, not read a syllabus", `Ends the first message without firing gate 1's widget, leaving the user to reply "ok" before anything happens`, "Rewrites the gate's own `next_steps` payload (its `question`, `label` or `description`) instead of mapping it into the widget verbatim, or merges two gates into a single multi-option widget", 'Fires a THIRD option, or turns the exit into an alternative route ("show me my lenses instead") \u2014 each gate carries exactly one forward action plus the `I\'m done for now` exit, never a menu of paths', `Fires a single-option widget \u2014 the host requires 2\u20134 options, so a lone option is rejected or silently degrades to prose ("say the word and I'll check it"), which is the exact defect this rule exists to prevent`, 'Launches the PAID reveal at gate 4 BEFORE the user has picked leads and confirmed \u2014 beat 1 must be the free `mode:"discover"` preview (no `titles`, no `confirm`, no `email`, no `phone`); the gate click bought the free look, not the reveal, and silence is never consent', "Stops at the free preview after the user DID pick leads and confirm \u2014 they asked for real contact details, so the second call must actually run with `confirm:true` and the chosen titles", "Reports the enrichment without polling `leadbay_bulk_enrich_status` to completion, so it claims contacts it never actually saw resolve", `Reveals contacts and never says what it cost \u2014 the user just spent credits and deserves the one-line "N contacts = N credits", which is also what makes gate 1's quota numbers concrete`, 'Reports "no leads" on an empty batch while `computing_wishlist` / `computing_scores` is true \u2014 the lens is still building; render the tool\'s own two-option warm-up widget verbatim and pause', "Rewords, reorders or prose-ifies the `next_steps` payload from `leadbay_pull_leads` instead of mapping `options[]` into the widget verbatim", "Runs all four steps in one turn without waiting for the user's click between gates \u2014 the walkthrough is a sequence of gates, not a script to recite", "Skips `leadbay_pull_leads` and jumps straight to enrichment, leaving gate 4 with no `leadIds` to scope", "Passes a singular `leadId` to `leadbay_enrich_titles` on the confirmed reveal \u2014 that key does not exist on this tool, so it is dropped and the paid call falls back to the whole default wishlist selection, charging for far more than the one lead the user agreed to. it is always the `leadIds` ARRAY, even for a single lead", "Drops the pinned `lens.id` between gates, so gate 4 enriches against a different lens than the one the user just saw", "Ends the completed walkthrough without the `keep_going` cheat-sheet \u2014 the buttons disappear with the tour, so a user who was never told what to TYPE learned to click a tutorial and nothing about using Leadbay tomorrow", "Invents phrases for the cheat-sheet, or rewords them into something that sounds nicer but doesn't match the tool's real triggers \u2014 teaching a phrase that doesn't route is worse than teaching none", "SENDS the gate 3 draft, or offers to send it \u2014 the walkthrough drafts and stops there; the email is the user's to judge, and nothing leaves the chat", "Passes `enrich:true` to `leadbay_prepare_outreach` at gate 3 \u2014 that launches a PAID contact reveal off the back of a DRAFT click, spending credits the user never agreed to", "Invents a contact NAME for the gate 3 draft \u2014 `recommended_contact` still has null email/name at that point, so the draft is addressed to the job TITLE; a fabricated name is the one thing that makes the whole draft untrustworthy", "Treats the null email at gate 3 as a failure \u2014 apologising for it, retrying, or calling another tool to fill it in. It is the setup for gate 4 \u2014 an email written, nobody to send it to yet", "Pastes the drafted email into chat prose alongside `message_compose_v1` instead of letting the composer BE the answer", "Enriches leads other than the one it drafted for at gate 3 \u2014 gate 4 reveals the person that email is going to, so it is scoped to that ONE lead, one contact, one credit", "Renders the cheat-sheet on the exit and stops there, dropping the 1:1 offer \u2014 the observed failure is that the agent feels finished once the table is on screen, so the user who just stepped out never hears about the help that would bring them back. ENDING B is not complete without the offer, and the offer goes LAST", "Treats the exit click as ENDING C (typed off-script) and closes in silence, or treats a typed request as ENDING B and buries their real answer under a cheat-sheet and a booking link", "Turns the exit offer into a pitch \u2014 several sentences, a re-opened gate, or an argument for finishing the tour. They said they were done; it is one line and a link", "Fires the 1:1 offer mid-tour, or at a user who left by TYPING a different request \u2014 a booking link on top of their real question is an interruption, not an offer", "Runs the four gates at a user whose actual problem is SETUP \u2014 the connector isn't installed, they can't sign in, or their Leadbay tools aren't appearing. The tour assumes a working connection and cannot fix any of it; the setup guide can", "Pastes the setup-guide link mid-tour, between gates, instead of once at the closing \u2014 a link in the middle of the walkthrough invites the user to leave the thing they're doing"] },
1297
2355
  leadbay_import_file: { "name": "leadbay_import_file", "short_description": "Import a user-supplied CSV/file into Leadbay through five phases with\nevidence gates \u2014 scan, derive, resolve identities, preserve & commit,\nthen optionally qualify and report. The job is to maximize how many\nrows the Leadbay system actually ingests and matches.\n", "arguments": [{ "name": "file", "description": "Path or user-visible name of the CSV/file to import. If omitted, use the file the user attached or referenced.", "required": false }, { "name": "instruction", "description": 'Additional user goal, e.g. "then qualify the leads", "preserve owner phone as a custom field", or "only import restaurants in Manhattan".', "required": false }], "expected_calls": ["leadbay_resolve_import_rows", "leadbay_list_mappable_fields", "leadbay_create_custom_field", "leadbay_import_leads", "leadbay_import_and_qualify", "leadbay_add_note", "leadbay_import_status"], "failure_modes": ["Picks LEADBAY_ID from score alone, name-only, fuzzy-name-only, root-domain-only, brand-only, postcode-only, or city-only evidence", "Drops meaningful business notes or CRM record links instead of preserving them as custom fields or lead notes", "Treats a consumer mailbox domain (gmail.com, hotmail.com, ...) as the company domain", "Skips deriving company_domain from a business email when no website column exists (this kills match rate)", "Skips the COLUMN PRESERVATION PLAN byproduct before importing", "Skips the DECISION LOG byproduct before writing LEADBAY_ID", "Returns the imported records WITHOUT writing LEADBAY_ID values back into the user's file (leaves the user no audit trail of what matched)", "Fabricates leadIds, contact emails, or mapping IDs not present in the file or a tool response"] },
1298
2356
  leadbay_log_outreach: { "name": "leadbay_log_outreach", "short_description": "Log outreach (an email I sent, a call I made, a meeting I had) on a\nspecific lead. Captures verification so the SDR pipeline trusts the entry.\n", "arguments": [{ "name": "lead_id", "description": "The lead UUID. Get it from leadbay_pull_leads or leadbay_research_lead_by_id.", "required": true }, { "name": "summary", "description": "1-2 sentences describing what I did (e.g. 'Sent intro email to CTO citing recent Hornsea contract').", "required": true }], "expected_calls": ["leadbay_report_outreach"], "failure_modes": ["Calls leadbay_report_outreach without first collecting a verification source", "Fabricates a gmail_message_id or calendar_event_id (the human team treats verification as canonical)", "Records outreach to a different lead_id than the one the user supplied", "Skips the dry_run step when the user is unsure what would be sent"] },
1299
2357
  leadbay_plan_tour_in_city: { "name": "leadbay_plan_tour_in_city", "short_description": 'Use whenever the user names a city they\'ll be in and asks who to see\n\u2014 "I\'m in SF next Tuesday, who\'s worth meeting?", "I\'m going to Berlin\n\u2014 who should I visit?", "plan my <city> tour". Any in-person/visit\nintent tied to a place routes here, NOT to `leadbay_pull_leads`. It\nsurfaces follow-ups + fresh Discover leads in the city via\n`leadbay_tour_plan`, ALWAYS offers to plot them on a map (rendering it\non yes), then offers outreach drafts + campaign persistence.\n', "arguments": [{ "name": "city", "description": "City or region the user is visiting (e.g. 'Limoges', 'Bay Area'). Used as the geo filter for both Monitor and Discover lookups.", "required": true }, { "name": "date", "description": "When the visit is (e.g. 'May 24', 'next Thursday'). Surfaced in the outreach drafts as 'I'll be in <city> on <date>'.", "required": false }], "expected_calls": ["leadbay_tour_plan", "leadbay_research_lead_by_id", "leadbay_prepare_outreach", "leadbay_create_campaign"], "failure_modes": ["Calls leadbay_followups_map (Monitor-only) instead of leadbay_tour_plan \u2014 loses the Discover (fresh-lead) half that the user explicitly asked for", "Calls leadbay_pull_leads then drops the geo filter \u2014 returns the lens-wide wishlist instead of city-relevant fresh leads", 'Skips the campaign-persist step ("would you like to save these as a tour?") \u2014 leaves the rep with a one-shot map but no follow-up artifact', "Creates a campaign WITHOUT asking the user first \u2014 the persist step is high-intent; offer it, don't assume", "Fabricates lead_ids when seeding the campaign instead of using the ids returned by tour_plan"] },
@@ -1308,6 +2366,7 @@ should I follow up on" to "I'll send via lemlist".
1308
2366
  leadbay_refine_audience: { "name": "leadbay_refine_audience", "short_description": "Refine the kind of leads Leadbay surfaces beyond firmographics, with a\nfree-text instruction. Handles the clarification round-trip if the new\nprompt is ambiguous.\n", "arguments": [{ "name": "instruction", "description": "The refinement (e.g. 'focus on hospitals running their own IT'). Set to plain English.", "required": true }], "expected_calls": ["leadbay_refine_prompt", "leadbay_account_status"], "failure_modes": ["Calls leadbay_answer_clarification on the user's behalf instead of surfacing the clarification verbatim", "Glosses over the clarification options instead of presenting them as offered", "Promises immediate effect when status='applied' actually triggers an async intelligence recompute"] },
1309
2367
  leadbay_research_a_domain: { "name": "leadbay_research_a_domain", "short_description": "Resolve a company by name or domain across the user's visible Discover,\nMonitor, and Activate corpus, then return everything Leadbay knows about it.\n", "arguments": [{ "name": "domain", "description": "Company name or domain (for example 'Acme Corporation' or 'acme.com'). The legacy argument key remains `domain` for client compatibility.", "required": true }], "expected_calls": ["leadbay_research_lead_by_name_fuzzy"], "failure_modes": ["Fabricates qualification answers not present in any tool response", "Calls leadbay_import_and_qualify before searching the existing visible corpus", "Treats the active lens as the entire search universe when the user did not request a lens scope", "Imports a missing company without the user's explicit permission", "Renders the research result as a freeform narrative instead of the canonical research-company-card layout (the card with header score bar, pill row, signal sections, contacts table is the structural contract; commentary belongs ABOVE or BELOW it)"] },
1310
2368
  leadbay_setup_team_prospecting: { "name": "leadbay_setup_team_prospecting", "short_description": "Manager-led prospecting setup: conversationally turn a natural-language\naudience ask into a Leadbay lens, validate the candidate leads, and\npersist them as one or more named campaigns the rep(s) can work\nthrough. Closes #3630 US3 end-to-end (within the current\ncreator-scoped campaign visibility model).\n", "arguments": [{ "name": "audience", "description": "Natural-language audience description (e.g. 'plumbing companies with 10-50 employees in Seine-Maritime'). The lens-creation step (`leadbay_refine_prompt` \u2192 `leadbay_create_lens`) interprets it.", "required": true }, { "name": "rep_split", "description": "Optional: how to split the validated leads into per-rep campaigns. Free text \u2014 e.g. 'split by city' or 'one campaign per rep: John gets Tulsa, Sarah gets OKC'.", "required": false }], "expected_calls": ["leadbay_refine_prompt", "leadbay_create_lens", "leadbay_promote_lens", "leadbay_pull_leads", "leadbay_research_lead_by_id", "leadbay_create_campaign", "leadbay_add_leads_to_campaign"], "failure_modes": ["Skips the validation step \u2014 creates a campaign of unvetted leads from a freshly-created lens without giving the manager a chance to drop weak fits", "Creates ONE campaign for all reps without asking about the split \u2014 the user explicitly mentioned per-rep distribution and the prompt should honor it", "Pretends the backend supports cross-user assignment \u2014 campaigns are owned by the caller (creator-scoped). Surface this honestly instead of fabricating an assignment model", "Asks ALL clarifying questions inline before tool calls \u2014 instead, run the lens refinement loop with `leadbay_refine_prompt` which handles the clarification protocol natively"] },
2369
+ leadbay_top_accounts_to_activate: { "name": "leadbay_top_accounts_to_activate", "short_description": 'Build a ranked account-conquest plan from Leadbay data \u2014 the accounts worth\nactivating, each with a motif, a pitch and a checklist, ranked by the\nstrongest Leadbay signal. Every figure carries its source, and anything\nLeadbay can\'t measure is shown as OMITTED rather than estimated. Uses\n`leadbay_bulk_qualify_leads` and `leadbay_enrich_titles`. Trigger on\n"top 50 accounts to activate", "who should we go after".\n', "arguments": [{ "name": "count", "description": "Optional: how many accounts the plan should hold (default 50).", "required": false }, { "name": "territory", "description": "Optional: restrict the plan to a territory (e.g. 'Indre-et-Loire', 'R\xE9gion Ouest'). Sets geography on the Discover lens.", "required": false }], "expected_calls": ["leadbay_account_status", "leadbay_get_qualification_questions", "leadbay_pull_leads", "leadbay_pull_followups", "leadbay_bulk_qualify_leads", "leadbay_qualify_status", "leadbay_scan_portfolio_signals", "leadbay_enrich_titles", "leadbay_bulk_enrich_status", "leadbay_account_history", "leadbay_artifact_kit", "leadbay_new_lens", "leadbay_adjust_audience"], "failure_modes": ["Invents, estimates or proxies a revenue-realized figure \u2014 the single worst failure. Leadbay does not hold what an account buys, and headcount, sector and lead score are NOT proxies for it.", "Sorts by cash-to-capture, synthesizing a revenue figure per account purely to make that ranking work. Leadbay has no revenue data: rank by the Leadbay signal, say so in the header, and never invent the key.", "Emits \u20AC figures with no provenance class, so modelled numbers read as measured fact in front of a paying client.", "Skips the PROVENANCE LEDGER, or drops un-sourceable fields from it instead of rendering them as OMITTED \u2014 which hides the gap.", "Fabricates registry/TAM counts (France or regional company counts) instead of querying the registry or marking the figure NOT COMPUTED. Leadbay does not proxy SIRENE.", "Invents the five qualification questions from this prompt's own recommendations instead of reading the org's actual questions via leadbay_get_qualification_questions.", "Leaves the deck's live layer dead \u2014 qualification and enrichment handles never wired in, so the pills and contacts stay empty while the deck still looks finished.", "Invents lead ids to make the qualification pills appear populated.", "Fabricates a plausible-sounding signal ('just won a public tender') for an account whose signals were never read. No signal read means an explicit dash.", "Assigns a motif outside the closed set of six, or assigns SAUVETAGE / PLAN DE COMPTE / MONT\xC9E EN GAMME / R\xC9VEIL from a lead score or sector when order history was never available.", "Labels Monitor membership as 'is a client' \u2014 Monitor is a Leadbay view whose membership is decided by lens scoring, not by whether the company ever bought anything.", "Launches paid enrichment on the whole plan without consent. Asking for a plan is not authorization to spend on 50 accounts.", "Re-launches enrichment from inside the built deck when a bulk handle already exists this session \u2014 double-spends the user's quota.", "Forces the interactive deck without offering it first, or ships the deck INSTEAD of a chat answer that stands on its own.", "Refuses the task because revenue data is missing, instead of delivering the conquest plan and naming what a cash-ranked version would need.", "Ends the turn without a ranked list of real accounts \u2014 gating the whole plan on a NON-blocking question (the territory, a missing lens, or a 3-vs-5 qualification-question gap) so the user gets a plan-of-a-plan. Only an unresolvable company-identity mismatch may stop delivery; every other open question rides alongside the delivered plan.", "Stops after the discovery contact preview to wait for enrichment consent, delivering no plan that turn \u2014 the ranked plan ships first; the paid reveal is offered alongside it.", "Renders a contact channel enrichment never returned (e.g. a phone link when only email was approved and revealed) instead of showing the returned channels and marking the rest omitted."] },
1311
2370
  leadbay_work_campaign: { "name": "leadbay_work_campaign", "short_description": "Work a campaign as a real outreach session: pick the campaign,\nassess what the user has (phones / emails / coords), then PROPOSE\nthe right session mode (call sheet, email sheet, enrich titles\nfirst, map). After they pick, render \u2014 and as they dictate\noutcomes per lead, record both note + epilogue via\n`leadbay_report_outreach` in one round trip.\n", "arguments": [{ "name": "campaign", "description": "Campaign name (fuzzy match against your own campaigns) or campaign UUID. Omit to list and pick interactively.", "required": false }, { "name": "mode", "description": "Optional: skip the readiness-assessment proposal and jump directly into 'call_sheet' / 'email_sheet' / 'map' / 'enrich_first'. Omit (recommended) and let the prompt propose based on the data.", "required": false }], "expected_calls": ["leadbay_list_campaigns", "leadbay_campaign_call_sheet", "leadbay_enrich_titles", "leadbay_report_outreach"], "failure_modes": ["Renders the call sheet immediately without proposing the right mode \u2014 if 60% of leads have no contacts, calling is futile; enrich first. Always assess `readiness` first.", "Auto-renders the map widget without asking \u2014 maps are intrusive when the user just wants to scroll a list. Map mode is a proposed option, not a default.", "Proposes map mode after the user has previously said they don't like maps \u2014 check conversation memory before adding 'View on a map' to the options list.", "Calls `leadbay_campaign_progression` instead of `leadbay_campaign_call_sheet` \u2014 progression has counts but no phones / LinkedIn / call-ready data; the user can't actually dial from progression rows.", "Renders contacts WITHOUT making the phone number a `[bare](tel:URL)` link \u2014 on mobile that breaks one-tap calling, which is the whole point of the cheat sheet.", "Records outreach WITHOUT epilogue_status \u2014 leaves the lead's pipeline state unchanged; the rep then sees the same lead surfaced again next session.", "Records outreach WITHOUT verification \u2014 verification.source/ref is REQUIRED. For calls, pass `{source: 'user_confirmed', ref: <user's exact words>}`.", "Loops through ALL leads in a 50-lead campaign before recording any outreach \u2014 the call-then-record loop must be per-lead, not batched."] }
1312
2371
  };
1313
2372
  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.`;
@@ -1316,6 +2375,7 @@ var PROMPT_CATALOG_BULLETS = {
1316
2375
  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\`.`,
1317
2376
  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.`,
1318
2377
  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\`.`,
2378
+ leadbay_getting_started: `- \`leadbay_getting_started\`: Guided first-run walkthrough \u2014 four clicks that actually use Leadbay: check the account, pull today's leads, draft a first email to the top one, then reveal who to send it to. Use when the user is new or asks to be SHOWN how Leadbay works ("walk me through Leadbay", "I'm new", "how do I use this", "give me a tour"). Don't use it for orientation prose with no clicking \u2014 that's leadbay_prospecting_overview.`,
1319
2379
  leadbay_import_file: `- \`leadbay_import_file\` (optional args: file, instruction): Import a user-supplied CSV/file into Leadbay through five phases with evidence gates \u2014 scan, derive, resolve identities, preserve & commit, then optionally qualify and report. The job is to maximize how many rows the Leadbay system actually ingests and matches.`,
1320
2380
  leadbay_log_outreach: `- \`leadbay_log_outreach\` (required args: lead_id, summary): Log outreach (an email I sent, a call I made, a meeting I had) on a specific lead. Captures verification so the SDR pipeline trusts the entry.`,
1321
2381
  leadbay_plan_tour_in_city: `- \`leadbay_plan_tour_in_city\` (required args: city; optional args: date): Use whenever the user names a city they'll be in and asks who to see \u2014 "I'm in SF next Tuesday, who's worth meeting?", "I'm going to Berlin \u2014 who should I visit?", "plan my <city> tour". Any in-person/visit intent tied to a place routes here, NOT to \`leadbay_pull_leads\`. It surfaces follow-ups + fresh Discover leads in the city via \`leadbay_tour_plan\`, ALWAYS offers to plot them on a map (rendering it on yes), then offers outreach drafts + campaign persistence.`,
@@ -1324,6 +2384,7 @@ var PROMPT_CATALOG_BULLETS = {
1324
2384
  leadbay_refine_audience: `- \`leadbay_refine_audience\` (required args: instruction): Refine the kind of leads Leadbay surfaces beyond firmographics, with a free-text instruction. Handles the clarification round-trip if the new prompt is ambiguous.`,
1325
2385
  leadbay_research_a_domain: `- \`leadbay_research_a_domain\` (required args: domain): Resolve a company by name or domain across the user's visible Discover, Monitor, and Activate corpus, then return everything Leadbay knows about it.`,
1326
2386
  leadbay_setup_team_prospecting: `- \`leadbay_setup_team_prospecting\` (required args: audience; optional args: rep_split): Manager-led prospecting setup: conversationally turn a natural-language audience ask into a Leadbay lens, validate the candidate leads, and persist them as one or more named campaigns the rep(s) can work through. Closes #3630 US3 end-to-end (within the current creator-scoped campaign visibility model).`,
2387
+ leadbay_top_accounts_to_activate: `- \`leadbay_top_accounts_to_activate\` (optional args: count, territory): Build a ranked account-conquest plan from Leadbay data \u2014 the accounts worth activating, each with a motif, a pitch and a checklist, ranked by the strongest Leadbay signal. Every figure carries its source, and anything Leadbay can't measure is shown as OMITTED rather than estimated. Uses \`leadbay_bulk_qualify_leads\` and \`leadbay_enrich_titles\`. Trigger on "top 50 accounts to activate", "who should we go after".`,
1327
2388
  leadbay_work_campaign: `- \`leadbay_work_campaign\` (optional args: campaign, mode): Work a campaign as a real outreach session: pick the campaign, assess what the user has (phones / emails / coords), then PROPOSE the right session mode (call sheet, email sheet, enrich titles first, map). After they pick, render \u2014 and as they dictate outcomes per lead, record both note + epilogue via \`leadbay_report_outreach\` in one round trip.`
1328
2389
  };
1329
2390
 
@@ -1568,6 +2629,42 @@ var CATALOG = [
1568
2629
  )
1569
2630
  ];
1570
2631
  }
2632
+ },
2633
+ {
2634
+ name: "leadbay_top_accounts_to_activate",
2635
+ description: PROMPT_META.leadbay_top_accounts_to_activate.short_description,
2636
+ arguments: [
2637
+ {
2638
+ name: "count",
2639
+ description: "Optional: how many accounts the plan should hold (default 50).",
2640
+ required: false
2641
+ },
2642
+ {
2643
+ name: "territory",
2644
+ description: "Optional: restrict the plan to a territory (e.g. 'Indre-et-Loire'). Sets geography on the Discover lens via `locations`.",
2645
+ required: false
2646
+ }
2647
+ ],
2648
+ render: (args) => {
2649
+ const n = args.count ?? "50";
2650
+ return [
2651
+ userMessage(
2652
+ substitutePlaceholders(leadbay_top_accounts_to_activate, {
2653
+ count_or_default: n,
2654
+ territory_block: args.territory ? `Scope the plan to **${args.territory}** \u2014 pass it as \`locations\` on the lens, never as a sector.` : ""
2655
+ })
2656
+ )
2657
+ ];
2658
+ }
2659
+ },
2660
+ {
2661
+ // Guided first-run walkthrough (issue #3952). No arguments — the tour is
2662
+ // the same for every new user, and asking a brand-new user to parameterize
2663
+ // their own onboarding defeats the point.
2664
+ name: "leadbay_getting_started",
2665
+ description: PROMPT_META.leadbay_getting_started.short_description,
2666
+ arguments: [],
2667
+ render: () => [userMessage(leadbay_getting_started)]
1571
2668
  }
1572
2669
  ];
1573
2670
  function listPrompts() {
@@ -6751,6 +7848,7 @@ var COMPOSITE_FILE_TOOL_NAMES = /* @__PURE__ */ new Set([
6751
7848
  "leadbay_followups_map",
6752
7849
  "leadbay_get_lead_custom_fields",
6753
7850
  "leadbay_get_qualification_questions",
7851
+ "leadbay_getting_started",
6754
7852
  "leadbay_import_and_qualify",
6755
7853
  "leadbay_import_leads",
6756
7854
  "leadbay_import_status",
@@ -8345,6 +9443,107 @@ WHEN TO USE: when the agent already qualified this lead and wants the underlying
8345
9443
 
8346
9444
  WHEN NOT TO USE: as the first read on a lead \u2014 the leadbay_research_lead_by_id composite bundles this with qualification answers and reshapes the dict into a stable array form.
8347
9445
  `;
9446
+ var leadbay_getting_started2 = `## WHEN TO USE
9447
+
9448
+ Trigger phrases: "walk me through leadbay", "I'm new", "how do I use this", "getting started", "show me how this works", "give me a tour", "help me get started", "I just installed this".
9449
+
9450
+ **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
9451
+
9452
+ Do NOT use for: "show me today's leads" \u2192 \`leadbay_pull_leads\`; "which audiences do I have" \u2192 \`leadbay_my_lenses\`; "where am I / what's my plan and quota" \u2192 \`leadbay_account_status\`.
9453
+
9454
+ Prefer when: the user has never used Leadbay, or asks to be SHOWN rather than told \u2014 the walkthrough runs real calls on their own account
9455
+
9456
+ Examples that SHOULD invoke this tool:
9457
+ - "Walk me through Leadbay."
9458
+ - "I'm new here \u2014 how do I use this?"
9459
+ - "Can you show me how this works?"
9460
+
9461
+ Examples that should NOT invoke this tool (sound similar, route elsewhere):
9462
+ - "Show me today's leads."
9463
+ - "Explain the difference between discovery and follow-up."
9464
+ - "Which of my lenses is active right now?"
9465
+
9466
+ ## RENDER (quick)
9467
+
9468
+ Not a data table. Run the walkthrough ONE gate at a time: fire your host's
9469
+ choice widget with that step's forward option + exit, wait for the click, make that
9470
+ step's tool call, then advance. Never dump all four steps at once, and never
9471
+ render a gate as a prose question.
9472
+
9473
+ ---
9474
+
9475
+ Returns the **guided first-run walkthrough** \u2014 a short script the agent drives so a brand-new user learns Leadbay by *doing*, not by reading. Makes no backend call and mutates nothing; the content is static and version-locked.
9476
+
9477
+ Every click in the walkthrough runs a real Leadbay call against the user's own account. By the end they have confirmed which account they're on, pulled today's leads, had a first email drafted to the best of them, and revealed the person to send it to. Every gate calls a real Leadbay tool \u2014 the tour ends where Leadbay's own value ends.
9478
+
9479
+ For orientation **prose** with no clicking \u2014 "explain how Leadbay works", "what's the difference between discovery and follow-up" \u2014 this tool is the wrong answer; that's the \`leadbay_prospecting_overview\` prompt.
9480
+
9481
+ And when the problem is **setup** rather than usage \u2014 the connector isn't installed, they can't sign in, their Leadbay tools aren't appearing, or they want to run this on another host \u2014 the walkthrough can't help either: it assumes a working connection, and step 1 is what proves it. Send them to the setup guide the manifest carries as \`docs_url\`: <https://docs.leadbay.app/doc/leadbay-mcp/quickstart>. Its \`docs_note\` names the only two moments the link should appear \u2014 that pre-check, and once at the closing. Never between gates.
9482
+
9483
+ ## THE ONE-FORWARD-OPTION RULE
9484
+
9485
+ Every gate carries **exactly one way forward, plus a way out** \u2014 two options, never more: the action, and \`I'm done for now\`.
9486
+
9487
+ A first-run user doesn't yet know enough to choose between PATHS \u2014 a menu of alternatives makes them stall. One forward move makes the next step obvious, and the click is what teaches the tool. The exit keeps the tour from being a trap and satisfies the host widget's 2\u20134 option requirement: a lone option is rejected or silently degrades to prose. Never add a third option, and never turn the exit into an alternative route. Typing works too: if they type something off-script, abandon the walkthrough and serve what they asked.
9488
+
9489
+ ## What it returns
9490
+
9491
+ \`\`\`
9492
+ {
9493
+ version, intro, one_option_rule, docs_url, docs_note,
9494
+ calendly_url, exit_offer,
9495
+ steps: [ { n, gate_label, gate_description, calls, args, ... } ],
9496
+ keep_going, stop
9497
+ }
9498
+ \`\`\`
9499
+
9500
+ Per step: \`gate_label\` / \`gate_description\` are the widget's forward option, \`calls\` is the tool to invoke on click (or \`null\`), and \`args\` is the literal argument shape. Render each \`gate_label\` verbatim \u2014 don't reword them.
9501
+
9502
+ | Step | Gate | Calls |
9503
+ |---|---|---|
9504
+ | 1 | Check my account | \`leadbay_account_status\` (no args) |
9505
+ | 2 | Pull today's leads | \`leadbay_pull_leads\` (no args) |
9506
+ | 3 | Draft the first email | \`leadbay_prepare_outreach\` \u2014 \`leadId\` ONLY, never \`enrich\` |
9507
+ | 4 | Find who to email | \`leadbay_enrich_titles\` \u2014 free preview, then a consented paid reveal |
9508
+
9509
+ Steps 1, 2 and 3 carry \`branches[]\`, and steps 3 and 4 carry \`spend\` (+ \`quota_note\` on 4). Every step also carries \`explain\` (say this BEFORE firing) and \`next_steps\` (\`{question, options[]}\` \u2014 already the widget's shape, map it verbatim).
9510
+
9511
+ When the user picks \`I'm done for now\`, don't just go quiet \u2014 **\`exit_offer\`** says what to do: one short line offering a 1:1 with Zoe (lens tuning, CRM wiring, automating the daily run) plus **\`calendly_url\`**, then stop. One sentence and the link, never a pitch, never a re-opened gate. Only on the EXIT click: if they left by *typing* a different request, skip it and serve what they asked.
9512
+
9513
+ The manifest also carries **\`keep_going\`**: the closing cheat-sheet of *what you want \u2192 what you say*. The buttons vanish when the tour ends, so render these rows as a small two-column table at the finish, phrases **verbatim**. Each one is lifted from that tool's own trigger list, so it genuinely routes \u2014 inventing or prettifying a phrase teaches the user something that won't work. Add \`docs_url\` beneath it as one plain link, for what the gates didn't cover.
9514
+
9515
+ ## Three hard rules the manifest encodes
9516
+
9517
+ **Step 1 shows the real account, and is silent about two things.** The click is labelled *check my account status*, so deliver it: user + org, then the **full quota windows** the way the web app renders them \u2014 Daily / Weekly / Monthly with a \`\u25B0\u25B1\` gauge, % used, $ spent against the cap, resets countdown, and the per-resource breakdown. Never raw "credits". But apply the silence gate first: when \`quota\` is null, \`quota_error\` is set, or the org has \`unlimited_credits\`, say **nothing** about quota \u2014 never mention a 401, never suggest logging in again (the token is fine, the same response just read their account), and never announce "unlimited". And **never volunteer the lens**: the response withholds it unless the user asked, so there is nothing to report and no other tool to reach for. Both are pinned regressions (WORKFLOWS #30 / #31).
9518
+
9519
+ **Step 3 drafts, and spends nothing.** Call \`leadbay_prepare_outreach\` with \`leadId\` alone \u2014 **never \`enrich: true\`**, which launches a paid contact reveal off the back of a *draft* click. \`recommended_contact\` returns with \`email\`/\`phone\` null; that is expected, and it is the hook for step 4. Render through \`message_compose_v1\` (2\u20133 strategy-labelled variants), address it to the job TITLE \u2014 no name exists yet, and inventing one is fabrication \u2014 and never send it or offer to.
9520
+
9521
+ **Step 4 runs in two beats \u2014 free first, paid only on consent.** Scoped to the ONE lead step 3 drafted for. Beat 1 omits \`titles\` and returns \`mode:"discover"\`, the free list of job titles at that company; say plainly that nothing has been spent. Beat 2 names the title the draft is addressed to, states the cost BEFORE they decide (one contact, one credit), and only on confirmation calls again with \`titles\` + \`confirm:true\` + \`email:true\` \u2014 polled via \`leadbay_bulk_enrich_status\` until done, reporting only what actually resolved. The gate click bought the free look, not the reveal: never launch without an explicit confirm.
9522
+
9523
+ ## Empty first batch is normal, not an error
9524
+
9525
+ A brand-new lens reads empty for the first minute while the backend computes its wishlist. When \`leadbay_pull_leads\` returns no leads but \`computing_wishlist\` / \`computing_scores\` is true, the lens is warming up: render that tool's own two-option warm-up payload verbatim and pause. **Never report "no leads found"** in that state.
9526
+
9527
+ ## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
9528
+
9529
+ 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.
9530
+
9531
+ **The Big Three** \u2014 when a tool result fits, route there:
9532
+
9533
+ | Host widget | Use when | Field map (from Leadbay payload) |
9534
+ |---|---|---|
9535
+ | \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
9536
+ | \`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") |
9537
+ | \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
9538
+
9539
+ 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.
9540
+
9541
+ **Rules:**
9542
+ - 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.
9543
+ - Pass identifiers (place_id, lead.id, contact_id) verbatim. Don't rewrite.
9544
+ - 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.
9545
+ - One short intro sentence in chat is enough \u2014 "Here are your 5 NYC follow-ups." Then route into the widget.
9546
+ `;
8348
9547
  var leadbay_import_and_qualify = `Import + qualify leads in one call. Pass either \`domains: [{domain, name?}]\` (Mode A) OR \`records[]\` with \`mappings\` (Mode B). At least one mapped field must be LEADBAY_ID, CRM_ID, SIREN, LEAD_NAME, or LEAD_WEBSITE. Discover the org's mappable surface via \`leadbay_list_mappable_fields\`. For messy files, prefer the \`leadbay_import_file\` prompt which walks an agent through scan \u2192 resolve \u2192 preserve \u2192 commit phases.
8349
9548
 
8350
9549
  WHEN TO USE: agent has a list of companies (domains, or CSV-shaped rows from the user's CRM) and wants the full AI qualification \u2014 qualification answers, web-research signals \u2014 without orchestrating import + bulk_qualify_leads + lead_profile chains by hand.
@@ -17845,6 +19044,197 @@ var getQualificationQuestions = {
17845
19044
  }
17846
19045
  };
17847
19046
 
19047
+ // ../core/dist/composite/getting-started.js
19048
+ var ONE_OPTION_RULE = "Every gate presents exactly ONE way forward, plus a way out \u2014 two options, never more: the action, and 'I'm done for now'. A first-run user does not yet know enough to choose between PATHS; one forward move makes the next step obvious, and the click is what teaches them the tool. The exit keeps the tour from being a trap, and satisfies the host widget's 2-4 option requirement \u2014 a lone option is rejected or silently degrades to prose, which kills the feature. Never add a third option, and never turn the exit into an alternative route ('show me my lenses instead'), which reintroduces the choice this rule removes. The gate IS the widget: never render it as a prose question \u2014 'say the word and I'll check it' is a defect, not a gate. Typing also works: if the user types something off-script, abandon the walkthrough and serve what they asked.";
19049
+ var DOCS_QUICKSTART = "https://docs.leadbay.app/doc/leadbay-mcp/quickstart";
19050
+ var DOCS_NOTE = "Surface this link in exactly TWO moments and nowhere else. (1) BEFORE the tour, when the user's problem is SETUP rather than usage \u2014 the connector isn't installed, they can't sign in, their Leadbay tools aren't appearing, or they want to run this on another host. The walkthrough cannot fix any of that: it assumes a working connection, and gate 1 is what proves it. Point them at the page instead of guessing at install steps. (2) At the CLOSING, as one plain link beside the keep_going cheat-sheet, for what the four gates didn't cover \u2014 installing on another machine, adding a teammate, signing in again later. NEVER paste it between gates: a link mid-tour is an invitation to leave the thing they're in the middle of doing.";
19051
+ var ZOE_CALENDLY = "https://calendly.com/zoe-leadbay/demo-leadbay";
19052
+ var EXIT_OFFER = "Picking 'I'm done for now' is ENDING B, and it has THREE beats in order: (1) one short line acknowledging the stop, (2) the keep_going cheat-sheet and the docs_url link, (3) the 1:1 offer \u2014 LAST, and REQUIRED. Beat 3 is the one that gets dropped: an agent that renders the cheat-sheet feels finished and stops, so the user who just stepped out never hears about the help that would have brought them back. An exit close WITHOUT the offer is incomplete. ONE SENTENCE and calendly_url, e.g. 'If you want a hand tuning this to your own market, Zoe on our team runs 1:1 sessions: <url>'. That length is the rule, not a suggestion: anything longer reads as a pitch. Do NOT enumerate everything Zoe could help with \u2014 that turns an offer into promotional copy, which is exactly what someone who just said they were done does not want. Never re-open the walkthrough, never re-fire the declined gate, and never argue for finishing the tour. If they instead left by TYPING something off-script that is ENDING C, not B \u2014 serve what they asked and skip the cheat-sheet, the link AND the offer.";
19053
+ var EXIT_OPTION = {
19054
+ label: "I'm done for now",
19055
+ description: "Stop the walkthrough here.",
19056
+ kind: "walkthrough_exit"
19057
+ };
19058
+ var INTRO = "Open with a SHORT paragraph \u2014 3-4 sentences, then the widget, all in your first message. Cover, in the user's own language and without jargon: (1) what Leadbay is \u2014 it brings you a fresh batch of companies worth selling to every day, rather than you hunting for them; (2) how it knows what to send \u2014 you describe who you sell to (that description is your LENS) and it goes and finds companies matching it, learning from what you engage with; (3) what this walkthrough will do \u2014 four quick steps, each one a real action on their own account, ending with leads in hand, a first email already written, and the person to send it to; (4) one line handing off to the first step, e.g. 'First, let's see which account you're on.' Then fire gate 1's widget immediately and stop. Keep it to a paragraph \u2014 do NOT walk through the four steps one at a time here (each gate explains itself when its turn arrives), and call no tool in the opening.";
19059
+ var KEEP_GOING = [
19060
+ { want: "Today's fresh leads", say: "Show me today's leads" },
19061
+ { want: "Who to follow up with", say: "What should I follow up on" },
19062
+ { want: "The story on one company", say: "Research <Company>" },
19063
+ { want: "An email to a contact", say: "Draft outreach for <Contact>" },
19064
+ { want: "Change who you target", say: "Narrow the audience to <sector>" },
19065
+ { want: "Switch target audience", say: "Show me my lenses" }
19066
+ ];
19067
+ var STOP = "The walkthrough DRAFTS an email at gate 3 but never SENDS one. The draft stays in the chat for the user to read and judge; nothing leaves. Never send it, never offer to send it on their behalf, and never call leadbay_report_outreach \u2014 logging an outreach that never happened poisons the human team's pipeline. End by waiting for the user.";
19068
+ var GETTING_STARTED_MANIFEST = {
19069
+ version: 1,
19070
+ intro: INTRO,
19071
+ one_option_rule: ONE_OPTION_RULE,
19072
+ docs_url: DOCS_QUICKSTART,
19073
+ docs_note: DOCS_NOTE,
19074
+ calendly_url: ZOE_CALENDLY,
19075
+ exit_offer: EXIT_OFFER,
19076
+ steps: [
19077
+ {
19078
+ n: 1,
19079
+ gate_label: "Check my account",
19080
+ gate_description: "Check my Leadbay account status.",
19081
+ explain: "The opening paragraph IS this gate's explanation \u2014 do not add another one. Just hand off in a line ('First, let's see which account you're on') and fire the widget in the SAME message. WHY IT'S USEFUL, if you say anything at all: this is where they can see at a glance how much they've used this week and what's left, so a batch that comes back small later has a visible reason. On click, the ANSWER is the account itself: user + org, then the full quota windows (see branches).",
19082
+ next_steps: {
19083
+ question: "Let's start with your account status.",
19084
+ options: [
19085
+ {
19086
+ label: "Check my account",
19087
+ description: "Check my Leadbay account status.",
19088
+ kind: "walkthrough_account_status"
19089
+ },
19090
+ EXIT_OPTION
19091
+ ]
19092
+ },
19093
+ calls: "leadbay_account_status",
19094
+ args: {},
19095
+ branches: [
19096
+ {
19097
+ when: "quota is readable",
19098
+ then: "Show them their ACTUAL account \u2014 this is the payoff of the click. One line on who they're signed in as and their organization, then render the quota windows in full the way the web app does: Daily / Weekly / Monthly, each with a \u25B0\u25B1 gauge, % used, $ spent against the cap, and when it resets, plus the per-resource breakdown underneath. Follow the canonical quota-windows rendering (never raw 'credits'). A one-line 'you're connected as X' under-delivers on a button labelled 'check my account status'. THEN EXPLAIN IT in one or two plain lines \u2014 a first-run user has never seen these numbers and can't tell if they're good or bad: say what it counts (the AI work Leadbay does for them \u2014 researching companies and qualifying leads, not something they spend by clicking around) and why it matters (it paces how many fresh leads arrive; heavy use now means a bigger batch queued for next time, and it's where a smaller-than-expected batch would show its reason). Keep it to a sentence or two, don't walk through every resource row, and don't turn it into a pricing pitch."
19099
+ },
19100
+ {
19101
+ when: "quota is null, quota_error is set, or organization.unlimited_credits is true",
19102
+ then: "Say NOTHING about quota \u2014 no gauge, no 'unreadable', no 'unlimited', and skip the quota EXPLANATION too (there is nothing on screen to explain, and describing an absent gauge just confuses). A brand-new org often has no billing plan yet, so the quota read fails; that is not an error worth showing. Do not mention a 401, and above all do NOT tell the user to log in again or reconnect: their token is fine, the same response just read their account. Fall back to the short user + org line and move on. (WORKFLOWS #30.)"
19103
+ },
19104
+ {
19105
+ when: "always",
19106
+ then: "Do NOT volunteer the lens. The response deliberately withholds it unless the user asked, so there is nothing to report, and no other tool should be called to find it. The lens appears naturally at gate 2. (WORKFLOWS #31.)"
19107
+ }
19108
+ ]
19109
+ },
19110
+ {
19111
+ n: 2,
19112
+ gate_label: "Pull today's leads",
19113
+ gate_description: "Pull today's leads from your lens.",
19114
+ explain: "Explain the LENS before firing: Leadbay keeps a lens \u2014 the description of who they sell to \u2014 and every day it finds fresh companies matching it. This click pulls today's batch. WHY IT'S USEFUL: it replaces the hour spent digging through directories and LinkedIn for someone worth calling \u2014 the list is already waiting, scored, when they sit down. And it gets sharper: the leads they like, contact or skip teach the lens what a good fit looks like, so tomorrow's batch is closer than today's.",
19115
+ next_steps: {
19116
+ question: "Now let's see today's leads. Ready?",
19117
+ options: [
19118
+ {
19119
+ label: "Pull today's leads",
19120
+ description: "Pull today's leads from your lens.",
19121
+ kind: "walkthrough_pull_leads"
19122
+ },
19123
+ EXIT_OPTION
19124
+ ]
19125
+ },
19126
+ calls: "leadbay_pull_leads",
19127
+ args: {},
19128
+ pin: "lens.id \u2014 pass as an explicit lensId on every later step, so step 4 enriches the same lens the user just saw. Also pin the TOP-SCORING lead's id and name: gate 3 drafts to it, and gate 4 reveals its contact",
19129
+ branches: [
19130
+ {
19131
+ when: "leads.length > 0",
19132
+ then: "Render the canonical pull_leads table, then advance to gate 3."
19133
+ },
19134
+ {
19135
+ when: "leads.length === 0 && (computing_wishlist || computing_scores)",
19136
+ then: "The lens is still building \u2014 normal on a new account. Say so in the user's terms, then render the tool's own next_steps payload VERBATIM (it carries two options: 'Re-pull in ~30s' / 'Refine audience'). This is the ONE place a gate carries two options, because the server built the payload. On re-pull, wait ~30s and return to gate 2. NEVER say 'no leads found'."
19137
+ },
19138
+ {
19139
+ when: "leads.length === 0 && !computing_wishlist && !computing_scores",
19140
+ then: "The lens is genuinely empty or too narrow and next_steps is null. Say so honestly, offer to widen the audience, and end the walkthrough \u2014 there is nothing to enrich."
19141
+ }
19142
+ ]
19143
+ },
19144
+ {
19145
+ n: 3,
19146
+ gate_label: "Draft the first email",
19147
+ gate_description: "Write a first email to the best company in today's batch.",
19148
+ explain: "Name the TOP-SCORING lead from gate 2 out loud, so the offer is about a real company and not an abstraction. Explain what's about to happen: Leadbay already worked out WHY this company fits them, so it can write the first email instead of leaving them at a blank page. WHY IT'S USEFUL: finding companies was never the hard part \u2014 writing the twentieth opener of the day is where prospecting actually dies. This turns a row in a table into something they could send in a minute. Say plainly that it only DRAFTS: nothing is sent, and they see it first.",
19149
+ next_steps: {
19150
+ question: "Want me to draft the first email to your top lead?",
19151
+ options: [
19152
+ {
19153
+ label: "Draft the first email",
19154
+ description: "Write a first email to the best company in today's batch. Nothing is sent.",
19155
+ kind: "walkthrough_draft_outreach"
19156
+ },
19157
+ EXIT_OPTION
19158
+ ]
19159
+ },
19160
+ calls: "leadbay_prepare_outreach",
19161
+ args: {
19162
+ leadId: "<the highest-scoring lead id from step 2>"
19163
+ },
19164
+ forbidden_args: [
19165
+ "enrich \u2014 enrich:true launches a PAID contact reveal off the back of a DRAFT click. They agreed to see an email written, not to spend. Gate 4 is where the reveal gets asked for, explicitly and on its own terms."
19166
+ ],
19167
+ spend: "This gate spends NOTHING. Call leadbay_prepare_outreach with leadId and nothing else. `recommended_contact` comes back in its post-enrichment shape with email and phone still null \u2014 that is EXPECTED, not a failure, and it is precisely the hook for gate 4: an email written, and nobody to send it to yet. Do not apologise for the missing contact, and do not reach for another tool to fill it in.",
19168
+ branches: [
19169
+ {
19170
+ when: "always",
19171
+ then: "Render the draft through message_compose_v1 \u2014 kind:'email', a summary_title naming the company, and 2-3 variants whose labels name the STRATEGY ('Lead with the growth signal', 'Ask about their current setup'), never the tone. Do NOT also paste the body into chat prose; the composer IS the answer. Address it to the recommended contact's JOB TITLE ('the Head of Operations at <Company>') \u2014 you do not have a name yet, and inventing one is fabrication. Say in one line what made this company the pick: its score and the fit reason from the lead's summary, so the draft reads as reasoned rather than generated."
19172
+ },
19173
+ {
19174
+ when: "the host exposes no message_compose_v1",
19175
+ then: "Fall back to the canonical prepare-outreach rendering: one short context line, then the subject and body as a quoted block. Same content, same no-name rule."
19176
+ }
19177
+ ]
19178
+ },
19179
+ {
19180
+ n: 4,
19181
+ gate_label: "Find who to email",
19182
+ gate_description: "Reveal the person at that company to send the draft to.",
19183
+ explain: "Point straight at the gap the draft just opened: they have an email ready and nobody to send it to \u2014 it's addressed to a job title, not a person. That's what this step fixes. Explain what enrichment IS: Leadbay can find which roles exist at that company, then reveal the actual human and how to reach them. WHY IT'S USEFUL: they ask for the operations director by name instead of pitching whoever answers the switchboard \u2014 the difference between a conversation and a dead end. Say plainly that the first look is free, and that revealing the contact costs credits and needs their say-so.",
19184
+ next_steps: {
19185
+ question: "Want to find out who to send that email to?",
19186
+ options: [
19187
+ {
19188
+ label: "Find who to email",
19189
+ description: "See the roles at that company. Free \u2014 no contact details revealed yet.",
19190
+ kind: "walkthrough_enrich_titles"
19191
+ },
19192
+ EXIT_OPTION
19193
+ ]
19194
+ },
19195
+ calls: "leadbay_enrich_titles",
19196
+ branches: [
19197
+ {
19198
+ when: "leadbay_enrich_titles is NOT in your tool set",
19199
+ then: "This is a read-only deployment (LEADBAY_MCP_WRITE=0) \u2014 the reveal tool simply is not registered. Do NOT fire this gate's widget, and do not hunt for another way to get contact details. Close the tour after gate 3 instead: say plainly that revealing contacts isn't enabled on this connection, that the draft they just watched being written is still theirs, and go to the closing. A gate whose tool cannot run is a dead end, and offering the button anyway is worse than ending one step early."
19200
+ }
19201
+ ],
19202
+ args: {
19203
+ leadIds: "[<the ONE lead you drafted for at step 3>] \u2014 an ARRAY, always",
19204
+ lensId: "<the pinned lens id from step 2>"
19205
+ },
19206
+ spend: "TWO BEATS \u2014 free preview FIRST, the real reveal only after the user confirms. Beat 1: call leadbay_enrich_titles with the drafted lead's id + lensId and NO titles / NO confirm / NO email / NO phone. That returns mode:'discover' \u2014 the FREE list of job titles at that company. Say plainly that nothing has been spent yet. Beat 2: name the title the draft is addressed to, tell them BEFORE they decide what it costs (one credit per contact revealed \u2014 here that is ONE contact, one credit), and ask them to confirm. Only then call leadbay_enrich_titles AGAIN with leadIds: [<that lead id>] \u2014 ALWAYS the array, even for a single lead: `leadId` singular is not a key this tool reads, so it is dropped and the paid call falls back to the default wishlist selection, charging for the whole batch \u2014 plus the chosen title, confirm:true and email:true. Poll leadbay_bulk_enrich_status with the returned bulk_id until all_done (or the count plateaus), and report the contact that actually resolved. NEVER launch the reveal without an explicit confirm: silence is not consent, and neither is 'they clicked the gate'. If they decline, keep the draft and the title and move on \u2014 that is a normal outcome, not a failure.",
19207
+ quota_note: "After the reveal, close the loop on gate 1 in one line: one credit per contact revealed, so this cost one. Then say the thing that makes it land \u2014 the draft from gate 3 now has a real person and a real address to go to. Re-check leadbay_account_status if you want to show the moved windows. This is where gate 1's numbers stop being abstract: they just watched them move, and got something for it. Keep it to a line; no pricing pitch."
19208
+ }
19209
+ ],
19210
+ keep_going: KEEP_GOING,
19211
+ stop: STOP
19212
+ };
19213
+ var gettingStarted = {
19214
+ name: "leadbay_getting_started",
19215
+ annotations: {
19216
+ title: "Guided Leadbay walkthrough",
19217
+ readOnlyHint: true,
19218
+ destructiveHint: false,
19219
+ idempotentHint: true,
19220
+ openWorldHint: false
19221
+ },
19222
+ description: leadbay_getting_started2,
19223
+ write: false,
19224
+ inputSchema: {
19225
+ type: "object",
19226
+ properties: {},
19227
+ additionalProperties: false
19228
+ },
19229
+ // No outputSchema by design — same trade-off as leadbay_artifact_kit:
19230
+ // declaring one enrolls the tool in the output-schema-conformance
19231
+ // drift-catcher (an existing test file we don't modify). The server still
19232
+ // emits the plain-object return as structuredContent.
19233
+ execute: async (_client, _params, _ctx) => {
19234
+ return structuredClone(GETTING_STARTED_MANIFEST);
19235
+ }
19236
+ };
19237
+
17848
19238
  // ../core/dist/composite/set-qualification-questions.js
17849
19239
  var setQualificationQuestions = {
17850
19240
  name: "leadbay_set_qualification_questions",
@@ -23401,6 +24791,16 @@ var compositeReadTools = [
23401
24791
  // is a first-session question, and the underlying get_taste_profile is
23402
24792
  // ADVANCED-gated. Read-only; no MCP edit endpoint exists (issue #3768).
23403
24793
  getQualificationQuestions,
24794
+ // Guided first-run walkthrough (issue #3952). ALWAYS exposed, read-only:
24795
+ // returns the six-gate script a brand-new user clicks through to learn
24796
+ // Leadbay by doing (check account → pull leads → draft the first email →
24797
+ // reveal who to send it to → CRM → schedule it).
24798
+ // Makes no backend call. In compositeReadTools so the tour is reachable on a
24799
+ // read-only (LEADBAY_MCP_WRITE=0) deployment — where gate 4's
24800
+ // leadbay_enrich_titles is NOT registered (it is write-gated), so the
24801
+ // manifest's gate-4 branch ends the tour after gate 3 rather than offering a
24802
+ // button whose tool cannot run.
24803
+ gettingStarted,
23404
24804
  // Per-lead custom-field VALUES. ALWAYS exposed: complements the always-on
23405
24805
  // list_mappable_fields (which returns DEFINITIONS only). The lead payload
23406
24806
  // embeds each field's definition, so no catalog join is needed (issue #3768).
@@ -24375,6 +25775,7 @@ function buildScoringParagraph(has) {
24375
25775
  }
24376
25776
  return base;
24377
25777
  }
25778
+ var FIRST_RUN_ROUTING = 'FIRST RUN \u2014 when the user asks to be SHOWN how Leadbay works ("walk me through Leadbay", "I\'m new", "how do I use this", "getting started", "give me a tour", "I just installed this"), invoke the `leadbay_getting_started` prompt via `prompts/get` and follow it. Do NOT improvise your own overview, tour, or summary of the product \u2014 it ships a five-gate walkthrough where each gate is a single-option choice widget the user clicks, so they learn by doing. Writing your own prose tour instead replaces the thing they asked for with a lecture.';
24378
25779
  function buildStartHereParagraph(has) {
24379
25780
  const base = "Start with leadbay_account_status to see the user's state, then leadbay_pull_leads to surface fresh leads. Use leadbay_research_lead_by_id to dig into one lead deeply (qualification answers, signals, contacts).";
24380
25781
  const compositeNames = ["bulk_qualify_leads", "adjust_audience", "refine_prompt", "enrich_titles"].filter((n) => has(`leadbay_${n}`));
@@ -24470,6 +25871,7 @@ function buildServerInstructions(exposed) {
24470
25871
  parts.push(QUOTA_TOPUP);
24471
25872
  parts.push(TRANSIENT_401);
24472
25873
  parts.push(buildScoringParagraph(has));
25874
+ parts.push(FIRST_RUN_ROUTING);
24473
25875
  parts.push(buildStartHereParagraph(has));
24474
25876
  parts.push(buildRhythmParagraph(has));
24475
25877
  const updateParagraph = buildUpdateAvailableParagraph(has);
@@ -25289,7 +26691,7 @@ function parseWriteEnv(env = process.env) {
25289
26691
  }
25290
26692
 
25291
26693
  // src/http-server.ts
25292
- var VERSION = true ? "0.27.0" : "0.0.0-dev";
26694
+ var VERSION = true ? "0.29.0" : "0.0.0-dev";
25293
26695
  var PORT = Number(process.env.PORT ?? 8080);
25294
26696
  var HOST = process.env.HOST ?? "0.0.0.0";
25295
26697
  var logger = {