@sellable/mcp 0.1.909 → 0.1.911

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.
@@ -35,7 +35,7 @@ function sanitizeForCampaign(text) {
35
35
  export const directCampaignToolDefinitions = [
36
36
  {
37
37
  name: "add_to_inmail_campaign",
38
- description: "Add leads to a sender's InMail direct campaign table (dedupes by default). Auto-creates the table if missing.",
38
+ description: "Add leads with caller-authored Subject and Message copy to a sender's direct InMail execution queue. This is the canonical one-off InMail path when recipients and exact copy are already known; it auto-creates the correctly typed table and dedupes by default. Do not create a normal campaign or a Generate Message column for this shape.",
39
39
  inputSchema: {
40
40
  type: "object",
41
41
  properties: {
@@ -63,7 +63,7 @@ export const directCampaignToolDefinitions = [
63
63
  },
64
64
  {
65
65
  name: "add_to_connection_campaign",
66
- description: "Add leads to a sender's Connection direct campaign table (dedupes by default). Auto-creates the table if missing.",
66
+ description: "Add leads with caller-authored Message copy to a sender's direct Connection execution queue. This is the canonical one-off connection-plus-DM path when recipients and exact copy are already known; it auto-creates the correctly typed table and dedupes by default.",
67
67
  inputSchema: {
68
68
  type: "object",
69
69
  properties: {
@@ -158,7 +158,7 @@ export const directCampaignToolDefinitions = [
158
158
  },
159
159
  {
160
160
  name: "start_direct_campaign",
161
- description: "Start a direct campaign for a sender (sets status to ACTIVE).",
161
+ description: "Start the sender's canonical direct campaign after its rows/copy are reviewed and the user explicitly approves launch (sets status to ACTIVE). Use the same type passed to the add tool. Do not use start_campaign for a table created by add_to_inmail_campaign or add_to_connection_campaign.",
162
162
  inputSchema: {
163
163
  type: "object",
164
164
  properties: {
@@ -200,7 +200,7 @@ export const directCampaignToolDefinitions = [
200
200
  },
201
201
  {
202
202
  name: "get_or_create_direct_campaign_table",
203
- description: "Get or create the direct campaign workflow table for a sender and type (comment_campaign, inmail_campaign, connection_campaign, post_campaign). Returns the tableId and whether it was created.",
203
+ description: "Inspect or explicitly create the canonical direct campaign workflow table for a sender and type (comment_campaign, inmail_campaign, connection_campaign, post_campaign). Usually call the matching add_to_* tool instead because it creates the table and rows together. Returns the tableId and whether it was created.",
204
204
  inputSchema: {
205
205
  type: "object",
206
206
  properties: {
@@ -73,7 +73,7 @@ export const onDemandToolDefinitions = [
73
73
  },
74
74
  {
75
75
  name: "create_on_demand_campaign",
76
- description: "Create a lightweight CampaignOffer + workflow table, set senders, and initialize sequence columns. Auto-selects template by sender tiers unless provided.\n\n" +
76
+ description: "Create a lightweight general CampaignOffer + workflow table, set senders, and initialize sequence columns. This is not the caller-authored direct InMail path: when exact recipients, Subject, and Message are already supplied, use add_to_inmail_campaign and start_direct_campaign instead. Auto-selects template by sender tiers unless provided.\n\n" +
77
77
  "INPUTS: Pass EITHER clientProspectId (existing prospect ID) OR senderLinkedinUrl (sender's LinkedIn profile URL). One is required.",
78
78
  inputSchema: {
79
79
  type: "object",
@@ -149,7 +149,7 @@ export const onDemandToolDefinitions = [
149
149
  },
150
150
  {
151
151
  name: "start_on_demand_campaign",
152
- description: "Start an on-demand campaign table (set campaignStatus ACTIVE).",
152
+ description: "Start a general on-demand campaign table (set campaignStatus ACTIVE). Do not use this for a typed direct InMail/Connection table; use start_direct_campaign with the matching type.",
153
153
  inputSchema: {
154
154
  type: "object",
155
155
  properties: {
@@ -1942,9 +1942,45 @@ export async function broadenSignalSearch(action, workspaceId) {
1942
1942
  name: campaign.name ?? "",
1943
1943
  },
1944
1944
  }, requestOptions);
1945
+ const estimates = estimateResponse.estimates ?? [];
1946
+ const estimatedPostIds = new Set(estimates
1947
+ .map((estimate) => stringValue(estimate.postId))
1948
+ .filter((postId) => Boolean(postId)));
1949
+ const reportedUnavailablePosts = (estimateResponse.unavailablePosts ?? []).flatMap((entry) => {
1950
+ const postId = stringValue(entry.postId);
1951
+ if (!postId)
1952
+ return [];
1953
+ return [
1954
+ {
1955
+ postId,
1956
+ reason: stringValue(entry.reason) ?? "evaluation_unavailable",
1957
+ },
1958
+ ];
1959
+ });
1960
+ const reportedUnavailablePostIds = new Set(reportedUnavailablePosts.map((entry) => entry.postId));
1961
+ const missingEstimatePosts = candidates.flatMap((candidate) => estimatedPostIds.has(candidate.postId) ||
1962
+ reportedUnavailablePostIds.has(candidate.postId)
1963
+ ? []
1964
+ : [{ postId: candidate.postId, reason: "missing_estimate" }]);
1965
+ const unavailablePosts = [
1966
+ ...reportedUnavailablePosts,
1967
+ ...missingEstimatePosts,
1968
+ ];
1969
+ if (unavailablePosts.length > 0) {
1970
+ return {
1971
+ candidatePostCount: candidates.length,
1972
+ engagerSum: 0,
1973
+ observedPassRate: 0,
1974
+ projectedYield: 0,
1975
+ floor: Math.min(SIGNAL_YIELD_FLOOR_CAP, Math.max(1, remainingPrepGap)),
1976
+ scrapePostIds: [],
1977
+ carriedPostIds: [],
1978
+ evaluationUnavailable: { posts: unavailablePosts },
1979
+ };
1980
+ }
1945
1981
  const sampled = selectRefillV3SignalPostsFromSamples({
1946
1982
  candidates,
1947
- estimates: estimateResponse.estimates ?? [],
1983
+ estimates,
1948
1984
  maxPosts: scrapeCap,
1949
1985
  });
1950
1986
  postYieldSamples = sampled.samples;
@@ -1970,6 +2006,34 @@ export async function broadenSignalSearch(action, workspaceId) {
1970
2006
  remainingPrepGap,
1971
2007
  maxPosts: maxPostsToScrape,
1972
2008
  });
2009
+ const evaluationUnavailable = "evaluationUnavailable" in yieldProjection
2010
+ ? recordValue(yieldProjection.evaluationUnavailable)
2011
+ : null;
2012
+ if (evaluationUnavailable) {
2013
+ const runScopedCleanup = await cleanupRunScopedDerivedTabs();
2014
+ return {
2015
+ status: "refused",
2016
+ refusalReason: "signal_post_icp_evaluation_unavailable: one or more candidate posts lacked a complete headline-ICP evaluation; no qualification or exhaustion receipt was recorded",
2017
+ result: {
2018
+ class: "source_retryable",
2019
+ code: "signal_post_icp_evaluation_unavailable",
2020
+ retryable: true,
2021
+ universeExhausted: false,
2022
+ campaignOfferId,
2023
+ keywordSource: effectiveKeywordSource,
2024
+ keywordsTried: searchKeywords,
2025
+ usedKeywords,
2026
+ searchedKeywords: negativeList,
2027
+ excludedPostIds,
2028
+ candidatePostCount: yieldProjection.candidatePostCount,
2029
+ unavailablePosts: Array.isArray(evaluationUnavailable.posts)
2030
+ ? evaluationUnavailable.posts
2031
+ : [],
2032
+ searchSummary,
2033
+ runScopedCleanup,
2034
+ },
2035
+ };
2036
+ }
1973
2037
  const noQualifyingV3Post = isRefillV3SinglePassAction(action) &&
1974
2038
  yieldProjection.scrapePostIds.length === 0;
1975
2039
  if (noQualifyingV3Post ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.909",
3
+ "version": "0.1.911",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",
@@ -77,6 +77,10 @@ allowed-tools:
77
77
  - mcp__sellable__attach_sequence
78
78
  - mcp__sellable__attach_recommended_sequence
79
79
  - mcp__sellable__start_campaign
80
+ - mcp__sellable__add_to_inmail_campaign
81
+ - mcp__sellable__add_to_connection_campaign
82
+ - mcp__sellable__get_or_create_direct_campaign_table
83
+ - mcp__sellable__start_direct_campaign
80
84
  ---
81
85
 
82
86
  # Sellable Create Campaign
@@ -86,6 +90,30 @@ It bootstraps auth and host capabilities, then loads the internal
86
90
  the internal campaign workflow prompt and `core/flow.v2.json` through MCP. Keep
87
91
  this wrapper thin; the packaged workflow is the operational source of truth.
88
92
 
93
+ ## Direct Manual-Copy Campaign Routing
94
+
95
+ Route a bounded one-off request to the direct campaign tools when the user
96
+ already supplies the recipients and exact message copy. For InMail, require a
97
+ LinkedIn URL, Subject, and Message per row, select the intended sender, then use
98
+ `add_to_inmail_campaign`. For a connection-plus-DM request, use
99
+ `add_to_connection_campaign`. These tools create or reuse the canonical typed
100
+ execution-queue table; do not call `create_campaign`,
101
+ `create_on_demand_campaign`, or a connection helper first.
102
+
103
+ Do not create a normal Campaign Builder Generate Message column, rewrite the
104
+ approved manual copy, or repair the typed direct table merely because its
105
+ message is stored in a plain `Message` text column. That is the owned schema for
106
+ `inmail_campaign` and `connection_campaign` tables.
107
+
108
+ Show the bounded recipients and exact copy for review before adding rows. Never
109
+ call `start_direct_campaign` until the user explicitly approves launch. When
110
+ approved, start the same direct type used by the add tool; do not call
111
+ `start_campaign` for that table.
112
+
113
+ Billing diagnostics are not launch authority. Honor a billing error returned by
114
+ the actual add/start mutation, but never interpret `billing_required`
115
+ diagnostics as an enforced block when the billing rollout gate is disabled.
116
+
89
117
  ## Inbox Reply Tool Boundary
90
118
 
91
119
  Inbox reply tools are outside campaign creation. If a user asks to search,
@@ -291,6 +319,7 @@ only from a source that can describe this turn:
291
319
  context. Do not infer an ID from the friendly name, do not infer effort from
292
320
  `alwaysThinkingEnabled`, and do not show this self-report to the user during
293
321
  normal campaign setup.
322
+
294
323
  - Do not run a nested `claude -p`, inspect `~/.claude/settings.json`, or read
295
324
  Claude CLI defaults as proof of the user's active Claude Code session. Those
296
325
  checks can validate a new child session or saved defaults, but not this