@sellable/mcp 0.1.119 → 0.1.121

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.
@@ -27,14 +27,25 @@ Process:
27
27
 
28
28
  1. Read the campaign brief, kickoff doc, or lane prompt supplied by the parent.
29
29
  2. Search 3-5 keyword/topic lanes, favoring fresh posts from the last 7-14 days.
30
- 3. Select 3-5 promising posts when available. If a `campaignOfferId` was
31
- supplied, call `select_promising_posts({ campaignOfferId, selectionMode:
32
- "replace", selections, headlineICPCriteria, currentStep:
33
- "signal-discovery" })` before sampling so the watched Signal Discovery table
34
- shows the promoted posts.
35
- 4. Fetch or sample engagers for promoted posts and score rough ICP fit from visible headline/display-name cues only. Do not enrich people during viability estimation.
36
- 5. Estimate usable prospects per selected post from sampled pass rate. If the sample is good but volume is low, say how many more similar posts should be added or scraped.
37
- 6. Return false positives and dead ends explicitly.
30
+ 3. Inspect finalist posts by content type before final selection. Prefer posts
31
+ where the topic matches the campaign wedge, not generic high-engagement news.
32
+ 4. Promote the first narrow sample set when campaign-attached. If a
33
+ `campaignOfferId` was supplied, call `select_promising_posts({
34
+ campaignOfferId, selectionMode: "replace", selections, headlineICPCriteria,
35
+ currentStep: "signal-discovery" })` before sampling so the watched Signal
36
+ Discovery table shows the exact posts being tested.
37
+ 5. Fetch or sample engagers for promoted posts and score rough ICP fit from
38
+ visible headline/display-name cues only. Do not enrich people during
39
+ viability estimation.
40
+ 6. Compute capacity before recommending the source: target good-fit leads
41
+ (default 500 unless the parent supplies a target), reachable engagers,
42
+ sampled ICP-fit rate as `n/N` plus an easy percentage/range, expected usable
43
+ leads per right-content post after dedupe/cleanup, and posts needed to hit
44
+ the target.
45
+ 7. Select/promote enough right-content posts to plausibly hit the target. If the
46
+ warm Signals pool is useful but too small, return the expected warm range and
47
+ recommend Sales Nav/Prospeo for scale instead of padding with noisy posts.
48
+ 8. Return false positives and dead ends explicitly.
38
49
 
39
50
  Return a concise structured result with:
40
51
 
@@ -43,8 +54,11 @@ Return a concise structured result with:
43
54
  - `keyword_lanes` with timeframe, raw posts found, finalist posts reviewed
44
55
  - `selected_posts` with URL/title, author/topic, age, engager count, sampled engagers, good fits as n/N, estimated usable prospects per post, use/discard
45
56
  - `sample_leads`, if any
57
+ - `approval_math` with eligible posts, target good-fit leads, sampled engagers,
58
+ ICP-fit rate as `n/N` plus percentage/range, posts needed for target, selected
59
+ post count, expected usable lead range, and scale fallback
46
60
  - `estimated_good_fit_range`
47
- - `expected_reply_rate_range`, directional if inferred
61
+ - `message_context_strength`, directional and source-specific
48
62
  - `false_positive_patterns`
49
63
  - `recommendation`
50
64
  - `confidence`
@@ -53,5 +67,8 @@ Evidence standards:
53
67
 
54
68
  - Do not trust raw post volume without inspecting finalist post quality.
55
69
  - Prefer sample-based pass rates over intuition.
70
+ - Do not make the user infer capacity. Say, plainly, how many eligible posts
71
+ exist, what percent of sampled engagers looked in-ICP, how many posts are
72
+ needed for 500+ good-fit leads, and which posts you would use.
56
73
  - If `fetch_post_engagers` is unavailable or fails, report that explicitly and mark the estimate lower-confidence.
57
74
  - Keep LinkedIn Engagement viable when selected posts can produce roughly 150+ ICP-fit warm prospects before final filtering, even if Sales Nav is more scalable.
