@sellable/mcp 0.1.149 → 0.1.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/agents/post-find-leads-message-scout.md +2 -2
- package/agents/source-scout-prospeo-contact.md +12 -5
- package/agents/source-scout-sales-nav.md +13 -3
- package/dist/tools/leads.d.ts +2 -1
- package/dist/tools/leads.js +162 -15
- package/dist/tools/prompts.d.ts +1 -0
- package/dist/tools/prompts.js +4 -3
- package/dist/tools/registry.d.ts +9 -1
- package/dist/tools/rows.d.ts +1 -0
- package/dist/tools/rows.js +2 -0
- package/dist/tools/rubrics.d.ts +55 -23
- package/dist/tools/rubrics.js +95 -10
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +57 -29
- package/skills/create-campaign/core/providers/apollo.json +4 -2
- package/skills/create-campaign/core/providers/prospeo.json +3 -2
- package/skills/create-campaign/references/provider-selection-strategy.md +66 -25
- package/skills/create-campaign-v2/SKILL.md +67 -37
- package/skills/create-campaign-v2/SOUL.md +11 -7
- package/skills/create-campaign-v2/core/flow.v2.json +70 -47
- package/skills/create-campaign-v2/references/approval-gate-framing.md +7 -8
- package/skills/create-campaign-v2/references/sample-validation-loop.md +9 -5
- package/skills/create-campaign-v2/references/step-13-import-leads.md +10 -4
- package/skills/create-campaign-v2/references/step-15-re-cascade.md +12 -7
- package/skills/create-campaign-v2/references/watch-guide-narration.md +16 -13
- package/skills/create-campaign-v2-tail/SKILL.md +24 -16
- package/skills/providers/apollo.md +8 -1
- package/skills/providers/prospeo.md +11 -1
- package/skills/providers/sales-nav.md +1 -1
- package/skills/research/config.json +9 -0
- package/skills/create-campaign-v2/references/message-review-safety-gate.md +0 -162
package/README.md
CHANGED
|
@@ -267,10 +267,14 @@ Parallel execution contract:
|
|
|
267
267
|
Codex config, Claude agent files, and prompts should consume the registry.
|
|
268
268
|
- Post-find-leads scout names also come from `agents/registry.json` and are
|
|
269
269
|
exposed through `get_post_find_leads_scout_registry`. After
|
|
270
|
-
`confirm_lead_list` imports a non-empty review batch and rows are proven,
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
270
|
+
`confirm_lead_list` imports a non-empty review batch and rows are proven, ask
|
|
271
|
+
the filter-choice question immediately. Do not load this registry or deep
|
|
272
|
+
filter/message prompts before that question. Once the user answers, launch the
|
|
273
|
+
message-generation scout from the same campaign/table basis. If the user
|
|
274
|
+
chooses filters, also launch the filter-leads scout, show Filter Rules, save
|
|
275
|
+
rubrics, then keep the browser on Filter Leads while the message
|
|
276
|
+
recommendation is reviewed. If filters are skipped, move to Messages/message
|
|
277
|
+
review. Product cells still wait for template approval.
|
|
274
278
|
- Claude host: use the installed `source-scout-linkedin-engagement`,
|
|
275
279
|
`source-scout-sales-nav`, and `source-scout-prospeo-contact` Task/Agent
|
|
276
280
|
subagents for parallel lead-source scouting only when the current session
|
|
@@ -40,8 +40,8 @@ the branch input.
|
|
|
40
40
|
`get_subskill_prompt({ subskillName: "generate-messages", offset, limit })`
|
|
41
41
|
until `hasMore` is false.
|
|
42
42
|
|
|
43
|
-
2. Use that prompt as the drafting contract.
|
|
44
|
-
|
|
43
|
+
2. Use that prompt as the drafting contract. Do not use create-campaign
|
|
44
|
+
safety/checklist instructions as a substitute for the full prompt.
|
|
45
45
|
3. Draft only from the campaign brief, selected source context, and imported
|
|
46
46
|
review-batch rows supplied by the parent.
|
|
47
47
|
4. Keep the work provisional until the user chooses `Use Template` in Messages.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
You are the Prospeo Contact Scout for Sellable find-leads.
|
|
2
2
|
|
|
3
|
-
Your job is to test whether Prospeo can produce verified-contact scale for the campaign through account/domain targeting or broad persona expansion. Work only on this source lane. Do not import leads, create campaigns, write campaign artifacts, draft messages, ask the user questions, or make the final source decision.
|
|
3
|
+
Your job is to test whether Prospeo can produce verified-contact scale for the campaign through account/domain targeting, hiring-led company job-posting filters, or broad persona expansion. Work only on this source lane. Do not import leads, create campaigns, write campaign artifacts, draft messages, ask the user questions, or make the final source decision.
|
|
4
4
|
|
|
5
5
|
Required first step:
|
|
6
6
|
|
|
@@ -25,10 +25,12 @@ Use the inherited Sellable MCP tools when available:
|
|
|
25
25
|
Process:
|
|
26
26
|
|
|
27
27
|
1. Read the campaign brief, source intake, kickoff doc, or lane prompt supplied by the parent.
|
|
28
|
-
2. Identify whether this is domain/account targeting or broad persona expansion.
|
|
28
|
+
2. Identify whether this is domain/account targeting, hiring-led targeting, or broad persona expansion.
|
|
29
29
|
3. For domain targeting, use or create the standalone `domainFilterId` before searching; never pass raw domains directly into `search_prospeo`.
|
|
30
|
-
4.
|
|
31
|
-
5.
|
|
30
|
+
4. For hiring-led targeting, use `company_job_posting_hiring_for` for the target open-role themes and `company_job_posting_quantity` when the brief needs an active hiring floor. Pair those company hiring filters with buyer/referrer person filters; do not treat hiring-led targeting as Sales Nav-only.
|
|
31
|
+
5. Run the narrowest useful Prospeo people preview and 1-2 refinements if quality or scale is unclear. Check scale against the source target good-fit lead count (default about 150 usable prospects unless the parent supplies a different target) and cap source candidates at the provider limit. Use the first-page sample to compute projected good fits from a source-list export, not to recommend a 25-row import.
|
|
32
|
+
6. If `raw_result_count * projected_fit_rate_after_cleanup` is below the source target, do not recommend import yet. Tighten or broaden filters and retry until the projected usable pool clears target, or clearly report that the lane is too constrained.
|
|
33
|
+
7. Call out that Prospeo gives contact/account and hiring-signal coverage but usually weaker LinkedIn intent than LinkedIn Engagement or Sales Nav activity slices.
|
|
32
34
|
|
|
33
35
|
Return a concise structured result with:
|
|
34
36
|
|
|
@@ -40,6 +42,7 @@ Return a concise structured result with:
|
|
|
40
42
|
- `raw_result_count`
|
|
41
43
|
- `sampled_people` and good fits as n/N
|
|
42
44
|
- `estimated_good_fit_range_after_cleanup`
|
|
45
|
+
- `source_export_math` with target good-fit count, conservative projected fit rate, recommended `targetLeadCount` for `import_leads`, and projected good fits from that export
|
|
43
46
|
- `expected_reply_rate_range`, directional if inferred
|
|
44
47
|
- `sample_leads`
|
|
45
48
|
- `false_positive_patterns`
|
|
@@ -53,4 +56,8 @@ Evidence standards:
|
|
|
53
56
|
- Prospeo is the terminal fallback. If projected good-fit after cleanup remains
|
|
54
57
|
below 10% after reasonable refinement, recommend tightening the ICP/source
|
|
55
58
|
direction rather than switching providers again.
|
|
56
|
-
-
|
|
59
|
+
- Never recommend "import 25 leads" as the Prospeo source action. Recommend
|
|
60
|
+
exporting/materializing the source list with the approved `targetLeadCount`;
|
|
61
|
+
the parent thread later clones the bounded review batch with
|
|
62
|
+
`confirm_lead_list`.
|
|
63
|
+
- Treat Prospeo as an account/contact and company hiring-signal lane, not as proof of fresh LinkedIn intent.
|
|
@@ -28,8 +28,9 @@ Process:
|
|
|
28
28
|
2. Preserve target role names with `CURRENT_TITLE` lookups; do not rely on seniority alone when the brief names concrete roles.
|
|
29
29
|
3. When `lookup_sales_nav_filter` returns multiple title options, choose the closest semantic title match instead of the first result.
|
|
30
30
|
4. Build a broad-but-reasonable baseline from role/title, geography, company size, industry/account context, and recent LinkedIn activity when relevant.
|
|
31
|
-
5. Check scale against the source target good-fit lead count (default
|
|
32
|
-
for Sales Nav
|
|
31
|
+
5. Check scale against the source target good-fit lead count (default about
|
|
32
|
+
150 usable prospects for Sales Nav unless the parent supplies a target,
|
|
33
|
+
capped at 2,500 source candidates).
|
|
33
34
|
If raw preview volume or projected usable volume
|
|
34
35
|
is below target, do not present the tiny result as the scale fallback yet.
|
|
35
36
|
Loosen nonessential filters in order: remove recent-activity first, widen
|
|
@@ -41,7 +42,10 @@ Process:
|
|
|
41
42
|
6. Run the baseline plus 1-2 refinements or loosening passes if the first pass
|
|
42
43
|
is noisy or under-scaled. Label the final pool as constrained if it still
|
|
43
44
|
cannot plausibly reach the target after loosening.
|
|
44
|
-
7.
|
|
45
|
+
7. Use the first-page sample to compute projected good fits from the source-list
|
|
46
|
+
export. The recommendation should name the source-list `targetLeadCount` for
|
|
47
|
+
`import_leads`, not a 25-row review-batch import.
|
|
48
|
+
8. Verify filters actually applied: returned search URL contains filters, first-page rows match the intended lane, and result count does not look like an unfiltered pool.
|
|
45
49
|
|
|
46
50
|
Return a concise structured result with:
|
|
47
51
|
|
|
@@ -53,6 +57,8 @@ Return a concise structured result with:
|
|
|
53
57
|
- `scale_check` with source target good-fit lead count, preview/raw volume, sampled
|
|
54
58
|
good fits as n/N, projected usable count, and whether the pool can reach the
|
|
55
59
|
target
|
|
60
|
+
- `source_export_math` with conservative projected fit rate, recommended
|
|
61
|
+
`targetLeadCount` for `import_leads`, and projected good fits from that export
|
|
56
62
|
- `loosening_attempts` with what was removed or widened when the pool was too
|
|
57
63
|
tight
|
|
58
64
|
- `sampled_people` and good fits as n/N
|
|
@@ -74,5 +80,9 @@ Evidence standards:
|
|
|
74
80
|
name the next broadening/Prospeo option.
|
|
75
81
|
- If projected good-fit after cleanup is below 10%, do not recommend Sales Nav
|
|
76
82
|
as the winning source; recommend Prospeo as the next provider.
|
|
83
|
+
- Never recommend "import 25 leads" as the Sales Nav source action. Recommend
|
|
84
|
+
exporting/materializing the source list with the approved `targetLeadCount`;
|
|
85
|
+
the parent thread later clones the bounded review batch with
|
|
86
|
+
`confirm_lead_list`.
|
|
77
87
|
- Do not hand-wave missing filter IDs.
|
|
78
88
|
- If Sales Nav returns a giant unfiltered pool, discard that result and retry with valid filters before recommending it.
|
package/dist/tools/leads.d.ts
CHANGED
|
@@ -1400,10 +1400,11 @@ export declare const leadToolDefinitions: ({
|
|
|
1400
1400
|
items: {
|
|
1401
1401
|
type: string;
|
|
1402
1402
|
};
|
|
1403
|
+
description: string;
|
|
1403
1404
|
};
|
|
1404
1405
|
company_job_posting_quantity: {
|
|
1405
|
-
type: string;
|
|
1406
1406
|
description: string;
|
|
1407
|
+
type: string;
|
|
1407
1408
|
properties: {
|
|
1408
1409
|
min: {
|
|
1409
1410
|
type: string;
|
package/dist/tools/leads.js
CHANGED
|
@@ -8,6 +8,7 @@ import { buildCsvLinkedinPreview, matchesLinkedinConfirmationToken, parseLinkedi
|
|
|
8
8
|
import { assertInteractionApproval } from "./interaction-mode.js";
|
|
9
9
|
import { assertProviderPromptLoaded, markProviderPromptLoaded, } from "./provider-preflight.js";
|
|
10
10
|
import { waitForLeadListReady } from "./readiness.js";
|
|
11
|
+
import { getTableRowsMinimal } from "./rows.js";
|
|
11
12
|
const entryPath = process.argv[1] ? resolve(process.argv[1]) : process.cwd();
|
|
12
13
|
const entryDir = dirname(entryPath);
|
|
13
14
|
const workspaceRoot = resolveWorkspaceRoot(entryDir);
|
|
@@ -42,11 +43,11 @@ const defaultCampaignSourceDefaults = {
|
|
|
42
43
|
postCoverageBuffer: 1.2,
|
|
43
44
|
},
|
|
44
45
|
"sales-nav": {
|
|
45
|
-
targetGoodFitLeads:
|
|
46
|
+
targetGoodFitLeads: 150,
|
|
46
47
|
maxSourceCandidates: 2500,
|
|
47
48
|
},
|
|
48
49
|
prospeo: {
|
|
49
|
-
targetGoodFitLeads:
|
|
50
|
+
targetGoodFitLeads: 150,
|
|
50
51
|
maxSourceCandidates: 2500,
|
|
51
52
|
},
|
|
52
53
|
},
|
|
@@ -525,8 +526,8 @@ function buildSourceImportWatchNarration({ provider, selectedPostCount, estimate
|
|
|
525
526
|
: "Importing source leads",
|
|
526
527
|
visibleState: `The browser is showing ${providerLabel} import progress for ${sourceDetail}.${targetDetail}`,
|
|
527
528
|
agentIntent: "Codex is materializing the approved source into a lead list before cloning only the bounded review batch into the campaign.",
|
|
528
|
-
nextAction: "Wait for source leads, then import the
|
|
529
|
-
safety: "
|
|
529
|
+
nextAction: "Wait for source leads, then import the bounded review batch",
|
|
530
|
+
safety: "This step materializes the source list; only the bounded review batch is cloned into the campaign afterward.",
|
|
530
531
|
progressLabel: "Source scouting",
|
|
531
532
|
};
|
|
532
533
|
}
|
|
@@ -558,19 +559,146 @@ function buildSourceImportRecoveryWatchNarration(args) {
|
|
|
558
559
|
safety: "The campaign is still at source recovery.",
|
|
559
560
|
};
|
|
560
561
|
}
|
|
561
|
-
function
|
|
562
|
+
function normalizeLeadText(value) {
|
|
563
|
+
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
564
|
+
}
|
|
565
|
+
function compactLeadText(value) {
|
|
566
|
+
return typeof value === "string" ? value.trim().replace(/\s+/g, " ") : "";
|
|
567
|
+
}
|
|
568
|
+
function rowSearchText(row) {
|
|
569
|
+
const carryValues = Object.values(row.carryData ?? {}).slice(0, 12);
|
|
570
|
+
return [row.name, row.title, row.company, ...carryValues]
|
|
571
|
+
.map(normalizeLeadText)
|
|
572
|
+
.filter(Boolean)
|
|
573
|
+
.join(" ");
|
|
574
|
+
}
|
|
575
|
+
function rowExample(row) {
|
|
576
|
+
const name = compactLeadText(row.name);
|
|
577
|
+
const title = compactLeadText(row.title);
|
|
578
|
+
const company = compactLeadText(row.company);
|
|
579
|
+
const label = name || company || title || "Unnamed lead";
|
|
580
|
+
return title ? `${label} (${title})` : label;
|
|
581
|
+
}
|
|
582
|
+
function collectExamples(rows, indexes) {
|
|
583
|
+
return indexes.slice(0, 2).map((index) => rowExample(rows[index]));
|
|
584
|
+
}
|
|
585
|
+
function formatExamples(prefix, examples) {
|
|
586
|
+
if (examples.length === 0)
|
|
587
|
+
return "";
|
|
588
|
+
const text = `${prefix} ${examples.join("; ")}.`;
|
|
589
|
+
return text.length > 180 ? `${text.slice(0, 177).trim()}...` : text;
|
|
590
|
+
}
|
|
591
|
+
function analyzeFilterChoiceSample(sampleRows) {
|
|
592
|
+
const rows = Array.isArray(sampleRows) ? sampleRows : [];
|
|
593
|
+
const strongIndexes = [];
|
|
594
|
+
const adjacentIndexes = [];
|
|
595
|
+
const riskIndexes = [];
|
|
596
|
+
const unknownIndexes = [];
|
|
597
|
+
const seniorPattern = /\b(founder|co[-\s]?founder|ceo|chief|cro|cmo|cco|vp|vice president|head of|director|owner|partner)\b/;
|
|
598
|
+
const gtmPattern = /\b(gtm|go[-\s]?to[-\s]?market|revenue|sales|growth|marketing|demand gen|demand generation|business development|bd\b|outbound|pipeline|revops|revenue operations|sales development|sdr)\b/;
|
|
599
|
+
const adjacentPattern = /\b(sales engineering|solutions? engineering|enablement|customer success|partnerships|partner|product marketing|marketing operations|sales operations)\b/;
|
|
600
|
+
const recruitingPattern = /\b(recruiter|recruiting|talent acquisition|people operations|human resources|\bhr\b)\b/;
|
|
601
|
+
const juniorOrNoisyPattern = /\b(intern|student|associate|coordinator|assistant|freelance|consultant|self-employed|open to work)\b/;
|
|
602
|
+
rows.forEach((row, index) => {
|
|
603
|
+
const text = rowSearchText(row);
|
|
604
|
+
if (!text) {
|
|
605
|
+
unknownIndexes.push(index);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (recruitingPattern.test(text) || juniorOrNoisyPattern.test(text)) {
|
|
609
|
+
riskIndexes.push(index);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (seniorPattern.test(text) && gtmPattern.test(text)) {
|
|
613
|
+
strongIndexes.push(index);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (seniorPattern.test(text) && adjacentPattern.test(text)) {
|
|
617
|
+
adjacentIndexes.push(index);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
unknownIndexes.push(index);
|
|
621
|
+
});
|
|
622
|
+
const sampleCount = rows.length;
|
|
623
|
+
if (sampleCount === 0) {
|
|
624
|
+
return {
|
|
625
|
+
sampleCount,
|
|
626
|
+
strongFitCount: 0,
|
|
627
|
+
adjacentCount: 0,
|
|
628
|
+
riskCount: 0,
|
|
629
|
+
unknownCount: 0,
|
|
630
|
+
strongExamples: [],
|
|
631
|
+
riskExamples: [],
|
|
632
|
+
recommendation: "manual-review",
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
const fitCount = strongIndexes.length + adjacentIndexes.length;
|
|
636
|
+
const riskCount = riskIndexes.length;
|
|
637
|
+
const unknownCount = unknownIndexes.length;
|
|
638
|
+
const cleanupCount = riskCount + unknownCount;
|
|
639
|
+
const fitRatio = fitCount / sampleCount;
|
|
640
|
+
const cleanupRatio = cleanupCount / sampleCount;
|
|
641
|
+
const adjacentRatio = adjacentIndexes.length / sampleCount;
|
|
642
|
+
const maxCleanRiskCount = Math.max(2, Math.floor(sampleCount * 0.12));
|
|
643
|
+
const recommendation = fitRatio >= 0.75 &&
|
|
644
|
+
cleanupCount <= maxCleanRiskCount &&
|
|
645
|
+
adjacentRatio <= 0.3
|
|
646
|
+
? "skip"
|
|
647
|
+
: fitRatio >= 0.6 && cleanupRatio <= 0.3
|
|
648
|
+
? "light-filter"
|
|
649
|
+
: "add-filter";
|
|
650
|
+
return {
|
|
651
|
+
sampleCount,
|
|
652
|
+
strongFitCount: strongIndexes.length,
|
|
653
|
+
adjacentCount: adjacentIndexes.length,
|
|
654
|
+
riskCount,
|
|
655
|
+
unknownCount,
|
|
656
|
+
strongExamples: collectExamples(rows, [
|
|
657
|
+
...strongIndexes,
|
|
658
|
+
...adjacentIndexes,
|
|
659
|
+
]),
|
|
660
|
+
riskExamples: collectExamples(rows, [...riskIndexes, ...unknownIndexes]),
|
|
661
|
+
recommendation,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function buildFilterChoiceWatchNarration({ sourceLeadCount, reviewRowCount, sampleRows, }) {
|
|
562
665
|
const sourceCopy = typeof sourceLeadCount === "number" && sourceLeadCount > 0
|
|
563
666
|
? ` from ${sourceLeadCount.toLocaleString("en-US")} source candidate${sourceLeadCount === 1 ? "" : "s"}`
|
|
564
667
|
: "";
|
|
565
668
|
const reviewCopy = typeof reviewRowCount === "number" && reviewRowCount > 0
|
|
566
669
|
? `${reviewRowCount.toLocaleString("en-US")} review lead${reviewRowCount === 1 ? " is" : "s are"} in the campaign table${sourceCopy}.`
|
|
567
670
|
: `The bounded review batch is in the campaign table${sourceCopy}.`;
|
|
671
|
+
const assessment = analyzeFilterChoiceSample(sampleRows);
|
|
672
|
+
const fitCount = assessment.strongFitCount + assessment.adjacentCount;
|
|
673
|
+
const cleanupCount = assessment.riskCount + assessment.unknownCount;
|
|
674
|
+
const sampleCopy = assessment.sampleCount > 0
|
|
675
|
+
? `Sample read: ${fitCount}/${assessment.sampleCount} look like senior target-role fits; ${cleanupCount} need cleanup or manual review.`
|
|
676
|
+
: "Sample rows were not available to inspect, so this is a manual filter decision.";
|
|
677
|
+
const fitExamples = formatExamples("Fit examples:", assessment.strongExamples);
|
|
678
|
+
const riskExamples = formatExamples("Cleanup examples:", assessment.riskExamples);
|
|
679
|
+
const headline = assessment.recommendation === "skip"
|
|
680
|
+
? "Filtering may not be needed"
|
|
681
|
+
: assessment.recommendation === "light-filter"
|
|
682
|
+
? "I recommend light cleanup filters"
|
|
683
|
+
: assessment.recommendation === "manual-review"
|
|
684
|
+
? "Review filters or skip"
|
|
685
|
+
: "I recommend adding filters";
|
|
686
|
+
const agentIntent = assessment.recommendation === "skip"
|
|
687
|
+
? `Codex is not recommending filters because the sample already looks clean. ${fitExamples} Add filters only for a narrow exclusion.`
|
|
688
|
+
: assessment.recommendation === "light-filter"
|
|
689
|
+
? `Codex is recommending light filters because most rows fit, but ${cleanupCount} of ${assessment.sampleCount} look adjacent, unknown, or risky. ${riskExamples}`
|
|
690
|
+
: assessment.recommendation === "manual-review"
|
|
691
|
+
? "Codex could not inspect the review rows, so it is pausing for a manual filter decision instead of assuming filters are required."
|
|
692
|
+
: `Codex is recommending filters because ${cleanupCount} of ${assessment.sampleCount} sampled rows look adjacent, unknown, or risky before message review. ${riskExamples}`;
|
|
693
|
+
const nextAction = assessment.recommendation === "skip"
|
|
694
|
+
? "Skip filters or add a narrow cleanup rule"
|
|
695
|
+
: "Choose filters or skip";
|
|
568
696
|
return {
|
|
569
697
|
stage: "fit-message",
|
|
570
|
-
headline
|
|
571
|
-
visibleState: `${reviewCopy} The browser is showing the filter-choice screen
|
|
572
|
-
agentIntent:
|
|
573
|
-
nextAction
|
|
698
|
+
headline,
|
|
699
|
+
visibleState: `${reviewCopy} ${sampleCopy} The browser is showing the filter-choice screen.`,
|
|
700
|
+
agentIntent: agentIntent.trim(),
|
|
701
|
+
nextAction,
|
|
574
702
|
safety: "This choice sets the filter path.",
|
|
575
703
|
workerStatuses: {
|
|
576
704
|
leadFitBuilder: "idle",
|
|
@@ -1011,7 +1139,7 @@ export const leadToolDefinitions = [
|
|
|
1011
1139
|
},
|
|
1012
1140
|
{
|
|
1013
1141
|
name: "search_prospeo",
|
|
1014
|
-
description: 'Search Prospeo for people using filters and optional domainFilterId. Requires get_provider_prompt({ provider: "prospeo" }) first. When targeting known accounts, call load_csv_domains for CSV-on-disk workflows or save_domain_filters for pasted/raw domain lists, then pass domainFilterId. Raw domain inputs and company-name targeting are NOT supported in this MCP tool. Strategy: start with 2-3 high-signal filters (title/seniority + industry or domainFilterId + headcount), then tighten one filter at a time. For security, AppSec, SOC, RevOps, Demand Gen, and similar function-specific lanes, do not widen with bare seniority labels like "Head" or "Director" alone; pair them with explicit function-title keywords and inspect the sample for off-function `Head of X` leakage. Prefer person location over company HQ unless HQ is explicitly needed. `campaignOfferId` routing rule: OMIT campaignOfferId ONLY in pre-mint Phase 84 `find leads` discovery mode (validating ICP before the commit gate). In every other context — post-mint lead additions, operator-driven searches on a live campaign, any search where the intent is to persist results to a specific campaign — you MUST pass campaignOfferId so the search shows up in that campaign\'s Contact Search panel. Post-mint create-campaign-v2 watch runs MUST pass currentStep: "prospeo". Omitting campaignOfferId post-mint orphans the search from the UI. Returns normalized results with pagination.',
|
|
1142
|
+
description: 'Search Prospeo for people using filters and optional domainFilterId. Requires get_provider_prompt({ provider: "prospeo" }) first. Use Prospeo first for hiring-led targeting because it supports company_job_posting_hiring_for and company_job_posting_quantity; Sales Nav does not filter companies by hiring role. When targeting known accounts, call load_csv_domains for CSV-on-disk workflows or save_domain_filters for pasted/raw domain lists, then pass domainFilterId. Raw domain inputs and company-name targeting are NOT supported in this MCP tool. Strategy: start with 2-3 high-signal filters (title/seniority + industry or domainFilterId + headcount), add job-posting filters for hiring-led campaigns, then tighten one filter at a time. For security, AppSec, SOC, RevOps, Demand Gen, and similar function-specific lanes, do not widen with bare seniority labels like "Head" or "Director" alone; pair them with explicit function-title keywords and inspect the sample for off-function `Head of X` leakage. Prefer person location over company HQ unless HQ is explicitly needed. `campaignOfferId` routing rule: OMIT campaignOfferId ONLY in pre-mint Phase 84 `find leads` discovery mode (validating ICP before the commit gate). In every other context — post-mint lead additions, operator-driven searches on a live campaign, any search where the intent is to persist results to a specific campaign — you MUST pass campaignOfferId so the search shows up in that campaign\'s Contact Search panel. Post-mint create-campaign-v2 watch runs MUST pass currentStep: "prospeo". Omitting campaignOfferId post-mint orphans the search from the UI. Returns normalized results with pagination.',
|
|
1015
1143
|
inputSchema: {
|
|
1016
1144
|
type: "object",
|
|
1017
1145
|
properties: {
|
|
@@ -1078,8 +1206,12 @@ export const leadToolDefinitions = [
|
|
|
1078
1206
|
company_job_posting_hiring_for: {
|
|
1079
1207
|
type: "array",
|
|
1080
1208
|
items: { type: "string" },
|
|
1209
|
+
description: "Company hiring filter. Use for hiring-led campaigns; values are open-role themes such as SDR, Account Executive, Growth, RevOps, Demand Generation, GTM Engineer, or AI Engineer.",
|
|
1210
|
+
},
|
|
1211
|
+
company_job_posting_quantity: {
|
|
1212
|
+
...prospeoRangeFilterSchema,
|
|
1213
|
+
description: "Active job posting count range. Use min when the campaign needs companies with a real hiring push.",
|
|
1081
1214
|
},
|
|
1082
|
-
company_job_posting_quantity: prospeoRangeFilterSchema,
|
|
1083
1215
|
company_headcount_by_department: {
|
|
1084
1216
|
type: "array",
|
|
1085
1217
|
items: prospeoCompanyHeadcountByDepartmentSchema,
|
|
@@ -1192,7 +1324,7 @@ export const leadToolDefinitions = [
|
|
|
1192
1324
|
},
|
|
1193
1325
|
{
|
|
1194
1326
|
name: "import_leads",
|
|
1195
|
-
description: "Create/select a lead list and start the provider import job. Requires provider prompt preflight via get_provider_prompt for the active provider. On success, this tool owns moving the watched campaign to confirm-lead-list with import/progress narration after a leadListId/jobId exists; do not call update_campaign to fix that step. After the user confirms the list, call confirm_lead_list.",
|
|
1327
|
+
description: "Create/select a source lead list and start the provider import/export job. Requires provider prompt preflight via get_provider_prompt for the active provider. For Sales Nav/Prospeo, targetLeadCount is the approved source-list export count computed from projected good-fit math, not the later bounded review-batch size. On success, this tool owns moving the watched campaign to confirm-lead-list with import/progress narration after a leadListId/jobId exists; do not call update_campaign to fix that step. After the user confirms the source list, call confirm_lead_list to clone only the bounded review batch into the campaign table.",
|
|
1196
1328
|
inputSchema: {
|
|
1197
1329
|
type: "object",
|
|
1198
1330
|
properties: {
|
|
@@ -1219,7 +1351,7 @@ export const leadToolDefinitions = [
|
|
|
1219
1351
|
},
|
|
1220
1352
|
targetLeadCount: {
|
|
1221
1353
|
type: "number",
|
|
1222
|
-
description: "
|
|
1354
|
+
description: "Provider source-list target count (max per provider from config). For Sales Nav/Prospeo this should be the export/materialization count needed to hit projected good-fit goals, not the 25-row review-batch size.",
|
|
1223
1355
|
},
|
|
1224
1356
|
mode: {
|
|
1225
1357
|
type: "string",
|
|
@@ -2741,6 +2873,20 @@ export async function confirmLeadList(input) {
|
|
|
2741
2873
|
await Promise.all(batch.map((rowId) => api.delete(`/api/v3/workflow-tables/${campaignTableId}/rows/${rowId}`)));
|
|
2742
2874
|
}
|
|
2743
2875
|
}
|
|
2876
|
+
let reviewSampleRows = [];
|
|
2877
|
+
if (campaignTableId && effectiveCurrentStep === "filter-choice") {
|
|
2878
|
+
try {
|
|
2879
|
+
const sampleLimit = Math.min(Math.max(keptReviewRowCount, 1), 25);
|
|
2880
|
+
const sample = await getTableRowsMinimal(campaignTableId, {
|
|
2881
|
+
limit: sampleLimit,
|
|
2882
|
+
page: 1,
|
|
2883
|
+
});
|
|
2884
|
+
reviewSampleRows = Array.isArray(sample?.rows) ? sample.rows : [];
|
|
2885
|
+
}
|
|
2886
|
+
catch {
|
|
2887
|
+
reviewSampleRows = [];
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2744
2890
|
// Persist currentStep if the caller asked for it. Do NOT touch
|
|
2745
2891
|
// selectedLeadListId here: the campaign table id is already saved to
|
|
2746
2892
|
// CampaignOffer.workflowTableId by /api/v3/campaign-builder/import-leads,
|
|
@@ -2758,6 +2904,7 @@ export async function confirmLeadList(input) {
|
|
|
2758
2904
|
watchNarration: buildFilterChoiceWatchNarration({
|
|
2759
2905
|
sourceLeadCount: leadListRowCount,
|
|
2760
2906
|
reviewRowCount: keptReviewRowCount,
|
|
2907
|
+
sampleRows: reviewSampleRows,
|
|
2761
2908
|
}),
|
|
2762
2909
|
}
|
|
2763
2910
|
: {}),
|
|
@@ -2800,8 +2947,8 @@ export async function confirmLeadList(input) {
|
|
|
2800
2947
|
: undefined,
|
|
2801
2948
|
message: requestedTargetLeadCount !== null &&
|
|
2802
2949
|
leadListRowCount > requestedTargetLeadCount
|
|
2803
|
-
? `I found ${leadListRowCount} source candidates and imported the first ${Math.min(importedRowCount, requestedTargetLeadCount)} into the campaign review table. The watched campaign is now on filter-choice
|
|
2804
|
-
: "Lead list imported into the campaign review table. The watched campaign is now on filter-choice
|
|
2950
|
+
? `I found ${leadListRowCount} source candidates and imported the first ${Math.min(importedRowCount, requestedTargetLeadCount)} into the campaign review table. The watched campaign is now on filter-choice; ask add filters vs skip filters before loading post-lead workers.`
|
|
2951
|
+
: "Lead list imported into the campaign review table. The watched campaign is now on filter-choice; ask add filters vs skip filters before loading post-lead workers.",
|
|
2805
2952
|
};
|
|
2806
2953
|
}
|
|
2807
2954
|
export function getProviderPrompt(input) {
|
package/dist/tools/prompts.d.ts
CHANGED
|
@@ -134,6 +134,7 @@ export interface PostFindLeadsScoutRegistryResponse {
|
|
|
134
134
|
forbiddenStarts: string[];
|
|
135
135
|
runtimeProofTransport: string;
|
|
136
136
|
runtimeProofRequiredFields: string[];
|
|
137
|
+
promptRequired?: string;
|
|
137
138
|
basisFields: string[];
|
|
138
139
|
compactOutputFields: string[];
|
|
139
140
|
reusePolicy?: string;
|
package/dist/tools/prompts.js
CHANGED
|
@@ -315,6 +315,7 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
315
315
|
"basis.workflowTableId",
|
|
316
316
|
"basis.reviewBatchRowHash or basis.reviewBatchRowIds",
|
|
317
317
|
],
|
|
318
|
+
promptRequired: 'get_subskill_prompt({ subskillName: "generate-messages", offset, limit }) until hasMore=false',
|
|
318
319
|
basisFields: [
|
|
319
320
|
"campaign revision or updatedAt",
|
|
320
321
|
"brief hash",
|
|
@@ -339,9 +340,9 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
339
340
|
reusePolicy: "The first completed Message Draft Builder recommendation remains the default review candidate. Later Lead Fit Builder, Filter Leads, enrichment, or rubric completion may make an enriched rewrite available, but does not automatically retry or replace the initial draft unless campaign/brief/source/list/table/review-batch identity mismatches or the initial output failed.",
|
|
340
341
|
},
|
|
341
342
|
usage: {
|
|
342
|
-
codex: "After confirm_lead_list imports a non-empty bounded review batch and get_rows_minimal proves rows for workflowTableId,
|
|
343
|
-
claude: "After confirm_lead_list imports a non-empty bounded review batch and get_rows_minimal proves rows for workflowTableId,
|
|
344
|
-
parentThreadRule:
|
|
343
|
+
codex: "After confirm_lead_list imports a non-empty bounded review batch and get_rows_minimal proves rows for workflowTableId, ask the filter-choice question immediately. Do not spawn returned post-lead scout names before that question. Once the user answers, spawn Message Draft Builder from the same campaign/table basis. If the user chooses filters, also spawn Lead Fit Builder, move to Filter Rules, save rubrics, then keep the browser on Filter Leads while the message recommendation is reviewed. If filters are skipped, move to Messages/message review.",
|
|
344
|
+
claude: "After confirm_lead_list imports a non-empty bounded review batch and get_rows_minimal proves rows for workflowTableId, ask the filter-choice question immediately. Do not invoke returned post-lead Task/Agent names before that question. Once the user answers, invoke Message Draft Builder from the same campaign/table basis. If the user chooses filters, also invoke Lead Fit Builder, move to Filter Rules, save rubrics, then keep the browser on Filter Leads while the message recommendation is reviewed. If filters are skipped, move to Messages/message review.",
|
|
345
|
+
parentThreadRule: 'Named agents are optional acceleration, but message drafting is not optional. If post-find-leads-message-scout is available, run it as the background Message Draft Builder after the filter-choice answer. If it is absent, do not customer-surface install status; the main thread must execute the same message branch from CampaignOffer state, selected source state, workflowTableId, and imported review-batch rows. Local markdown/json files are not normal-path inputs. The filter-choice question is the first post-import user gate; do not load post-lead registries, filter references, or the full generate-messages prompt before it. Message drafting starts after the filter-choice answer, must load get_subskill_prompt({ subskillName: "generate-messages", offset, limit }) until hasMore=false, must read live campaign/review-batch state through scoped MCP/product tools, and must reject mismatched selectedLeadListId/workflowTableId/campaign/workspace input. On the filter path, keep the browser on Filter Rules then Filter Leads while the background message agent prepares the template; enrichment/filtering/Generate Message cells wait for template approval. On the skip path, move to Messages/message review and wait for template approval before enrichment. Do not render message review from checklist or shortcut instructions; message review requires a messageDraftRecommendation whose basis proves the full generate-messages prompt ran for the current campaign/table/review batch. Do not automatically rerun Message Draft Builder after filters/enrichment finish; show the initial draft by default and offer an enriched rewrite only with explicit user opt-in.',
|
|
345
346
|
},
|
|
346
347
|
};
|
|
347
348
|
}
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -2557,10 +2557,11 @@ export declare const allTools: ({
|
|
|
2557
2557
|
items: {
|
|
2558
2558
|
type: string;
|
|
2559
2559
|
};
|
|
2560
|
+
description: string;
|
|
2560
2561
|
};
|
|
2561
2562
|
company_job_posting_quantity: {
|
|
2562
|
-
type: string;
|
|
2563
2563
|
description: string;
|
|
2564
|
+
type: string;
|
|
2564
2565
|
properties: {
|
|
2565
2566
|
min: {
|
|
2566
2567
|
type: string;
|
|
@@ -3702,6 +3703,7 @@ export declare const allTools: ({
|
|
|
3702
3703
|
tableId?: undefined;
|
|
3703
3704
|
targetCount?: undefined;
|
|
3704
3705
|
minPassedCount?: undefined;
|
|
3706
|
+
minMessagesCount?: undefined;
|
|
3705
3707
|
timeoutMs?: undefined;
|
|
3706
3708
|
intervalMs?: undefined;
|
|
3707
3709
|
includeRows?: undefined;
|
|
@@ -3735,6 +3737,7 @@ export declare const allTools: ({
|
|
|
3735
3737
|
tableId?: undefined;
|
|
3736
3738
|
targetCount?: undefined;
|
|
3737
3739
|
minPassedCount?: undefined;
|
|
3740
|
+
minMessagesCount?: undefined;
|
|
3738
3741
|
timeoutMs?: undefined;
|
|
3739
3742
|
intervalMs?: undefined;
|
|
3740
3743
|
includeRows?: undefined;
|
|
@@ -3793,6 +3796,7 @@ export declare const allTools: ({
|
|
|
3793
3796
|
tableId?: undefined;
|
|
3794
3797
|
targetCount?: undefined;
|
|
3795
3798
|
minPassedCount?: undefined;
|
|
3799
|
+
minMessagesCount?: undefined;
|
|
3796
3800
|
timeoutMs?: undefined;
|
|
3797
3801
|
intervalMs?: undefined;
|
|
3798
3802
|
includeRows?: undefined;
|
|
@@ -3851,6 +3855,7 @@ export declare const allTools: ({
|
|
|
3851
3855
|
tableId?: undefined;
|
|
3852
3856
|
targetCount?: undefined;
|
|
3853
3857
|
minPassedCount?: undefined;
|
|
3858
|
+
minMessagesCount?: undefined;
|
|
3854
3859
|
timeoutMs?: undefined;
|
|
3855
3860
|
intervalMs?: undefined;
|
|
3856
3861
|
includeRows?: undefined;
|
|
@@ -3911,6 +3916,7 @@ export declare const allTools: ({
|
|
|
3911
3916
|
tableId?: undefined;
|
|
3912
3917
|
targetCount?: undefined;
|
|
3913
3918
|
minPassedCount?: undefined;
|
|
3919
|
+
minMessagesCount?: undefined;
|
|
3914
3920
|
timeoutMs?: undefined;
|
|
3915
3921
|
intervalMs?: undefined;
|
|
3916
3922
|
includeRows?: undefined;
|
|
@@ -3941,6 +3947,7 @@ export declare const allTools: ({
|
|
|
3941
3947
|
tableId?: undefined;
|
|
3942
3948
|
targetCount?: undefined;
|
|
3943
3949
|
minPassedCount?: undefined;
|
|
3950
|
+
minMessagesCount?: undefined;
|
|
3944
3951
|
timeoutMs?: undefined;
|
|
3945
3952
|
intervalMs?: undefined;
|
|
3946
3953
|
includeRows?: undefined;
|
|
@@ -3971,6 +3978,7 @@ export declare const allTools: ({
|
|
|
3971
3978
|
tableId?: undefined;
|
|
3972
3979
|
targetCount?: undefined;
|
|
3973
3980
|
minPassedCount?: undefined;
|
|
3981
|
+
minMessagesCount?: undefined;
|
|
3974
3982
|
timeoutMs?: undefined;
|
|
3975
3983
|
intervalMs?: undefined;
|
|
3976
3984
|
includeRows?: undefined;
|
package/dist/tools/rows.d.ts
CHANGED
|
@@ -120,6 +120,7 @@ export declare function getTableRows(tableId: string, options?: {
|
|
|
120
120
|
export declare function getTableRowsMinimal(tableId: string, options?: {
|
|
121
121
|
limit?: number;
|
|
122
122
|
page?: number;
|
|
123
|
+
includeMessages?: boolean;
|
|
123
124
|
}): Promise<RowsResponse>;
|
|
124
125
|
export declare function getRows(tableId: string, rowIds: string | string[]): Promise<{
|
|
125
126
|
rows: FullRow[];
|
package/dist/tools/rows.js
CHANGED
|
@@ -90,6 +90,8 @@ export async function getTableRowsMinimal(tableId, options) {
|
|
|
90
90
|
if (options?.page)
|
|
91
91
|
params.set("page", String(options.page));
|
|
92
92
|
params.set("minimal", "1");
|
|
93
|
+
if (options?.includeMessages)
|
|
94
|
+
params.set("includeMessages", "1");
|
|
93
95
|
const queryString = params.toString();
|
|
94
96
|
const path = `/api/v3/mcp/tables/${tableId}/rows${queryString ? `?${queryString}` : ""}`;
|
|
95
97
|
return api.get(path);
|