@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.
@@ -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;
@@ -1127,15 +1127,15 @@ function normalizeEngagementCount(value) {
1127
1127
  : 0;
1128
1128
  }
1129
1129
  function buildSignalDiscoverySourceRecommendation({ selectedPosts, targetEngagerCount, maxPostsToScrape, }) {
1130
- const { targetGoodFitLeads, defaultFitRate, minPlanningFitRate, sourceCandidateTarget, reviewBatchSize, } = getSignalDiscoverySourcePlanDefaults();
1131
- const effectiveTargetEngagerCount = normalizePositiveInteger(targetEngagerCount) ?? sourceCandidateTarget;
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: effectiveTargetEngagerCount,
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 = targetEngagerCount && effectiveTargetEngagerCount > 0
1147
- ? Math.min(1, targetGoodFitLeads / effectiveTargetEngagerCount)
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 = targetEngagerCount && effectiveTargetEngagerCount > 0
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:** about ${effectiveTargetEngagerCount.toLocaleString("en-US")} people who reacted or commented<br>
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: effectiveTargetEngagerCount,
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. Default planning target is about 300 likely prospects at a 20% starting pass assumption, or about 1500 people to check. Use real sample math when available: 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. Limits selected posts before starting scrape, with a backend hard cap of 10 posts.",
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, uses the default Signal Discovery source target.",
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 defaultSignalTargetEngagers = getSignalDiscoverySourcePlanDefaults().sourceCandidateTarget;
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 only connection invites (send_invite) or paid InMails (send_inmail_closed). Do not count send_dm or send_inmail_open as horizon target capacity; they may exist in sequences, but the planner must pick the refill lane from the most recent active campaign-backed send evidence for each sender.",
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: 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 and open-profile InMails are 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 feasibility, 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.",
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",
@@ -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 `If Open Profile -> INMAIL_OPEN, else INVITE` " +
69
- "for Sales Nav/Recruiter senders and `INVITE -> accepted -> DM` for Basic, Premium, or mixed sender sets. " +
70
- "Paid InMail Campaign is not auto-selected because it can spend InMail credits; " +
71
- "use attach_sequence with an explicit paid-InMail template only after the user opts in. " +
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.475",
3
+ "version": "0.1.476",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -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 Open Profile
62
- strategy, Basic/Premium/mixed senders get the Premium invite-to-DM strategy,
63
- and Paid InMail Campaign is only an explicit paid-InMail opt-in because it
64
- can spend InMail credits. If that response does not persist `currentStep:
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 non-paid product template the recommended selector would have chosen,
73
- or stop and report the stale-shell sequence blocker if replacement was not
74
- explicitly approved. Never choose a paid-InMail template for this repair.
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 non-paid product
90
- template the backend would select; never choose a paid-InMail template and never
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 Open Profile
537
- strategy, Basic/Premium/mixed senders get the Premium invite-to-DM strategy,
538
- and Paid InMail Campaign is available only as an explicit paid-InMail
539
- opt-in because it can spend InMail credits. If the tool response
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 non-paid product template the recommended
548
- selector would have chosen, or stop and report the stale-shell sequence
549
- blocker if replacement was not explicitly approved.
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 templates here. Never select a paid-InMail template.
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 non-paid sequence is attached to the current campaign
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 non-paid product template
1606
- `attach_recommended_sequence` would have selected. Never use a paid-InMail
1607
- template and never attach/replace sequence outside the current table unless
1608
- the current lane packet explicitly allowed sequence repair.
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
- are only connection invites (`send_invite`) or paid InMails
136
- (`send_inmail_closed`), chosen per sender from the most recent current
137
- dashboard-active campaign-backed send evidence. DMs (`send_dm`) and
138
- open-profile InMails (`send_inmail_open`) may exist in sequences, but they are
139
- not refill horizon target capacity and must not be counted as refill sent,
140
- scheduled, or ready coverage. When `actionTypes` are omitted, trust the target
141
- plan's inferred connection-or-paid-InMail lane rather than asking which of those
142
- two lanes to fill. If a stale target plan selects `send_dm` or
143
- `send_inmail_open`, stop and re-plan with the current planner before mutation.
144
- Short form: trust the target plan's inferred lane only when it is a
145
- connection-invite or paid-InMail refill lane.
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 continuation.
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; `--yolo` does not
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 binary per sender: connection invites
79
- (`send_invite`) or paid InMails (`send_inmail_closed`). DMs
80
- (`send_dm`) and open-profile InMails (`send_inmail_open`) can remain in the
81
- sequence, but they are follow-up actions, not refill horizon capacity. Do not
82
- count their sent, scheduled, or ready cells when deciding whether a sender
83
- needs refill. When `actionTypes` are omitted, trust the target plan's
84
- inferred connection-or-paid-InMail lane; do not ask the operator which of
85
- those two lanes to fill after the plan has inferred that from active
86
- campaign future scheduled, recent scheduled, and ready evidence. If a stale
87
- planner/tool response selects `send_dm` or `send_inmail_open`, treat that as
88
- unsupported stale refill state and re-plan with a current planner before any
89
- mutation.
90
- Short form: trust the target plan's inferred lane only when it is a
91
- connection-invite or paid-InMail refill lane.
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 or a manual continuation.
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 only `send_invite` and `send_inmail_closed`.
286
- 3. If the most recent active campaign evidence is DM or open InMail, ignore it
287
- for refill lane selection and use the nearest connection-invite or paid-InMail
288
- evidence instead.
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:"capacity-target"` with the
422
- approved `targetEngagerCount` and `maxPostsToScrape`. If the approved post set cannot cover the target, do not knowingly run an under-capacity scrape; ask for approval to expand the selected post set or switch source.
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
- - `If Open Profile -> INMAIL_OPEN, else INVITE`
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