package/dist/index-dev.js CHANGED
File without changes
package/dist/index.js CHANGED
File without changes
@@ -2,10 +2,13 @@ import { getApi } from "../api.js";
2
2
  const knownBlueprintColumnTypes = new Set([
3
3
  "ai_column",
4
4
  "checkbox",
5
+ "check_connection",
6
+ "datetime",
5
7
  "formula",
6
8
  "generate_message",
7
9
  "http_request",
8
10
  "inbound_webhook",
11
+ "react_and_comment",
9
12
  "score_icp_mcp",
10
13
  "send_dm",
11
14
  "send_inmail_closed",
@@ -1,6 +1,7 @@
1
1
  import * as fs from "fs";
2
2
  import * as os from "os";
3
3
  import * as path from "path";
4
+ import { fileURLToPath } from "url";
4
5
  const MCP_PACKAGE = "@sellable/mcp";
5
6
  const INSTALL_PACKAGE = "@sellable/install";
6
7
  const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
@@ -8,11 +9,23 @@ const DEFAULT_TIMEOUT_MS = 1500;
8
9
  function cachePath() {
9
10
  return path.join(os.homedir(), ".sellable", "update-check.json");
10
11
  }
12
+ function readEntrypointDir() {
13
+ if (!process.argv[1])
14
+ return process.cwd();
15
+ const entrypointPath = path.resolve(process.argv[1]);
16
+ try {
17
+ return path.dirname(fs.realpathSync.native(entrypointPath));
18
+ }
19
+ catch {
20
+ return path.dirname(entrypointPath);
21
+ }
22
+ }
11
23
  function readCurrentVersion() {
12
- const entrypointDir = process.argv[1]
13
- ? path.dirname(path.resolve(process.argv[1]))
14
- : process.cwd();
24
+ // @ts-ignore - this MCP package is NodeNext ESM, but the app build typechecks MCP tests through its CommonJS tsconfig.
25
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
26
+ const entrypointDir = readEntrypointDir();
15
27
  const candidates = [
28
+ path.resolve(moduleDir, "..", "package.json"),
16
29
  path.resolve(entrypointDir, "..", "package.json"),
17
30
  path.resolve(process.cwd(), "mcp/sellable/package.json"),
18
31
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.119",
3
+ "version": "0.1.121",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -808,9 +808,11 @@ Required behavior:
808
808
  1. Start with LinkedIn Engagement / active LinkedIn posts (internal provider:
809
809
  Signals / `signal-discovery`) because recent engagement gives the strongest
810
810
  message context and expected reply-rate upside. Search relevant keyword
811
- lanes, review finalist posts, promote the sampled posts with
811
+ lanes, review finalist posts, promote a narrow sample set with
812
812
  `select_promising_posts` before fetching engagers when a campaign shell
813
- exists, fetch top-post engagers, and estimate warm-fit volume.
813
+ exists, fetch top-post engagers, estimate ICP-fit rate, compute posts
814
+ needed for the target good-fit lead count, and only then recommend the
815
+ selected post set or move to the next source.
814
816
  2. If Signals does not have enough recent, relevant, ICP-looking engagers,
815
817
  switch to Sales Nav with recent activity when the target can be expressed
816
818
  as title/persona/company filters. Run preview filters, inspect preview rows,
@@ -867,12 +869,12 @@ Required behavior:
867
869
  smaller, higher-reply-upside first batch versus Sales Nav/Prospeo for broader
868
870
  scale. Recommend the source you believe should win, often Sales Nav when scale
869
871
  matters, but keep the smaller Signals lane available as a real option.
870
- - user-facing source logic must use numbers, not vibes. Never write percentages
871
- in the source review. Use the numerator, denominator, and sample basis instead,
872
- e.g. `8 of 11 sampled engagers fit the ICP`. If the sample is small, say it is
873
- directional. If an estimate depends on assumptions, show the math without
874
- percentage notation: `5 selected posts x ~40-80 reachable engagers/post x
875
- roughly 1 in 4 to 2 in 5 expected fit = ~50-160 likely usable leads`.
872
+ - user-facing source logic must use sample-backed numbers, not vibes. Show the
873
+ numerator, denominator, sample basis, and an easy percentage or range, e.g.
874
+ `18 of 40 sampled engagers fit the ICP (~45%, directional)`. If the sample is
875
+ small, say it is directional. If an estimate depends on assumptions, show the
876
+ math plainly: `8 eligible posts x ~140 reachable engagers/post x ~35-45% ICP
877
+ fit x cleanup factor = ~300-500 likely usable warm leads`.
876
878
  - source progress updates should expose the confidence-building numbers as soon
877
879
  as they exist: keyword lanes searched, timeframe used, post results by lane,
878
880
  finalist posts reviewed, engagers fetched, sampled engagers, sampled fits,
@@ -882,10 +884,12 @@ roughly 1 in 4 to 2 in 5 expected fit = ~50-160 likely usable leads`.
882
884
  last 30 days, prefer the last 7-14 days when quality is comparable, and call
883
885
  out any older post as a deliberate tradeoff. Do not hide post age inside the
884
886
  raw search count
885
- - default source quality target is 300-500+ likely usable leads for scalable
886
- outbound; accept ~150+ likely ICP-fit warm prospects as viable for a focused
887
- Signals-first campaign or first review batch. Name the volume tradeoff in
888
- `lead-review.md` and the message review context rather than forcing a discard.
887
+ - default source quality target is 500+ likely good-fit leads for scalable
888
+ outbound unless the campaign or user supplies a different target. Accept
889
+ ~150+ likely ICP-fit warm prospects as viable for a focused Signals-first
890
+ campaign or first review batch only when the user is choosing warmth over
891
+ scale. Name the volume tradeoff in `lead-review.md` and the source approval
892
+ card rather than forcing a discard.
889
893
  - if `lead-source-intake.json` is present, read `sourceType`,
890
894
  `sourceInputMode`, file path or existing lead-list ID, selected columns,
891
895
  confirmation token, normalized counts, and any preview-created
@@ -919,7 +923,8 @@ roughly 1 in 4 to 2 in 5 expected fit = ~50-160 likely usable leads`.
919
923
  - row/domain counts, invalid counts, duplicate counts, and sample method for
920
924
  supplied sources
921
925
  - preview count
922
- - ICP match rate with numerator/denominator and sample basis; never percent-only
926
+ - ICP match rate with numerator/denominator, sample basis, and a simple
927
+ percentage/range; never percent-only
923
928
  - volume comparison
924
929
  - source viability: expected source volume, expected usable leads after
925
930
  filtering, source activity/warmth indicators, cleanup risk, and confidence
@@ -977,7 +982,7 @@ table with one row per selected or finalist post:
977
982
  - raw results found
978
983
  - finalist posts or preview rows reviewed
979
984
  - sampled people
980
- - sampled fits, shown as `n/N` only; do not include a percentage
985
+ - sampled fits, shown as `n/N` plus a simple percentage/range
981
986
  - estimated usable people
982
987
  - confidence note (`sample-backed`, `directional`, or `needs more sample`)
983
988
 
@@ -994,47 +999,34 @@ workspace/sender.
994
999
  When showing `lead-review.md` to the user, render a slim decision summary in
995
1000
  chat, not the full evidence table. Use rendered Markdown directly with short
996
1001
  indexed sections and bullet lines; do not use fenced code blocks for the
997
- user-facing lead review. The visible response must include:
1002
+ user-facing lead review. The visible response must be a compact math-first card,
1003
+ not a research memo. It must include only:
998
1004
 
999
1005
  - `Lead source decision`
1000
- - `Recommendation`
1001
- - `Primary source and filters` with the concrete source recipe the agent would
1002
- use if approved. For Sales Nav, name the actual role/title, company-size,
1003
- geography, industry/account, and activity filters. For Signals, name the
1004
- selected post set. For Prospeo, name the account/domain and title recipe.
1005
- - `Runner-up sources` with the second-best choice and why it lost. Make the
1006
- tradeoff explicit, e.g. Sales Nav = clearer scale and fit control, Signals =
1007
- warmer but smaller/noisier, Prospeo = broader account coverage but weaker
1008
- LinkedIn intent.
1009
- - `Why it won`
1010
- - `Quick numbers` as bullet points, with one provider/source angle per bullet.
1011
- Each bullet must include raw volume, sampled fit rate as `n/N`, estimated
1012
- good-fit range after cleanup, source activity/warmth basis, and confidence
1013
- note.
1014
- - If Signals was searched or considered, `Signal keyword lanes` as a compact
1015
- table with keyword lane, timeframe, posts found, and finalist posts reviewed.
1016
- - If Signals was searched or considered, `LinkedIn posts sampled` as a compact
1017
- table with one row per selected or finalist post: post URL/title,
1018
- author/topic, age, engagers, sampled engagers, good fits as `n/N`, estimated
1019
- usable prospects per post, and use/discard decision.
1020
- - `Sample leads` with 3-5 representative `Name Title, Company` rows
1021
- - `Tradeoff`
1022
- - `Watch link: {watchUrl}` when the campaign shell exists
1023
- - `Source math before approval` as the final content block immediately above
1024
- the lead-source approval question. This is the bottom-line arithmetic that
1025
- makes the recommendation feel obvious. It must use concrete counts and a
1026
- simple formula, for example:
1027
- `Signals: 5 relevant posts -> ~320 reachable engagers; 18 of 40 sampled
1028
- engagers fit the ICP, so the expected usable range is roughly 120-160 warm
1029
- prospects after cleanup. If we want more volume than that, use Sales Nav:
1030
- ~1,900 preview rows with 14 of 25 sampled rows looking usable, but the message
1031
- context is colder.`
1032
- For Signals, include selected/relevant posts, total or per-post engagers,
1033
- sampled engagers, sampled ICP fits as `n/N`, estimated usable prospects, and
1034
- the volume tradeoff. For Sales Nav/Prospeo alternatives, include preview/raw
1035
- volume, sampled usable rows as `n/N`, estimated usable range, and the warmth
1036
- or context tradeoff. Do not use percent-only fit rates or unsupported reply
1037
- rate claims.
1006
+ - `Source recommendation: {source}` with the concrete recipe. For Sales Nav,
1007
+ name the actual role/title, company-size, geography, industry/account, and
1008
+ activity filters. For Signals, name the selected post set. For Prospeo, name
1009
+ the account/domain and title recipe.
1010
+ - `Math:` with these labels and values:
1011
+ `Eligible posts`, `Sample`, `ICP-fit`, `Target`, `Posts needed`, `Selected`,
1012
+ `Expected good-fit leads`, and `Scale option`.
1013
+ - `Why this source:` with at most two bullets.
1014
+ - `Watch link: {watchUrl}` only when the campaign shell exists and the host
1015
+ needs a handoff link.
1016
+ - the concrete source approval question and options.
1017
+
1018
+ Do not include keyword-lane tables, LinkedIn-post-sampled tables, sample-lead
1019
+ lists, or long tradeoff prose in the default approval packet. Keep those details
1020
+ in `lead-review.md` or campaign/source decision state. The math block is the
1021
+ bottom-line arithmetic that makes the recommendation obvious. For example:
1022
+ `Eligible posts: 8 outbound + Claude posts. Sample: 120 engagers. ICP-fit: 52/120
1023
+ (~43%, directional). Target: 500 good-fit leads. Posts needed: about 8-10 at
1024
+ this fit rate. Selected: 8 posts. Expected good-fit leads: ~420-560 after
1025
+ dedupe/cleanup. Scale option: Sales Nav gives more rows but loses the engaged
1026
+ with this content message hook.` For Sales Nav/Prospeo alternatives, include
1027
+ preview/raw volume, sampled usable rows as `n/N` plus percentage/range,
1028
+ estimated usable range, and the warmth or context tradeoff. Do not use
1029
+ percent-only fit rates or unsupported reply-rate claims.
1038
1030
 
1039
1031
  The first sentence of the visible decision must make the actual choice clear:
1040
1032
  `I recommend {primary source} using {exact filter/source recipe}. The runner-up
@@ -1068,7 +1060,8 @@ posts`, `Try Prospeo/account search`, and `Revise source`.
1068
1060
  Immediately above that approval question, render the `Source math before
1069
1061
  approval` block. Do not ask the user to approve from the recommendation alone;
1070
1062
  show the math tying relevant posts, available engagers, sampled fit, expected
1071
- usable lead count, and the scale alternative together first.
1063
+ usable lead count, posts needed for the target, and the scale alternative
1064
+ together first.
1072
1065
 
1073
1066
  Do not skip or discard Signals based only on raw post count or vibes. If
1074
1067
  Signals was considered, show the post-level math in chat first so the user can
@@ -1077,13 +1070,16 @@ sampled engagers looked like good fits, and roughly how many usable prospects
1077
1070
  each post can produce. If no engagers could be fetched, say that explicitly and
1078
1071
  lower confidence instead of presenting a precise estimate.
1079
1072
 
1080
- For Signals, default to sampling a few promising posts first rather than trying
1081
- to prove the entire source can scale before the user sees evidence. Pick 3-5
1082
- fresh, high-density posts when available. In campaign-attached watch runs,
1083
- promote those posts with `select_promising_posts` before sampling so the user
1084
- sees which posts are being tested. Then sample engagers, show fit rate, and
1085
- state how many additional posts could be added/scraped if that first sample is
1086
- good but volume is low.
1073
+ For Signals, default to a quick capacity test before final recommendation. Pick
1074
+ a narrow first sample of fresh, high-density posts, and in campaign-attached
1075
+ watch runs promote those posts with `select_promising_posts` before sampling so
1076
+ the user sees which posts are being tested. Then sample engagers, show ICP-fit
1077
+ rate as `n/N` plus percentage/range, calculate how many right-content posts are
1078
+ needed to reach the target good-fit lead count, and select/promote enough
1079
+ right-content posts if the lane can hit the target. If the first sample is good
1080
+ but volume is low, say how many more posts would be needed and offer the Sales
1081
+ Nav/Prospeo scale alternative instead of recommending an under-sized source as
1082
+ if it can produce 500+ leads.
1087
1083
 
1088
1084
  Keep discarded paths and full sample rows in the campaign/source decision
1089
1085
  context. In explicit debug/UAT runs they may also be copied into
@@ -525,7 +525,7 @@
525
525
  "suppliedProfilesOrCsv": "supplied-list",
526
526
  "explicitCompare": "compare-requested-sources"
527
527
  },
528
- "quickViabilityRule": "Run only enough of the current lane to decide whether it can supply relevant, reachable ICP-looking leads. Stop on the first viable source unless the user asked for comparison.",
528
+ "quickViabilityRule": "Run only enough of the current lane to decide whether it can supply relevant, reachable ICP-looking leads. For Signals, viability requires sampled ICP-fit rate plus posts-needed math against the target good-fit lead count, not raw post count. Stop on the first viable source unless the user asked for comparison.",
529
529
  "parallelAllowedOnlyWhen": [
530
530
  "user explicitly requested source comparison",
531
531
  "resuming already-started parallel scouts",
@@ -565,7 +565,7 @@
565
565
  "uploadedDomains": "prospeo"
566
566
  },
567
567
  "when": "before_first_source_attempt",
568
- "rule": "Choose the first visible source lane from explicit source direction when present, otherwise default to Signal Discovery. Send watchNarration with stage find-leads that says why this lane is being tried now, what quick sample or filter gate will pass/fail it, and why that helps this campaign. For Signal Discovery sampling, promote/select the posts with select_promising_posts before fetch_post_engagers so the watched table shows the exact posts being sampled; the guide copy should say Codex is pulling sample engagers from these posts to confirm the ICP is actually engaging and the source is viable. If the lane fails, update currentStep and watchNarration before moving to Sales Nav recent activity, normal Sales Nav, or Prospeo."
568
+ "rule": "Choose the first visible source lane from explicit source direction when present, otherwise default to Signal Discovery. Send watchNarration with stage find-leads that says why this lane is being tried now, what quick sample or filter gate will pass/fail it, and why that helps this campaign. For Signal Discovery sampling, promote/select the first narrow sample posts with select_promising_posts before fetch_post_engagers so the watched table shows the exact posts being sampled; the guide copy should say Codex is pulling sample engagers from these posts to confirm the ICP is actually engaging and estimate whether enough right-content posts exist for the target lead count. Before final source approval, compute eligible posts, sampled ICP-fit rate, target good-fit lead count, posts needed, selected posts, expected good-fit range, and scale fallback. If the lane fails, update currentStep and watchNarration before moving to Sales Nav recent activity, normal Sales Nav, or Prospeo."
569
569
  },
570
570
  {
571
571
  "action": "run_first_campaign_attached_source_search",
@@ -614,7 +614,7 @@
614
614
  "action": "run_subskill",
615
615
  "target": "find-leads",
616
616
  "mode": "campaign-attached-required",
617
- "sourceScoutRule": "Shell-first flow requires the CampaignOffer campaignId from durable state. Pass campaignId as campaignOfferId into every provider prompt/search that can persist source state (`get_provider_prompt({ provider, campaignOfferId, confirmed: true })`, `search_signals`, `search_sales_nav`, `search_prospeo`) and include currentStep for tools that accept it so the user can watch the selected source inside the campaign. Use the default sequential source viability funnel when the user did not specify a source: Signal Discovery first, then Sales Nav with recent activity, then general Sales Nav, then Prospeo only as fallback. Stop on the first viable source unless the user explicitly asked to compare. If the user names hiring signals, domains/accounts, supplied lists, posts/comments, or title/persona filters, start with the matching source instead. Parallel source scouts only when the user requested comparison, an existing parallel run is being resumed, or the first viable source is borderline and one cheap fallback check is needed. The later import_leads call must use the same campaignOfferId. Do not import, confirm, enrich, queue, or start leads during source discovery."
617
+ "sourceScoutRule": "Shell-first flow requires the CampaignOffer campaignId from durable state. Pass campaignId as campaignOfferId into every provider prompt/search that can persist source state (`get_provider_prompt({ provider, campaignOfferId, confirmed: true })`, `search_signals`, `search_sales_nav`, `search_prospeo`) and include currentStep for tools that accept it so the user can watch the selected source inside the campaign. Use the default sequential source viability funnel when the user did not specify a source: Signal Discovery first, then Sales Nav with recent activity, then general Sales Nav, then Prospeo only as fallback. For Signal Discovery, do not recommend from raw post count; sample engagers, calculate ICP-fit rate, target good-fit lead count, posts needed, selected posts, expected good-fit range, and scale fallback. Stop on the first viable source unless the user explicitly asked to compare. If the user names hiring signals, domains/accounts, supplied lists, posts/comments, or title/persona filters, start with the matching source instead. Parallel source scouts only when the user requested comparison, an existing parallel run is being resumed, or the first viable source is borderline and one cheap fallback check is needed. The later import_leads call must use the same campaignOfferId. Do not import, confirm, enrich, queue, or start leads during source discovery."
618
618
  },
619
619
  {
620
620
  "action": "optional_debug_artifacts",
@@ -712,16 +712,17 @@
712
712
  "artifact": "lead-review.md",
713
713
  "renderInlineSections": [
714
714
  "## Lead source decision",
715
- "Recommendation",
716
- "Primary source and filters",
717
- "Runner-up sources",
718
- "Why it won",
719
- "Quick numbers",
720
- "Signal keyword lanes",
721
- "LinkedIn posts sampled",
722
- "Sample leads",
723
- "Tradeoff",
724
- "Source math before approval"
715
+ "Source recommendation",
716
+ "Math",
717
+ "Eligible posts",
718
+ "Sample",
719
+ "ICP-fit",
720
+ "Target",
721
+ "Posts needed",
722
+ "Selected",
723
+ "Expected good-fit leads",
724
+ "Scale option",
725
+ "Why this source"
725
726
  ],
726
727
  "signalsFirstRequiredFields": [
727
728
  "post URL",
@@ -742,7 +743,7 @@
742
743
  "raw results found",
743
744
  "finalist posts or preview rows reviewed",
744
745
  "sampled people",
745
- "sampled fits as n/N only; no percentage",
746
+ "sampled fits as n/N plus percentage or range",
746
747
  "estimated usable people",
747
748
  "estimated good-fit range after cleanup",
748
749
  "confidence note"
@@ -756,13 +757,17 @@
756
757
  ],
757
758
  "approvalMathRequiredFields": [
758
759
  "selected or relevant posts count",
760
+ "eligible right-content posts count",
759
761
  "reachable engagers count",
760
762
  "sampled engagers count",
761
- "sampled ICP fits as n/N only; no percentage",
763
+ "sampled ICP fits as n/N plus percentage or range",
764
+ "target good-fit lead count",
765
+ "posts needed for target",
766
+ "selected posts count",
762
767
  "estimated usable prospects range",
763
768
  "scale alternative source",
764
769
  "scale alternative raw or preview volume",
765
- "scale alternative sampled usable rows as n/N only; no percentage",
770
+ "scale alternative sampled usable rows as n/N plus percentage or range",
766
771
  "warmth or message-context tradeoff",
767
772
  "what to choose if the user wants more volume"
768
773
  ],
@@ -786,7 +791,7 @@
786
791
  "doNotCompressToSummaryOnly": false,
787
792
  "doNotRenderArtifactLinksOnly": true,
788
793
  "sourceRecommendationReadyWatchRule": "When the source recommendation decision card is ready in chat with counts and sample quality, and before asking for source approval, call update_campaign with leadSourceType `new`, leadSourceProvider set to the recommended primary provider, currentStep set to that provider lane (`sales-nav`, `signal-discovery`, or `prospeo`), and find-leads watchNarration. If the recommendation changed from the lane last sampled, switch the watched provider page to the recommended lane first so the user can inspect what they are approving. Use a headline like `Review the source in Codex`, body copy that says the browser is showing the evaluated source/results, and nextAction like `Approve in Codex`. Do not keep future-tense copy like `I'll show a source recommendation` after the decision is visible. Include a safety note that no leads import until the user approves the source.",
789
- "chatRenderRule": "Show a slim rendered-Markdown decision summary only, never a fenced code block. The first sentence must make the decision explicit: 'I recommend {primary source} using {exact filter/source recipe}. The runner-up is {source} because {reason}.' Use indexed sections and short bullets: recommendation, Primary source and filters, Runner-up sources, why it won, Quick numbers with one provider/source angle per bullet, raw volume, sampled fit count as n/N only (no percentages), estimated good-fit range after cleanup, activity/warmth basis, confidence note, 3-5 representative sample leads, one tradeoff, and a final Source math before approval block immediately above the source approval question. Do not forecast connection acceptance rates, reply rates, meetings, pipeline, revenue, or ROI unless the user supplied verified benchmark data for this exact workspace/sender. If Signals was searched or considered, include two compact inline Markdown tables before the recommendation is treated as final: Signal keyword lanes with keyword lane, timeframe, posts found, and finalist posts reviewed; and LinkedIn posts sampled with post URL/title, author/topic, age, engagers, sampled engagers, good fits as n/N only, estimated usable prospects per post, and use/discard decision. Default to selecting a few promising Signals posts for the first sample instead of trying to prove full Signals scale up front; if the sample is good but volume is low, say how many more posts to add/scrape next. The Source math before approval block must tie the approval choice to concrete arithmetic: relevant/selected posts, available engagers, sampled engagers, sampled ICP fits as n/N only, estimated usable prospects, and the scale alternative math. If the user wants more volume than the warm post-engagement estimate, explicitly say which Sales Nav or Prospeo alternative provides that volume and what message-context warmth is lost. Do not skip or discard Signals based only on raw post count or vibes; show the post-level math first, or explicitly say no engagers could be fetched and lower confidence. Keep discarded paths, full sample rows, and lead-sample.json details in lead-review.md. Do not show plain filesystem paths unless links cannot be created."
794
+ "chatRenderRule": "Show a compact rendered-Markdown decision card only, never a fenced code block. The first sentence must make the decision explicit: 'I recommend {primary source} using {exact filter/source recipe}. The runner-up is {source} because {reason}.' Use only these sections: Lead source decision, Source recommendation, Math, Why this source, and the concrete approval question. The Math section must include labeled lines for Eligible posts, Sample, ICP-fit, Target, Posts needed, Selected, Expected good-fit leads, and Scale option. Show fit as n/N plus an easy percentage or range, never percent-only. Do not include keyword-lane tables, LinkedIn-post-sampled tables, sample-lead lists, or long tradeoff prose in the default approval packet; keep those details in lead-review.md or campaign/source decision state. Do not forecast connection acceptance rates, reply rates, meetings, pipeline, revenue, or ROI unless the user supplied verified benchmark data for this exact workspace/sender. For Signals, do not recommend from raw post count. Compute eligible right-content posts, reachable engagers, sampled ICP-fit rate, target good-fit lead count, posts needed, selected posts, expected good-fit range, and scale fallback. If the user wants more volume than the warm post-engagement estimate, explicitly say which Sales Nav or Prospeo alternative provides that volume and what message-context warmth is lost. If no engagers could be fetched, say that explicitly and lower confidence. Do not show plain filesystem paths unless links cannot be created."
790
795
  },
791
796
  {
792
797
  "action": "render_post_lead_parallel_progress",
@@ -808,7 +813,7 @@
808
813
  {
809
814
  "action": "ask_continue_revise_or_confirm_only_if_needed",
810
815
  "approvalQuestionRule": "If asking the user, the question and options must name the concrete decision. Prefer: 'Approve Sales Nav with {filters}, or use warmer Signals instead?' over 'Approve this lead source?' Option labels must name the source choice, such as 'Approve Sales Nav filters', 'Use warmer Signals posts', 'Try Prospeo/account search', and 'Revise source'.",
811
- "approvalMathRule": "Immediately above the approval question, render the Source math before approval block: given this many relevant posts, this many reachable engagers, and this sampled ICP fit as n/N, we expect this usable lead range; if the user wants more volume, name the Sales Nav or Prospeo alternative, its preview/sample math, and the colder/weaker-context tradeoff. Do not ask the approval question until this math is visible.",
816
+ "approvalMathRule": "Immediately above the approval question, render the compact Math block: given this many eligible relevant posts, this many reachable engagers, this sampled ICP fit as n/N plus percentage/range, this target good-fit lead count, and this many posts needed, we expect this usable lead range from the selected posts; if the user wants more volume, name the Sales Nav or Prospeo alternative, its preview/sample math, and the colder/weaker-context tradeoff. Do not ask the approval question until this math is visible.",
812
817
  "autoContinueWhen": {
813
818
  "status": "confirmed",
814
819
  "confidenceIn": [
@@ -332,6 +332,22 @@ before `fetch_post_engagers` so the user sees the exact posts being sampled in
332
332
  the watched Signal Discovery table. Use `selectionMode: "replace"` for the first
333
333
  sample set unless the user explicitly wants to add to existing promoted posts.
334
334
 
335
+ For `create-campaign-v2` source approval, do not treat the default
336
+ `selectionTarget` of 3 posts as enough by itself. Before the final source
337
+ recommendation, estimate source capacity from real sample math:
338
+
339
+ - target good-fit leads (default 500 unless the campaign says otherwise)
340
+ - eligible right-content posts by lane/content type
341
+ - reachable engagers from those posts
342
+ - sampled ICP-fit rate as `n/N` plus an easy percentage/range
343
+ - expected good-fit leads per selected post after dedupe/cleanup
344
+ - posts needed to reach the target
345
+
346
+ Then select enough right-content posts to plausibly hit the target. If the math
347
+ says the warm post lane only supports a smaller first batch, say that and name
348
+ the Sales Nav or Prospeo scale fallback rather than padding the selection with
349
+ noisy posts.
350
+
335
351
  ```json
336
352
  select_promising_posts({
337
353
  "campaignOfferId": "cmp_xxx",