@sellable/mcp 0.1.492 → 0.1.494
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tools/leads.js
CHANGED
|
@@ -3641,11 +3641,11 @@ export async function searchSignals(input) {
|
|
|
3641
3641
|
const effectiveHeadlineICPCriteria = headlineICPCriteria && headlineICPCriteria.length > 0
|
|
3642
3642
|
? headlineICPCriteria
|
|
3643
3643
|
: rubricGuidelines;
|
|
3644
|
-
if (campaignOfferId) {
|
|
3644
|
+
if (campaignOfferId && searchCurrentStep) {
|
|
3645
3645
|
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
|
3646
3646
|
leadSourceType: "new",
|
|
3647
3647
|
leadSourceProvider: "signal-discovery",
|
|
3648
|
-
|
|
3648
|
+
currentStep: searchCurrentStep,
|
|
3649
3649
|
...(currentStepTransition ? { currentStepTransition } : {}),
|
|
3650
3650
|
watchNarration: buildSignalDiscoverySearchWatchNarration(),
|
|
3651
3651
|
});
|
|
@@ -3661,9 +3661,9 @@ export async function searchSignals(input) {
|
|
|
3661
3661
|
}
|
|
3662
3662
|
const response = await api.post(`/api/v1/signal-discovery/search-signals`, searchRequest);
|
|
3663
3663
|
const summary = summarizeSignalSearchResponse(response);
|
|
3664
|
-
if (campaignOfferId) {
|
|
3664
|
+
if (campaignOfferId && searchCurrentStep) {
|
|
3665
3665
|
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
|
3666
|
-
|
|
3666
|
+
currentStep: searchCurrentStep,
|
|
3667
3667
|
...(currentStepTransition ? { currentStepTransition } : {}),
|
|
3668
3668
|
watchNarration: buildSignalDiscoveryResultsWatchNarration(summary.postsReturned),
|
|
3669
3669
|
});
|
|
@@ -4572,7 +4572,7 @@ export function getProviderPrompt(input) {
|
|
|
4572
4572
|
}
|
|
4573
4573
|
export async function selectPromisingPosts(input) {
|
|
4574
4574
|
const api = getApi();
|
|
4575
|
-
const { campaignOfferId, selections, headlineICPCriteria, selectionMode, mode, scrapePlanMode, targetEngagerCount, maxPostsToScrape, } = input;
|
|
4575
|
+
const { campaignOfferId, selections, headlineICPCriteria, currentStep, selectionMode, mode, scrapePlanMode, targetEngagerCount, maxPostsToScrape, } = input;
|
|
4576
4576
|
const effectiveMode = selectionMode ?? mode ?? "replace";
|
|
4577
4577
|
const effectiveScrapePlanMode = scrapePlanMode ??
|
|
4578
4578
|
(targetEngagerCount || maxPostsToScrape
|
|
@@ -4672,10 +4672,13 @@ Approval card should say:
|
|
|
4672
4672
|
|
|
4673
4673
|
**Approve scraping ${selectionResult.selectedCount} recommended LinkedIn post${selectionResult.selectedCount === 1 ? "" : "s"}?**`;
|
|
4674
4674
|
}
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4675
|
+
const selectionCurrentStep = currentStep === null ? undefined : (currentStep ?? "signal-discovery");
|
|
4676
|
+
if (selectionCurrentStep) {
|
|
4677
|
+
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
|
4678
|
+
currentStep: selectionCurrentStep,
|
|
4679
|
+
watchNarration: buildSelectedPostApprovalWatchNarration(selectionResult.selectedCount, recommendedPostCount, recommendationTargetEngagerCount),
|
|
4680
|
+
});
|
|
4681
|
+
}
|
|
4679
4682
|
return {
|
|
4680
4683
|
success: true,
|
|
4681
4684
|
selectedCount: selectionResult.selectedCount,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getRefillTargetPlan } from "./refill-target-plan.js";
|
|
2
2
|
import { refreshPaidInmailCredits } from "./senders.js";
|
|
3
3
|
import { startPrepareCampaignMessages } from "./campaign-message-preparation.js";
|
|
4
|
-
import { confirmLeadList } from "./leads.js";
|
|
4
|
+
import { confirmLeadList, importLeads, searchSignals, selectPromisingPosts, } from "./leads.js";
|
|
5
|
+
import { markProviderPromptLoaded } from "./provider-preflight.js";
|
|
5
6
|
import { getApi } from "../api.js";
|
|
6
7
|
const DEFAULT_SCHEDULER_FORWARD_HOURS = 48;
|
|
7
8
|
const PAID_INMAIL_REFRESH_MAX_ATTEMPTS = 3;
|
|
@@ -320,6 +321,21 @@ function stringArray(value) {
|
|
|
320
321
|
return [];
|
|
321
322
|
return value.filter((item) => typeof item === "string");
|
|
322
323
|
}
|
|
324
|
+
function uniqueStrings(values) {
|
|
325
|
+
const seen = new Set();
|
|
326
|
+
const result = [];
|
|
327
|
+
for (const value of values) {
|
|
328
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
329
|
+
if (!normalized)
|
|
330
|
+
continue;
|
|
331
|
+
const key = normalized.toLowerCase();
|
|
332
|
+
if (seen.has(key))
|
|
333
|
+
continue;
|
|
334
|
+
seen.add(key);
|
|
335
|
+
result.push(normalized);
|
|
336
|
+
}
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
323
339
|
function paidRefreshActionFrom(value) {
|
|
324
340
|
const action = recordValue(value);
|
|
325
341
|
if (!action || action.type !== "refresh_paid_inmail_credits")
|
|
@@ -427,6 +443,53 @@ function actionSourceFingerprint(action) {
|
|
|
427
443
|
stringValue(ids.sourceFingerprint) ??
|
|
428
444
|
stringValue(action.sourceFingerprint));
|
|
429
445
|
}
|
|
446
|
+
function actionLeadSourceProvider(action) {
|
|
447
|
+
const ids = actionIds(action);
|
|
448
|
+
const toolInput = actionToolInput(action);
|
|
449
|
+
return (stringValue(toolInput.leadSourceProvider) ??
|
|
450
|
+
stringValue(toolInput.provider) ??
|
|
451
|
+
stringValue(ids.leadSourceProvider) ??
|
|
452
|
+
stringValue(action.leadSourceProvider));
|
|
453
|
+
}
|
|
454
|
+
function normalizedSignalKeyword(value) {
|
|
455
|
+
if (typeof value !== "string")
|
|
456
|
+
return null;
|
|
457
|
+
const keyword = value.trim();
|
|
458
|
+
if (!keyword)
|
|
459
|
+
return null;
|
|
460
|
+
if (/^(https?:\/\/|www\.|linkedin\.com\/|\/?in\/)/i.test(keyword)) {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
return keyword;
|
|
464
|
+
}
|
|
465
|
+
function keywordsFromSignalTabs(tabs) {
|
|
466
|
+
const selectedKeywords = [];
|
|
467
|
+
const fallbackKeywords = [];
|
|
468
|
+
for (const tab of tabs) {
|
|
469
|
+
const keyword = normalizedSignalKeyword(tab.keyword);
|
|
470
|
+
if (!keyword)
|
|
471
|
+
continue;
|
|
472
|
+
const selected = (tab.posts ?? []).some((post) => post.isSelected === true);
|
|
473
|
+
if (selected) {
|
|
474
|
+
selectedKeywords.push(keyword);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
fallbackKeywords.push(keyword);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return uniqueStrings([...selectedKeywords, ...fallbackKeywords]).slice(0, 5);
|
|
481
|
+
}
|
|
482
|
+
function postIdsFromSignalSearch(summary, maxPosts) {
|
|
483
|
+
const recommendedPostIds = stringArray(summary?.recommendedPostIds);
|
|
484
|
+
const topPostIds = Array.isArray(summary?.topPosts)
|
|
485
|
+
? summary.topPosts
|
|
486
|
+
.map((post) => post && typeof post === "object"
|
|
487
|
+
? stringValue(post.id)
|
|
488
|
+
: null)
|
|
489
|
+
.filter((id) => Boolean(id))
|
|
490
|
+
: [];
|
|
491
|
+
return uniqueStrings([...recommendedPostIds, ...topPostIds]).slice(0, Math.max(1, maxPosts));
|
|
492
|
+
}
|
|
430
493
|
function refillPrepareRequestHash(params) {
|
|
431
494
|
return [
|
|
432
495
|
"refill_sends",
|
|
@@ -470,6 +533,141 @@ async function approveGeneratedMessagesBatch(action) {
|
|
|
470
533
|
});
|
|
471
534
|
return { status: "executed_and_reread", result };
|
|
472
535
|
}
|
|
536
|
+
async function continueSignalDiscoverySource(action) {
|
|
537
|
+
const campaignOfferId = actionCampaignId(action);
|
|
538
|
+
const sourceLeadListId = actionSourceLeadListId(action);
|
|
539
|
+
const sourceFingerprint = actionSourceFingerprint(action);
|
|
540
|
+
const toolInput = actionToolInput(action);
|
|
541
|
+
const sourceRowLimit = Math.min(1500, Math.max(100, Math.floor(numberValue(toolInput.sourceRowLimit) ??
|
|
542
|
+
numberValue(toolInput.targetRows) ??
|
|
543
|
+
numberValue(action.targetRows) ??
|
|
544
|
+
100)));
|
|
545
|
+
const maxPostsToScrape = Math.min(5, Math.max(3, Math.ceil(sourceRowLimit / 500)));
|
|
546
|
+
if (!campaignOfferId || !sourceLeadListId || !sourceFingerprint) {
|
|
547
|
+
return {
|
|
548
|
+
status: "refused",
|
|
549
|
+
refusalReason: "continue_signal_discovery_source action is missing campaignOfferId, sourceLeadListId, or sourceFingerprint",
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
const requestedProvider = actionLeadSourceProvider(action);
|
|
553
|
+
if (requestedProvider &&
|
|
554
|
+
requestedProvider !== "signal-discovery" &&
|
|
555
|
+
requestedProvider !== "campaign-tracked-post") {
|
|
556
|
+
return {
|
|
557
|
+
status: "refused",
|
|
558
|
+
refusalReason: "continue_signal_discovery_source can only run for Signal Discovery source families",
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
const api = getApi();
|
|
562
|
+
const campaign = await api.get(`/api/v2/campaign-offers/${campaignOfferId}`);
|
|
563
|
+
if (campaign.leadSourceProvider !== "signal-discovery" &&
|
|
564
|
+
campaign.leadSourceProvider !== "campaign-tracked-post") {
|
|
565
|
+
return {
|
|
566
|
+
status: "refused",
|
|
567
|
+
refusalReason: "campaign leadSourceProvider is not Signal Discovery; refusing same-source continuation",
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
if (campaign.selectedLeadListId &&
|
|
571
|
+
campaign.selectedLeadListId !== sourceLeadListId) {
|
|
572
|
+
return {
|
|
573
|
+
status: "refused",
|
|
574
|
+
refusalReason: "campaign selectedLeadListId changed since the plan packet; rerun get_refill_target_plan before source continuation",
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
const sourceMeta = await api.get(`/api/v3/workflow-tables/${sourceLeadListId}?mode=meta`);
|
|
578
|
+
const sourceConfig = sourceMeta.table?.config ?? null;
|
|
579
|
+
const headlineICPCriteria = stringArray(sourceConfig?.headlineICPCriteria).length > 0
|
|
580
|
+
? stringArray(sourceConfig?.headlineICPCriteria)
|
|
581
|
+
: stringArray(sourceConfig?.rubricGuidelines);
|
|
582
|
+
const tabsResponse = await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`);
|
|
583
|
+
const keywords = keywordsFromSignalTabs(tabsResponse.tabs ?? []);
|
|
584
|
+
if (keywords.length === 0) {
|
|
585
|
+
return {
|
|
586
|
+
status: "refused",
|
|
587
|
+
refusalReason: "no reusable Signal Discovery keywords were found on the campaign tabs",
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
markProviderPromptLoaded({
|
|
591
|
+
provider: "signal-discovery",
|
|
592
|
+
campaignOfferId,
|
|
593
|
+
});
|
|
594
|
+
const searchSummary = await searchSignals({
|
|
595
|
+
type: "keywords",
|
|
596
|
+
keywords: keywords.map((keyword) => ({
|
|
597
|
+
keyword,
|
|
598
|
+
source: "refill-sends-source-continuation",
|
|
599
|
+
})),
|
|
600
|
+
campaignOfferId,
|
|
601
|
+
currentStep: null,
|
|
602
|
+
headlineICPCriteria,
|
|
603
|
+
rubricGuidelines: headlineICPCriteria,
|
|
604
|
+
confirmed: true,
|
|
605
|
+
limit: 50,
|
|
606
|
+
});
|
|
607
|
+
const selectedPostIds = postIdsFromSignalSearch(searchSummary, maxPostsToScrape);
|
|
608
|
+
if (selectedPostIds.length === 0) {
|
|
609
|
+
return {
|
|
610
|
+
status: "refused",
|
|
611
|
+
refusalReason: "Signal Discovery search returned no recommended posts to continue the source",
|
|
612
|
+
result: { keywords, searchSummary },
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
const selectionResult = await selectPromisingPosts({
|
|
616
|
+
campaignOfferId,
|
|
617
|
+
selections: selectedPostIds.map((postId) => ({
|
|
618
|
+
postId,
|
|
619
|
+
reason: "refill_sends same-source continuation: recent post from the campaign's existing Signal Discovery keyword family",
|
|
620
|
+
})),
|
|
621
|
+
headlineICPCriteria,
|
|
622
|
+
currentStep: null,
|
|
623
|
+
selectionMode: "replace",
|
|
624
|
+
scrapePlanMode: "all-selected",
|
|
625
|
+
});
|
|
626
|
+
if (selectionResult.success === false) {
|
|
627
|
+
return {
|
|
628
|
+
status: "refused",
|
|
629
|
+
refusalReason: stringValue(selectionResult.message) ??
|
|
630
|
+
"select_promising_posts did not select any posts",
|
|
631
|
+
result: { keywords, selectedPostIds, selectionResult },
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const importResult = await importLeads({
|
|
635
|
+
campaignOfferId,
|
|
636
|
+
provider: "signal-discovery",
|
|
637
|
+
sourceLeadListId,
|
|
638
|
+
currentStep: null,
|
|
639
|
+
headlineICPCriteria,
|
|
640
|
+
rubricGuidelines: headlineICPCriteria,
|
|
641
|
+
confirmed: true,
|
|
642
|
+
maxPostsToScrape: selectedPostIds.length,
|
|
643
|
+
allowInvalidSignalPosts: true,
|
|
644
|
+
});
|
|
645
|
+
const importRecord = recordValue(importResult);
|
|
646
|
+
if (importRecord?.error) {
|
|
647
|
+
return {
|
|
648
|
+
status: "refused",
|
|
649
|
+
refusalReason: stringValue(importRecord.message) ??
|
|
650
|
+
`Signal Discovery import returned ${String(importRecord.error)}`,
|
|
651
|
+
result: { keywords, selectedPostIds, selectionResult, importResult },
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
return {
|
|
655
|
+
status: "executed_and_reread",
|
|
656
|
+
result: {
|
|
657
|
+
provider: "signal-discovery",
|
|
658
|
+
campaignOfferId,
|
|
659
|
+
previousSourceLeadListId: sourceLeadListId,
|
|
660
|
+
sourceFingerprint,
|
|
661
|
+
keywords,
|
|
662
|
+
selectedPostIds,
|
|
663
|
+
sourceRowLimit,
|
|
664
|
+
maxPostsToScrape: selectedPostIds.length,
|
|
665
|
+
searchSummary,
|
|
666
|
+
selectionResult,
|
|
667
|
+
importResult,
|
|
668
|
+
},
|
|
669
|
+
};
|
|
670
|
+
}
|
|
473
671
|
async function refreshPaidInmailCreditsWithRetry(senderId) {
|
|
474
672
|
const errors = [];
|
|
475
673
|
for (let attempt = 1; attempt <= PAID_INMAIL_REFRESH_MAX_ATTEMPTS; attempt += 1) {
|
|
@@ -567,6 +765,8 @@ async function executeOneYoloPrimitive(action) {
|
|
|
567
765
|
});
|
|
568
766
|
return { status: "executed_and_reread", result };
|
|
569
767
|
}
|
|
768
|
+
case "continue_signal_discovery_source":
|
|
769
|
+
return continueSignalDiscoverySource(action);
|
|
570
770
|
case "approve_messages":
|
|
571
771
|
return approveGeneratedMessagesBatch(action);
|
|
572
772
|
default:
|
|
@@ -25,7 +25,6 @@ const MANUAL_ONLY_PACKET_ACTION_TYPES = new Set([
|
|
|
25
25
|
"lower_paid_inmail_threshold",
|
|
26
26
|
"switch_to_connection_lane",
|
|
27
27
|
"create_connection_campaign",
|
|
28
|
-
"continue_signal_discovery_source",
|
|
29
28
|
"continue_sales_nav_source",
|
|
30
29
|
"continue_prospeo_source",
|
|
31
30
|
"manual_source_replenishment",
|