@sellable/mcp 0.1.475 → 0.1.476
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.d.ts +3 -3
- package/dist/tools/leads.js +17 -16
- package/dist/tools/refill-sends.js +2 -2
- package/dist/tools/refill-target-plan.js +11 -2
- package/dist/tools/sequencer.js +5 -6
- package/package.json +1 -1
- package/skills/create-campaign-v2/references/final-handoff-contract.md +9 -7
- package/skills/create-campaign-v2-tail/SKILL.md +13 -10
- package/skills/create-evergreen-campaigns/SKILL.md +11 -6
- package/skills/refill-sends/SKILL.md +19 -15
- package/skills/refill-sends-workflow/SKILL.md +36 -22
- package/skills/workflow-sequences/SKILL.md +1 -1
package/dist/tools/leads.d.ts
CHANGED
|
@@ -4617,7 +4617,7 @@ export declare function importLeads(input: ImportLeadsInput): Promise<{
|
|
|
4617
4617
|
validSelectedPostCount: number;
|
|
4618
4618
|
recommendedValidPostCount: number;
|
|
4619
4619
|
estimatedValidEngagers: number;
|
|
4620
|
-
targetEngagerCount: number;
|
|
4620
|
+
targetEngagerCount: number | null;
|
|
4621
4621
|
suggestedToolCalls: never[];
|
|
4622
4622
|
leadListId?: undefined;
|
|
4623
4623
|
existingLeadListId?: undefined;
|
|
@@ -4651,7 +4651,7 @@ export declare function importLeads(input: ImportLeadsInput): Promise<{
|
|
|
4651
4651
|
validSelectedPostCount: number;
|
|
4652
4652
|
recommendedValidPostCount: number;
|
|
4653
4653
|
estimatedValidEngagers: number;
|
|
4654
|
-
targetEngagerCount: number;
|
|
4654
|
+
targetEngagerCount: number | null;
|
|
4655
4655
|
message: string;
|
|
4656
4656
|
suggestedToolCalls: {
|
|
4657
4657
|
tool: string;
|
|
@@ -4698,7 +4698,7 @@ export declare function importLeads(input: ImportLeadsInput): Promise<{
|
|
|
4698
4698
|
estimatedEngagers: number;
|
|
4699
4699
|
selectedPostCount: number;
|
|
4700
4700
|
availableSelectedPostCount: number;
|
|
4701
|
-
targetEngagerCount: number;
|
|
4701
|
+
targetEngagerCount: number | null;
|
|
4702
4702
|
maxPostsToScrape: number | null;
|
|
4703
4703
|
limitedSelectedPosts: boolean;
|
|
4704
4704
|
invalidSelectedPostCount: number;
|
package/dist/tools/leads.js
CHANGED
|
@@ -1127,15 +1127,15 @@ function normalizeEngagementCount(value) {
|
|
|
1127
1127
|
: 0;
|
|
1128
1128
|
}
|
|
1129
1129
|
function buildSignalDiscoverySourceRecommendation({ selectedPosts, targetEngagerCount, maxPostsToScrape, }) {
|
|
1130
|
-
const { targetGoodFitLeads, defaultFitRate, minPlanningFitRate,
|
|
1131
|
-
const
|
|
1130
|
+
const { targetGoodFitLeads, defaultFitRate, minPlanningFitRate, reviewBatchSize, } = getSignalDiscoverySourcePlanDefaults();
|
|
1131
|
+
const explicitTargetEngagerCount = normalizePositiveInteger(targetEngagerCount);
|
|
1132
1132
|
const normalizedSelectedPosts = selectedPosts.map((post) => ({
|
|
1133
1133
|
...post,
|
|
1134
1134
|
likes: normalizeEngagementCount(post.likes),
|
|
1135
1135
|
comments: normalizeEngagementCount(post.comments),
|
|
1136
1136
|
}));
|
|
1137
1137
|
const scrapePlan = selectSignalPostsForImport(normalizedSelectedPosts, {
|
|
1138
|
-
targetEngagerCount:
|
|
1138
|
+
targetEngagerCount: explicitTargetEngagerCount,
|
|
1139
1139
|
maxPostsToScrape: maxPostsToScrape ?? undefined,
|
|
1140
1140
|
});
|
|
1141
1141
|
const recommendedPosts = scrapePlan.posts;
|
|
@@ -1143,8 +1143,8 @@ function buildSignalDiscoverySourceRecommendation({ selectedPosts, targetEngager
|
|
|
1143
1143
|
const recommendedCount = recommendedPosts.length;
|
|
1144
1144
|
const totalVisibleEngagement = normalizedSelectedPosts.reduce((sum, post) => sum + post.likes + post.comments, 0);
|
|
1145
1145
|
const recommendedVisibleEngagement = recommendedPosts.reduce((sum, post) => sum + post.likes + post.comments, 0);
|
|
1146
|
-
const fitRateForEstimate =
|
|
1147
|
-
? Math.min(1, targetGoodFitLeads /
|
|
1146
|
+
const fitRateForEstimate = explicitTargetEngagerCount && explicitTargetEngagerCount > 0
|
|
1147
|
+
? Math.min(1, targetGoodFitLeads / explicitTargetEngagerCount)
|
|
1148
1148
|
: defaultFitRate;
|
|
1149
1149
|
const estimatedGoodFit = scrapePlan.estimatedEngagers * fitRateForEstimate;
|
|
1150
1150
|
const tableRows = recommendedPosts
|
|
@@ -1154,9 +1154,12 @@ function buildSignalDiscoverySourceRecommendation({ selectedPosts, targetEngager
|
|
|
1154
1154
|
return `| ${escapeMarkdownTableCell(post.authorName)} | ${escapeMarkdownTableCell(post.reason)} | ${formatApproxInteger(engagement)} | ${formatApproxInteger(estimatedPostGoodFit)} |`;
|
|
1155
1155
|
})
|
|
1156
1156
|
.join("\n");
|
|
1157
|
-
const fitRateLabel =
|
|
1157
|
+
const fitRateLabel = explicitTargetEngagerCount && explicitTargetEngagerCount > 0
|
|
1158
1158
|
? "the approved buyer-search target"
|
|
1159
1159
|
: `the ${Math.round(defaultFitRate * 100)}% starting estimate`;
|
|
1160
|
+
const peopleToCheckCopy = explicitTargetEngagerCount && explicitTargetEngagerCount > 0
|
|
1161
|
+
? `about ${explicitTargetEngagerCount.toLocaleString("en-US")} people who reacted or commented`
|
|
1162
|
+
: `up to ${formatApproxInteger(scrapePlan.estimatedEngagers)} people from the selected posts`;
|
|
1160
1163
|
const selectedPoolCopy = recommendedCount < selectedCount
|
|
1161
1164
|
? `**Posts considered:** ${selectedCount.toLocaleString("en-US")} selected posts with ${formatApproxInteger(totalVisibleEngagement)} public reactions/comments<br>\n**Recommended first set:** ${recommendedCount.toLocaleString("en-US")} post${recommendedCount === 1 ? "" : "s"} with ${formatApproxInteger(recommendedVisibleEngagement)} public reactions/comments and up to ${formatApproxInteger(scrapePlan.estimatedEngagers)} people to check<br>`
|
|
1162
1165
|
: `**Public activity in the selected posts:** ${formatApproxInteger(totalVisibleEngagement)} reactions/comments<br>\n**People we can check from this set:** up to ${formatApproxInteger(scrapePlan.estimatedEngagers)}<br>`;
|
|
@@ -1167,7 +1170,7 @@ Start with LinkedIn posts.
|
|
|
1167
1170
|
I found ${recommendedCount.toLocaleString("en-US")} recommended LinkedIn post${recommendedCount === 1 ? "" : "s"} to check first. People there are already reacting or commenting around this problem.
|
|
1168
1171
|
|
|
1169
1172
|
**Goal:** find about ${targetGoodFitLeads.toLocaleString("en-US")} likely prospects<br>
|
|
1170
|
-
**People to check:**
|
|
1173
|
+
**People to check:** ${peopleToCheckCopy}<br>
|
|
1171
1174
|
**Good sign:** at least ${Math.round(minPlanningFitRate * 100)}% of the first sample should look like real prospects; below that, switch to active LinkedIn profiles with the right titles<br>
|
|
1172
1175
|
**First review:** after the list is built, review the first ${reviewBatchSize.toLocaleString("en-US")} leads before we scale
|
|
1173
1176
|
|
|
@@ -1195,7 +1198,7 @@ Approval card should say:
|
|
|
1195
1198
|
message,
|
|
1196
1199
|
recommendedPostCount: recommendedCount,
|
|
1197
1200
|
estimatedEngagers: scrapePlan.estimatedEngagers,
|
|
1198
|
-
targetEngagerCount:
|
|
1201
|
+
targetEngagerCount: explicitTargetEngagerCount ?? null,
|
|
1199
1202
|
};
|
|
1200
1203
|
}
|
|
1201
1204
|
function hasUsableSignalValidationPost(post) {
|
|
@@ -2114,7 +2117,7 @@ export const leadToolDefinitions = [
|
|
|
2114
2117
|
},
|
|
2115
2118
|
targetEngagerCount: {
|
|
2116
2119
|
type: "number",
|
|
2117
|
-
description: "Signal Discovery: target number of people from selected posts to check.
|
|
2120
|
+
description: "Signal Discovery: optional explicit target number of people from selected posts to check. If omitted, import all selected posts up to backend Signal Discovery caps. Use only when the approved source plan has real sample math: target likely prospects divided by sampled pass rate. If the sampled/projected fit rate is below the 10% planning floor, switch to the next provider instead of scaling noisy post reactions. When provided, this can limit selected posts before starting scrape, with a backend hard cap of 10 posts.",
|
|
2118
2121
|
},
|
|
2119
2122
|
maxPostsToScrape: {
|
|
2120
2123
|
type: "number",
|
|
@@ -2263,7 +2266,7 @@ export const leadToolDefinitions = [
|
|
|
2263
2266
|
},
|
|
2264
2267
|
targetEngagerCount: {
|
|
2265
2268
|
type: "number",
|
|
2266
|
-
description: "Optional source-candidate target from sample math. When provided, the recommendation ranks selected posts by scrapable engagement and asks approval for only enough posts to cover this target. If omitted,
|
|
2269
|
+
description: "Optional source-candidate target from sample math. When provided, the recommendation ranks selected posts by scrapable engagement and asks approval for only enough posts to cover this target. If omitted, all selected posts are recommended up to backend Signal Discovery caps.",
|
|
2267
2270
|
},
|
|
2268
2271
|
maxPostsToScrape: {
|
|
2269
2272
|
type: "number",
|
|
@@ -3793,9 +3796,7 @@ export async function importLeads(input) {
|
|
|
3793
3796
|
: undefined;
|
|
3794
3797
|
// === SIGNAL DISCOVERY FLOW ===
|
|
3795
3798
|
if (provider === "signal-discovery") {
|
|
3796
|
-
const
|
|
3797
|
-
const effectiveTargetEngagerCount = normalizePositiveInteger(targetEngagerCount) ??
|
|
3798
|
-
defaultSignalTargetEngagers;
|
|
3799
|
+
const effectiveTargetEngagerCount = normalizePositiveInteger(targetEngagerCount) ?? null;
|
|
3799
3800
|
// Get selected posts from the campaign's signal search tabs
|
|
3800
3801
|
// Note: API returns flat fields (postUrl, postContent, authorName, etc.)
|
|
3801
3802
|
const tabsResponse = await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`);
|
|
@@ -3843,7 +3844,7 @@ export async function importLeads(input) {
|
|
|
3843
3844
|
throw new Error(`Maximum ${MAX_SIGNAL_DISCOVERY_POSTS} Signal Discovery posts can be imported for scraping. ${uniqueSelectedPosts.length} unique posts are currently selected; reduce the selected posts to the strongest ${MAX_SIGNAL_DISCOVERY_POSTS} before calling import_leads.`);
|
|
3844
3845
|
}
|
|
3845
3846
|
let importSelection = selectSignalPostsForImport(uniqueSelectedPosts, {
|
|
3846
|
-
targetEngagerCount: effectiveTargetEngagerCount,
|
|
3847
|
+
targetEngagerCount: effectiveTargetEngagerCount ?? undefined,
|
|
3847
3848
|
maxPostsToScrape,
|
|
3848
3849
|
});
|
|
3849
3850
|
let postsToScrape = importSelection.posts;
|
|
@@ -3876,7 +3877,7 @@ export async function importLeads(input) {
|
|
|
3876
3877
|
}
|
|
3877
3878
|
if (invalidPlannedPosts.length > 0 && !allowInvalidSignalPosts) {
|
|
3878
3879
|
const validImportSelection = selectSignalPostsForImport(postValidation.validPosts, {
|
|
3879
|
-
targetEngagerCount: effectiveTargetEngagerCount,
|
|
3880
|
+
targetEngagerCount: effectiveTargetEngagerCount ?? undefined,
|
|
3880
3881
|
maxPostsToScrape,
|
|
3881
3882
|
});
|
|
3882
3883
|
const validRecommendedCount = validImportSelection.posts.length;
|
|
@@ -3915,7 +3916,7 @@ export async function importLeads(input) {
|
|
|
3915
3916
|
}
|
|
3916
3917
|
if (allowInvalidSignalPosts && postValidation.invalidPosts.length > 0) {
|
|
3917
3918
|
importSelection = selectSignalPostsForImport(postValidation.validPosts, {
|
|
3918
|
-
targetEngagerCount: effectiveTargetEngagerCount,
|
|
3919
|
+
targetEngagerCount: effectiveTargetEngagerCount ?? undefined,
|
|
3919
3920
|
maxPostsToScrape,
|
|
3920
3921
|
});
|
|
3921
3922
|
postsToScrape = importSelection.posts;
|
|
@@ -197,7 +197,7 @@ export function refillSendsCommand(input = {}) {
|
|
|
197
197
|
"Render target.eligibleSenderLedger and target.senderRefillPlans before mutation. Preserve the coverage labels Need to prepare, Goal, Already sent, Scheduled, Ready and waiting to be scheduled, and Still need.",
|
|
198
198
|
"Use target.globalActionQueue as the only cross-sender yolo queue: execute only target.globalActionQueue[0], one globally ranked primitive, then rerun get_refill_target_plan before choosing another action.",
|
|
199
199
|
"If get_refill_target_plan returns status complete, report eligible sender ledger, target.senderRefillPlans, gross target, selected days, sent count, scheduled count, projected count, campaign ids, targetShapeRevision, and no-op proof without asking for approval.",
|
|
200
|
-
"Refill target lanes are
|
|
200
|
+
"Refill target lanes are connection invites (send_invite), standalone paid InMails (send_inmail_closed), or unified Sales Nav cascades represented publicly as send_inmail_closed with campaign classification sales_nav_cascade. For a Sales Nav cascade, refill the selected campaign first; its sequence can route prospects to Open InMail, paid InMail while fresh credits are >= 5, or same-campaign connection fallback without asking for separate open/paid/connection campaigns. Do not count send_dm as horizon target capacity.",
|
|
201
201
|
"If remainingReadyOrProjectedGap is 0 but remainingProjectedGap is positive, run only a persistent read-only scheduler wait/reread loop; do not ask for prep/import/approval and do not close out while scheduler pickup is the only remaining state.",
|
|
202
202
|
`Resolve route with resolve_campaign_fill_route({ intent: "${intent}"${input.campaignId ? `, campaignId: "${input.campaignId}"` : ""}${input.tableId ? `, tableId: "${input.tableId}"` : ""} }).`,
|
|
203
203
|
'If the plain route shows stale managed waterfall evidence, archived/completed shared slots, or targets that do not cover the selected sender set, immediately call resolve_campaign_fill_route({ intent: "active" }) and inspect current dashboard-active ACTIVE/PAUSED campaign-backed sequence campaigns before declaring a sender blocked.',
|
|
@@ -215,7 +215,7 @@ export function refillSendsCommand(input = {}) {
|
|
|
215
215
|
"Maintain a target-window saturation ledger per selected sender from get_refill_target_plan: selected days, gross capacity, actual sent counts, future scheduler-owned scheduled counts, projected counts, ready-to-schedule buffer, remaining projected gap, paid InMail threshold feasibility, targetShapeRevision, stateRevision, and next MCP primitive.",
|
|
216
216
|
"In --yolo, this tool automatically maintains a run-local refreshedPaidInmailSenderIds set: when the first target plan returns refresh_paid_inmail_credits candidates for selected paid-InMail lanes, it refreshes each exact sender at most once, reruns get_refill_target_plan, and returns the post-refresh targetPlan before any prep/import/approval/start action is chosen.",
|
|
217
217
|
"Do not present paid-credit refresh as the next operator action after refill_sends returns autoPaidInmailRefresh and the post-refresh targetPlan.",
|
|
218
|
-
"If paid InMail feasibility remains below threshold after that automatic refresh, report the exact sender/campaign/table/column threshold action or connection fallback; --yolo must not lower paid InMail thresholds or create campaigns.",
|
|
218
|
+
"If paid InMail feasibility remains below threshold after that automatic refresh, report the exact sender/campaign/table/column threshold action or same-campaign connection fallback; --yolo must not lower paid InMail thresholds or create campaigns.",
|
|
219
219
|
"In --yolo or after one Accept, continue through every safe selected sender/campaign action covered by the rendered target packet; sent/scheduled-count progress changes stateRevision and should continue while targetShapeRevision is stable.",
|
|
220
220
|
"Do not complete a fill/schedule request until a final get_refill_target_plan or refill-state reread proves projected coverage (sent + scheduled) fills the scheduler-forward target window. Treat awaiting_scheduler_after_ready_buffer as loaded, awaiting scheduler when ready buffer covers the gap; do not import/prep more rows in that state, and keep polling unless Christian stops/statuses the run or a concrete non-scheduler blocker such as paid_inmail_below_threshold appears.",
|
|
221
221
|
],
|
|
@@ -57,6 +57,9 @@ function stringValue(value) {
|
|
|
57
57
|
function booleanValue(value) {
|
|
58
58
|
return typeof value === "boolean" ? value : undefined;
|
|
59
59
|
}
|
|
60
|
+
function campaignClassificationValue(value) {
|
|
61
|
+
return value === "sales_nav_cascade" ? "sales_nav_cascade" : undefined;
|
|
62
|
+
}
|
|
60
63
|
function stringArray(value) {
|
|
61
64
|
if (!Array.isArray(value))
|
|
62
65
|
return [];
|
|
@@ -307,6 +310,7 @@ function sanitizeCampaignRanking(value) {
|
|
|
307
310
|
tableId: stringValue(option.tableId) ?? "",
|
|
308
311
|
tableName: stringValue(option.tableName) ?? null,
|
|
309
312
|
campaignStatus: stringValue(option.campaignStatus) ?? null,
|
|
313
|
+
classification: campaignClassificationValue(option.classification),
|
|
310
314
|
supportsSelectedLane: option.supportsSelectedLane === true,
|
|
311
315
|
rankSignals: stringArray(option.rankSignals),
|
|
312
316
|
rankReason: stringValue(option.rankReason) ?? "",
|
|
@@ -351,6 +355,11 @@ function sanitizePaidInmail(value) {
|
|
|
351
355
|
tableId: stringValue(value.tableId) ?? null,
|
|
352
356
|
tableName: stringValue(value.tableName) ?? null,
|
|
353
357
|
columnId: stringValue(value.columnId) ?? null,
|
|
358
|
+
maxStalenessSeconds: numberValue(value.maxStalenessSeconds),
|
|
359
|
+
campaignClassification: campaignClassificationValue(value.campaignClassification) ?? null,
|
|
360
|
+
connectionFallbackAvailable: booleanValue(value.connectionFallbackAvailable) ?? false,
|
|
361
|
+
connectionFallbackCampaignId: stringValue(value.connectionFallbackCampaignId) ?? null,
|
|
362
|
+
connectionFallbackCampaignName: stringValue(value.connectionFallbackCampaignName) ?? null,
|
|
354
363
|
suggestedThreshold: typeof value.suggestedThreshold === "number"
|
|
355
364
|
? value.suggestedThreshold
|
|
356
365
|
: null,
|
|
@@ -622,7 +631,7 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
622
631
|
export const refillTargetPlanToolDefinitions = [
|
|
623
632
|
{
|
|
624
633
|
name: "get_refill_target_plan",
|
|
625
|
-
description: "read-only refill target planner to call before any refill mutation. It identifies eligible senders and infers one implicit refill lane per sender
|
|
634
|
+
description: "read-only refill target planner to call before any refill mutation. It identifies eligible senders and infers one implicit refill lane per sender. For unified Sales Nav cascade campaigns, the public refill lane is the paid-InMail cascade target (send_inmail_closed) with campaign classification sales_nav_cascade; the same campaign can still route prospects to Open InMail or connection-request fallback based on row eligibility and fresh paid credits >= 5. Non-cascade lanes remain connection invites or paid InMails, chosen from future scheduled, recent scheduled, and ready-to-schedule evidence in active campaign-backed sequence campaigns before falling back to campaign sequences. DMs remain follow-up sequence actions, not refill target lanes. By default the planner computes the scheduler-forward 48-hour target window, selected sender-local days whose sending windows overlap that window, gross target, actual sent coverage, scheduler-owned scheduled coverage, projected coverage (sent + scheduled), ready buffer, remaining projected gap, paid InMail credit/threshold/freshness feasibility, same-campaign connection fallback availability, bounded action candidates, and targetRevision drift proof. This tool does not create rows, import leads, prepare messages, approve messages, does not schedule sends, start campaigns, lower paid InMail thresholds, create campaigns, launch, spend InMail credits, or write scheduler fields. Complete projected targets no-op without approval; loaded, awaiting scheduler targets use read-only wait/reread until projected coverage fills.",
|
|
626
635
|
inputSchema: {
|
|
627
636
|
type: "object",
|
|
628
637
|
properties: {
|
|
@@ -676,7 +685,7 @@ export const refillTargetPlanToolDefinitions = [
|
|
|
676
685
|
type: "string",
|
|
677
686
|
enum: ["send_invite", "send_inmail_closed"],
|
|
678
687
|
},
|
|
679
|
-
description: "Optional refill target lane override. Supported refill lanes are connection invites (send_invite) and paid InMails (send_inmail_closed) only. If omitted, the planner infers one per-sender lane from active campaign future scheduled, recent scheduled, and ready-to-schedule evidence before falling back to selected active campaign sequences.",
|
|
688
|
+
description: "Optional refill target lane override. Supported public refill lanes are connection invites (send_invite) and paid InMails / unified Sales Nav cascades (send_inmail_closed) only. If omitted, the planner infers one per-sender lane from active campaign future scheduled, recent scheduled, and ready-to-schedule evidence before falling back to selected active campaign sequences.",
|
|
680
689
|
},
|
|
681
690
|
approvalMode: {
|
|
682
691
|
type: "string",
|
package/dist/tools/sequencer.js
CHANGED
|
@@ -65,13 +65,12 @@ export const sequencerToolDefinitions = [
|
|
|
65
65
|
name: "attach_recommended_sequence",
|
|
66
66
|
description: "Attach the tier-recommended sequence template to a campaign. " +
|
|
67
67
|
"Delegates template selection to the backend's existing tier-aware selector " +
|
|
68
|
-
"(selectTemplateForTiers), which picks
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
68
|
+
"(selectTemplateForTiers), which picks the unified Sales Nav cascade for Sales Nav/Recruiter senders: " +
|
|
69
|
+
"Open Profile check -> Open InMail, otherwise fresh paid credits >= 5 -> paid InMail, otherwise same-campaign connection request -> accepted wait -> DM. " +
|
|
70
|
+
"Basic, Premium, or mixed sender sets still get `INVITE -> accepted -> DM`. " +
|
|
71
|
+
"This is the default Sales Nav product path; use attach_sequence directly only when the caller needs a custom non-recommended cadence. " +
|
|
72
72
|
"Prefer this over attach_sequence during the create-campaign-v2 autonomous tail — " +
|
|
73
|
-
"it removes the risk of hand-authoring an invalid template mid-long-context.
|
|
74
|
-
"Use attach_sequence directly only when the caller needs a custom non-recommended cadence.",
|
|
73
|
+
"it removes the risk of hand-authoring an invalid template mid-long-context.",
|
|
75
74
|
inputSchema: {
|
|
76
75
|
type: "object",
|
|
77
76
|
properties: {
|
package/package.json
CHANGED
|
@@ -58,10 +58,12 @@ currentStep: "sequence" })` to attach the sender via the v3 senders route and
|
|
|
58
58
|
move the watched app to Sequence.
|
|
59
59
|
8. Call `attach_recommended_sequence({ campaignId, currentStep: "send" })` to
|
|
60
60
|
bind the tier-aware recommended sequence. Explain the sequence choice in the
|
|
61
|
-
handoff: Sales Nav/Recruiter senders get the Sales Nav
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
handoff: Sales Nav/Recruiter senders get the unified Sales Nav cascade
|
|
62
|
+
(Open Profile -> Open InMail, otherwise fresh paid credits >= 5 -> paid
|
|
63
|
+
InMail, otherwise connection request -> accepted wait -> DM), while
|
|
64
|
+
Basic/Premium/mixed senders get the Premium invite-to-DM strategy. The manual
|
|
65
|
+
Paid InMail Campaign remains an explicit custom template, not the normal
|
|
66
|
+
recommended path. If that response does not persist `currentStep:
|
|
65
67
|
"send"`, call `update_campaign({ campaignId, currentStep: "send" })`.
|
|
66
68
|
Then reread `get_campaign({ campaignId })` and `list_tables()` and verify the
|
|
67
69
|
current `workflowTableId` has `hasSequence:true`. A campaign-level
|
|
@@ -69,9 +71,9 @@ currentStep: "sequence" })` to attach the sender via the v3 senders route and
|
|
|
69
71
|
copy step has moved the campaign to a new current campaign table while an old
|
|
70
72
|
stale shell table still owns the sequence columns. In that stale-shell case,
|
|
71
73
|
either repair only the current workflowTableId with `attach_sequence` using
|
|
72
|
-
the same
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
the same product template the recommended selector would have chosen, or stop
|
|
75
|
+
and report the stale-shell sequence blocker if replacement was not explicitly
|
|
76
|
+
approved. Do not substitute the manual Paid InMail Campaign for this repair.
|
|
75
77
|
9. Surface the `handoff.orientation` string from `auto-execute.yaml` and
|
|
76
78
|
summarize the visible Settings/Sequence/Send state without repeating the
|
|
77
79
|
watch URL.
|
|
@@ -86,9 +86,9 @@ only when the caller explicitly needs a custom non-recommended cadence, or when
|
|
|
86
86
|
you have just reread the campaign after `confirm_lead_list` and proved the
|
|
87
87
|
current campaign table has no sequence while an older stale shell table is
|
|
88
88
|
causing campaign-level `SEQUENCE_EXISTS`. In that stale-shell repair case, use
|
|
89
|
-
`attach_sequence` on the current workflowTableId with the same
|
|
90
|
-
|
|
91
|
-
repair a table other than the current workflowTableId.
|
|
89
|
+
`attach_sequence` on the current workflowTableId with the same product template
|
|
90
|
+
the backend would select; do not substitute the manual Paid InMail Campaign and
|
|
91
|
+
never repair a table other than the current workflowTableId.
|
|
92
92
|
|
|
93
93
|
Hard gates — if you find yourself about to violate any of these, stop
|
|
94
94
|
first:
|
|
@@ -533,10 +533,12 @@ Shape:
|
|
|
533
533
|
route and describe the Sequence view.
|
|
534
534
|
8. Call `attach_recommended_sequence({ campaignId, currentStep: "send" })`
|
|
535
535
|
(bind the tier-recommended sequence to the campaign). Explain the sequence
|
|
536
|
-
choice plainly: Sales Nav/Recruiter senders get the Sales Nav
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
536
|
+
choice plainly: Sales Nav/Recruiter senders get the unified Sales Nav
|
|
537
|
+
cascade (Open Profile -> Open InMail, otherwise fresh paid credits >= 5 ->
|
|
538
|
+
paid InMail, otherwise connection request -> accepted wait -> DM), while
|
|
539
|
+
Basic/Premium/mixed senders get the Premium invite-to-DM strategy. The manual
|
|
540
|
+
Paid InMail Campaign remains an explicit custom template, not the normal
|
|
541
|
+
recommended path. If the tool response
|
|
540
542
|
does not persist `currentStep: "send"`, call
|
|
541
543
|
`update_campaign({ campaignId, currentStep: "send" })`.
|
|
542
544
|
Then reread `get_campaign({ campaignId })` and `list_tables()` and verify the
|
|
@@ -544,9 +546,10 @@ Shape:
|
|
|
544
546
|
`attach_recommended_sequence` returned `SEQUENCE_EXISTS` but the current table
|
|
545
547
|
still has `hasSequence:false`, do not continue to launch handoff from that
|
|
546
548
|
stale shell proof. Repair only the current workflowTableId with
|
|
547
|
-
`attach_sequence` using the same
|
|
548
|
-
|
|
549
|
-
|
|
549
|
+
`attach_sequence` using the same product template the recommended selector
|
|
550
|
+
would have chosen, or stop and report the stale-shell sequence blocker if
|
|
551
|
+
replacement was not explicitly approved. Do not substitute the manual Paid
|
|
552
|
+
InMail Campaign for this repair.
|
|
550
553
|
9. Surface the `handoff.orientation` string from `auto-execute.yaml` without
|
|
551
554
|
repeating the watch URL.
|
|
552
555
|
10. Ask the final launch greenlight with the structured question function:
|
|
@@ -1515,7 +1515,11 @@ Message, and verify current-revision sample messages before final completion.
|
|
|
1515
1515
|
- DM lanes: add a `Delivery format:` line — either `multiline (each paragraph sends as its own DM message)` or `single message`. When multiline, the template's blank-line paragraphs ARE the message boundaries — write each one as a standalone typed message.
|
|
1516
1516
|
- **InMail lanes can never be multiline**: an InMail is one message and the recipient must reply before anything else can be sent. InMail-bound templates must read as one cohesive message — declare `Delivery format: single message (InMail — no follow-up until reply)` and never structure the copy to depend on multi-message pacing.
|
|
1517
1517
|
|
|
1518
|
-
- The sequence is auto-selected by sender tier; do not hand-author sequence
|
|
1518
|
+
- The sequence is auto-selected by sender tier; do not hand-author sequence
|
|
1519
|
+
templates here. Sales Nav/Recruiter senders may receive the unified Sales
|
|
1520
|
+
Nav cascade through `attach_recommended_sequence`; attaching it does not
|
|
1521
|
+
spend paid InMail credits by itself. Do not substitute the manual Paid
|
|
1522
|
+
InMail Campaign template.
|
|
1519
1523
|
3. **Customer-Visible Completion Contract**: a named evergreen lane that appears
|
|
1520
1524
|
as a campaign card or campaign-backed table is not done when the shell exists.
|
|
1521
1525
|
It is done only when the customer can open the campaign and land on final
|
|
@@ -1592,7 +1596,7 @@ Message, and verify current-revision sample messages before final completion.
|
|
|
1592
1596
|
completion, approve exactly one quality-valid generated row. If one or more
|
|
1593
1597
|
rows are already approved, do not add more approvals during evergreen
|
|
1594
1598
|
completion. Never broad approve all rows.
|
|
1595
|
-
- The recommended
|
|
1599
|
+
- The recommended tier-aware sequence is attached to the current campaign
|
|
1596
1600
|
table, and the watched campaign is on Send. Use
|
|
1597
1601
|
`attach_recommended_sequence({ campaignId, currentStep:"send" })` when a
|
|
1598
1602
|
safe attach is needed. After `confirm_lead_list` or any source-list copy,
|
|
@@ -1602,10 +1606,11 @@ Message, and verify current-revision sample messages before final completion.
|
|
|
1602
1606
|
before reporting completion. If a stale shell table has sequence columns but
|
|
1603
1607
|
the current campaign table does not, `SEQUENCE_EXISTS` is not enough:
|
|
1604
1608
|
record the stale shell table id, then repair the current workflowTableId
|
|
1605
|
-
with `attach_sequence` using the same
|
|
1606
|
-
`attach_recommended_sequence` would have selected.
|
|
1607
|
-
|
|
1608
|
-
the current lane packet explicitly allowed sequence
|
|
1609
|
+
with `attach_sequence` using the same product template
|
|
1610
|
+
`attach_recommended_sequence` would have selected. Do not substitute the
|
|
1611
|
+
manual Paid InMail Campaign, and never attach/replace sequence outside the
|
|
1612
|
+
current table unless the current lane packet explicitly allowed sequence
|
|
1613
|
+
repair.
|
|
1609
1614
|
- If the current campaign table is still `DRAFT` after sequence/readiness
|
|
1610
1615
|
proof, call `pause_campaign({ campaignId })` and reread. `pause_campaign`
|
|
1611
1616
|
is the product-native review-state transition; it is not a launch and does
|
|
@@ -131,18 +131,21 @@ eligible senders, selected sender-local days, gross target, actual sent
|
|
|
131
131
|
coverage, scheduler-owned scheduled coverage across active enrolled campaigns,
|
|
132
132
|
projected coverage (`sent + scheduled`), inferred per-sender send lane/action
|
|
133
133
|
selections, ready buffer, remaining projected gap, paid-InMail credit/threshold
|
|
134
|
-
feasibility, `targetShapeRevision`, and `stateRevision`. Refill target lanes
|
|
135
|
-
|
|
136
|
-
(`send_inmail_closed`),
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
`
|
|
144
|
-
|
|
145
|
-
|
|
134
|
+
feasibility, `targetShapeRevision`, and `stateRevision`. Refill target lanes are
|
|
135
|
+
connection invites (`send_invite`), standalone paid InMails
|
|
136
|
+
(`send_inmail_closed`), or unified Sales Nav cascades represented publicly as
|
|
137
|
+
`send_inmail_closed` with `campaignClassification:"sales_nav_cascade"`. For a
|
|
138
|
+
Sales Nav cascade, refill the selected campaign first; its sequence can route
|
|
139
|
+
prospects to Open InMail, paid InMail while fresh credits are >= 5, or
|
|
140
|
+
same-campaign connection fallback without asking for separate open/paid/
|
|
141
|
+
connection campaigns. DMs (`send_dm`) are follow-up actions, not refill horizon
|
|
142
|
+
target capacity, and must not be counted as refill sent, scheduled, or ready
|
|
143
|
+
coverage. When `actionTypes` are omitted, trust the target plan's inferred lane
|
|
144
|
+
rather than asking which campaign class to fill. If a stale target plan selects
|
|
145
|
+
`send_dm` or `send_inmail_open` as the lane, stop and re-plan with the current
|
|
146
|
+
planner before mutation.
|
|
147
|
+
Short form: trust the target plan's inferred lane when it is a connection invite,
|
|
148
|
+
paid-InMail refill lane, or unified Sales Nav cascade.
|
|
146
149
|
|
|
147
150
|
Structured planner packet:
|
|
148
151
|
|
|
@@ -168,7 +171,8 @@ facts, it refreshes each selected sender at most once, reruns
|
|
|
168
171
|
`get_refill_target_plan`, and returns the post-refresh `targetPlan` before
|
|
169
172
|
choosing the next prep/approval/start action. If fresh facts are still below
|
|
170
173
|
threshold, below-threshold paid-InMail facts fall back to an existing connection
|
|
171
|
-
lane or a manual
|
|
174
|
+
lane, the same Sales Nav cascade campaign's connection branch, or a manual
|
|
175
|
+
continuation.
|
|
172
176
|
|
|
173
177
|
Compact refill lessons: sender-level target plan is final truth; trust
|
|
174
178
|
`schedulerGate.sendable` and scheduler gate blockers, not raw
|
|
@@ -201,8 +205,8 @@ post-refresh `targetPlan`. Trust `refill_sends.autoPaidInmailRefresh`: it should
|
|
|
201
205
|
show the exact sender ids refreshed once, sender-credit-cache write receipts,
|
|
202
206
|
and a returned post-refresh `targetPlan`. Continue from that post-refresh packet.
|
|
203
207
|
If paid InMail is below threshold after the fresh credit read, report the exact
|
|
204
|
-
campaign/table/column threshold action or connection fallback;
|
|
205
|
-
lower paid-InMail thresholds or create campaigns.
|
|
208
|
+
campaign/table/column threshold action or same-campaign connection fallback;
|
|
209
|
+
`--yolo` does not lower paid-InMail thresholds or create campaigns.
|
|
206
210
|
|
|
207
211
|
If the plain route's managed waterfall targets are stale, for example skipped
|
|
208
212
|
targets show archived/completed shared slots or the returned targets do not cover
|
|
@@ -75,20 +75,23 @@ in-progress wait state, not a reason to mark the goal complete or blocked.
|
|
|
75
75
|
campaigns, projected coverage (`sent + scheduled`), ready buffer, remaining
|
|
76
76
|
projected gap, paid-InMail credit/threshold feasibility, bounded action
|
|
77
77
|
candidates, `targetShapeRevision`, and `stateRevision`.
|
|
78
|
-
Refill target lanes are
|
|
79
|
-
(`
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
Refill target lanes are connection invites (`send_invite`), standalone paid
|
|
79
|
+
InMails (`send_inmail_closed`), or unified Sales Nav cascades represented
|
|
80
|
+
publicly as `send_inmail_closed` with
|
|
81
|
+
`campaignClassification:"sales_nav_cascade"`. For a Sales Nav cascade, fill
|
|
82
|
+
the selected campaign first; its sequence can route prospects to Open InMail,
|
|
83
|
+
paid InMail while fresh credits are >= 5, or same-campaign connection
|
|
84
|
+
fallback without asking for separate open/paid/connection campaigns. DMs
|
|
85
|
+
(`send_dm`) are follow-up actions, not refill horizon capacity. Do not count
|
|
86
|
+
their sent, scheduled, or ready cells when deciding whether a sender needs
|
|
87
|
+
refill. When `actionTypes` are omitted, trust the target plan's inferred lane;
|
|
88
|
+
do not ask the operator which campaign class to fill after the plan has
|
|
89
|
+
inferred that from active campaign future scheduled, recent scheduled, and
|
|
90
|
+
ready evidence. If a stale planner/tool response selects `send_dm` or
|
|
91
|
+
`send_inmail_open`, treat that as unsupported stale refill state and re-plan
|
|
92
|
+
with a current planner before any mutation.
|
|
93
|
+
Short form: trust the target plan's inferred lane when it is a connection
|
|
94
|
+
invite, paid-InMail refill lane, or unified Sales Nav cascade.
|
|
92
95
|
Structured planner packet:
|
|
93
96
|
- `target.eligibleSenderLedger` is the public sender eligibility ledger.
|
|
94
97
|
- `target.senderRefillPlans[]` is the canonical sender-level packet; read and
|
|
@@ -112,7 +115,8 @@ in-progress wait state, not a reason to mark the goal complete or blocked.
|
|
|
112
115
|
`get_refill_target_plan`, and returns the post-refresh `targetPlan` before
|
|
113
116
|
choosing the next prep/approval/start action. If fresh facts are still below
|
|
114
117
|
threshold, below-threshold paid-InMail facts fall back to an existing
|
|
115
|
-
connection lane
|
|
118
|
+
connection lane, the same Sales Nav cascade campaign's connection branch, or
|
|
119
|
+
a manual continuation.
|
|
116
120
|
Compact refill lessons: sender-level target plan is final truth; trust
|
|
117
121
|
`schedulerGate.sendable` and scheduler gate blockers, not raw
|
|
118
122
|
`unipileAccountStatus` labels alone; use compact prep status checks for
|
|
@@ -147,7 +151,7 @@ in-progress wait state, not a reason to mark the goal complete or blocked.
|
|
|
147
151
|
should show the exact sender ids refreshed once, sender-credit-cache write
|
|
148
152
|
receipts, and a returned post-refresh `targetPlan`. Continue from that
|
|
149
153
|
post-refresh packet. If paid InMail is below threshold after the fresh credit read, report
|
|
150
|
-
the exact campaign/table/column threshold action or
|
|
154
|
+
the exact campaign/table/column threshold action or same-campaign
|
|
151
155
|
connection fallback; `--yolo` does not lower paid-InMail thresholds or create campaigns.
|
|
152
156
|
2. Call `resolve_campaign_fill_route`. Use `intent:"plain"` for generic
|
|
153
157
|
fill/load language, `intent:"active"` only when the user explicitly narrowed
|
|
@@ -282,10 +286,13 @@ dashboard-active campaign-backed sequence evidence:
|
|
|
282
286
|
|
|
283
287
|
1. Prefer the sender's most recent future scheduled refill action, then the most
|
|
284
288
|
recent recent scheduled refill action, then ready refill rows.
|
|
285
|
-
2. Refill actions are
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
+
2. Refill actions are `send_invite` or `send_inmail_closed`; unified Sales Nav
|
|
290
|
+
cascades also appear as `send_inmail_closed` with
|
|
291
|
+
`campaignClassification:"sales_nav_cascade"`.
|
|
292
|
+
3. If the most recent active campaign evidence is DM, ignore it for refill lane
|
|
293
|
+
selection. If open InMail evidence belongs to a unified Sales Nav cascade,
|
|
294
|
+
keep the cascade campaign as the selected `send_inmail_closed` target; do not
|
|
295
|
+
create side open/paid/connection campaigns.
|
|
289
296
|
4. If no connection/paid-InMail evidence exists, fall back to the selected
|
|
290
297
|
active campaign sequence, still choosing only one of `send_invite` or
|
|
291
298
|
`send_inmail_closed` for that sender.
|
|
@@ -418,8 +425,15 @@ search_signals({ campaignOfferId, currentStep:"signal-discovery", confirmed:true
|
|
|
418
425
|
```
|
|
419
426
|
|
|
420
427
|
Use `select_promising_posts` before importing. For a fresh source refill, prefer
|
|
421
|
-
`selectionMode:"replace"` and `scrapePlanMode:"
|
|
422
|
-
|
|
428
|
+
`selectionMode:"replace"` and `scrapePlanMode:"all-selected"`, then call
|
|
429
|
+
`import_leads` without `targetEngagerCount` or `maxPostsToScrape` so the scrape
|
|
430
|
+
uses the selected posts' max available engagers within backend provider caps.
|
|
431
|
+
Do not invent, lower, or pass a people-to-check target for refill imports unless
|
|
432
|
+
Christian explicitly supplied that target or post cap in the current request or
|
|
433
|
+
approval packet. If Christian did supply an explicit `targetEngagerCount` or
|
|
434
|
+
`maxPostsToScrape`, use `scrapePlanMode:"capacity-target"` and respect the
|
|
435
|
+
under-capacity refusal: ask for approval to expand the selected post set or
|
|
436
|
+
switch source instead of scraping a target-mismatched set.
|
|
423
437
|
|
|
424
438
|
If `import_leads` returns `reusedExistingSourceList` but the user explicitly
|
|
425
439
|
approved a different selected-post scrape, retry `import_leads` with the existing `sourceLeadListId` from that response or from refill state. For Signal
|
|
@@ -77,7 +77,7 @@ This is the canonical MCP workflow for creating sequencer tables. Prefer this ov
|
|
|
77
77
|
- `INVITE` only
|
|
78
78
|
- `INVITE -> accepted -> DM`
|
|
79
79
|
- `COMMENT` only
|
|
80
|
-
-
|
|
80
|
+
- Sales Nav unified cascade: Open Profile -> Open InMail, otherwise paid-credit check -> Paid InMail or connection fallback
|
|
81
81
|
|
|
82
82
|
## Output
|
|
83
83
|
|