@sellable/mcp 0.1.191 → 0.1.193

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.
@@ -0,0 +1,304 @@
1
+ import { getApi } from "../api.js";
2
+ import { resolveWaitTimeout } from "./readiness.js";
3
+ const DEFAULT_TIMEOUT_MS = 90000;
4
+ const DEFAULT_INTERVAL_MS = 2000;
5
+ function sleep(ms) {
6
+ return new Promise((resolve) => setTimeout(resolve, ms));
7
+ }
8
+ function stableHash(value) {
9
+ const text = JSON.stringify(value, Object.keys(value).sort());
10
+ let hash = 2166136261;
11
+ for (let index = 0; index < text.length; index += 1) {
12
+ hash ^= text.charCodeAt(index);
13
+ hash = Math.imul(hash, 16777619);
14
+ }
15
+ return (hash >>> 0).toString(16).padStart(8, "0");
16
+ }
17
+ async function postCampaignProcessing(body) {
18
+ const api = getApi();
19
+ return api.post("/api/v3/mcp/campaign-processing", body);
20
+ }
21
+ async function resolveSchema(input) {
22
+ return postCampaignProcessing({
23
+ action: "schema",
24
+ campaignId: input.campaignId,
25
+ tableId: input.tableId,
26
+ });
27
+ }
28
+ export const campaignProcessingToolDefinitions = [
29
+ {
30
+ name: "get_campaign_table_schema",
31
+ description: "Return compact semantic campaign-table schema: column roles, row count, review-batch metadata, and current approved brief hash. Use for debugging or recovery, not normal queueing.",
32
+ inputSchema: {
33
+ type: "object",
34
+ properties: {
35
+ campaignId: { type: "string" },
36
+ tableId: { type: "string" },
37
+ },
38
+ additionalProperties: false,
39
+ },
40
+ },
41
+ {
42
+ name: "select_campaign_cells",
43
+ description: "Dry-run semantic campaign-cell selection. Returns compact counts and a tiny sample, never bulky row data. Use before queue_campaign_cells only when debugging or explaining a recovery action.",
44
+ inputSchema: {
45
+ type: "object",
46
+ properties: {
47
+ campaignId: { type: "string" },
48
+ tableId: { type: "string" },
49
+ columnRole: {
50
+ type: "string",
51
+ enum: [
52
+ "enrich",
53
+ "passesRubric",
54
+ "icpScore",
55
+ "generateMessage",
56
+ "approved",
57
+ ],
58
+ },
59
+ rowSelector: {
60
+ type: "object",
61
+ properties: {
62
+ type: {
63
+ type: "string",
64
+ enum: [
65
+ "reviewBatch",
66
+ "rowIds",
67
+ "passedRows",
68
+ "needsGeneratedMessage",
69
+ "staleGeneratedMessages",
70
+ ],
71
+ },
72
+ basisHash: { type: "string" },
73
+ rowIds: { type: "array", items: { type: "string" } },
74
+ limit: { type: "number" },
75
+ },
76
+ required: ["type"],
77
+ additionalProperties: false,
78
+ },
79
+ limit: { type: "number" },
80
+ },
81
+ required: ["columnRole", "rowSelector"],
82
+ additionalProperties: false,
83
+ },
84
+ },
85
+ {
86
+ name: "queue_campaign_cells",
87
+ description: "Resolve and queue campaign cells by semantic role and row selector. Normal create-campaign tail should use this instead of fetching rows only to discover cell IDs. Use forceRerun:true for message-template revisions.",
88
+ inputSchema: {
89
+ type: "object",
90
+ properties: {
91
+ campaignId: { type: "string" },
92
+ tableId: { type: "string" },
93
+ columnRole: {
94
+ type: "string",
95
+ enum: ["enrich", "generateMessage", "approved"],
96
+ },
97
+ rowSelector: {
98
+ type: "object",
99
+ properties: {
100
+ type: {
101
+ type: "string",
102
+ enum: [
103
+ "reviewBatch",
104
+ "rowIds",
105
+ "passedRows",
106
+ "needsGeneratedMessage",
107
+ "staleGeneratedMessages",
108
+ ],
109
+ },
110
+ basisHash: { type: "string" },
111
+ rowIds: { type: "array", items: { type: "string" } },
112
+ limit: { type: "number" },
113
+ },
114
+ required: ["type"],
115
+ additionalProperties: false,
116
+ },
117
+ forceRerun: { type: "boolean" },
118
+ limit: { type: "number" },
119
+ reason: { type: "string" },
120
+ },
121
+ required: ["columnRole", "rowSelector"],
122
+ additionalProperties: false,
123
+ },
124
+ },
125
+ {
126
+ name: "wait_for_campaign_processing",
127
+ description: "Stats-only campaign processing wait. Use for fit/message readiness instead of wait_for_rubric_results when waiting on campaign-table processing. Generated messages are counted separately from rubric passes; current template revision is enforced through messageDraftBasis.approvedBriefHash.",
128
+ inputSchema: {
129
+ type: "object",
130
+ properties: {
131
+ campaignId: { type: "string" },
132
+ tableId: { type: "string" },
133
+ minPassedCount: { type: "number" },
134
+ minGeneratedMessages: { type: "number" },
135
+ templateRevision: {
136
+ type: "string",
137
+ description: '"current" (default when minGeneratedMessages is set) or an explicit current approved brief hash.',
138
+ },
139
+ timeoutMs: { type: "number" },
140
+ intervalMs: { type: "number" },
141
+ },
142
+ additionalProperties: false,
143
+ },
144
+ },
145
+ {
146
+ name: "revise_message_template_and_rerun",
147
+ description: "Update the approved message template in the campaign brief, mark prior generated messages stale, reset unsent approvals, and force-rerun Generate Message cells. Does not directly overwrite row message cells.",
148
+ inputSchema: {
149
+ type: "object",
150
+ properties: {
151
+ campaignId: { type: "string" },
152
+ tableId: { type: "string" },
153
+ templateMarkdown: { type: "string" },
154
+ approvedMessageTemplate: { type: "string" },
155
+ rowSelector: {
156
+ type: "object",
157
+ properties: {
158
+ type: {
159
+ type: "string",
160
+ enum: [
161
+ "passedRows",
162
+ "needsGeneratedMessage",
163
+ "staleGeneratedMessages",
164
+ "rowIds",
165
+ ],
166
+ },
167
+ rowIds: { type: "array", items: { type: "string" } },
168
+ limit: { type: "number" },
169
+ },
170
+ required: ["type"],
171
+ additionalProperties: false,
172
+ },
173
+ limit: { type: "number" },
174
+ },
175
+ required: ["campaignId"],
176
+ additionalProperties: false,
177
+ },
178
+ },
179
+ ];
180
+ export async function getCampaignTableSchema(input) {
181
+ return resolveSchema(input);
182
+ }
183
+ export async function selectCampaignCells(input) {
184
+ return postCampaignProcessing({
185
+ action: "select",
186
+ ...input,
187
+ });
188
+ }
189
+ export async function queueCampaignCells(input) {
190
+ return postCampaignProcessing({
191
+ action: "queue",
192
+ ...input,
193
+ });
194
+ }
195
+ export async function reviseMessageTemplateAndRerun(input) {
196
+ return postCampaignProcessing({
197
+ action: "reviseTemplateAndRerun",
198
+ ...input,
199
+ });
200
+ }
201
+ export async function waitForCampaignProcessing(input) {
202
+ const api = getApi();
203
+ const schema = await resolveSchema(input);
204
+ const { effectiveTimeoutMs, guardApplied, requestedTimeoutMs } = resolveWaitTimeout(input.timeoutMs ?? DEFAULT_TIMEOUT_MS);
205
+ const intervalMs = Math.max(500, input.intervalMs ?? DEFAULT_INTERVAL_MS);
206
+ const minPassedCount = typeof input.minPassedCount === "number" &&
207
+ Number.isFinite(input.minPassedCount)
208
+ ? Math.max(1, Math.floor(input.minPassedCount))
209
+ : null;
210
+ const minGeneratedMessages = typeof input.minGeneratedMessages === "number" &&
211
+ Number.isFinite(input.minGeneratedMessages)
212
+ ? Math.max(1, Math.floor(input.minGeneratedMessages))
213
+ : null;
214
+ const templateRevision = input.templateRevision ?? (minGeneratedMessages !== null ? "current" : null);
215
+ const pollKey = stableHash({
216
+ tableId: schema.tableId,
217
+ minPassedCount,
218
+ minGeneratedMessages,
219
+ templateRevision,
220
+ });
221
+ const start = Date.now();
222
+ let attempts = 0;
223
+ let lastStats = null;
224
+ while (Date.now() - start <= effectiveTimeoutMs) {
225
+ attempts += 1;
226
+ const stats = await api.get(`/api/v3/workflow-tables/${schema.tableId}/stats`);
227
+ lastStats = stats;
228
+ const currentRevisionHash = stats.currentTemplateRevisionHash ?? null;
229
+ if (templateRevision &&
230
+ templateRevision !== "current" &&
231
+ currentRevisionHash &&
232
+ templateRevision !== currentRevisionHash) {
233
+ return {
234
+ ready: false,
235
+ reason: "template_revision_mismatch",
236
+ attempts,
237
+ elapsedMs: Date.now() - start,
238
+ tableId: schema.tableId,
239
+ pollKey,
240
+ requestedTemplateRevision: templateRevision,
241
+ currentTemplateRevisionHash: currentRevisionHash,
242
+ guidance: "The requested templateRevision is not current. Re-read campaign state or wait using templateRevision:'current'.",
243
+ stats,
244
+ };
245
+ }
246
+ const passed = stats.passRate?.passed ?? 0;
247
+ const generated = templateRevision === "current" || templateRevision !== null
248
+ ? (stats.currentRevisionGeneratedMessagesCount ?? 0)
249
+ : (stats.generatedMessagesCount ?? stats.messagesCount ?? 0);
250
+ const passFloorMet = minPassedCount === null || passed >= minPassedCount;
251
+ const generatedFloorMet = minGeneratedMessages === null || generated >= minGeneratedMessages;
252
+ if (passFloorMet && generatedFloorMet) {
253
+ return {
254
+ ready: true,
255
+ attempts,
256
+ elapsedMs: Date.now() - start,
257
+ tableId: schema.tableId,
258
+ pollKey,
259
+ floors: {
260
+ minPassedCount,
261
+ minGeneratedMessages,
262
+ templateRevision,
263
+ },
264
+ result: {
265
+ passedCount: passed,
266
+ generatedMessagesCount: stats.generatedMessagesCount ?? stats.messagesCount ?? 0,
267
+ currentRevisionGeneratedMessagesCount: stats.currentRevisionGeneratedMessagesCount ?? 0,
268
+ staleGeneratedMessagesCount: stats.staleGeneratedMessagesCount ?? 0,
269
+ generatedWithoutPassCount: stats.generatedWithoutPassCount ?? 0,
270
+ currentTemplateRevisionHash: currentRevisionHash,
271
+ },
272
+ stats,
273
+ };
274
+ }
275
+ await sleep(intervalMs);
276
+ }
277
+ const passed = lastStats?.passRate?.passed ?? 0;
278
+ return {
279
+ ready: false,
280
+ reason: guardApplied ? "tool_timeout_guard" : "timeout",
281
+ attempts,
282
+ elapsedMs: Date.now() - start,
283
+ tableId: schema.tableId,
284
+ pollKey,
285
+ warning: guardApplied
286
+ ? `Stopped after ${effectiveTimeoutMs}ms to avoid host tool timeout (requested ${requestedTimeoutMs}ms).`
287
+ : undefined,
288
+ partialResult: {
289
+ passedCount: passed,
290
+ generatedMessagesCount: lastStats?.generatedMessagesCount ?? lastStats?.messagesCount ?? 0,
291
+ currentRevisionGeneratedMessagesCount: lastStats?.currentRevisionGeneratedMessagesCount ?? 0,
292
+ staleGeneratedMessagesCount: lastStats?.staleGeneratedMessagesCount ?? 0,
293
+ generatedWithoutPassCount: lastStats?.generatedWithoutPassCount ?? 0,
294
+ currentTemplateRevisionHash: lastStats?.currentTemplateRevisionHash ??
295
+ schema.currentApprovedBriefHash,
296
+ passFloorMet: minPassedCount === null || passed >= minPassedCount,
297
+ generatedFloorMet: minGeneratedMessages === null ||
298
+ (lastStats?.currentRevisionGeneratedMessagesCount ?? 0) >=
299
+ minGeneratedMessages,
300
+ },
301
+ guidance: "Do not repoll with identical arguments after this timeout. Use the partial diagnostics to revise filters/messages, queue missing work, or surface the blocked sample state.",
302
+ stats: lastStats,
303
+ };
304
+ }
@@ -146,6 +146,7 @@ export type ConfirmLeadListInput = {
146
146
  keepInSync?: boolean;
147
147
  jobId?: string;
148
148
  reviewBatchLimit?: number;
149
+ includeRawImportResult?: boolean;
149
150
  allowPartialSourceList?: boolean;
150
151
  /**
151
152
  * Deprecated alias for reviewBatchLimit. Confirming a lead list now copies the
@@ -246,6 +247,7 @@ export declare const leadToolDefinitions: ({
246
247
  jobId?: undefined;
247
248
  reviewBatchLimit?: undefined;
248
249
  allowPartialSourceList?: undefined;
250
+ includeRawImportResult?: undefined;
249
251
  selections?: undefined;
250
252
  selectionMode?: undefined;
251
253
  };
@@ -423,6 +425,7 @@ export declare const leadToolDefinitions: ({
423
425
  jobId?: undefined;
424
426
  reviewBatchLimit?: undefined;
425
427
  allowPartialSourceList?: undefined;
428
+ includeRawImportResult?: undefined;
426
429
  selections?: undefined;
427
430
  selectionMode?: undefined;
428
431
  };
@@ -499,6 +502,7 @@ export declare const leadToolDefinitions: ({
499
502
  jobId?: undefined;
500
503
  reviewBatchLimit?: undefined;
501
504
  allowPartialSourceList?: undefined;
505
+ includeRawImportResult?: undefined;
502
506
  selections?: undefined;
503
507
  selectionMode?: undefined;
504
508
  };
@@ -647,6 +651,7 @@ export declare const leadToolDefinitions: ({
647
651
  jobId?: undefined;
648
652
  reviewBatchLimit?: undefined;
649
653
  allowPartialSourceList?: undefined;
654
+ includeRawImportResult?: undefined;
650
655
  selections?: undefined;
651
656
  selectionMode?: undefined;
652
657
  };
@@ -737,6 +742,7 @@ export declare const leadToolDefinitions: ({
737
742
  jobId?: undefined;
738
743
  reviewBatchLimit?: undefined;
739
744
  allowPartialSourceList?: undefined;
745
+ includeRawImportResult?: undefined;
740
746
  selections?: undefined;
741
747
  selectionMode?: undefined;
742
748
  };
@@ -836,6 +842,7 @@ export declare const leadToolDefinitions: ({
836
842
  jobId?: undefined;
837
843
  reviewBatchLimit?: undefined;
838
844
  allowPartialSourceList?: undefined;
845
+ includeRawImportResult?: undefined;
839
846
  selections?: undefined;
840
847
  selectionMode?: undefined;
841
848
  };
@@ -917,6 +924,7 @@ export declare const leadToolDefinitions: ({
917
924
  jobId?: undefined;
918
925
  reviewBatchLimit?: undefined;
919
926
  allowPartialSourceList?: undefined;
927
+ includeRawImportResult?: undefined;
920
928
  selections?: undefined;
921
929
  selectionMode?: undefined;
922
930
  };
@@ -1537,6 +1545,7 @@ export declare const leadToolDefinitions: ({
1537
1545
  jobId?: undefined;
1538
1546
  reviewBatchLimit?: undefined;
1539
1547
  allowPartialSourceList?: undefined;
1548
+ includeRawImportResult?: undefined;
1540
1549
  selections?: undefined;
1541
1550
  selectionMode?: undefined;
1542
1551
  };
@@ -1670,6 +1679,7 @@ export declare const leadToolDefinitions: ({
1670
1679
  jobId?: undefined;
1671
1680
  reviewBatchLimit?: undefined;
1672
1681
  allowPartialSourceList?: undefined;
1682
+ includeRawImportResult?: undefined;
1673
1683
  selections?: undefined;
1674
1684
  selectionMode?: undefined;
1675
1685
  };
@@ -1791,6 +1801,7 @@ export declare const leadToolDefinitions: ({
1791
1801
  jobId?: undefined;
1792
1802
  reviewBatchLimit?: undefined;
1793
1803
  allowPartialSourceList?: undefined;
1804
+ includeRawImportResult?: undefined;
1794
1805
  selections?: undefined;
1795
1806
  selectionMode?: undefined;
1796
1807
  };
@@ -1870,6 +1881,7 @@ export declare const leadToolDefinitions: ({
1870
1881
  jobId?: undefined;
1871
1882
  reviewBatchLimit?: undefined;
1872
1883
  allowPartialSourceList?: undefined;
1884
+ includeRawImportResult?: undefined;
1873
1885
  selections?: undefined;
1874
1886
  selectionMode?: undefined;
1875
1887
  };
@@ -1921,6 +1933,10 @@ export declare const leadToolDefinitions: ({
1921
1933
  type: string;
1922
1934
  description: string;
1923
1935
  };
1936
+ includeRawImportResult: {
1937
+ type: string;
1938
+ description: string;
1939
+ };
1924
1940
  provider?: undefined;
1925
1941
  searchMode?: undefined;
1926
1942
  organization_num_employees_ranges?: undefined;
@@ -2083,6 +2099,7 @@ export declare const leadToolDefinitions: ({
2083
2099
  jobId?: undefined;
2084
2100
  reviewBatchLimit?: undefined;
2085
2101
  allowPartialSourceList?: undefined;
2102
+ includeRawImportResult?: undefined;
2086
2103
  };
2087
2104
  required: string[];
2088
2105
  };
@@ -2164,6 +2181,7 @@ export declare const leadToolDefinitions: ({
2164
2181
  jobId?: undefined;
2165
2182
  reviewBatchLimit?: undefined;
2166
2183
  allowPartialSourceList?: undefined;
2184
+ includeRawImportResult?: undefined;
2167
2185
  selections?: undefined;
2168
2186
  selectionMode?: undefined;
2169
2187
  };
@@ -2376,6 +2394,8 @@ export declare function searchSignals(input: SignalSearchInput): Promise<{
2376
2394
  id: string;
2377
2395
  url: string | undefined;
2378
2396
  matchedKeyword: string | null;
2397
+ searchType: "company" | "keyword" | "profile" | "post" | null;
2398
+ displayLabel: string | null;
2379
2399
  authorName: string;
2380
2400
  authorHeadline: string;
2381
2401
  authorProfileUrl: string;
@@ -2393,6 +2413,9 @@ export declare function searchSignals(input: SignalSearchInput): Promise<{
2393
2413
  }[];
2394
2414
  keywordResults: {
2395
2415
  keyword: string;
2416
+ searchType: "company" | "keyword" | "profile" | "post";
2417
+ displayLabel: string;
2418
+ tabId: string | null;
2396
2419
  postCount: number;
2397
2420
  avgEngagement: number | null;
2398
2421
  hasMore: boolean;
@@ -2608,25 +2631,13 @@ export declare function cancelLeadImport(input: CancelLeadImportInput): Promise<
2608
2631
  previousStatus?: undefined;
2609
2632
  }>;
2610
2633
  export declare function confirmLeadList(input: ConfirmLeadListInput): Promise<{
2611
- sourceLeadListId: string;
2612
- campaignTableId: string | null;
2613
- importResult: {
2614
- workflowTableId?: string;
2615
- campaignTableId?: string;
2616
- campaignTableName?: string;
2617
- leadsImported?: number;
2618
- leadsSkipped?: number;
2619
- rowIds?: string[];
2620
- requestedTargetLeadCount?: number;
2621
- sourceRowLimit?: number;
2622
- async?: boolean;
2623
- hybrid?: boolean;
2624
- campaignTableReady?: boolean;
2625
- importStatus?: string | null;
2626
- remainingRowCount?: number | null;
2627
- rowCount?: number | null;
2628
- sourceRowCount?: number | null;
2629
- clonedSourceRowCount?: number | null;
2634
+ reviewBatch: {
2635
+ tableId: string | null;
2636
+ rowCount: number;
2637
+ rowIds: string[];
2638
+ enrichCellIds: string[];
2639
+ basisHash: string | null;
2640
+ recordedAt: string | null;
2630
2641
  };
2631
2642
  messageDraftBuilder: {
2632
2643
  firstAllowedStartPoint: string;
@@ -2637,6 +2648,7 @@ export declare function confirmLeadList(input: ConfirmLeadListInput): Promise<{
2637
2648
  selectedLeadListId: string;
2638
2649
  workflowTableId: string | null;
2639
2650
  reviewBatchRowIds: string[];
2651
+ reviewBatchBasisHash: string | null;
2640
2652
  reviewBatchRowCount: number;
2641
2653
  copiedCampaignRowCount: number;
2642
2654
  };
@@ -2665,6 +2677,43 @@ export declare function confirmLeadList(input: ConfirmLeadListInput): Promise<{
2665
2677
  warning: string | null;
2666
2678
  };
2667
2679
  message: string;
2680
+ importResult?: {
2681
+ workflowTableId?: string;
2682
+ campaignTableId?: string;
2683
+ campaignTableName?: string;
2684
+ leadsImported?: number;
2685
+ leadsSkipped?: number;
2686
+ rowIds?: string[];
2687
+ requestedTargetLeadCount?: number;
2688
+ sourceRowLimit?: number;
2689
+ async?: boolean;
2690
+ hybrid?: boolean;
2691
+ campaignTableReady?: boolean;
2692
+ importStatus?: string | null;
2693
+ remainingRowCount?: number | null;
2694
+ rowCount?: number | null;
2695
+ sourceRowCount?: number | null;
2696
+ clonedSourceRowCount?: number | null;
2697
+ } | undefined;
2698
+ sourceLeadListId: string;
2699
+ campaignTableId: string | null;
2700
+ importSummary: {
2701
+ workflowTableId: string | null;
2702
+ campaignTableId: string | null;
2703
+ campaignTableName: string | null;
2704
+ leadsImported: number | null;
2705
+ leadsSkipped: number | null;
2706
+ rowCount: number;
2707
+ requestedTargetLeadCount: number | null;
2708
+ sourceRowLimit: number | null;
2709
+ async: boolean;
2710
+ hybrid: boolean;
2711
+ campaignTableReady: true;
2712
+ importStatus: string | null;
2713
+ remainingRowCount: number;
2714
+ sourceRowCount: number | null;
2715
+ clonedSourceRowCount: number | null;
2716
+ };
2668
2717
  }>;
2669
2718
  export declare function getProviderPrompt(input: GetProviderPromptInput): string;
2670
2719
  export declare function selectPromisingPosts(input: SelectPromisingPostsInput): Promise<{
@@ -471,6 +471,8 @@ function summarizeSignalPost(post) {
471
471
  id: post.id,
472
472
  url: post.url,
473
473
  matchedKeyword: post.matchedKeyword ?? null,
474
+ searchType: post.searchType ?? null,
475
+ displayLabel: post.displayLabel ?? null,
474
476
  authorName: post.author?.name ?? "",
475
477
  authorHeadline: post.author?.headline ?? "",
476
478
  authorProfileUrl: post.author?.profileUrl ?? "",
@@ -500,6 +502,9 @@ function summarizeSignalSearchResponse(response) {
500
502
  .map(summarizeSignalPost);
501
503
  const keywordResults = (response.keywordResults ?? []).map((kw) => ({
502
504
  keyword: kw.keyword,
505
+ searchType: kw.searchType ?? "keyword",
506
+ displayLabel: kw.displayLabel ?? kw.keyword,
507
+ tabId: kw.tabId ?? null,
503
508
  postCount: kw.postCount,
504
509
  avgEngagement: kw.avgEngagement ?? null,
505
510
  hasMore: kw.hasMore ?? false,
@@ -529,7 +534,7 @@ function buildSourceImportWatchNarration({ provider, selectedPostCount, estimate
529
534
  : "Apollo";
530
535
  const sourceDetail = provider === "signal-discovery"
531
536
  ? `${selectedPostCount ?? "selected"} approved post${selectedPostCount === 1 ? "" : "s"}${typeof estimatedEngagers === "number"
532
- ? ` with up to ${estimatedEngagers.toLocaleString("en-US")} people to check before top-level scrape adjustment`
537
+ ? ` with about ${estimatedEngagers.toLocaleString("en-US")} people to check`
533
538
  : ""}`
534
539
  : `the approved ${providerLabel} source`;
535
540
  const targetDetail = typeof targetLeadCount === "number"
@@ -806,8 +811,8 @@ function buildSignalDiscoverySourceRecommendation({ selectedPosts, targetEngager
806
811
  ? "the approved buyer-search target"
807
812
  : `the ${Math.round(defaultFitRate * 100)}% starting estimate`;
808
813
  const selectedPoolCopy = recommendedCount < selectedCount
809
- ? `**Posts considered:** ${selectedCount.toLocaleString("en-US")} selected posts with ${formatApproxInteger(totalVisibleEngagement)} visible public reactions/comments<br>\n**Recommended first set:** ${recommendedCount.toLocaleString("en-US")} post${recommendedCount === 1 ? "" : "s"} with ${formatApproxInteger(recommendedVisibleEngagement)} visible public reactions/comments and up to ${formatApproxInteger(scrapePlan.estimatedEngagers)} people to check before top-level comment and dedupe adjustment<br>`
810
- : `**Visible public activity in the selected posts:** ${formatApproxInteger(totalVisibleEngagement)} reactions/comments<br>\n**People we can check from this set:** up to ${formatApproxInteger(scrapePlan.estimatedEngagers)} before top-level comment and dedupe adjustment<br>`;
814
+ ? `**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>`
815
+ : `**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>`;
811
816
  const message = `## Source Recommendation
812
817
 
813
818
  Start with LinkedIn posts.
@@ -821,7 +826,7 @@ I found ${recommendedCount.toLocaleString("en-US")} recommended LinkedIn post${r
821
826
 
822
827
  ### Recommended posts
823
828
 
824
- | Post | Why this post | Visible public activity | People we can check |
829
+ | Post | Why this post | Public activity | People we can check |
825
830
  |---|---|---:|---:|
826
831
  ${tableRows || "| Recommended posts | Campaign-matched public activity | - | - |"}
827
832
 
@@ -1518,6 +1523,10 @@ export const leadToolDefinitions = [
1518
1523
  type: "boolean",
1519
1524
  description: "Set true after user approval when interaction mode requires confirmation for confirm/import.",
1520
1525
  },
1526
+ includeRawImportResult: {
1527
+ type: "boolean",
1528
+ description: "Debug only. When true, include the raw import response; default compact output omits large row-id payloads.",
1529
+ },
1521
1530
  },
1522
1531
  required: ["campaignOfferId"],
1523
1532
  },
@@ -2579,7 +2588,7 @@ export async function importLeads(input) {
2579
2588
  maxPostsToScrape: normalizePositiveInteger(maxPostsToScrape) ?? null,
2580
2589
  limitedSelectedPosts: importSelection.limited,
2581
2590
  targetLeadCount: cappedTargetLeadCount ?? null,
2582
- message: `Started scraping ${postsToScrape.length} posts (up to ~${result.estimatedEngagers} people to check before top-level scrape adjustment). Leads will appear as scraping completes.${importSelection.limited
2591
+ message: `Started scraping ${postsToScrape.length} posts (up to ~${result.estimatedEngagers} people to check). Leads will appear as scraping completes.${importSelection.limited
2583
2592
  ? ` Limited from ${uniqueSelectedPosts.length} selected posts by the approved source-capacity scrape plan.`
2584
2593
  : ""} The watched campaign has been moved to confirm-lead-list with import progress copy. Keep calling wait_for_lead_list_ready until it reports ready, failed, or cancelled before confirm_lead_list; only confirm a partial list with allowPartialSourceList after the user explicitly asks to keep going early. Do not call update_campaign to fix that step.`,
2585
2594
  };
@@ -2741,7 +2750,7 @@ export async function cancelLeadImport(input) {
2741
2750
  }
2742
2751
  export async function confirmLeadList(input) {
2743
2752
  const api = getApi();
2744
- const { campaignOfferId, currentStep, confirmed, sourceLeadListId, campaignName, keepInSync, jobId, reviewBatchLimit, allowPartialSourceList, targetLeadCount, } = input;
2753
+ const { campaignOfferId, currentStep, confirmed, sourceLeadListId, campaignName, keepInSync, jobId, reviewBatchLimit, includeRawImportResult, allowPartialSourceList, targetLeadCount, } = input;
2745
2754
  assertInteractionApproval({
2746
2755
  campaignId: campaignOfferId,
2747
2756
  action: "confirm-lead-list",
@@ -2873,7 +2882,7 @@ export async function confirmLeadList(input) {
2873
2882
  rowCount: leadListRowCount,
2874
2883
  status: typeof readiness.status === "string"
2875
2884
  ? readiness.status
2876
- : leadListConfig?.importStatus ?? null,
2885
+ : (leadListConfig?.importStatus ?? null),
2877
2886
  targetLeadCount: typeof readiness.targetLeadCount === "number"
2878
2887
  ? readiness.targetLeadCount
2879
2888
  : typeof leadListConfig?.targetLeadCount === "number"
@@ -2988,6 +2997,50 @@ export async function confirmLeadList(input) {
2988
2997
  reviewSampleRows = [];
2989
2998
  }
2990
2999
  }
3000
+ const sampleReviewRowIds = reviewSampleRows
3001
+ .map((row) => row.id)
3002
+ .filter((rowId) => typeof rowId === "string");
3003
+ const reviewBatchRowIds = importedRowIds.length > 0
3004
+ ? importedRowIds.slice(0, keptReviewRowCount)
3005
+ : sampleReviewRowIds.slice(0, keptReviewRowCount);
3006
+ const reviewBatchEnrichCellIds = reviewSampleRows
3007
+ .map((row) => row.enrichCellId)
3008
+ .filter((cellId) => typeof cellId === "string");
3009
+ let recordedReviewBatch = null;
3010
+ if (campaignTableId && reviewBatchRowIds.length > 0) {
3011
+ const recordResult = await api.post("/api/v3/mcp/campaign-processing", {
3012
+ action: "recordReviewBatch",
3013
+ tableId: campaignTableId,
3014
+ rowIds: reviewBatchRowIds,
3015
+ enrichCellIds: reviewBatchEnrichCellIds,
3016
+ });
3017
+ recordedReviewBatch = recordResult.reviewBatch ?? null;
3018
+ }
3019
+ const compactReviewBatch = {
3020
+ tableId: campaignTableId ?? null,
3021
+ rowCount: recordedReviewBatch?.rowCount ?? reviewBatchRowIds.length,
3022
+ rowIds: recordedReviewBatch?.rowIds ?? reviewBatchRowIds,
3023
+ enrichCellIds: recordedReviewBatch?.enrichCellIds ?? reviewBatchEnrichCellIds,
3024
+ basisHash: recordedReviewBatch?.basisHash ?? null,
3025
+ recordedAt: recordedReviewBatch?.recordedAt ?? null,
3026
+ };
3027
+ const importSummary = {
3028
+ workflowTableId: campaignTableId ?? null,
3029
+ campaignTableId: campaignTableId ?? null,
3030
+ campaignTableName: importResult.campaignTableName ?? null,
3031
+ leadsImported: importResult.leadsImported ?? null,
3032
+ leadsSkipped: importResult.leadsSkipped ?? null,
3033
+ rowCount: importResult.rowCount ?? importedRowCount,
3034
+ requestedTargetLeadCount: importResult.requestedTargetLeadCount ?? null,
3035
+ sourceRowLimit: importResult.sourceRowLimit ?? null,
3036
+ async: importResult.async ?? false,
3037
+ hybrid: importResult.hybrid ?? false,
3038
+ campaignTableReady,
3039
+ importStatus: importResult.importStatus ?? null,
3040
+ remainingRowCount,
3041
+ sourceRowCount: importResult.sourceRowCount ?? null,
3042
+ clonedSourceRowCount: importResult.clonedSourceRowCount ?? null,
3043
+ };
2991
3044
  // Persist currentStep if the caller asked for it. Do NOT touch
2992
3045
  // selectedLeadListId here: the campaign table id is already saved to
2993
3046
  // CampaignOffer.workflowTableId by /api/v3/campaign-builder/import-leads,
@@ -3014,7 +3067,9 @@ export async function confirmLeadList(input) {
3014
3067
  return {
3015
3068
  sourceLeadListId: resolvedLeadListId,
3016
3069
  campaignTableId: campaignTableId ?? null,
3017
- importResult,
3070
+ importSummary,
3071
+ ...(includeRawImportResult ? { importResult } : {}),
3072
+ reviewBatch: compactReviewBatch,
3018
3073
  messageDraftBuilder: {
3019
3074
  firstAllowedStartPoint: "confirm_lead_list",
3020
3075
  startAllowed: true,
@@ -3023,7 +3078,8 @@ export async function confirmLeadList(input) {
3023
3078
  campaignOfferId,
3024
3079
  selectedLeadListId: resolvedLeadListId,
3025
3080
  workflowTableId: campaignTableId ?? null,
3026
- reviewBatchRowIds: importedRowIds.slice(0, keptReviewRowCount),
3081
+ reviewBatchRowIds,
3082
+ reviewBatchBasisHash: compactReviewBatch.basisHash,
3027
3083
  reviewBatchRowCount: keptReviewRowCount,
3028
3084
  copiedCampaignRowCount: importedRowCount,
3029
3085
  },