@sellable/mcp 0.1.318 → 0.1.320

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.
@@ -16,6 +16,7 @@ export declare const linkedinToolDefinitions: ({
16
16
  postUrl?: undefined;
17
17
  sources?: undefined;
18
18
  full?: undefined;
19
+ fetchMinimal?: undefined;
19
20
  companyUrl?: undefined;
20
21
  sortBy?: undefined;
21
22
  linkedin_url?: undefined;
@@ -46,6 +47,7 @@ export declare const linkedinToolDefinitions: ({
46
47
  };
47
48
  linkedinUrl?: undefined;
48
49
  full?: undefined;
50
+ fetchMinimal?: undefined;
49
51
  companyUrl?: undefined;
50
52
  sortBy?: undefined;
51
53
  linkedin_url?: undefined;
@@ -68,6 +70,11 @@ export declare const linkedinToolDefinitions: ({
68
70
  description: string;
69
71
  default: boolean;
70
72
  };
73
+ fetchMinimal: {
74
+ type: string;
75
+ description: string;
76
+ default: boolean;
77
+ };
71
78
  limit?: undefined;
72
79
  postUrl?: undefined;
73
80
  sources?: undefined;
@@ -93,6 +100,7 @@ export declare const linkedinToolDefinitions: ({
93
100
  postUrl?: undefined;
94
101
  sources?: undefined;
95
102
  full?: undefined;
103
+ fetchMinimal?: undefined;
96
104
  sortBy?: undefined;
97
105
  linkedin_url?: undefined;
98
106
  max_posts?: undefined;
@@ -124,6 +132,7 @@ export declare const linkedinToolDefinitions: ({
124
132
  postUrl?: undefined;
125
133
  sources?: undefined;
126
134
  full?: undefined;
135
+ fetchMinimal?: undefined;
127
136
  linkedin_url?: undefined;
128
137
  max_posts?: undefined;
129
138
  };
@@ -149,6 +158,7 @@ export declare const linkedinToolDefinitions: ({
149
158
  postUrl?: undefined;
150
159
  sources?: undefined;
151
160
  full?: undefined;
161
+ fetchMinimal?: undefined;
152
162
  companyUrl?: undefined;
153
163
  sortBy?: undefined;
154
164
  };
@@ -169,6 +179,7 @@ export declare const linkedinToolDefinitions: ({
169
179
  postUrl?: undefined;
170
180
  sources?: undefined;
171
181
  full?: undefined;
182
+ fetchMinimal?: undefined;
172
183
  companyUrl?: undefined;
173
184
  sortBy?: undefined;
174
185
  max_posts?: undefined;
@@ -237,6 +248,70 @@ interface SerializedPostMedia {
237
248
  articleUrl?: string;
238
249
  mediaTypes: string[];
239
250
  }
251
+ interface CompactLinkedInProfile {
252
+ id?: string;
253
+ publicIdentifier?: string;
254
+ linkedinUrl?: string;
255
+ firstName?: string;
256
+ lastName?: string;
257
+ fullName?: string;
258
+ headline?: string;
259
+ about?: string;
260
+ location?: string;
261
+ followerCount?: number;
262
+ connectionCount?: number;
263
+ isPremium?: boolean;
264
+ isCreator?: boolean;
265
+ isVerified?: boolean;
266
+ currentRole?: CompactExperience;
267
+ company?: CompactCompany;
268
+ experience?: CompactExperience[];
269
+ education?: CompactEducation[];
270
+ skills?: string[];
271
+ languages?: CompactLanguage[];
272
+ certifications?: CompactNamedItem[];
273
+ projects?: CompactNamedItem[];
274
+ }
275
+ interface CompactCompany {
276
+ name?: string;
277
+ linkedinUrl?: string;
278
+ website?: string;
279
+ domain?: string;
280
+ industry?: string;
281
+ employeeCount?: number;
282
+ employeeRange?: string;
283
+ description?: string;
284
+ founded?: string | number;
285
+ }
286
+ interface CompactExperience {
287
+ title?: string;
288
+ companyName?: string;
289
+ companyLinkedinUrl?: string;
290
+ dateRange?: string;
291
+ duration?: string;
292
+ location?: string;
293
+ description?: string;
294
+ isCurrent?: boolean;
295
+ }
296
+ interface CompactEducation {
297
+ schoolName?: string;
298
+ schoolLinkedinUrl?: string;
299
+ degree?: string;
300
+ fieldOfStudy?: string;
301
+ dateRange?: string;
302
+ }
303
+ interface CompactLanguage {
304
+ name?: string;
305
+ proficiency?: string;
306
+ }
307
+ interface CompactNamedItem {
308
+ name?: string;
309
+ organization?: string;
310
+ dateRange?: string;
311
+ description?: string;
312
+ url?: string;
313
+ }
314
+ export declare function compactLinkedInProfile(profile: unknown): CompactLinkedInProfile;
240
315
  export declare function serializeLinkedInPosts(rawPosts: RawLinkedInPost[] | undefined, context: string): SerializedPost[];
241
316
  export declare function fetchLinkedInPosts(linkedinUrl: string, limit?: number): Promise<{
242
317
  posts: SerializedPost[];
@@ -244,6 +319,7 @@ export declare function fetchLinkedInPosts(linkedinUrl: string, limit?: number):
244
319
  }>;
245
320
  export declare function fetchLinkedInProfile(linkedinUrl: string, options?: {
246
321
  full?: boolean;
322
+ fetchMinimal?: boolean;
247
323
  }): Promise<any>;
248
324
  export declare function fetchCompany(companyUrl: string): Promise<unknown>;
249
325
  export declare function fetchCompanyPosts(companyUrl: string, limit?: number, sortBy?: "recent" | "top"): Promise<{
@@ -47,7 +47,7 @@ export const linkedinToolDefinitions = [
47
47
  },
48
48
  {
49
49
  name: "fetch_linkedin_profile",
50
- description: "Fetch LinkedIn profile details. Defaults to the cheaper 'main' payload same envelope shape with most-recent role, headline, about, location, education, top skills, certifications, languages, projects, etc. Pass full=true ONLY when you need the long-tail experience history (positions 6+) or complete skill list (skills 3+) for deep research. Most personalization, qualification, and outreach use cases work with main; reach for full only when the user explicitly asks for full work history or comprehensive skill audit.",
50
+ description: "Fetch LinkedIn profile details. Defaults to compact context (fetchMinimal=true) that strips images/media/provider metadata and returns identity, headline, about, current role, company, limited experience, education, skills, certifications, languages, and projects. Pass fetchMinimal=false only when you need raw provider fields or media URLs. Pass full=true ONLY when you need the long-tail experience history (positions 6+) or complete skill list (skills 3+) for deep research; full still returns compact output unless fetchMinimal=false.",
51
51
  inputSchema: {
52
52
  type: "object",
53
53
  properties: {
@@ -60,6 +60,11 @@ export const linkedinToolDefinitions = [
60
60
  description: "Opt into the full payload (~38% more expensive, slightly slower). Set true only when you specifically need the long-tail experience history or full skill list. Default false (main payload).",
61
61
  default: false,
62
62
  },
63
+ fetchMinimal: {
64
+ type: "boolean",
65
+ description: "Return compact agent context by stripping images/media/provider metadata and limiting long lists. Default true. Set false only when raw provider fields or media URLs are needed.",
66
+ default: true,
67
+ },
63
68
  },
64
69
  required: ["linkedinUrl"],
65
70
  },
@@ -187,6 +192,348 @@ function collectImageUrls(value, urls = new Set()) {
187
192
  function pickString(value) {
188
193
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
189
194
  }
195
+ function isRecord(value) {
196
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
197
+ }
198
+ function pickRecord(value, keys) {
199
+ if (!isRecord(value))
200
+ return undefined;
201
+ for (const key of keys) {
202
+ const candidate = value[key];
203
+ if (isRecord(candidate))
204
+ return candidate;
205
+ }
206
+ return undefined;
207
+ }
208
+ function pickStringFrom(value, keys, maxLength = 1200) {
209
+ if (!isRecord(value))
210
+ return undefined;
211
+ for (const key of keys) {
212
+ const candidate = value[key];
213
+ const picked = typeof candidate === "string" || typeof candidate === "number"
214
+ ? compactString(candidate, maxLength)
215
+ : undefined;
216
+ if (picked)
217
+ return picked;
218
+ }
219
+ return undefined;
220
+ }
221
+ function pickNumberFrom(value, keys) {
222
+ if (!isRecord(value))
223
+ return undefined;
224
+ for (const key of keys) {
225
+ const candidate = value[key];
226
+ if (typeof candidate === "number" && Number.isFinite(candidate)) {
227
+ return candidate;
228
+ }
229
+ if (typeof candidate === "string" && candidate.trim()) {
230
+ const parsed = Number(candidate.replace(/,/g, ""));
231
+ if (Number.isFinite(parsed))
232
+ return parsed;
233
+ }
234
+ }
235
+ return undefined;
236
+ }
237
+ function pickBooleanFrom(value, keys) {
238
+ if (!isRecord(value))
239
+ return undefined;
240
+ for (const key of keys) {
241
+ const candidate = value[key];
242
+ if (typeof candidate === "boolean")
243
+ return candidate;
244
+ }
245
+ return undefined;
246
+ }
247
+ function compactString(value, maxLength = 1200) {
248
+ if (typeof value !== "string" && typeof value !== "number") {
249
+ return undefined;
250
+ }
251
+ const compacted = String(value)
252
+ .replace(/[ \t]+\n/g, "\n")
253
+ .replace(/\n{3,}/g, "\n\n")
254
+ .trim();
255
+ if (!compacted)
256
+ return undefined;
257
+ return compacted.length > maxLength
258
+ ? `${compacted.slice(0, maxLength - 3).trim()}...`
259
+ : compacted;
260
+ }
261
+ function stripEmpty(value) {
262
+ if (Array.isArray(value)) {
263
+ return value
264
+ .map((item) => stripEmpty(item))
265
+ .filter((item) => {
266
+ if (item == null)
267
+ return false;
268
+ if (Array.isArray(item))
269
+ return item.length > 0;
270
+ if (isRecord(item))
271
+ return Object.keys(item).length > 0;
272
+ return true;
273
+ });
274
+ }
275
+ if (!isRecord(value))
276
+ return value;
277
+ const entries = Object.entries(value)
278
+ .map(([key, item]) => [key, stripEmpty(item)])
279
+ .filter(([, item]) => {
280
+ if (item == null || item === "")
281
+ return false;
282
+ if (Array.isArray(item))
283
+ return item.length > 0;
284
+ if (isRecord(item))
285
+ return Object.keys(item).length > 0;
286
+ return true;
287
+ });
288
+ return Object.fromEntries(entries);
289
+ }
290
+ function asArray(value) {
291
+ if (Array.isArray(value))
292
+ return value;
293
+ return value == null ? [] : [value];
294
+ }
295
+ function pickProfileArray(profile, keys) {
296
+ for (const key of keys) {
297
+ const value = profile[key];
298
+ if (Array.isArray(value))
299
+ return value;
300
+ }
301
+ return [];
302
+ }
303
+ function datePartText(value) {
304
+ if (typeof value === "string")
305
+ return compactString(value, 80);
306
+ if (!isRecord(value))
307
+ return undefined;
308
+ const text = pickStringFrom(value, ["text", "date"], 80);
309
+ if (text)
310
+ return text;
311
+ const year = pickNumberFrom(value, ["year"]);
312
+ const month = pickStringFrom(value, ["month"], 20) || pickNumberFrom(value, ["month"]);
313
+ if (!year)
314
+ return undefined;
315
+ return month ? `${month} ${year}` : String(year);
316
+ }
317
+ function dateRangeFromItem(item) {
318
+ const explicit = pickStringFrom(item, ["dateRange", "date_range", "period"], 120);
319
+ if (explicit)
320
+ return explicit;
321
+ const startText = datePartText(item.startDate) ||
322
+ datePartText(stripEmpty({
323
+ month: item.start_month ?? item.startMonth,
324
+ year: item.start_year ?? item.startYear,
325
+ }));
326
+ const endText = datePartText(item.endDate) ||
327
+ datePartText(stripEmpty({
328
+ month: item.end_month ?? item.endMonth,
329
+ year: item.end_year ?? item.endYear,
330
+ }));
331
+ return [startText, endText].filter(Boolean).join(" - ") || undefined;
332
+ }
333
+ function compactExperienceItem(value) {
334
+ if (!isRecord(value))
335
+ return {};
336
+ const companyRecord = pickRecord(value, ["company"]);
337
+ return stripEmpty({
338
+ title: pickStringFrom(value, ["title", "position", "job_title"], 160),
339
+ companyName: pickStringFrom(value, ["companyName", "company_name", "company"], 160) ||
340
+ pickStringFrom(companyRecord, ["name"], 160),
341
+ companyLinkedinUrl: pickStringFrom(value, [
342
+ "companyLinkedinUrl",
343
+ "companyLinkedInUrl",
344
+ "company_linkedin_url",
345
+ "companyLink",
346
+ "company_public_url",
347
+ ], 240),
348
+ dateRange: dateRangeFromItem(value),
349
+ duration: pickStringFrom(value, ["duration", "current_job_duration"], 80),
350
+ location: pickStringFrom(value, ["location"], 180),
351
+ description: pickStringFrom(value, ["description", "summary"], 700),
352
+ isCurrent: pickBooleanFrom(value, ["isCurrent", "is_current"]),
353
+ });
354
+ }
355
+ function compactEducationItem(value) {
356
+ if (!isRecord(value))
357
+ return {};
358
+ return stripEmpty({
359
+ schoolName: pickStringFrom(value, ["schoolName", "school_name", "school"], 180),
360
+ schoolLinkedinUrl: pickStringFrom(value, ["schoolLinkedinUrl", "schoolLinkedInUrl", "school_linkedin_url"], 240),
361
+ degree: pickStringFrom(value, ["degree"], 180),
362
+ fieldOfStudy: pickStringFrom(value, ["fieldOfStudy", "field_of_study"], 180),
363
+ dateRange: dateRangeFromItem(value),
364
+ });
365
+ }
366
+ function compactStringList(value, limit) {
367
+ return asArray(value)
368
+ .map((item) => {
369
+ if (typeof item === "string" || typeof item === "number") {
370
+ return compactString(item, 120);
371
+ }
372
+ return pickStringFrom(item, ["name", "title", "skill"], 120);
373
+ })
374
+ .filter((item) => Boolean(item))
375
+ .slice(0, limit);
376
+ }
377
+ function compactLanguages(value) {
378
+ return asArray(value)
379
+ .map((item) => {
380
+ if (typeof item === "string")
381
+ return { name: compactString(item, 80) };
382
+ if (!isRecord(item))
383
+ return {};
384
+ return stripEmpty({
385
+ name: pickStringFrom(item, ["name", "language"], 80),
386
+ proficiency: pickStringFrom(item, ["proficiency"], 120),
387
+ });
388
+ })
389
+ .filter((item) => Object.keys(item).length > 0)
390
+ .slice(0, 10);
391
+ }
392
+ function compactNamedItems(value) {
393
+ return asArray(value)
394
+ .map((item) => {
395
+ if (typeof item === "string")
396
+ return { name: compactString(item, 160) };
397
+ if (!isRecord(item))
398
+ return {};
399
+ return stripEmpty({
400
+ name: pickStringFrom(item, ["name", "title"], 180),
401
+ organization: pickStringFrom(item, ["organization", "authority", "issuer", "companyName"], 180),
402
+ dateRange: dateRangeFromItem(item),
403
+ description: pickStringFrom(item, ["description"], 500),
404
+ url: pickStringFrom(item, ["url", "link"], 240),
405
+ });
406
+ })
407
+ .filter((item) => Object.keys(item).length > 0)
408
+ .slice(0, 5);
409
+ }
410
+ function firstCurrentRole(profile) {
411
+ const current = pickProfileArray(profile, [
412
+ "currentPosition",
413
+ "current_position",
414
+ ])
415
+ .map(compactExperienceItem)
416
+ .find((item) => Object.keys(item).length > 0);
417
+ if (current)
418
+ return current;
419
+ const firstExperience = pickProfileArray(profile, [
420
+ "experience",
421
+ "experiences",
422
+ ])
423
+ .map(compactExperienceItem)
424
+ .find((item) => Object.keys(item).length > 0);
425
+ if (firstExperience)
426
+ return firstExperience;
427
+ return stripEmpty({
428
+ title: pickStringFrom(profile, ["job_title", "jobTitle", "title"], 160),
429
+ companyName: pickStringFrom(profile, ["company", "companyName"], 160),
430
+ dateRange: dateRangeFromItem(stripEmpty({
431
+ start_month: profile.current_company_join_month ?? profile.currentCompanyJoinMonth,
432
+ start_year: profile.current_company_join_year ?? profile.currentCompanyJoinYear,
433
+ })),
434
+ duration: pickStringFrom(profile, ["current_job_duration", "currentJobDuration"], 80),
435
+ });
436
+ }
437
+ function compactCompanyFromProfile(profile, currentRole) {
438
+ return stripEmpty({
439
+ name: pickStringFrom(profile, ["company", "companyName"], 180) ||
440
+ currentRole.companyName,
441
+ linkedinUrl: pickStringFrom(profile, [
442
+ "company_linkedin_url",
443
+ "companyLinkedinUrl",
444
+ "companyLinkedInUrl",
445
+ "currentCompanyLinkedinUrl",
446
+ ], 240) || currentRole.companyLinkedinUrl,
447
+ website: pickStringFrom(profile, ["company_website", "companyWebsite", "website"], 240),
448
+ domain: pickStringFrom(profile, ["company_domain", "companyDomain", "currentCompanyDomain"], 120),
449
+ industry: pickStringFrom(profile, ["company_industry", "industry"], 160),
450
+ employeeCount: pickNumberFrom(profile, [
451
+ "company_employee_count",
452
+ "companyEmployeeCount",
453
+ "employeeCount",
454
+ ]),
455
+ employeeRange: pickStringFrom(profile, ["company_employee_range", "companyEmployeeRange", "employeeCountRange"], 80),
456
+ description: pickStringFrom(profile, ["company_description", "companyDescription"], 700),
457
+ founded: pickStringFrom(profile, ["company_year_founded", "founded"], 80) ||
458
+ pickNumberFrom(profile, ["company_year_founded", "founded"]),
459
+ });
460
+ }
461
+ export function compactLinkedInProfile(profile) {
462
+ if (!isRecord(profile))
463
+ return {};
464
+ const currentRole = firstCurrentRole(profile);
465
+ const experience = pickProfileArray(profile, ["experience", "experiences"])
466
+ .map(compactExperienceItem)
467
+ .filter((item) => Object.keys(item).length > 0)
468
+ .slice(0, 5);
469
+ const education = pickProfileArray(profile, ["education", "educations"])
470
+ .map(compactEducationItem)
471
+ .filter((item) => Object.keys(item).length > 0)
472
+ .slice(0, 3);
473
+ const skills = compactStringList(profile.topSkills ?? profile.skills, 12);
474
+ return stripEmpty({
475
+ id: pickStringFrom(profile, ["id", "profile_id"], 180),
476
+ publicIdentifier: pickStringFrom(profile, ["publicIdentifier", "public_id", "publicIdentifier"], 120),
477
+ linkedinUrl: pickStringFrom(profile, ["linkedinUrl", "linkedin_url", "profile_url"], 240),
478
+ firstName: pickStringFrom(profile, ["firstName", "first_name"], 120),
479
+ lastName: pickStringFrom(profile, ["lastName", "last_name"], 120),
480
+ fullName: pickStringFrom(profile, ["fullName", "full_name", "name"], 180) ||
481
+ [
482
+ pickStringFrom(profile, ["firstName", "first_name"], 120),
483
+ pickStringFrom(profile, ["lastName", "last_name"], 120),
484
+ ]
485
+ .filter(Boolean)
486
+ .join(" "),
487
+ headline: pickStringFrom(profile, ["headline"], 320),
488
+ about: pickStringFrom(profile, ["about", "summary"], 1600),
489
+ location: pickStringFrom(profile, ["location"], 180),
490
+ followerCount: pickNumberFrom(profile, ["followerCount", "follower_count"]),
491
+ connectionCount: pickNumberFrom(profile, [
492
+ "connectionsCount",
493
+ "connection_count",
494
+ "connectionCount",
495
+ ]),
496
+ isPremium: pickBooleanFrom(profile, ["premium", "is_premium", "isPremium"]),
497
+ isCreator: pickBooleanFrom(profile, ["is_creator", "isCreator"]),
498
+ isVerified: pickBooleanFrom(profile, [
499
+ "verified",
500
+ "is_verified",
501
+ "isVerified",
502
+ ]),
503
+ currentRole,
504
+ company: compactCompanyFromProfile(profile, currentRole),
505
+ experience,
506
+ education,
507
+ skills,
508
+ languages: compactLanguages(profile.languages),
509
+ certifications: compactNamedItems(profile.certifications),
510
+ projects: compactNamedItems(profile.projects),
511
+ });
512
+ }
513
+ function canonicalizeLinkedInProfileResponse(raw) {
514
+ if (raw &&
515
+ typeof raw === "object" &&
516
+ raw.status === "success" &&
517
+ (raw.data || raw.person || raw.profile?.person)) {
518
+ const canonical = raw.data ?? raw.person ?? raw.profile?.person;
519
+ const { status, data, person, profile, ...rest } = raw;
520
+ return { status, profile: canonical, ...rest };
521
+ }
522
+ return raw;
523
+ }
524
+ function compactLinkedInProfileResponse(raw) {
525
+ const normalized = canonicalizeLinkedInProfileResponse(raw);
526
+ if (normalized &&
527
+ typeof normalized === "object" &&
528
+ normalized.status === "success" &&
529
+ normalized.profile) {
530
+ return {
531
+ status: normalized.status,
532
+ profile: compactLinkedInProfile(normalized.profile),
533
+ };
534
+ }
535
+ return normalized;
536
+ }
190
537
  function serializePostMedia(post) {
191
538
  const media = post.media && typeof post.media === "object"
192
539
  ? post.media
@@ -295,23 +642,9 @@ export async function fetchLinkedInProfile(linkedinUrl, options = {}) {
295
642
  params.set("full", "true");
296
643
  }
297
644
  const raw = await api.get(`/api/v1/scrape/linkedin/profile?${params.toString()}`);
298
- // Server returns the same profile object triplicated as `.data`,
299
- // `.person`, AND `.profile.person` for backwards-compat with web
300
- // callers (signal-discovery, profile-enrichment, canonical-cache, etc).
301
- // For the MCP layer, that ~3xs the JSON size and routinely breaches
302
- // Claude Code's max-token cap on tool results — which triggers the
303
- // harness's "save to file + read in chunks via python3" overflow
304
- // fallback. Strip the duplicates here so the agent gets one clean
305
- // copy of the data under a single `profile` key.
306
- if (raw &&
307
- typeof raw === "object" &&
308
- raw.status === "success" &&
309
- (raw.data || raw.person || raw.profile?.person)) {
310
- const canonical = raw.data ?? raw.person ?? raw.profile?.person;
311
- const { status, data, person, profile, ...rest } = raw;
312
- return { status, profile: canonical, ...rest };
313
- }
314
- return raw;
645
+ return options.fetchMinimal === false
646
+ ? canonicalizeLinkedInProfileResponse(raw)
647
+ : compactLinkedInProfileResponse(raw);
315
648
  }
316
649
  export async function fetchCompany(companyUrl) {
317
650
  const api = getApi();
@@ -20,6 +20,23 @@ function normalizeLeadSourceProvider(provider) {
20
20
  return "csv-linkedin";
21
21
  return undefined;
22
22
  }
23
+ function isPositiveNumber(value) {
24
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
25
+ }
26
+ function hasSignalDiscoveryProgressEvidence(rowCount, importProgress) {
27
+ const phase = importProgress?.phase ?? null;
28
+ return (rowCount > 0 ||
29
+ isPositiveNumber(importProgress?.processed) ||
30
+ isPositiveNumber(importProgress?.leadsImported) ||
31
+ isPositiveNumber(importProgress?.scrapedEngagers) ||
32
+ isPositiveNumber(importProgress?.evaluatedEngagers) ||
33
+ isPositiveNumber(importProgress?.evaluationTotal) ||
34
+ isPositiveNumber(importProgress?.activeEvaluationBatches) ||
35
+ isPositiveNumber(importProgress?.queuedForEvaluation) ||
36
+ isPositiveNumber(importProgress?.postsScraped) ||
37
+ phase === "scraping" ||
38
+ phase === "processing");
39
+ }
23
40
  function sleep(ms) {
24
41
  return new Promise((resolve) => setTimeout(resolve, ms));
25
42
  }
@@ -233,6 +250,8 @@ export async function waitForLeadListReady(input) {
233
250
  if (typeof configTarget === "number" && !targetLeadCount) {
234
251
  targetLeadCount = configTarget;
235
252
  }
253
+ const signalDiscoveryHasProgressEvidence = provider === "signal-discovery" &&
254
+ hasSignalDiscoveryProgressEvidence(rowCount, importProgress);
236
255
  let jobId = input.jobId;
237
256
  if (!jobId && typeof config?.importJobId === "string") {
238
257
  jobId = config.importJobId;
@@ -253,7 +272,9 @@ export async function waitForLeadListReady(input) {
253
272
  targetLeadCount,
254
273
  };
255
274
  }
256
- if (configError && configStatus !== "complete") {
275
+ if (configError &&
276
+ configStatus !== "complete" &&
277
+ !signalDiscoveryHasProgressEvidence) {
257
278
  return {
258
279
  ready: false,
259
280
  reason: "import_failed",
@@ -267,21 +288,31 @@ export async function waitForLeadListReady(input) {
267
288
  error: configError,
268
289
  };
269
290
  }
291
+ if (configError &&
292
+ configStatus !== "complete" &&
293
+ signalDiscoveryHasProgressEvidence) {
294
+ lastError = configError;
295
+ }
270
296
  if (configStatus) {
271
297
  lastStatus = configStatus;
272
298
  if (configStatus === "error") {
273
- return {
274
- ready: false,
275
- reason: "import_failed",
276
- leadListId,
277
- provider: provider ?? null,
278
- attempts,
279
- elapsedMs: Date.now() - start,
280
- rowCount,
281
- status: configStatus,
282
- targetLeadCount,
283
- error: configError,
284
- };
299
+ if (signalDiscoveryHasProgressEvidence) {
300
+ lastError = configError ?? lastError;
301
+ }
302
+ else {
303
+ return {
304
+ ready: false,
305
+ reason: "import_failed",
306
+ leadListId,
307
+ provider: provider ?? null,
308
+ attempts,
309
+ elapsedMs: Date.now() - start,
310
+ rowCount,
311
+ status: configStatus,
312
+ targetLeadCount,
313
+ error: configError,
314
+ };
315
+ }
285
316
  }
286
317
  if (configStatus === "complete") {
287
318
  importComplete = true;