@sellable/mcp 0.1.151 → 0.1.153
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 +4 -3
- package/agents/post-find-leads-filter-scout.md +5 -4
- package/agents/post-find-leads-message-scout.md +15 -14
- package/agents/source-scout-linkedin-engagement.md +6 -5
- package/agents/source-scout-prospeo-contact.md +4 -4
- package/agents/source-scout-sales-nav.md +4 -4
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/tools/cells.js +1 -1
- package/dist/tools/leads.d.ts +37 -3
- package/dist/tools/leads.js +85 -77
- package/dist/tools/prompts.js +9 -9
- package/dist/tools/readiness.d.ts +7 -1
- package/dist/tools/readiness.js +10 -17
- package/dist/tools/registry.d.ts +17 -0
- package/dist/tools/rubrics.js +23 -20
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +59 -56
- package/skills/create-campaign-v2/SKILL.md +44 -42
- package/skills/create-campaign-v2/SOUL.md +16 -13
- package/skills/create-campaign-v2/core/auto-execute.README.md +16 -17
- package/skills/create-campaign-v2/core/auto-execute.yaml +8 -7
- package/skills/create-campaign-v2/core/flow.v2.json +81 -149
- package/skills/create-campaign-v2/core/policy.md +13 -12
- package/skills/create-campaign-v2/references/approval-gate-framing.md +4 -3
- package/skills/create-campaign-v2/references/filter-leads.md +5 -4
- package/skills/create-campaign-v2/references/lead-validation-preview.md +2 -2
- package/skills/create-campaign-v2/references/sample-validation-loop.md +32 -27
- package/skills/create-campaign-v2/references/step-13-import-leads.md +44 -33
- package/skills/create-campaign-v2/references/watch-guide-narration.md +27 -28
- package/skills/create-campaign-v2-tail/SKILL.md +44 -44
- package/skills/create-rubric/SKILL.md +5 -5
- package/skills/find-leads/SKILL.md +2 -2
- package/skills/generate-messages/SKILL.md +2 -1
- package/skills/providers/prospeo.md +3 -3
- package/skills/providers/sales-nav.md +7 -7
- package/skills/providers/signal-discovery.md +47 -23
package/dist/tools/leads.js
CHANGED
|
@@ -29,29 +29,30 @@ const defaultSignalDiscoveryConfig = {
|
|
|
29
29
|
};
|
|
30
30
|
const defaultCampaignSourceDefaults = {
|
|
31
31
|
reviewBatch: {
|
|
32
|
-
defaultSize:
|
|
33
|
-
minProjectedPass:
|
|
32
|
+
defaultSize: 15,
|
|
33
|
+
minProjectedPass: 1,
|
|
34
34
|
},
|
|
35
35
|
planning: {
|
|
36
36
|
minFitRate: 0.1,
|
|
37
37
|
},
|
|
38
38
|
providers: {
|
|
39
39
|
"signal-discovery": {
|
|
40
|
-
targetGoodFitLeads:
|
|
41
|
-
defaultFitRate: 0.
|
|
40
|
+
targetGoodFitLeads: 300,
|
|
41
|
+
defaultFitRate: 0.2,
|
|
42
42
|
maxSourceCandidates: 2500,
|
|
43
43
|
postCoverageBuffer: 1.2,
|
|
44
44
|
},
|
|
45
45
|
"sales-nav": {
|
|
46
|
-
targetGoodFitLeads:
|
|
46
|
+
targetGoodFitLeads: 300,
|
|
47
47
|
maxSourceCandidates: 2500,
|
|
48
48
|
},
|
|
49
49
|
prospeo: {
|
|
50
|
-
targetGoodFitLeads:
|
|
50
|
+
targetGoodFitLeads: 300,
|
|
51
51
|
maxSourceCandidates: 2500,
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
54
|
};
|
|
55
|
+
const defaultProviderSourceListTarget = 1000;
|
|
55
56
|
const prospeoFilterValueSchema = {
|
|
56
57
|
type: "object",
|
|
57
58
|
description: "Include/exclude list filter (values must match Prospeo enums)",
|
|
@@ -517,7 +518,7 @@ function buildSourceImportWatchNarration({ provider, selectedPostCount, estimate
|
|
|
517
518
|
: ""}`
|
|
518
519
|
: `the approved ${providerLabel} source`;
|
|
519
520
|
const targetDetail = typeof targetLeadCount === "number"
|
|
520
|
-
? ` Targeting ${targetLeadCount.toLocaleString("en-US")} source leads before the
|
|
521
|
+
? ` Targeting ${targetLeadCount.toLocaleString("en-US")} source leads before the first review sample is processed.`
|
|
521
522
|
: "";
|
|
522
523
|
return {
|
|
523
524
|
stage: "find-leads",
|
|
@@ -525,9 +526,9 @@ function buildSourceImportWatchNarration({ provider, selectedPostCount, estimate
|
|
|
525
526
|
? "Scraping source leads from posts"
|
|
526
527
|
: "Importing source leads",
|
|
527
528
|
visibleState: `The browser is showing ${providerLabel} import progress for ${sourceDetail}.${targetDetail}`,
|
|
528
|
-
agentIntent: "Codex is materializing the approved source into a lead list before
|
|
529
|
-
nextAction: "Wait for source leads, then
|
|
530
|
-
safety: "This step materializes the source list;
|
|
529
|
+
agentIntent: "Codex is materializing the approved source into a lead list before copying the confirmed list into the campaign.",
|
|
530
|
+
nextAction: "Wait for source leads, then confirm the list and review the first sample",
|
|
531
|
+
safety: "This step materializes the source list; the first review sample is processed only after the later filter and message approvals.",
|
|
531
532
|
progressLabel: "Source scouting",
|
|
532
533
|
};
|
|
533
534
|
}
|
|
@@ -551,8 +552,8 @@ function buildSourceImportRecoveryWatchNarration(args) {
|
|
|
551
552
|
headline: args.reason === "failed" || args.reason === "zero"
|
|
552
553
|
? "Source import needs attention"
|
|
553
554
|
: "Source import still running",
|
|
554
|
-
visibleState: `${reasonCopy} The browser should stay on the
|
|
555
|
-
agentIntent: "Codex is holding the campaign before filter-choice until
|
|
555
|
+
visibleState: `${reasonCopy} The browser should stay on the source-list import screen.`,
|
|
556
|
+
agentIntent: "Codex is holding the campaign before filter-choice until confirmed source rows exist in the campaign table.",
|
|
556
557
|
nextAction: args.reason === "failed" || args.reason === "zero"
|
|
557
558
|
? "Retry the import or change the approved source"
|
|
558
559
|
: "Wait again, retry readiness, or change the source",
|
|
@@ -666,8 +667,8 @@ function buildFilterChoiceWatchNarration({ sourceLeadCount, reviewRowCount, samp
|
|
|
666
667
|
? ` from ${sourceLeadCount.toLocaleString("en-US")} source candidate${sourceLeadCount === 1 ? "" : "s"}`
|
|
667
668
|
: "";
|
|
668
669
|
const reviewCopy = typeof reviewRowCount === "number" && reviewRowCount > 0
|
|
669
|
-
? `${reviewRowCount.toLocaleString("en-US")} review lead${reviewRowCount === 1 ? " is" : "s are"} in the campaign table${sourceCopy}.`
|
|
670
|
-
: `The
|
|
670
|
+
? `${reviewRowCount.toLocaleString("en-US")} review/process lead${reviewRowCount === 1 ? " is" : "s are"} in the campaign table${sourceCopy}.`
|
|
671
|
+
: `The review/process sample is in the campaign table${sourceCopy}.`;
|
|
671
672
|
const assessment = analyzeFilterChoiceSample(sampleRows);
|
|
672
673
|
const fitCount = assessment.strongFitCount + assessment.adjacentCount;
|
|
673
674
|
const cleanupCount = assessment.riskCount + assessment.unknownCount;
|
|
@@ -734,7 +735,7 @@ function buildSelectedPostApprovalWatchNarration(selectedPostCount) {
|
|
|
734
735
|
stage: "find-leads",
|
|
735
736
|
headline: "Approve selected-post scrape",
|
|
736
737
|
visibleState: `${selectedPostCount.toLocaleString("en-US")} LinkedIn post${selectedPostCount === 1 ? "" : "s"} selected in Signal Discovery.`,
|
|
737
|
-
agentIntent: "Codex is asking before scraping this selected engager pool into a source list
|
|
738
|
+
agentIntent: "Codex is asking before scraping this selected engager pool into a source list.",
|
|
738
739
|
nextAction: `Approve scraping ${selectedPostCount.toLocaleString("en-US")} Signal Discovery post${selectedPostCount === 1 ? "" : "s"}`,
|
|
739
740
|
safety: "Scrape approval is the next gate.",
|
|
740
741
|
};
|
|
@@ -761,10 +762,11 @@ function buildSignalDiscoverySourceRecommendation({ selectedPosts, }) {
|
|
|
761
762
|
|
|
762
763
|
Use Signal Discovery first.
|
|
763
764
|
|
|
764
|
-
**
|
|
765
|
-
**
|
|
765
|
+
**Goal:** ~${targetGoodFitLeads.toLocaleString("en-US")} good-fit prospects after cleanup, enrichment, and filters<br>
|
|
766
|
+
**Working assumption:** ~${Math.round(defaultFitRate * 100)}% of raw post engagers become good-fit prospects<br>
|
|
767
|
+
**Engagers needed:** ~${sourceCandidateTarget.toLocaleString("en-US")} raw engagers<br>
|
|
766
768
|
**Planning floor:** continue with Signal Discovery only when sampled/projected fit is at least ${Math.round(minPlanningFitRate * 100)}% after cleanup; below that, switch to Sales Nav recent activity<br>
|
|
767
|
-
**Review checkpoint:**
|
|
769
|
+
**Review checkpoint:** copy the confirmed source list into the campaign, then process the first ${reviewBatchSize.toLocaleString("en-US")} leads for fit and message review before scaling
|
|
768
770
|
|
|
769
771
|
### Selected posts
|
|
770
772
|
|
|
@@ -779,9 +781,9 @@ ${tableRows || "| Selected posts | Campaign-matched public engagement | - |"}
|
|
|
779
781
|
|
|
780
782
|
Approve scraping these ${selectedCount} posts.
|
|
781
783
|
|
|
782
|
-
This gives enough volume to
|
|
784
|
+
This gives enough volume to target ~${targetGoodFitLeads.toLocaleString("en-US")} good-fit prospects after cleanup, while keeping the source tied to people already engaging with the campaign's strongest public buying signals.
|
|
783
785
|
|
|
784
|
-
**First pass:** build the source list, then
|
|
786
|
+
**First pass:** build the source list, copy it into the campaign, then process only the first ${reviewBatchSize.toLocaleString("en-US")} leads so we can inspect quality before scaling.
|
|
785
787
|
|
|
786
788
|
**Fallback:** if the sampled/projected fit rate is below ${Math.round(minPlanningFitRate * 100)}%, or if the review batch is too vendor-heavy, agency-heavy, or off-ICP, switch to Sales Nav recent activity.
|
|
787
789
|
|
|
@@ -1324,7 +1326,7 @@ export const leadToolDefinitions = [
|
|
|
1324
1326
|
},
|
|
1325
1327
|
{
|
|
1326
1328
|
name: "import_leads",
|
|
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
|
|
1329
|
+
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 15-row review/process sample 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 copy the confirmed source rows into the campaign table and use only the first review/process sample for the initial flow.",
|
|
1328
1330
|
inputSchema: {
|
|
1329
1331
|
type: "object",
|
|
1330
1332
|
properties: {
|
|
@@ -1351,7 +1353,7 @@ export const leadToolDefinitions = [
|
|
|
1351
1353
|
},
|
|
1352
1354
|
targetLeadCount: {
|
|
1353
1355
|
type: "number",
|
|
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
|
|
1356
|
+
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 15-row review/process sample size.",
|
|
1355
1357
|
},
|
|
1356
1358
|
mode: {
|
|
1357
1359
|
type: "string",
|
|
@@ -1373,7 +1375,7 @@ export const leadToolDefinitions = [
|
|
|
1373
1375
|
},
|
|
1374
1376
|
targetEngagerCount: {
|
|
1375
1377
|
type: "number",
|
|
1376
|
-
description: "Signal Discovery: target number of post engagers/source candidates to scrape.
|
|
1378
|
+
description: "Signal Discovery: target number of post engagers/source candidates to scrape. Default planning target is about 300 good fits at a 20% raw-engager fit assumption, or about 1500 engagers. If the sampled/projected fit rate is below the 10% planning floor after cleanup, switch to the next provider instead of scaling noisy engagers. Limits selected posts before starting scrape.",
|
|
1377
1379
|
},
|
|
1378
1380
|
maxPostsToScrape: {
|
|
1379
1381
|
type: "number",
|
|
@@ -1417,7 +1419,7 @@ export const leadToolDefinitions = [
|
|
|
1417
1419
|
},
|
|
1418
1420
|
{
|
|
1419
1421
|
name: "confirm_lead_list",
|
|
1420
|
-
description: "After the user confirms the lead list looks good,
|
|
1422
|
+
description: "After the user confirms the lead list looks good, copy the confirmed source list into the campaign table and mark the first review/process sample for the flow. This tool owns moving the watched campaign to filter-choice with sample-assessment narration only after non-empty campaign rows exist; do not call update_campaign to fix filter-choice afterward. selectedLeadListId remains the source lead list; workflowTableId is the campaign table.",
|
|
1421
1423
|
inputSchema: {
|
|
1422
1424
|
type: "object",
|
|
1423
1425
|
properties: {
|
|
@@ -1447,7 +1449,11 @@ export const leadToolDefinitions = [
|
|
|
1447
1449
|
},
|
|
1448
1450
|
targetLeadCount: {
|
|
1449
1451
|
type: "number",
|
|
1450
|
-
description: "
|
|
1452
|
+
description: "Deprecated alias for reviewBatchLimit. Does not cap the campaign-table clone; confirmed source rows are copied into the campaign table.",
|
|
1453
|
+
},
|
|
1454
|
+
reviewBatchLimit: {
|
|
1455
|
+
type: "number",
|
|
1456
|
+
description: "Number of campaign rows to use as the initial review/process sample. Defaults to 15.",
|
|
1451
1457
|
},
|
|
1452
1458
|
confirmed: {
|
|
1453
1459
|
type: "boolean",
|
|
@@ -2399,12 +2405,17 @@ export async function importLeads(input) {
|
|
|
2399
2405
|
const maxImportCount = getMaxImportCount(provider);
|
|
2400
2406
|
const normalizedTargetLeadCount = normalizeTargetLeadCount(targetLeadCount, maxImportCount);
|
|
2401
2407
|
const requestedLeadCount = normalizedTargetLeadCount ??
|
|
2402
|
-
(provider === "sales-nav" || provider === "prospeo"
|
|
2408
|
+
(provider === "sales-nav" || provider === "prospeo"
|
|
2409
|
+
? defaultProviderSourceListTarget
|
|
2410
|
+
: undefined);
|
|
2403
2411
|
const cappedTargetLeadCount = requestedLeadCount !== undefined
|
|
2404
2412
|
? Math.min(requestedLeadCount, maxImportCount)
|
|
2405
2413
|
: undefined;
|
|
2406
2414
|
// === SIGNAL DISCOVERY FLOW ===
|
|
2407
2415
|
if (provider === "signal-discovery") {
|
|
2416
|
+
const defaultSignalTargetEngagers = getSignalDiscoverySourcePlanDefaults().sourceCandidateTarget;
|
|
2417
|
+
const effectiveTargetEngagerCount = normalizePositiveInteger(targetEngagerCount) ??
|
|
2418
|
+
defaultSignalTargetEngagers;
|
|
2408
2419
|
// Get selected posts from the campaign's signal search tabs
|
|
2409
2420
|
// Note: API returns flat fields (postUrl, postContent, authorName, etc.)
|
|
2410
2421
|
const tabsResponse = await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`);
|
|
@@ -2449,7 +2460,7 @@ export async function importLeads(input) {
|
|
|
2449
2460
|
}
|
|
2450
2461
|
const uniqueSelectedPosts = Array.from(uniqueByUrl.values());
|
|
2451
2462
|
const importSelection = selectSignalPostsForImport(uniqueSelectedPosts, {
|
|
2452
|
-
targetEngagerCount,
|
|
2463
|
+
targetEngagerCount: effectiveTargetEngagerCount,
|
|
2453
2464
|
maxPostsToScrape,
|
|
2454
2465
|
});
|
|
2455
2466
|
const postsToScrape = importSelection.posts;
|
|
@@ -2485,8 +2496,7 @@ export async function importLeads(input) {
|
|
|
2485
2496
|
provider: "signal-discovery",
|
|
2486
2497
|
selectedPostCount: postsToScrape.length,
|
|
2487
2498
|
estimatedEngagers: result.estimatedEngagers,
|
|
2488
|
-
targetLeadCount:
|
|
2489
|
-
result.estimatedEngagers,
|
|
2499
|
+
targetLeadCount: effectiveTargetEngagerCount,
|
|
2490
2500
|
}),
|
|
2491
2501
|
}
|
|
2492
2502
|
: {}),
|
|
@@ -2498,7 +2508,7 @@ export async function importLeads(input) {
|
|
|
2498
2508
|
estimatedEngagers: result.estimatedEngagers,
|
|
2499
2509
|
selectedPostCount: postsToScrape.length,
|
|
2500
2510
|
availableSelectedPostCount: uniqueSelectedPosts.length,
|
|
2501
|
-
targetEngagerCount:
|
|
2511
|
+
targetEngagerCount: effectiveTargetEngagerCount,
|
|
2502
2512
|
maxPostsToScrape: normalizePositiveInteger(maxPostsToScrape) ?? null,
|
|
2503
2513
|
limitedSelectedPosts: importSelection.limited,
|
|
2504
2514
|
targetLeadCount: cappedTargetLeadCount ?? null,
|
|
@@ -2540,7 +2550,7 @@ export async function importLeads(input) {
|
|
|
2540
2550
|
searchId,
|
|
2541
2551
|
workflowTableId: leadListId,
|
|
2542
2552
|
campaignOfferId,
|
|
2543
|
-
targetLeadCount: cappedTargetLeadCount ??
|
|
2553
|
+
targetLeadCount: cappedTargetLeadCount ?? defaultProviderSourceListTarget,
|
|
2544
2554
|
...(normalizedMode ? { mode: normalizedMode } : {}),
|
|
2545
2555
|
});
|
|
2546
2556
|
}
|
|
@@ -2548,7 +2558,7 @@ export async function importLeads(input) {
|
|
|
2548
2558
|
return api.post(`/api/v3/lead-lists/${leadListId}/prospeo-import/start`, {
|
|
2549
2559
|
searchId,
|
|
2550
2560
|
campaignOfferId,
|
|
2551
|
-
targetLeadCount: cappedTargetLeadCount,
|
|
2561
|
+
targetLeadCount: cappedTargetLeadCount ?? defaultProviderSourceListTarget,
|
|
2552
2562
|
...(normalizedMode ? { mode: normalizedMode } : {}),
|
|
2553
2563
|
});
|
|
2554
2564
|
}
|
|
@@ -2664,7 +2674,7 @@ export async function cancelLeadImport(input) {
|
|
|
2664
2674
|
}
|
|
2665
2675
|
export async function confirmLeadList(input) {
|
|
2666
2676
|
const api = getApi();
|
|
2667
|
-
const { campaignOfferId, currentStep, confirmed, sourceLeadListId, campaignName, keepInSync, jobId, targetLeadCount, } = input;
|
|
2677
|
+
const { campaignOfferId, currentStep, confirmed, sourceLeadListId, campaignName, keepInSync, jobId, reviewBatchLimit, targetLeadCount, } = input;
|
|
2668
2678
|
assertInteractionApproval({
|
|
2669
2679
|
campaignId: campaignOfferId,
|
|
2670
2680
|
action: "confirm-lead-list",
|
|
@@ -2731,13 +2741,6 @@ export async function confirmLeadList(input) {
|
|
|
2731
2741
|
leadListConfig.targetLeadCount > 0
|
|
2732
2742
|
? leadListConfig.targetLeadCount
|
|
2733
2743
|
: null;
|
|
2734
|
-
if (resolvedProvider === "signal-discovery" &&
|
|
2735
|
-
signalSourceTargetLeadCount !== null &&
|
|
2736
|
-
leadListConfig?.importStatus === "complete" &&
|
|
2737
|
-
leadListRowCount > 0 &&
|
|
2738
|
-
leadListRowCount < signalSourceTargetLeadCount) {
|
|
2739
|
-
throw new Error(`Signal Discovery source list is under capacity: it completed with ${leadListRowCount.toLocaleString("en-US")} source candidates, below the approved ${signalSourceTargetLeadCount.toLocaleString("en-US")} source-candidate target. Do not import the bounded review batch from this source. Select more posts, rerun Signal Discovery, or move to Sales Nav/Prospeo.`);
|
|
2740
|
-
}
|
|
2741
2744
|
const progressProcessed = typeof importProgress?.processed === "number"
|
|
2742
2745
|
? importProgress.processed
|
|
2743
2746
|
: null;
|
|
@@ -2770,12 +2773,17 @@ export async function confirmLeadList(input) {
|
|
|
2770
2773
|
};
|
|
2771
2774
|
}
|
|
2772
2775
|
else {
|
|
2776
|
+
const readinessTargetLeadCount = typeof leadListConfig?.targetLeadCount === "number" &&
|
|
2777
|
+
Number.isFinite(leadListConfig.targetLeadCount) &&
|
|
2778
|
+
leadListConfig.targetLeadCount > 0
|
|
2779
|
+
? leadListConfig.targetLeadCount
|
|
2780
|
+
: undefined;
|
|
2773
2781
|
readiness = await waitForLeadListReady({
|
|
2774
2782
|
leadListId: resolvedLeadListId,
|
|
2775
2783
|
campaignOfferId,
|
|
2776
2784
|
provider: resolvedProvider,
|
|
2777
2785
|
jobId,
|
|
2778
|
-
targetLeadCount,
|
|
2786
|
+
targetLeadCount: readinessTargetLeadCount,
|
|
2779
2787
|
timeoutMs: 5000,
|
|
2780
2788
|
intervalMs: 1000,
|
|
2781
2789
|
});
|
|
@@ -2814,7 +2822,6 @@ export async function confirmLeadList(input) {
|
|
|
2814
2822
|
campaignOfferId,
|
|
2815
2823
|
campaignName,
|
|
2816
2824
|
keepInSync,
|
|
2817
|
-
...(typeof targetLeadCount === "number" ? { targetLeadCount } : {}),
|
|
2818
2825
|
currentStep: null,
|
|
2819
2826
|
})
|
|
2820
2827
|
.catch((error) => {
|
|
@@ -2824,15 +2831,13 @@ export async function confirmLeadList(input) {
|
|
|
2824
2831
|
throw error;
|
|
2825
2832
|
});
|
|
2826
2833
|
const campaignTableId = importResult.workflowTableId ?? importResult.campaignTableId;
|
|
2827
|
-
const
|
|
2828
|
-
|
|
2829
|
-
|
|
2834
|
+
const defaults = loadCampaignSourceDefaults();
|
|
2835
|
+
const requestedReviewBatchLimit = normalizePositiveInteger(reviewBatchLimit) ??
|
|
2836
|
+
normalizePositiveInteger(targetLeadCount) ??
|
|
2837
|
+
defaults.reviewBatch.defaultSize;
|
|
2830
2838
|
const importedRowIds = Array.isArray(importResult.rowIds)
|
|
2831
2839
|
? importResult.rowIds.filter((rowId) => typeof rowId === "string" && rowId.trim().length > 0)
|
|
2832
2840
|
: [];
|
|
2833
|
-
const overflowRowIds = campaignTableId && requestedTargetLeadCount !== null
|
|
2834
|
-
? importedRowIds.slice(requestedTargetLeadCount)
|
|
2835
|
-
: [];
|
|
2836
2841
|
const importedRowCount = importedRowIds.length > 0
|
|
2837
2842
|
? importedRowIds.length
|
|
2838
2843
|
: typeof importResult.rowCount === "number"
|
|
@@ -2843,21 +2848,22 @@ export async function confirmLeadList(input) {
|
|
|
2843
2848
|
: typeof importResult.leadsImported === "number"
|
|
2844
2849
|
? importResult.leadsImported
|
|
2845
2850
|
: 0;
|
|
2846
|
-
const keptReviewRowCount =
|
|
2847
|
-
? Math.min(importedRowCount, requestedTargetLeadCount)
|
|
2848
|
-
: importedRowCount;
|
|
2851
|
+
const keptReviewRowCount = Math.min(importedRowCount, requestedReviewBatchLimit);
|
|
2849
2852
|
const remainingRowCount = typeof importResult.remainingRowCount === "number"
|
|
2850
2853
|
? importResult.remainingRowCount
|
|
2851
2854
|
: 0;
|
|
2852
2855
|
const importStatus = String(importResult.importStatus ?? "").toLowerCase();
|
|
2856
|
+
const copyStillRunning = importResult.async === true &&
|
|
2857
|
+
(importResult.hybrid === true || remainingRowCount > 0);
|
|
2853
2858
|
const campaignTableReady = importResult.campaignTableReady === true ||
|
|
2859
|
+
(importResult.hybrid === true && importedRowCount > 0) ||
|
|
2854
2860
|
(!importResult.async &&
|
|
2855
2861
|
importStatus !== "pending" &&
|
|
2856
2862
|
importStatus !== "partial" &&
|
|
2857
2863
|
remainingRowCount <= 0 &&
|
|
2858
2864
|
importedRowCount > 0);
|
|
2859
2865
|
if (!campaignTableReady) {
|
|
2860
|
-
throw new Error("Campaign
|
|
2866
|
+
throw new Error("Campaign source rows are still copying and no review sample rows are available yet. Stay on lead import until the campaign table has rows, then retry confirm_lead_list or call wait_for_campaign_table_ready.");
|
|
2861
2867
|
}
|
|
2862
2868
|
if (importedRowCount <= 0) {
|
|
2863
2869
|
const recoveryNarration = buildSourceImportRecoveryWatchNarration({
|
|
@@ -2866,17 +2872,10 @@ export async function confirmLeadList(input) {
|
|
|
2866
2872
|
});
|
|
2867
2873
|
throw new Error(`${recoveryNarration.headline}: No usable review rows were kept for the campaign table. Retry the import or change the approved source before continuing. ${recoveryNarration.safety}`);
|
|
2868
2874
|
}
|
|
2869
|
-
if (campaignTableId && overflowRowIds.length > 0) {
|
|
2870
|
-
const deleteBatchSize = 25;
|
|
2871
|
-
for (let index = 0; index < overflowRowIds.length; index += deleteBatchSize) {
|
|
2872
|
-
const batch = overflowRowIds.slice(index, index + deleteBatchSize);
|
|
2873
|
-
await Promise.all(batch.map((rowId) => api.delete(`/api/v3/workflow-tables/${campaignTableId}/rows/${rowId}`)));
|
|
2874
|
-
}
|
|
2875
|
-
}
|
|
2876
2875
|
let reviewSampleRows = [];
|
|
2877
2876
|
if (campaignTableId && effectiveCurrentStep === "filter-choice") {
|
|
2878
2877
|
try {
|
|
2879
|
-
const sampleLimit = Math.min(Math.max(keptReviewRowCount, 1),
|
|
2878
|
+
const sampleLimit = Math.min(Math.max(keptReviewRowCount, 1), requestedReviewBatchLimit);
|
|
2880
2879
|
const sample = await getTableRowsMinimal(campaignTableId, {
|
|
2881
2880
|
limit: sampleLimit,
|
|
2882
2881
|
page: 1,
|
|
@@ -2924,31 +2923,40 @@ export async function confirmLeadList(input) {
|
|
|
2924
2923
|
workflowTableId: campaignTableId ?? null,
|
|
2925
2924
|
reviewBatchRowIds: importedRowIds.slice(0, keptReviewRowCount),
|
|
2926
2925
|
reviewBatchRowCount: keptReviewRowCount,
|
|
2926
|
+
copiedCampaignRowCount: importedRowCount,
|
|
2927
2927
|
},
|
|
2928
2928
|
branchBasisFields: [
|
|
2929
2929
|
"campaign revision or updatedAt",
|
|
2930
2930
|
"brief hash",
|
|
2931
2931
|
"selectedLeadListId",
|
|
2932
2932
|
"workflowTableId",
|
|
2933
|
-
"
|
|
2933
|
+
"first review/process sample row ids/hash",
|
|
2934
2934
|
"filter choice at branch start",
|
|
2935
2935
|
],
|
|
2936
2936
|
promptRequired: 'Load get_subskill_prompt({ subskillName: "generate-messages", offset, limit }) until hasMore=false before drafting.',
|
|
2937
2937
|
},
|
|
2938
|
-
boundedReviewBatch:
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
:
|
|
2938
|
+
boundedReviewBatch: {
|
|
2939
|
+
requestedTargetLeadCount: requestedReviewBatchLimit,
|
|
2940
|
+
requestedReviewBatchLimit,
|
|
2941
|
+
importedRowCount,
|
|
2942
|
+
keptRowCount: keptReviewRowCount,
|
|
2943
|
+
reviewRowCount: keptReviewRowCount,
|
|
2944
|
+
copiedCampaignRowCount: importedRowCount,
|
|
2945
|
+
sourceCandidateRowCount: importResult.sourceRowCount ??
|
|
2946
|
+
importResult.clonedSourceRowCount ??
|
|
2947
|
+
leadListRowCount,
|
|
2948
|
+
remainingCopyRowCount: remainingRowCount,
|
|
2949
|
+
copyStillRunning,
|
|
2950
|
+
trimmedOverflowRowCount: 0,
|
|
2951
|
+
sourceShortfall: signalSourceTargetLeadCount !== null &&
|
|
2952
|
+
leadListRowCount > 0 &&
|
|
2953
|
+
leadListRowCount < signalSourceTargetLeadCount,
|
|
2954
|
+
},
|
|
2955
|
+
message: copyStillRunning
|
|
2956
|
+
? `First ${importedRowCount.toLocaleString("en-US")} source candidate${importedRowCount === 1 ? "" : "s"} are copied into the campaign table and the rest of the confirmed source list is still copying. Use the first ${keptReviewRowCount.toLocaleString("en-US")} as the review/process sample. The watched campaign is now on filter-choice; ask add filters vs skip filters before loading post-lead workers.`
|
|
2957
|
+
: importedRowCount > keptReviewRowCount
|
|
2958
|
+
? `Copied ${importedRowCount.toLocaleString("en-US")} source candidate${importedRowCount === 1 ? "" : "s"} into the campaign table. Use the first ${keptReviewRowCount.toLocaleString("en-US")} as the review/process sample. The watched campaign is now on filter-choice; ask add filters vs skip filters before loading post-lead workers.`
|
|
2959
|
+
: `Copied ${importedRowCount.toLocaleString("en-US")} source candidate${importedRowCount === 1 ? "" : "s"} into the campaign table for the review/process sample. The watched campaign is now on filter-choice; ask add filters vs skip filters before loading post-lead workers.`,
|
|
2952
2960
|
};
|
|
2953
2961
|
}
|
|
2954
2962
|
export function getProviderPrompt(input) {
|
|
@@ -3058,7 +3066,7 @@ Use Signal Discovery first.
|
|
|
3058
3066
|
|
|
3059
3067
|
**Recommendation:** approve scraping the ${selectionResult.selectedCount} selected Signal Discovery post${selectionResult.selectedCount === 1 ? "" : "s"}.
|
|
3060
3068
|
|
|
3061
|
-
**First pass:** build the source list, then
|
|
3069
|
+
**First pass:** build the source list, copy it into the campaign, then process only the first ${reviewBatchSize.toLocaleString("en-US")} leads so we can inspect quality before scaling.
|
|
3062
3070
|
|
|
3063
3071
|
Approval card should say:
|
|
3064
3072
|
|
|
@@ -3071,7 +3079,7 @@ Approval card should say:
|
|
|
3071
3079
|
criteriaCount: selectionResult.criteriaCount,
|
|
3072
3080
|
message: `${sourceRecommendation}
|
|
3073
3081
|
|
|
3074
|
-
Selected ${selectionResult.selectedCount} posts with ${selectionResult.criteriaCount} ICP criteria persisted. Ask the user to approve
|
|
3082
|
+
Selected ${selectionResult.selectedCount} posts with ${selectionResult.criteriaCount} ICP criteria persisted. Ask the user to approve this specific scraping action once; after approval, call import_leads immediately and do not repeat this source card.`,
|
|
3075
3083
|
};
|
|
3076
3084
|
}
|
|
3077
3085
|
export async function setHeadlineICPCriteria(input) {
|
package/dist/tools/prompts.js
CHANGED
|
@@ -260,7 +260,7 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
260
260
|
"campaignBrief",
|
|
261
261
|
"source decision and selectedLeadList/source state",
|
|
262
262
|
"workflowTableId",
|
|
263
|
-
"
|
|
263
|
+
"first review/process sample rows from selectedLeadList with row ids/hash",
|
|
264
264
|
"filter choice and filter/rubric basis when present",
|
|
265
265
|
],
|
|
266
266
|
scouts: scouts.map((agent) => ({
|
|
@@ -297,11 +297,11 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
297
297
|
nextStage: "message-review",
|
|
298
298
|
},
|
|
299
299
|
messageDraftBranchContract: {
|
|
300
|
-
firstAllowedStart: "after confirm_lead_list
|
|
300
|
+
firstAllowedStart: "after confirm_lead_list copies source rows and the first review/process sample exists",
|
|
301
301
|
forbiddenStarts: [
|
|
302
302
|
"source recommendation",
|
|
303
303
|
"provider import job alone",
|
|
304
|
-
"zero-row review
|
|
304
|
+
"zero-row review/process sample",
|
|
305
305
|
],
|
|
306
306
|
runtimeProofTransport: "CampaignOffer.watchNarration.workerDetails.messageDraftBuilder",
|
|
307
307
|
runtimeProofRequiredFields: [
|
|
@@ -313,7 +313,7 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
313
313
|
"basisToken",
|
|
314
314
|
"basis.selectedLeadListId",
|
|
315
315
|
"basis.workflowTableId",
|
|
316
|
-
"basis.
|
|
316
|
+
"basis.reviewSampleRowHash or basis.reviewSampleRowIds",
|
|
317
317
|
],
|
|
318
318
|
promptRequired: 'get_subskill_prompt({ subskillName: "generate-messages", offset, limit }) until hasMore=false',
|
|
319
319
|
basisFields: [
|
|
@@ -321,7 +321,7 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
321
321
|
"brief hash",
|
|
322
322
|
"selectedLeadListId",
|
|
323
323
|
"workflowTableId",
|
|
324
|
-
"
|
|
324
|
+
"first review/process sample row ids/hash",
|
|
325
325
|
"filter choice",
|
|
326
326
|
"filter/rubric basis when present",
|
|
327
327
|
],
|
|
@@ -337,12 +337,12 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
337
337
|
"outputHash",
|
|
338
338
|
"error or retry detail",
|
|
339
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-
|
|
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-sample identity mismatches or the initial output failed.",
|
|
341
341
|
},
|
|
342
342
|
usage: {
|
|
343
|
-
codex: "After confirm_lead_list
|
|
344
|
-
claude: "After confirm_lead_list
|
|
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
|
|
343
|
+
codex: "After confirm_lead_list copies source rows and the first review/process sample exists, 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, ask for filter approval, 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 copies source rows and the first review/process sample exists, 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, ask for filter approval, 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 first review/process sample 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-sample 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 after save_rubrics so the user can approve the saved criteria; only then move to Filter Leads and wait there for message approval. Enrichment, filtering, Generate Message cells, sender setup, sequence attach, and launch wait for template approval on the Use Template path. On the skip path, move to Messages/message review and wait for message approval before enrichment or Settings. 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 sample. 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.',
|
|
346
346
|
},
|
|
347
347
|
};
|
|
348
348
|
}
|
|
@@ -273,6 +273,7 @@ export declare function waitForLeadListReady(input: WaitForLeadListReadyInput):
|
|
|
273
273
|
status?: undefined;
|
|
274
274
|
targetLeadCount?: undefined;
|
|
275
275
|
error?: undefined;
|
|
276
|
+
sourceShortfall?: undefined;
|
|
276
277
|
warning?: undefined;
|
|
277
278
|
} | {
|
|
278
279
|
ready: boolean;
|
|
@@ -285,6 +286,7 @@ export declare function waitForLeadListReady(input: WaitForLeadListReadyInput):
|
|
|
285
286
|
status: string;
|
|
286
287
|
targetLeadCount: number | undefined;
|
|
287
288
|
error?: undefined;
|
|
289
|
+
sourceShortfall?: undefined;
|
|
288
290
|
warning?: undefined;
|
|
289
291
|
} | {
|
|
290
292
|
ready: boolean;
|
|
@@ -297,6 +299,7 @@ export declare function waitForLeadListReady(input: WaitForLeadListReadyInput):
|
|
|
297
299
|
status: string | null;
|
|
298
300
|
targetLeadCount: number | undefined;
|
|
299
301
|
error: string;
|
|
302
|
+
sourceShortfall?: undefined;
|
|
300
303
|
warning?: undefined;
|
|
301
304
|
} | {
|
|
302
305
|
ready: boolean;
|
|
@@ -309,6 +312,7 @@ export declare function waitForLeadListReady(input: WaitForLeadListReadyInput):
|
|
|
309
312
|
status: string;
|
|
310
313
|
targetLeadCount: number | undefined;
|
|
311
314
|
error: string | null;
|
|
315
|
+
sourceShortfall?: undefined;
|
|
312
316
|
warning?: undefined;
|
|
313
317
|
} | {
|
|
314
318
|
ready: boolean;
|
|
@@ -319,9 +323,10 @@ export declare function waitForLeadListReady(input: WaitForLeadListReadyInput):
|
|
|
319
323
|
rowCount: number;
|
|
320
324
|
status: string | null;
|
|
321
325
|
targetLeadCount: number | null;
|
|
326
|
+
sourceShortfall: boolean;
|
|
327
|
+
warning: string | undefined;
|
|
322
328
|
reason?: undefined;
|
|
323
329
|
error?: undefined;
|
|
324
|
-
warning?: undefined;
|
|
325
330
|
} | {
|
|
326
331
|
ready: boolean;
|
|
327
332
|
reason: string;
|
|
@@ -334,5 +339,6 @@ export declare function waitForLeadListReady(input: WaitForLeadListReadyInput):
|
|
|
334
339
|
targetLeadCount: number | undefined;
|
|
335
340
|
warning: string | undefined;
|
|
336
341
|
error: string | undefined;
|
|
342
|
+
sourceShortfall?: undefined;
|
|
337
343
|
}>;
|
|
338
344
|
export {};
|
package/dist/tools/readiness.js
CHANGED
|
@@ -101,7 +101,7 @@ export const readinessToolDefinitions = [
|
|
|
101
101
|
},
|
|
102
102
|
targetLeadCount: {
|
|
103
103
|
type: "number",
|
|
104
|
-
description: "Target number of leads requested. Used as a fallback completion check when status is unavailable. For Signal Discovery, pass the approved source-candidate target; if the completed source list lands below it, the tool returns
|
|
104
|
+
description: "Target number of leads requested. Used as a fallback completion check when status is unavailable. For Signal Discovery, pass the approved source-candidate target; if the completed source list lands below it, the tool still returns ready with a source_shortfall warning so the confirmed list can be copied and the first review sample can proceed.",
|
|
105
105
|
},
|
|
106
106
|
timeoutMs: {
|
|
107
107
|
type: "number",
|
|
@@ -472,23 +472,12 @@ export async function waitForLeadListReady(input) {
|
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
474
|
if ((!requireRows || rowCount > 0) && importComplete) {
|
|
475
|
-
|
|
476
|
-
|
|
475
|
+
const signalSourceShortfallTarget = provider === "signal-discovery" && typeof targetLeadCount === "number"
|
|
476
|
+
? targetLeadCount
|
|
477
|
+
: null;
|
|
478
|
+
const signalSourceShortfall = signalSourceShortfallTarget !== null &&
|
|
477
479
|
rowCount > 0 &&
|
|
478
|
-
rowCount <
|
|
479
|
-
return {
|
|
480
|
-
ready: false,
|
|
481
|
-
reason: "source_under_capacity",
|
|
482
|
-
leadListId,
|
|
483
|
-
provider: provider ?? null,
|
|
484
|
-
attempts,
|
|
485
|
-
elapsedMs: Date.now() - start,
|
|
486
|
-
rowCount,
|
|
487
|
-
status: lastStatus,
|
|
488
|
-
targetLeadCount,
|
|
489
|
-
error: `Signal Discovery completed with ${rowCount.toLocaleString("en-US")} source candidates, below the approved ${targetLeadCount.toLocaleString("en-US")} source-candidate target. Do not confirm this lead list; select more posts, rerun source discovery, or move to Sales Nav/Prospeo.`,
|
|
490
|
-
};
|
|
491
|
-
}
|
|
480
|
+
rowCount < signalSourceShortfallTarget;
|
|
492
481
|
return {
|
|
493
482
|
ready: true,
|
|
494
483
|
leadListId,
|
|
@@ -498,6 +487,10 @@ export async function waitForLeadListReady(input) {
|
|
|
498
487
|
rowCount,
|
|
499
488
|
status: lastStatus,
|
|
500
489
|
targetLeadCount: targetLeadCount ?? null,
|
|
490
|
+
sourceShortfall: signalSourceShortfall,
|
|
491
|
+
warning: signalSourceShortfall
|
|
492
|
+
? `Signal Discovery completed with ${rowCount.toLocaleString("en-US")} source candidates, below the approved ${signalSourceShortfallTarget.toLocaleString("en-US")} source-candidate target. Confirm/copy the completed list for the first review sample, then add more approved posts or switch provider if the sample quality/scale is not enough.`
|
|
493
|
+
: undefined,
|
|
501
494
|
};
|
|
502
495
|
}
|
|
503
496
|
}
|