@sellable/mcp 0.1.80 → 0.1.82
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/agents/post-find-leads-filter-scout.md +19 -8
- package/agents/post-find-leads-message-scout.md +21 -6
- package/agents/registry.json +33 -18
- package/agents/source-scout-linkedin-engagement.md +10 -2
- package/agents/source-scout-prospeo-contact.md +9 -2
- package/agents/source-scout-sales-nav.md +10 -2
- package/dist/server.js +7 -1
- package/dist/tools/bootstrap.js +2 -2
- package/dist/tools/campaigns.d.ts +12 -0
- package/dist/tools/campaigns.js +40 -5
- package/dist/tools/context.d.ts +10 -4
- package/dist/tools/context.js +8 -4
- package/dist/tools/leads.d.ts +6 -3
- package/dist/tools/leads.js +39 -43
- package/dist/tools/navigation.d.ts +28 -1
- package/dist/tools/navigation.js +220 -10
- package/dist/tools/prompts.js +1 -1
- package/dist/tools/provider-preflight.js +2 -3
- package/dist/tools/readiness.d.ts +31 -6
- package/dist/tools/readiness.js +6 -1
- package/dist/tools/rubrics.d.ts +2 -0
- package/dist/tools/rubrics.js +2 -0
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +24 -12
- package/skills/create-campaign-v2/SKILL.md +166 -114
- package/skills/create-campaign-v2/SOUL.md +19 -11
- package/skills/create-campaign-v2/core/auto-execute.yaml +14 -13
- package/skills/create-campaign-v2/core/flow.v2.json +964 -310
- package/skills/create-campaign-v2/references/approval-gate-framing.md +64 -32
- package/skills/create-campaign-v2/references/filter-leads.md +8 -0
- package/skills/create-campaign-v2/references/final-handoff-contract.md +55 -20
- package/skills/create-campaign-v2/references/lead-validation-preview.md +30 -17
- package/skills/create-campaign-v2/references/step-13-import-leads.md +62 -17
- package/skills/create-campaign-v2/references/validation-criteria.md +10 -5
- package/skills/create-campaign-v2/references/watch-link-handoff.md +72 -72
- package/skills/create-campaign-v2-tail/SKILL.md +123 -92
package/dist/tools/leads.js
CHANGED
|
@@ -18,6 +18,7 @@ const defaultLeadImportLimits = {
|
|
|
18
18
|
apollo: { maxImportCount: 2500 },
|
|
19
19
|
"sales-nav": { maxImportCount: 2500 },
|
|
20
20
|
prospeo: { maxImportCount: 10000 },
|
|
21
|
+
"signal-discovery": { maxImportCount: 2500 },
|
|
21
22
|
};
|
|
22
23
|
const defaultSignalDiscoveryConfig = {
|
|
23
24
|
selection: {
|
|
@@ -388,6 +389,25 @@ function summarizeSignalSearchResponse(response) {
|
|
|
388
389
|
],
|
|
389
390
|
};
|
|
390
391
|
}
|
|
392
|
+
function normalizeImportProvider(provider) {
|
|
393
|
+
if (provider === "apollo-ai" || provider === "apollo")
|
|
394
|
+
return "apollo";
|
|
395
|
+
if (provider === "sales-nav")
|
|
396
|
+
return "sales-nav";
|
|
397
|
+
if (provider === "prospeo")
|
|
398
|
+
return "prospeo";
|
|
399
|
+
if (provider === "signal-discovery")
|
|
400
|
+
return "signal-discovery";
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
function assertPhase126ImportProvider(provider, campaignOfferId) {
|
|
404
|
+
if (!provider) {
|
|
405
|
+
throw new Error(`import_leads requires a supported leadSourceProvider for campaign ${campaignOfferId}. Provide provider or set campaign.leadSourceProvider to sales-nav, prospeo, or signal-discovery, then call get_provider_prompt({ provider, campaignOfferId }).`);
|
|
406
|
+
}
|
|
407
|
+
if (provider === "apollo") {
|
|
408
|
+
throw new Error(`Apollo is not offered as a Phase 126 post-mint source for campaign ${campaignOfferId}. Choose sales-nav, prospeo, or signal-discovery; call get_provider_prompt({ provider, campaignOfferId }), then retry import_leads.`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
391
411
|
export const leadToolDefinitions = [
|
|
392
412
|
{
|
|
393
413
|
name: "get_provider_prompt",
|
|
@@ -985,7 +1005,7 @@ export const leadToolDefinitions = [
|
|
|
985
1005
|
},
|
|
986
1006
|
{
|
|
987
1007
|
name: "confirm_lead_list",
|
|
988
|
-
description: "After the user confirms the lead list looks good, import it into the campaign table (clone)
|
|
1008
|
+
description: "After the user confirms the lead list looks good, import it into the campaign table (clone). selectedLeadListId remains the source lead list; workflowTableId is the campaign table. Recommended post-confirm order: update_campaign(currentStep='filter-choice') -> wait_for_campaign_table_ready -> get_rows_minimal.",
|
|
989
1009
|
inputSchema: {
|
|
990
1010
|
type: "object",
|
|
991
1011
|
properties: {
|
|
@@ -1841,17 +1861,7 @@ export async function importLeads(input) {
|
|
|
1841
1861
|
// Pull campaign once when we need provider or to determine default step behavior.
|
|
1842
1862
|
const campaign = await api.get(`/api/v2/campaign-offers/${campaignOfferId}`);
|
|
1843
1863
|
if (!provider) {
|
|
1844
|
-
|
|
1845
|
-
provider =
|
|
1846
|
-
leadSourceProvider === "apollo-ai" || leadSourceProvider === "apollo"
|
|
1847
|
-
? "apollo"
|
|
1848
|
-
: leadSourceProvider === "sales-nav"
|
|
1849
|
-
? "sales-nav"
|
|
1850
|
-
: leadSourceProvider === "prospeo"
|
|
1851
|
-
? "prospeo"
|
|
1852
|
-
: leadSourceProvider === "signal-discovery"
|
|
1853
|
-
? "signal-discovery"
|
|
1854
|
-
: undefined;
|
|
1864
|
+
provider = normalizeImportProvider(campaign.leadSourceProvider);
|
|
1855
1865
|
}
|
|
1856
1866
|
if (currentStep === undefined) {
|
|
1857
1867
|
campaignCurrentStep = campaign.currentStep ?? null;
|
|
@@ -1875,6 +1885,7 @@ export async function importLeads(input) {
|
|
|
1875
1885
|
: currentStep;
|
|
1876
1886
|
const shouldSetCurrentStep = typeof effectiveCurrentStep === "string" && effectiveCurrentStep.length > 0;
|
|
1877
1887
|
const normalizedMode = mode === "add" || mode === "replace" ? mode : undefined;
|
|
1888
|
+
assertPhase126ImportProvider(provider, campaignOfferId);
|
|
1878
1889
|
// If no sourceLeadListId but campaign has one selected, handle mode selection
|
|
1879
1890
|
if (!sourceLeadListId && campaignSelectedLeadListId) {
|
|
1880
1891
|
// If mode is provided but sourceLeadListId is missing, remind LLM to pass it
|
|
@@ -1896,7 +1907,7 @@ export async function importLeads(input) {
|
|
|
1896
1907
|
}
|
|
1897
1908
|
// No mode provided - ask user to choose
|
|
1898
1909
|
return {
|
|
1899
|
-
provider
|
|
1910
|
+
provider,
|
|
1900
1911
|
leadListId: campaignSelectedLeadListId,
|
|
1901
1912
|
existingLeadListId: campaignSelectedLeadListId,
|
|
1902
1913
|
needsModeSelection: true,
|
|
@@ -1922,22 +1933,14 @@ export async function importLeads(input) {
|
|
|
1922
1933
|
],
|
|
1923
1934
|
};
|
|
1924
1935
|
}
|
|
1925
|
-
const normalizedProvider = provider === "sales-nav" || provider === "prospeo" ? provider : "apollo";
|
|
1926
|
-
const providerForGuard = provider === "sales-nav" ||
|
|
1927
|
-
provider === "prospeo" ||
|
|
1928
|
-
provider === "signal-discovery"
|
|
1929
|
-
? provider
|
|
1930
|
-
: "apollo";
|
|
1931
1936
|
assertProviderPromptLoaded({
|
|
1932
|
-
provider
|
|
1937
|
+
provider,
|
|
1933
1938
|
campaignOfferId,
|
|
1934
1939
|
});
|
|
1935
|
-
const maxImportCount = getMaxImportCount(
|
|
1940
|
+
const maxImportCount = getMaxImportCount(provider);
|
|
1936
1941
|
const normalizedTargetLeadCount = normalizeTargetLeadCount(targetLeadCount, maxImportCount);
|
|
1937
1942
|
const requestedLeadCount = normalizedTargetLeadCount ??
|
|
1938
|
-
(
|
|
1939
|
-
? 100
|
|
1940
|
-
: undefined);
|
|
1943
|
+
(provider === "sales-nav" || provider === "prospeo" ? 100 : undefined);
|
|
1941
1944
|
const cappedTargetLeadCount = requestedLeadCount !== undefined
|
|
1942
1945
|
? Math.min(requestedLeadCount, maxImportCount)
|
|
1943
1946
|
: undefined;
|
|
@@ -1968,7 +1971,7 @@ export async function importLeads(input) {
|
|
|
1968
1971
|
}
|
|
1969
1972
|
}
|
|
1970
1973
|
if (selectedPosts.length === 0) {
|
|
1971
|
-
throw new Error("No posts selected.
|
|
1974
|
+
throw new Error("No usable Signal Discovery posts are selected for import. Keep currentStep at provider/search, refine the search, switch provider, or call select_promising_posts with usable posts before import_leads.");
|
|
1972
1975
|
}
|
|
1973
1976
|
// De-duplicate selected posts by canonical URL to avoid double scraping.
|
|
1974
1977
|
const uniqueByUrl = new Map();
|
|
@@ -2016,19 +2019,15 @@ export async function importLeads(input) {
|
|
|
2016
2019
|
message: `Started scraping ${uniqueSelectedPosts.length} posts (~${result.estimatedEngagers} engagers). Leads will appear as scraping completes.`,
|
|
2017
2020
|
};
|
|
2018
2021
|
}
|
|
2019
|
-
// ===
|
|
2022
|
+
// === SALES NAV / PROSPEO FLOW ===
|
|
2020
2023
|
let leadListId = sourceLeadListId;
|
|
2021
2024
|
let createdLeadList = null;
|
|
2022
2025
|
// Create lead list if not provided
|
|
2023
2026
|
if (!leadListId) {
|
|
2024
2027
|
if (!searchId) {
|
|
2025
|
-
throw new Error(`import_leads for ${provider
|
|
2028
|
+
throw new Error(`import_leads for ${provider} requires sourceLeadListId or searchId`);
|
|
2026
2029
|
}
|
|
2027
|
-
const providerLabel = provider === "sales-nav"
|
|
2028
|
-
? "Sales Nav"
|
|
2029
|
-
: provider === "prospeo"
|
|
2030
|
-
? "Prospeo"
|
|
2031
|
-
: "Apollo";
|
|
2030
|
+
const providerLabel = provider === "sales-nav" ? "Sales Nav" : "Prospeo";
|
|
2032
2031
|
const fallbackName = leadListName ||
|
|
2033
2032
|
(searchName ? `${providerLabel} - ${searchName}` : undefined) ||
|
|
2034
2033
|
`${providerLabel} Import ${new Date().toISOString().slice(0, 10)}`;
|
|
@@ -2052,6 +2051,7 @@ export async function importLeads(input) {
|
|
|
2052
2051
|
return api.post(`/api/v3/sales-nav/export`, {
|
|
2053
2052
|
searchId,
|
|
2054
2053
|
workflowTableId: leadListId,
|
|
2054
|
+
campaignOfferId,
|
|
2055
2055
|
targetLeadCount: cappedTargetLeadCount ?? 100,
|
|
2056
2056
|
...(normalizedMode ? { mode: normalizedMode } : {}),
|
|
2057
2057
|
});
|
|
@@ -2059,16 +2059,12 @@ export async function importLeads(input) {
|
|
|
2059
2059
|
if (provider === "prospeo") {
|
|
2060
2060
|
return api.post(`/api/v3/lead-lists/${leadListId}/prospeo-import/start`, {
|
|
2061
2061
|
searchId,
|
|
2062
|
+
campaignOfferId,
|
|
2062
2063
|
targetLeadCount: cappedTargetLeadCount,
|
|
2063
2064
|
...(normalizedMode ? { mode: normalizedMode } : {}),
|
|
2064
2065
|
});
|
|
2065
2066
|
}
|
|
2066
|
-
|
|
2067
|
-
return api.post(`/api/v3/lead-lists/${leadListId}/apollo-import/start`, {
|
|
2068
|
-
searchId,
|
|
2069
|
-
targetLeadCount: cappedTargetLeadCount,
|
|
2070
|
-
...(normalizedMode ? { mode: normalizedMode } : {}),
|
|
2071
|
-
});
|
|
2067
|
+
throw new Error(`Unsupported import provider ${provider}. Choose sales-nav, prospeo, or signal-discovery.`);
|
|
2072
2068
|
};
|
|
2073
2069
|
try {
|
|
2074
2070
|
jobResult = await startImport();
|
|
@@ -2079,7 +2075,7 @@ export async function importLeads(input) {
|
|
|
2079
2075
|
const parsed = parseApiErrorBody(error.body) || {};
|
|
2080
2076
|
if (parsed.error === "lead_list_exists" || parsed.modeRequired) {
|
|
2081
2077
|
return {
|
|
2082
|
-
provider
|
|
2078
|
+
provider,
|
|
2083
2079
|
leadListId,
|
|
2084
2080
|
existingLeadListId: leadListId,
|
|
2085
2081
|
needsModeSelection: true,
|
|
@@ -2119,7 +2115,7 @@ export async function importLeads(input) {
|
|
|
2119
2115
|
...(shouldSetCurrentStep ? { currentStep: effectiveCurrentStep } : {}),
|
|
2120
2116
|
});
|
|
2121
2117
|
return {
|
|
2122
|
-
provider
|
|
2118
|
+
provider,
|
|
2123
2119
|
leadListId,
|
|
2124
2120
|
createdLeadList,
|
|
2125
2121
|
jobResult,
|
|
@@ -2254,12 +2250,12 @@ export async function confirmLeadList(input) {
|
|
|
2254
2250
|
}
|
|
2255
2251
|
if (!readiness.ready) {
|
|
2256
2252
|
if (readiness.reason === "missing_job_id") {
|
|
2257
|
-
throw new Error("Import job ID is missing.
|
|
2253
|
+
throw new Error("Import job ID is missing. Keep the campaign at confirm-lead-list; provide the jobId, retry readiness, cancel the import, or re-run-source before confirming.");
|
|
2258
2254
|
}
|
|
2259
2255
|
if (readiness.reason === "import_failed") {
|
|
2260
|
-
throw new Error("Import failed.
|
|
2256
|
+
throw new Error("Import failed. Keep the campaign at confirm-lead-list; retry the provider import, cancel it, or re-run-source before confirming.");
|
|
2261
2257
|
}
|
|
2262
|
-
throw new Error("Import still in progress.
|
|
2258
|
+
throw new Error("Import still in progress. Keep the campaign at confirm-lead-list; retry readiness, cancel the import, or re-run-source before launching post-import scouts.");
|
|
2263
2259
|
}
|
|
2264
2260
|
const importResult = await api.post(`/api/v3/campaign-builder/import-leads`, {
|
|
2265
2261
|
sourceLeadListId: resolvedLeadListId,
|
|
@@ -9,15 +9,26 @@ export type CampaignOfferNavigation = {
|
|
|
9
9
|
campaignBrief?: {
|
|
10
10
|
content?: string | null;
|
|
11
11
|
} | string | null;
|
|
12
|
+
useMessagingTemplate?: boolean | null;
|
|
13
|
+
messagingTemplateId?: string | null;
|
|
14
|
+
messagingTemplate?: unknown | null;
|
|
12
15
|
leadSourceType?: string | null;
|
|
13
16
|
leadSourceProvider?: string | null;
|
|
14
17
|
selectedLeadListId?: string | null;
|
|
15
18
|
workflowTableId?: string | null;
|
|
19
|
+
enableICPFilters?: boolean | null;
|
|
20
|
+
leadScoringRubrics?: Array<{
|
|
21
|
+
useCheckForScoring?: boolean | null;
|
|
22
|
+
}> | null;
|
|
23
|
+
senderIds?: string[] | null;
|
|
24
|
+
sequenceTemplate?: unknown | null;
|
|
25
|
+
campaignStatus?: string | null;
|
|
26
|
+
status?: string | null;
|
|
16
27
|
currentStep?: string | null;
|
|
17
28
|
};
|
|
18
29
|
type NavigationDebugPayload = Record<string, unknown>;
|
|
19
30
|
export declare function logNavigationDebug(event: string, payload?: NavigationDebugPayload, campaignId?: string | null): void;
|
|
20
|
-
type CreateCampaignStepId = "campaign-created" | "pick-provider" | "provider-search" | "confirm-lead-list";
|
|
31
|
+
type CreateCampaignStepId = "campaign-created" | "pick-provider" | "provider-search" | "confirm-lead-list" | "filter-rules" | "messages" | "settings" | "sequence" | "send" | "running";
|
|
21
32
|
export declare const navigationToolDefinitions: {
|
|
22
33
|
name: string;
|
|
23
34
|
description: string;
|
|
@@ -42,6 +53,10 @@ type NavigationComputeOptions = {
|
|
|
42
53
|
tableChecked: boolean;
|
|
43
54
|
rowCount: number | null;
|
|
44
55
|
hasWorkflowRows: boolean | null;
|
|
56
|
+
selectedLeadListChecked?: boolean;
|
|
57
|
+
selectedLeadListAccessible?: boolean | null;
|
|
58
|
+
selectedLeadListRowCount?: number | null;
|
|
59
|
+
workflowTableAccessible?: boolean | null;
|
|
45
60
|
initialWarnings?: string[];
|
|
46
61
|
};
|
|
47
62
|
export declare function computeCampaignNavigationStateFromCampaign(campaign: CampaignOfferNavigation, options: NavigationComputeOptions): {
|
|
@@ -63,6 +78,12 @@ export declare function computeCampaignNavigationStateFromCampaign(campaign: Cam
|
|
|
63
78
|
leadSourceProvider: string | null;
|
|
64
79
|
selectedLeadListId: string | null;
|
|
65
80
|
providerCurrentStep: string | null;
|
|
81
|
+
enableICPFilters: boolean | null;
|
|
82
|
+
activeRubricCount: number;
|
|
83
|
+
hasApprovedMessageTemplate: boolean;
|
|
84
|
+
senderCount: number;
|
|
85
|
+
hasSequenceTemplate: boolean;
|
|
86
|
+
campaignStatus: string | null;
|
|
66
87
|
};
|
|
67
88
|
warnings: string[];
|
|
68
89
|
};
|
|
@@ -85,6 +106,12 @@ export declare function getCampaignNavigationState(input: GetCampaignNavigationS
|
|
|
85
106
|
leadSourceProvider: string | null;
|
|
86
107
|
selectedLeadListId: string | null;
|
|
87
108
|
providerCurrentStep: string | null;
|
|
109
|
+
enableICPFilters: boolean | null;
|
|
110
|
+
activeRubricCount: number;
|
|
111
|
+
hasApprovedMessageTemplate: boolean;
|
|
112
|
+
senderCount: number;
|
|
113
|
+
hasSequenceTemplate: boolean;
|
|
114
|
+
campaignStatus: string | null;
|
|
88
115
|
};
|
|
89
116
|
warnings: string[];
|
|
90
117
|
}>;
|
package/dist/tools/navigation.js
CHANGED
|
@@ -134,6 +134,56 @@ function getBriefContent(brief) {
|
|
|
134
134
|
return brief;
|
|
135
135
|
return brief.content || "";
|
|
136
136
|
}
|
|
137
|
+
function hasActiveRubrics(campaign) {
|
|
138
|
+
return (campaign.leadScoringRubrics?.some((rubric) => rubric.useCheckForScoring !== false) ?? false);
|
|
139
|
+
}
|
|
140
|
+
function hasApprovedMessageTemplate(campaign) {
|
|
141
|
+
if (campaign.messagingTemplateId || campaign.messagingTemplate)
|
|
142
|
+
return true;
|
|
143
|
+
const briefContent = getBriefContent(campaign.campaignBrief);
|
|
144
|
+
if (!briefContent.trim())
|
|
145
|
+
return false;
|
|
146
|
+
return (/approved message template/i.test(briefContent) &&
|
|
147
|
+
(/token fill rules/i.test(briefContent) ||
|
|
148
|
+
/token fill examples/i.test(briefContent) ||
|
|
149
|
+
/{{[^}]+}}/.test(briefContent)));
|
|
150
|
+
}
|
|
151
|
+
function hasSenderIds(campaign) {
|
|
152
|
+
return Array.isArray(campaign.senderIds) && campaign.senderIds.length > 0;
|
|
153
|
+
}
|
|
154
|
+
function hasSequenceTemplate(campaign) {
|
|
155
|
+
return Boolean(campaign.sequenceTemplate);
|
|
156
|
+
}
|
|
157
|
+
function isRunningCampaign(campaign) {
|
|
158
|
+
const status = String(campaign.campaignStatus ?? campaign.status ?? "")
|
|
159
|
+
.trim()
|
|
160
|
+
.toLowerCase();
|
|
161
|
+
return (campaign.currentStep === "running" ||
|
|
162
|
+
status === "running" ||
|
|
163
|
+
status === "active" ||
|
|
164
|
+
status === "started");
|
|
165
|
+
}
|
|
166
|
+
function shouldEvaluateTailState(campaign) {
|
|
167
|
+
const tailCurrentSteps = new Set([
|
|
168
|
+
"messages",
|
|
169
|
+
"auto-execute-leads",
|
|
170
|
+
"auto-execute-messaging",
|
|
171
|
+
"awaiting-user-greenlight",
|
|
172
|
+
"settings",
|
|
173
|
+
"sequence",
|
|
174
|
+
"send",
|
|
175
|
+
"claude-greenlight",
|
|
176
|
+
"running",
|
|
177
|
+
]);
|
|
178
|
+
return ((campaign.currentStep
|
|
179
|
+
? tailCurrentSteps.has(campaign.currentStep)
|
|
180
|
+
: false) ||
|
|
181
|
+
campaign.useMessagingTemplate === true ||
|
|
182
|
+
hasApprovedMessageTemplate(campaign) ||
|
|
183
|
+
hasSenderIds(campaign) ||
|
|
184
|
+
hasSequenceTemplate(campaign) ||
|
|
185
|
+
isRunningCampaign(campaign));
|
|
186
|
+
}
|
|
137
187
|
function checkCampaignCreated(campaign) {
|
|
138
188
|
const missing = [];
|
|
139
189
|
if (!campaign.id)
|
|
@@ -161,36 +211,94 @@ function checkProviderSearch(campaign) {
|
|
|
161
211
|
missing.push("leadSourceProvider");
|
|
162
212
|
return { stepId: "provider-search", missing };
|
|
163
213
|
}
|
|
164
|
-
function checkConfirmLeadList(campaign, hasWorkflowRows, enforceRowCheck) {
|
|
214
|
+
function checkConfirmLeadList(campaign, hasWorkflowRows, enforceRowCheck, refs) {
|
|
165
215
|
const missing = [];
|
|
166
216
|
if (!campaign.selectedLeadListId)
|
|
167
217
|
missing.push("selectedLeadListId");
|
|
168
218
|
if (!campaign.workflowTableId)
|
|
169
219
|
missing.push("workflowTableId");
|
|
220
|
+
if (campaign.selectedLeadListId &&
|
|
221
|
+
refs.selectedLeadListChecked &&
|
|
222
|
+
refs.selectedLeadListAccessible === false) {
|
|
223
|
+
missing.push("selectedLeadList");
|
|
224
|
+
}
|
|
225
|
+
if (campaign.workflowTableId && refs.workflowTableAccessible === false) {
|
|
226
|
+
missing.push("workflowTable");
|
|
227
|
+
}
|
|
170
228
|
if (enforceRowCheck && hasWorkflowRows === false) {
|
|
171
229
|
missing.push("workflowTableRows");
|
|
172
230
|
}
|
|
173
231
|
return { stepId: "confirm-lead-list", missing };
|
|
174
232
|
}
|
|
233
|
+
function checkRubrics(campaign) {
|
|
234
|
+
const missing = [];
|
|
235
|
+
const filtersRequired = campaign.enableICPFilters === true;
|
|
236
|
+
if (filtersRequired && !hasActiveRubrics(campaign)) {
|
|
237
|
+
missing.push("leadScoringRubrics");
|
|
238
|
+
}
|
|
239
|
+
return { stepId: "filter-rules", missing };
|
|
240
|
+
}
|
|
241
|
+
function checkMessages(campaign) {
|
|
242
|
+
const missing = [];
|
|
243
|
+
if (!hasApprovedMessageTemplate(campaign)) {
|
|
244
|
+
missing.push("approvedMessageTemplate");
|
|
245
|
+
}
|
|
246
|
+
return { stepId: "messages", missing };
|
|
247
|
+
}
|
|
248
|
+
function checkSettings(campaign) {
|
|
249
|
+
const missing = [];
|
|
250
|
+
if (!hasSenderIds(campaign))
|
|
251
|
+
missing.push("senderIds");
|
|
252
|
+
return { stepId: "settings", missing };
|
|
253
|
+
}
|
|
254
|
+
function checkSequence(campaign) {
|
|
255
|
+
const missing = [];
|
|
256
|
+
if (!hasSequenceTemplate(campaign))
|
|
257
|
+
missing.push("sequenceTemplate");
|
|
258
|
+
return { stepId: "sequence", missing };
|
|
259
|
+
}
|
|
175
260
|
function mapHeadlessToUiStep(step) {
|
|
176
261
|
if (!step)
|
|
177
262
|
return null;
|
|
178
|
-
if (step === "configure-columns" || step === "confirm-lead-list") {
|
|
179
|
-
return "leads";
|
|
180
|
-
}
|
|
181
263
|
if (step === "create-offer")
|
|
182
264
|
return "plan";
|
|
265
|
+
if (step === "select-lead-source")
|
|
266
|
+
return "choose-source";
|
|
183
267
|
if (step === "pick-provider")
|
|
184
268
|
return "pick-provider";
|
|
185
269
|
if (step === "apollo" || step === "sales-nav" || step === "prospeo") {
|
|
186
270
|
return "contact-search";
|
|
187
271
|
}
|
|
272
|
+
if (step === "configure-columns" ||
|
|
273
|
+
step === "apollo-select-leads" ||
|
|
274
|
+
step === "sales-nav-select-leads" ||
|
|
275
|
+
step === "prospeo-select-leads" ||
|
|
276
|
+
step === "signal-discovery-results" ||
|
|
277
|
+
step === "confirm-lead-list") {
|
|
278
|
+
return "leads";
|
|
279
|
+
}
|
|
188
280
|
if (step === "signal-discovery")
|
|
189
281
|
return "signal-discovery";
|
|
190
|
-
if (step === "signal-discovery-results")
|
|
191
|
-
return "leads";
|
|
192
282
|
if (step === "filter-choice")
|
|
193
283
|
return "filter-choice";
|
|
284
|
+
if (step === "create-icp-rubric")
|
|
285
|
+
return "filter-rules";
|
|
286
|
+
if (step === "apply-icp-rubric" || step === "validate-sample") {
|
|
287
|
+
return "filter-leads";
|
|
288
|
+
}
|
|
289
|
+
if (step === "messages" ||
|
|
290
|
+
step === "auto-execute-leads" ||
|
|
291
|
+
step === "auto-execute-messaging") {
|
|
292
|
+
return "messages";
|
|
293
|
+
}
|
|
294
|
+
if (step === "settings" || step === "awaiting-user-greenlight") {
|
|
295
|
+
return "settings";
|
|
296
|
+
}
|
|
297
|
+
if (step === "sequence")
|
|
298
|
+
return "sequence";
|
|
299
|
+
if (step === "send" || step === "claude-greenlight" || step === "running") {
|
|
300
|
+
return "send";
|
|
301
|
+
}
|
|
194
302
|
return null;
|
|
195
303
|
}
|
|
196
304
|
export const navigationToolDefinitions = [
|
|
@@ -227,6 +335,11 @@ export function computeCampaignNavigationStateFromCampaign(campaign, options) {
|
|
|
227
335
|
leadSourceProvider: campaign.leadSourceProvider ?? null,
|
|
228
336
|
selectedLeadListId: campaign.selectedLeadListId ?? null,
|
|
229
337
|
workflowTableId: campaign.workflowTableId ?? null,
|
|
338
|
+
enableICPFilters: campaign.enableICPFilters ?? null,
|
|
339
|
+
activeRubricCount: campaign.leadScoringRubrics?.filter((rubric) => rubric.useCheckForScoring !== false).length ?? 0,
|
|
340
|
+
senderCount: campaign.senderIds?.length ?? 0,
|
|
341
|
+
hasSequenceTemplate: hasSequenceTemplate(campaign),
|
|
342
|
+
campaignStatus: campaign.campaignStatus ?? campaign.status ?? null,
|
|
230
343
|
currentStep: campaign.currentStep ?? null,
|
|
231
344
|
},
|
|
232
345
|
options: {
|
|
@@ -234,6 +347,9 @@ export function computeCampaignNavigationStateFromCampaign(campaign, options) {
|
|
|
234
347
|
tableChecked: options.tableChecked,
|
|
235
348
|
rowCount: options.rowCount,
|
|
236
349
|
hasWorkflowRows: options.hasWorkflowRows,
|
|
350
|
+
selectedLeadListChecked: options.selectedLeadListChecked ?? false,
|
|
351
|
+
selectedLeadListAccessible: options.selectedLeadListAccessible ?? null,
|
|
352
|
+
workflowTableAccessible: options.workflowTableAccessible ?? null,
|
|
237
353
|
initialWarningsCount: options.initialWarnings?.length ?? 0,
|
|
238
354
|
},
|
|
239
355
|
}, campaign.id);
|
|
@@ -252,8 +368,19 @@ export function computeCampaignNavigationStateFromCampaign(campaign, options) {
|
|
|
252
368
|
checkCampaignCreated(campaign),
|
|
253
369
|
checkPickProvider(campaign),
|
|
254
370
|
checkProviderSearch(campaign),
|
|
255
|
-
checkConfirmLeadList(campaign, options.hasWorkflowRows, enforceRowCheck
|
|
371
|
+
checkConfirmLeadList(campaign, options.hasWorkflowRows, enforceRowCheck, {
|
|
372
|
+
selectedLeadListChecked: options.selectedLeadListChecked === true,
|
|
373
|
+
selectedLeadListAccessible: options.selectedLeadListAccessible ?? null,
|
|
374
|
+
workflowTableAccessible: options.workflowTableAccessible ?? null,
|
|
375
|
+
}),
|
|
256
376
|
];
|
|
377
|
+
if (campaign.enableICPFilters === true) {
|
|
378
|
+
checks.push(checkRubrics(campaign));
|
|
379
|
+
}
|
|
380
|
+
const evaluateTailState = shouldEvaluateTailState(campaign);
|
|
381
|
+
if (evaluateTailState) {
|
|
382
|
+
checks.push(checkMessages(campaign), checkSettings(campaign), checkSequence(campaign));
|
|
383
|
+
}
|
|
257
384
|
let computedStep = "campaign-created";
|
|
258
385
|
let blockedAt = null;
|
|
259
386
|
let missing = [];
|
|
@@ -271,11 +398,24 @@ export function computeCampaignNavigationStateFromCampaign(campaign, options) {
|
|
|
271
398
|
}
|
|
272
399
|
computedStep = check.stepId;
|
|
273
400
|
}
|
|
401
|
+
if (!blockedAt && evaluateTailState) {
|
|
402
|
+
computedStep = isRunningCampaign(campaign) ? "running" : "send";
|
|
403
|
+
}
|
|
274
404
|
const expectedHeadlessStep = computedStep === "campaign-created"
|
|
275
405
|
? "create-offer"
|
|
276
406
|
: computedStep === "provider-search"
|
|
277
407
|
? providerConfig?.currentStep || campaign.leadSourceProvider || null
|
|
278
|
-
: computedStep
|
|
408
|
+
: computedStep === "filter-rules"
|
|
409
|
+
? "create-icp-rubric"
|
|
410
|
+
: computedStep === "messages"
|
|
411
|
+
? "messages"
|
|
412
|
+
: computedStep === "settings"
|
|
413
|
+
? "settings"
|
|
414
|
+
: computedStep === "sequence"
|
|
415
|
+
? "sequence"
|
|
416
|
+
: computedStep === "running"
|
|
417
|
+
? "running"
|
|
418
|
+
: computedStep;
|
|
279
419
|
const stepAligned = !campaign.currentStep || !expectedHeadlessStep
|
|
280
420
|
? null
|
|
281
421
|
: campaign.currentStep === expectedHeadlessStep;
|
|
@@ -304,6 +444,12 @@ export function computeCampaignNavigationStateFromCampaign(campaign, options) {
|
|
|
304
444
|
leadSourceProvider: campaign.leadSourceProvider ?? null,
|
|
305
445
|
selectedLeadListId: campaign.selectedLeadListId ?? null,
|
|
306
446
|
providerCurrentStep: providerConfig?.currentStep ?? null,
|
|
447
|
+
enableICPFilters: campaign.enableICPFilters ?? null,
|
|
448
|
+
activeRubricCount: campaign.leadScoringRubrics?.filter((rubric) => rubric.useCheckForScoring !== false).length ?? 0,
|
|
449
|
+
hasApprovedMessageTemplate: hasApprovedMessageTemplate(campaign),
|
|
450
|
+
senderCount: campaign.senderIds?.length ?? 0,
|
|
451
|
+
hasSequenceTemplate: hasSequenceTemplate(campaign),
|
|
452
|
+
campaignStatus: campaign.campaignStatus ?? campaign.status ?? null,
|
|
307
453
|
},
|
|
308
454
|
warnings,
|
|
309
455
|
};
|
|
@@ -328,7 +474,31 @@ export async function getCampaignNavigationState(input) {
|
|
|
328
474
|
campaignId: input.campaignId,
|
|
329
475
|
includeTableCheck,
|
|
330
476
|
}, input.campaignId);
|
|
331
|
-
|
|
477
|
+
let campaign;
|
|
478
|
+
try {
|
|
479
|
+
campaign = await api.get(`/api/v2/campaign-offers/${input.campaignId}`);
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
483
|
+
const unavailable = computeCampaignNavigationStateFromCampaign({
|
|
484
|
+
id: input.campaignId,
|
|
485
|
+
campaignBrief: null,
|
|
486
|
+
currentStep: null,
|
|
487
|
+
}, {
|
|
488
|
+
includeTableCheck,
|
|
489
|
+
tableChecked: false,
|
|
490
|
+
rowCount: null,
|
|
491
|
+
hasWorkflowRows: null,
|
|
492
|
+
initialWarnings: [
|
|
493
|
+
`Campaign is missing or inaccessible. Remediation: confirm the campaignId and active workspace, then retry. Detail: ${message}`,
|
|
494
|
+
],
|
|
495
|
+
});
|
|
496
|
+
return {
|
|
497
|
+
...unavailable,
|
|
498
|
+
blockedAt: "campaign-created",
|
|
499
|
+
missing: ["campaign"],
|
|
500
|
+
};
|
|
501
|
+
}
|
|
332
502
|
logNavigationDebug("state.fetch_campaign.success", {
|
|
333
503
|
campaignId: input.campaignId,
|
|
334
504
|
workflowTableId: campaign.workflowTableId ?? null,
|
|
@@ -338,8 +508,39 @@ export async function getCampaignNavigationState(input) {
|
|
|
338
508
|
}, input.campaignId);
|
|
339
509
|
let tableRowCount = null;
|
|
340
510
|
let hasWorkflowRows = null;
|
|
511
|
+
let workflowTableAccessible = campaign.workflowTableId
|
|
512
|
+
? null
|
|
513
|
+
: true;
|
|
514
|
+
let selectedLeadListChecked = false;
|
|
515
|
+
let selectedLeadListAccessible = campaign.selectedLeadListId
|
|
516
|
+
? null
|
|
517
|
+
: true;
|
|
518
|
+
let selectedLeadListRowCount = null;
|
|
341
519
|
const warnings = [];
|
|
342
520
|
let tableChecked = false;
|
|
521
|
+
if (includeTableCheck &&
|
|
522
|
+
campaign.selectedLeadListId &&
|
|
523
|
+
campaign.selectedLeadListId !== campaign.workflowTableId) {
|
|
524
|
+
try {
|
|
525
|
+
const rows = await getTableRowsMinimal(campaign.selectedLeadListId, {
|
|
526
|
+
limit: 1,
|
|
527
|
+
page: 1,
|
|
528
|
+
});
|
|
529
|
+
selectedLeadListChecked = true;
|
|
530
|
+
selectedLeadListAccessible = true;
|
|
531
|
+
selectedLeadListRowCount = rows.pagination?.totalCount ?? 0;
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
selectedLeadListChecked = true;
|
|
535
|
+
selectedLeadListAccessible = false;
|
|
536
|
+
warnings.push(`Selected source list is inaccessible or deleted. Remediation: re-select or recreate the lead list before resuming. Detail: ${error instanceof Error ? error.message : String(error)}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
else if (campaign.selectedLeadListId) {
|
|
540
|
+
selectedLeadListChecked =
|
|
541
|
+
campaign.selectedLeadListId === campaign.workflowTableId;
|
|
542
|
+
selectedLeadListAccessible = null;
|
|
543
|
+
}
|
|
343
544
|
if (includeTableCheck && campaign.workflowTableId) {
|
|
344
545
|
logNavigationDebug("state.table_check.start", {
|
|
345
546
|
campaignId: input.campaignId,
|
|
@@ -351,6 +552,7 @@ export async function getCampaignNavigationState(input) {
|
|
|
351
552
|
page: 1,
|
|
352
553
|
});
|
|
353
554
|
tableChecked = true;
|
|
555
|
+
workflowTableAccessible = true;
|
|
354
556
|
tableRowCount = rows.pagination?.totalCount ?? 0;
|
|
355
557
|
hasWorkflowRows = tableRowCount > 0;
|
|
356
558
|
logNavigationDebug("state.table_check.success", {
|
|
@@ -361,7 +563,11 @@ export async function getCampaignNavigationState(input) {
|
|
|
361
563
|
}, input.campaignId);
|
|
362
564
|
}
|
|
363
565
|
catch (error) {
|
|
364
|
-
|
|
566
|
+
tableChecked = true;
|
|
567
|
+
workflowTableAccessible = false;
|
|
568
|
+
tableRowCount = null;
|
|
569
|
+
hasWorkflowRows = null;
|
|
570
|
+
warnings.push(`Workflow table is inaccessible or deleted. Remediation: re-import confirmed leads before resuming. Detail: ${error instanceof Error ? error.message : String(error)}`);
|
|
365
571
|
logNavigationDebug("state.table_check.error", {
|
|
366
572
|
campaignId: input.campaignId,
|
|
367
573
|
workflowTableId: campaign.workflowTableId,
|
|
@@ -374,6 +580,10 @@ export async function getCampaignNavigationState(input) {
|
|
|
374
580
|
tableChecked,
|
|
375
581
|
rowCount: tableRowCount,
|
|
376
582
|
hasWorkflowRows,
|
|
583
|
+
selectedLeadListChecked,
|
|
584
|
+
selectedLeadListAccessible,
|
|
585
|
+
selectedLeadListRowCount,
|
|
586
|
+
workflowTableAccessible,
|
|
377
587
|
initialWarnings: warnings,
|
|
378
588
|
});
|
|
379
589
|
logNavigationDebug("state.complete", {
|
package/dist/tools/prompts.js
CHANGED
|
@@ -283,7 +283,7 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
283
283
|
joinGate: {
|
|
284
284
|
afterAllComplete: true,
|
|
285
285
|
requiredArtifacts: ["lead-filter.md", "message-validation.md"],
|
|
286
|
-
show: ["
|
|
286
|
+
show: ["readable_filters_with_reasons", "sample_message_for_approval"],
|
|
287
287
|
nextStage: "message-review",
|
|
288
288
|
},
|
|
289
289
|
usage: {
|
|
@@ -4,9 +4,8 @@ function scopedKey(provider, campaignOfferId) {
|
|
|
4
4
|
}
|
|
5
5
|
function resolveLoadedState(provider, campaignOfferId) {
|
|
6
6
|
if (campaignOfferId) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return campaignScoped;
|
|
7
|
+
return (providerPromptLoadedState.get(scopedKey(provider, campaignOfferId)) ??
|
|
8
|
+
null);
|
|
10
9
|
}
|
|
11
10
|
const providerScoped = providerPromptLoadedState.get(scopedKey(provider));
|
|
12
11
|
return providerScoped ?? null;
|