@sellable/mcp 0.1.512 → 0.1.514
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +15 -12
- package/dist/refill-journal.d.ts +26 -0
- package/dist/refill-journal.js +70 -2
- package/dist/refill-local-state.d.ts +21 -0
- package/dist/refill-local-state.js +177 -1
- package/dist/refill-run-client.d.ts +62 -0
- package/dist/refill-run-client.js +101 -0
- package/dist/refill-run-loop.d.ts +100 -0
- package/dist/refill-run-loop.js +1238 -0
- package/dist/server.js +9 -7
- package/dist/tools/campaign-processing.js +3 -3
- package/dist/tools/evergreen-refill-plan.d.ts +29 -3
- package/dist/tools/evergreen-refill-plan.js +93 -16
- package/dist/tools/prompts.d.ts +4 -3
- package/dist/tools/prompts.js +5 -1
- package/dist/tools/refill-executors.d.ts +178 -0
- package/dist/tools/refill-executors.js +788 -0
- package/dist/tools/refill-sends-v2.d.ts +115 -0
- package/dist/tools/refill-sends-v2.js +162 -0
- package/dist/tools/refill-sends.js +2 -657
- package/dist/tools/registry.d.ts +184 -16
- package/dist/tools/registry.js +6 -6
- package/package.json +1 -1
- package/skills/refill-sends-evergreen/SKILL.md +6 -71
- package/skills/refill-sends-evergreen-workflow/SKILL.md +9 -96
- package/skills/refill-sends-v2/SKILL.md +156 -0
- package/skills/refill-sends-v2-workflow/SKILL.md +138 -0
- package/skills/refill-sends-v2-workflow/core/flow.v1.json +372 -0
|
@@ -1,30 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { startPrepareCampaignMessages } from "./campaign-message-preparation.js";
|
|
3
|
-
import { confirmLeadList, importLeads, searchSignals, selectPromisingPosts, } from "./leads.js";
|
|
4
|
-
import { markProviderPromptLoaded } from "./provider-preflight.js";
|
|
1
|
+
import { actionIds, collectPaidInmailRefreshActions, executeOneYoloPrimitive, firstGlobalAction, normalizeStrings, numberValue, recordValue, refreshPaidInmailCreditsWithRetry, stringValue, } from "./refill-executors.js";
|
|
5
2
|
import { getRefillTargetPlan } from "./refill-target-plan.js";
|
|
6
|
-
import {
|
|
7
|
-
import { createWorkspaceContext, normalizeExplicitWorkspaceId, workspaceRequestOptions, } from "./workspace-context.js";
|
|
3
|
+
import { createWorkspaceContext, normalizeExplicitWorkspaceId, } from "./workspace-context.js";
|
|
8
4
|
const DEFAULT_SCHEDULER_FORWARD_HOURS = 48;
|
|
9
|
-
const PAID_INMAIL_REFRESH_MAX_ATTEMPTS = 3;
|
|
10
|
-
const PAID_INMAIL_REFRESH_RETRY_DELAY_MS = Number(process.env.SELLABLE_MCP_PAID_REFRESH_RETRY_DELAY_MS ?? "1000");
|
|
11
|
-
const SIGNAL_DISCOVERY_MIN_REFILL_POSTS = 3;
|
|
12
|
-
const SIGNAL_DISCOVERY_MAX_REFILL_POSTS = 10;
|
|
13
|
-
const SIGNAL_DISCOVERY_TARGET_ROWS_PER_POST = 150;
|
|
14
|
-
function normalizeStrings(values) {
|
|
15
|
-
if (!Array.isArray(values))
|
|
16
|
-
return [];
|
|
17
|
-
return [
|
|
18
|
-
...new Set(values
|
|
19
|
-
.map((value) => (typeof value === "string" ? value.trim() : ""))
|
|
20
|
-
.filter(Boolean)),
|
|
21
|
-
];
|
|
22
|
-
}
|
|
23
|
-
function sleep(ms) {
|
|
24
|
-
if (!Number.isFinite(ms) || ms <= 0)
|
|
25
|
-
return Promise.resolve();
|
|
26
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
27
|
-
}
|
|
28
5
|
function normalizeHorizonSendDays(value) {
|
|
29
6
|
if (value === undefined || value === null)
|
|
30
7
|
return null;
|
|
@@ -70,20 +47,6 @@ function normalizeTargetDate(value) {
|
|
|
70
47
|
}
|
|
71
48
|
return trimmed;
|
|
72
49
|
}
|
|
73
|
-
function userAddedRowsLimitPayloadFromError(error) {
|
|
74
|
-
if (!(error instanceof SellableApiError) || error.status !== 400) {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
const parsed = JSON.parse(error.body);
|
|
79
|
-
if (parsed?.code !== "USER_ADDED_ROWS_LIMIT_EXCEEDED")
|
|
80
|
-
return null;
|
|
81
|
-
return parsed;
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
50
|
export const refillSendsToolDefinitions = [
|
|
88
51
|
{
|
|
89
52
|
name: "refill_sends",
|
|
@@ -350,67 +313,6 @@ function targetPlanInputFor(input = {}) {
|
|
|
350
313
|
...(workspaceId ? { workspaceId } : {}),
|
|
351
314
|
};
|
|
352
315
|
}
|
|
353
|
-
function recordValue(value) {
|
|
354
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
355
|
-
? value
|
|
356
|
-
: null;
|
|
357
|
-
}
|
|
358
|
-
function stringValue(value) {
|
|
359
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
360
|
-
}
|
|
361
|
-
function numberValue(value) {
|
|
362
|
-
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
363
|
-
}
|
|
364
|
-
function stringArray(value) {
|
|
365
|
-
if (!Array.isArray(value))
|
|
366
|
-
return [];
|
|
367
|
-
return value.filter((item) => typeof item === "string");
|
|
368
|
-
}
|
|
369
|
-
function prepareRowSelectorValue(value) {
|
|
370
|
-
const selector = recordValue(value);
|
|
371
|
-
const type = stringValue(selector?.type);
|
|
372
|
-
if (type !== "needsEnrichment" &&
|
|
373
|
-
type !== "needsApproval" &&
|
|
374
|
-
type !== "needsGeneratedMessage" &&
|
|
375
|
-
type !== "reviewBatch" &&
|
|
376
|
-
type !== "staleGeneratedMessages") {
|
|
377
|
-
return undefined;
|
|
378
|
-
}
|
|
379
|
-
const limit = numberValue(selector?.limit);
|
|
380
|
-
return {
|
|
381
|
-
type,
|
|
382
|
-
...(limit && limit > 0 ? { limit: Math.floor(limit) } : {}),
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
function uniqueStrings(values) {
|
|
386
|
-
const seen = new Set();
|
|
387
|
-
const result = [];
|
|
388
|
-
for (const value of values) {
|
|
389
|
-
const normalized = typeof value === "string" ? value.trim() : "";
|
|
390
|
-
if (!normalized)
|
|
391
|
-
continue;
|
|
392
|
-
const key = normalized.toLowerCase();
|
|
393
|
-
if (seen.has(key))
|
|
394
|
-
continue;
|
|
395
|
-
seen.add(key);
|
|
396
|
-
result.push(normalized);
|
|
397
|
-
}
|
|
398
|
-
return result;
|
|
399
|
-
}
|
|
400
|
-
function paidRefreshActionFrom(value) {
|
|
401
|
-
const action = recordValue(value);
|
|
402
|
-
if (!action || action.type !== "refresh_paid_inmail_credits")
|
|
403
|
-
return null;
|
|
404
|
-
const ids = recordValue(action.ids);
|
|
405
|
-
const senderId = stringValue(ids?.senderId) ?? stringValue(action.senderId);
|
|
406
|
-
if (!senderId)
|
|
407
|
-
return null;
|
|
408
|
-
return {
|
|
409
|
-
senderId,
|
|
410
|
-
actionKey: stringValue(action.actionKey),
|
|
411
|
-
action,
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
316
|
function targetPlanFingerprint(plan) {
|
|
415
317
|
const root = recordValue(plan);
|
|
416
318
|
return {
|
|
@@ -440,563 +342,6 @@ function paidRefreshApprovalPacket(params) {
|
|
|
440
342
|
targetPlanFingerprint: targetPlanFingerprint(params.targetPlan),
|
|
441
343
|
};
|
|
442
344
|
}
|
|
443
|
-
function collectPaidInmailRefreshActions(plan) {
|
|
444
|
-
const root = recordValue(plan);
|
|
445
|
-
const target = recordValue(root?.target);
|
|
446
|
-
const bySender = new Map();
|
|
447
|
-
const add = (candidate) => {
|
|
448
|
-
const action = paidRefreshActionFrom(candidate);
|
|
449
|
-
if (action && !bySender.has(action.senderId)) {
|
|
450
|
-
bySender.set(action.senderId, action);
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
const senderPlans = Array.isArray(target?.senderRefillPlans)
|
|
454
|
-
? target.senderRefillPlans
|
|
455
|
-
: [];
|
|
456
|
-
for (const senderPlan of senderPlans) {
|
|
457
|
-
const planRecord = recordValue(senderPlan);
|
|
458
|
-
const nextActions = Array.isArray(planRecord?.nextActions)
|
|
459
|
-
? planRecord.nextActions
|
|
460
|
-
: [];
|
|
461
|
-
for (const action of nextActions)
|
|
462
|
-
add(action);
|
|
463
|
-
const paidInmail = recordValue(planRecord?.paidInmail);
|
|
464
|
-
const paidStatus = stringValue(paidInmail?.status);
|
|
465
|
-
const senderId = stringValue(planRecord?.senderId) ?? stringValue(paidInmail?.senderId);
|
|
466
|
-
if (senderId &&
|
|
467
|
-
(paidStatus === "missing_credit_facts" ||
|
|
468
|
-
paidStatus === "stale_credit_facts")) {
|
|
469
|
-
add({
|
|
470
|
-
type: "refresh_paid_inmail_credits",
|
|
471
|
-
actionKey: [
|
|
472
|
-
"refresh_paid_inmail_credits",
|
|
473
|
-
senderId,
|
|
474
|
-
stringValue(paidInmail?.campaignId) ?? "unknown",
|
|
475
|
-
stringValue(paidInmail?.columnId) ?? "unknown",
|
|
476
|
-
].join(":"),
|
|
477
|
-
actionType: "send_inmail_closed",
|
|
478
|
-
senderId,
|
|
479
|
-
campaignId: stringValue(paidInmail?.campaignId) ?? undefined,
|
|
480
|
-
tableId: stringValue(paidInmail?.tableId) ?? undefined,
|
|
481
|
-
columnId: stringValue(paidInmail?.columnId) ?? undefined,
|
|
482
|
-
oldThreshold: numberValue(paidInmail?.threshold) ?? undefined,
|
|
483
|
-
maxStalenessSeconds: numberValue(paidInmail?.maxStalenessSeconds) ?? undefined,
|
|
484
|
-
ids: {
|
|
485
|
-
senderId,
|
|
486
|
-
campaignId: stringValue(paidInmail?.campaignId) ?? undefined,
|
|
487
|
-
tableId: stringValue(paidInmail?.tableId) ?? undefined,
|
|
488
|
-
columnId: stringValue(paidInmail?.columnId) ?? undefined,
|
|
489
|
-
},
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
const globalActionQueue = Array.isArray(target?.globalActionQueue)
|
|
494
|
-
? target.globalActionQueue
|
|
495
|
-
: [];
|
|
496
|
-
for (const action of globalActionQueue)
|
|
497
|
-
add(action);
|
|
498
|
-
const actionCandidates = Array.isArray(root?.actionCandidates)
|
|
499
|
-
? root.actionCandidates
|
|
500
|
-
: [];
|
|
501
|
-
for (const action of actionCandidates)
|
|
502
|
-
add(action);
|
|
503
|
-
return [...bySender.values()];
|
|
504
|
-
}
|
|
505
|
-
function firstGlobalAction(plan) {
|
|
506
|
-
const root = recordValue(plan);
|
|
507
|
-
const target = recordValue(root?.target);
|
|
508
|
-
const globalActionQueue = Array.isArray(target?.globalActionQueue)
|
|
509
|
-
? target.globalActionQueue
|
|
510
|
-
: [];
|
|
511
|
-
const [first] = globalActionQueue;
|
|
512
|
-
return recordValue(first);
|
|
513
|
-
}
|
|
514
|
-
function actionIds(action) {
|
|
515
|
-
return recordValue(action.ids) ?? {};
|
|
516
|
-
}
|
|
517
|
-
function actionToolInput(action) {
|
|
518
|
-
return recordValue(action.toolInput) ?? {};
|
|
519
|
-
}
|
|
520
|
-
function actionCampaignId(action) {
|
|
521
|
-
const ids = actionIds(action);
|
|
522
|
-
const toolInput = actionToolInput(action);
|
|
523
|
-
return (stringValue(toolInput.campaignId) ??
|
|
524
|
-
stringValue(toolInput.campaignOfferId) ??
|
|
525
|
-
stringValue(ids.campaignId) ??
|
|
526
|
-
stringValue(action.campaignId));
|
|
527
|
-
}
|
|
528
|
-
function actionTableId(action) {
|
|
529
|
-
const ids = actionIds(action);
|
|
530
|
-
const toolInput = actionToolInput(action);
|
|
531
|
-
return (stringValue(toolInput.tableId) ??
|
|
532
|
-
stringValue(ids.tableId) ??
|
|
533
|
-
stringValue(action.tableId));
|
|
534
|
-
}
|
|
535
|
-
function actionSourceLeadListId(action) {
|
|
536
|
-
const ids = actionIds(action);
|
|
537
|
-
const toolInput = actionToolInput(action);
|
|
538
|
-
return (stringValue(toolInput.sourceLeadListId) ??
|
|
539
|
-
stringValue(ids.sourceLeadListId) ??
|
|
540
|
-
stringValue(action.sourceLeadListId));
|
|
541
|
-
}
|
|
542
|
-
function actionSenderId(action) {
|
|
543
|
-
const ids = actionIds(action);
|
|
544
|
-
const toolInput = actionToolInput(action);
|
|
545
|
-
return (stringValue(toolInput.senderId) ??
|
|
546
|
-
stringValue(ids.senderId) ??
|
|
547
|
-
stringValue(action.senderId));
|
|
548
|
-
}
|
|
549
|
-
function actionActionType(action) {
|
|
550
|
-
const ids = actionIds(action);
|
|
551
|
-
const toolInput = actionToolInput(action);
|
|
552
|
-
return (stringValue(toolInput.actionType) ??
|
|
553
|
-
stringValue(toolInput.selectedLane) ??
|
|
554
|
-
stringValue(ids.actionType) ??
|
|
555
|
-
stringValue(action.actionType) ??
|
|
556
|
-
stringValue(action.selectedLane));
|
|
557
|
-
}
|
|
558
|
-
function actionSourceFingerprint(action) {
|
|
559
|
-
const ids = actionIds(action);
|
|
560
|
-
const toolInput = actionToolInput(action);
|
|
561
|
-
return (stringValue(toolInput.sourceFingerprint) ??
|
|
562
|
-
stringValue(ids.sourceFingerprint) ??
|
|
563
|
-
stringValue(action.sourceFingerprint));
|
|
564
|
-
}
|
|
565
|
-
function actionLeadSourceProvider(action) {
|
|
566
|
-
const ids = actionIds(action);
|
|
567
|
-
const toolInput = actionToolInput(action);
|
|
568
|
-
return (stringValue(toolInput.leadSourceProvider) ??
|
|
569
|
-
stringValue(toolInput.provider) ??
|
|
570
|
-
stringValue(ids.leadSourceProvider) ??
|
|
571
|
-
stringValue(action.leadSourceProvider));
|
|
572
|
-
}
|
|
573
|
-
function normalizedSignalKeyword(value) {
|
|
574
|
-
if (typeof value !== "string")
|
|
575
|
-
return null;
|
|
576
|
-
const keyword = value.trim();
|
|
577
|
-
if (!keyword)
|
|
578
|
-
return null;
|
|
579
|
-
if (/^(https?:\/\/|www\.|linkedin\.com\/|\/?in\/)/i.test(keyword)) {
|
|
580
|
-
return null;
|
|
581
|
-
}
|
|
582
|
-
return keyword;
|
|
583
|
-
}
|
|
584
|
-
function keywordsFromSignalTabs(tabs) {
|
|
585
|
-
const selectedKeywords = [];
|
|
586
|
-
const fallbackKeywords = [];
|
|
587
|
-
for (const tab of tabs) {
|
|
588
|
-
const keyword = normalizedSignalKeyword(tab.keyword);
|
|
589
|
-
if (!keyword)
|
|
590
|
-
continue;
|
|
591
|
-
const selected = (tab.posts ?? []).some((post) => post.isSelected === true);
|
|
592
|
-
if (selected) {
|
|
593
|
-
selectedKeywords.push(keyword);
|
|
594
|
-
}
|
|
595
|
-
else {
|
|
596
|
-
fallbackKeywords.push(keyword);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
return uniqueStrings([...selectedKeywords, ...fallbackKeywords]).slice(0, 5);
|
|
600
|
-
}
|
|
601
|
-
function selectedPostIdsFromSignalTabs(tabs) {
|
|
602
|
-
return uniqueStrings(tabs.flatMap((tab) => (tab.posts ?? [])
|
|
603
|
-
.filter((post) => post.isSelected === true)
|
|
604
|
-
.map((post) => post.id)));
|
|
605
|
-
}
|
|
606
|
-
function postIdsFromSignalSearch(summary, maxPosts, options = {}) {
|
|
607
|
-
const excludedPostIds = new Set((options.excludePostIds ?? []).map((postId) => postId.toLowerCase()));
|
|
608
|
-
const recommendedPostIds = stringArray(summary?.recommendedPostIds);
|
|
609
|
-
const topPostIds = Array.isArray(summary?.topPosts)
|
|
610
|
-
? summary.topPosts
|
|
611
|
-
.map((post) => post && typeof post === "object"
|
|
612
|
-
? stringValue(post.id)
|
|
613
|
-
: null)
|
|
614
|
-
.filter((id) => Boolean(id))
|
|
615
|
-
: [];
|
|
616
|
-
return uniqueStrings([...recommendedPostIds, ...topPostIds])
|
|
617
|
-
.filter((postId) => !excludedPostIds.has(postId.toLowerCase()))
|
|
618
|
-
.slice(0, Math.max(1, maxPosts));
|
|
619
|
-
}
|
|
620
|
-
function maxSignalDiscoveryPostsForSourceLimit(sourceRowLimit) {
|
|
621
|
-
return Math.min(SIGNAL_DISCOVERY_MAX_REFILL_POSTS, Math.max(SIGNAL_DISCOVERY_MIN_REFILL_POSTS, Math.ceil(sourceRowLimit / SIGNAL_DISCOVERY_TARGET_ROWS_PER_POST)));
|
|
622
|
-
}
|
|
623
|
-
function refillPrepareRequestHash(params) {
|
|
624
|
-
return [
|
|
625
|
-
"refill_sends",
|
|
626
|
-
"prepare_messages",
|
|
627
|
-
params.campaignId,
|
|
628
|
-
params.tableId ?? "no-table",
|
|
629
|
-
actionSenderId(params.action) ?? "all-senders",
|
|
630
|
-
actionSourceLeadListId(params.action) ?? "no-source-list",
|
|
631
|
-
actionActionType(params.action) ?? "no-action-type",
|
|
632
|
-
params.approvalMode,
|
|
633
|
-
params.rowSelector
|
|
634
|
-
? `${params.rowSelector.type}:${params.rowSelector.limit ?? "no-limit"}`
|
|
635
|
-
: "no-row-selector",
|
|
636
|
-
].join(":");
|
|
637
|
-
}
|
|
638
|
-
function boundedApprovalLimit(action) {
|
|
639
|
-
const toolInput = actionToolInput(action);
|
|
640
|
-
const rowSelector = recordValue(toolInput.rowSelector);
|
|
641
|
-
const selectorLimit = numberValue(rowSelector?.limit);
|
|
642
|
-
const inputLimit = numberValue(toolInput.limit);
|
|
643
|
-
const limit = selectorLimit ?? inputLimit;
|
|
644
|
-
if (!limit || limit <= 0)
|
|
645
|
-
return null;
|
|
646
|
-
return Math.floor(limit);
|
|
647
|
-
}
|
|
648
|
-
async function approveGeneratedMessagesBatch(action, workspaceId) {
|
|
649
|
-
const tableId = actionTableId(action);
|
|
650
|
-
const toolInput = actionToolInput(action);
|
|
651
|
-
const columnId = stringValue(toolInput.columnId) ?? stringValue(actionIds(action).columnId);
|
|
652
|
-
const limit = boundedApprovalLimit(action);
|
|
653
|
-
if (!tableId || !limit) {
|
|
654
|
-
return {
|
|
655
|
-
status: "refused",
|
|
656
|
-
refusalReason: "approve_messages action is missing tableId or a bounded rowSelector.limit",
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
const api = getApi();
|
|
660
|
-
const requestOptions = workspaceRequestOptions(workspaceId);
|
|
661
|
-
const result = await api.post("/api/v3/workflow-tables/cells/approve-batch", {
|
|
662
|
-
tableId,
|
|
663
|
-
...(columnId ? { columnId } : {}),
|
|
664
|
-
limit,
|
|
665
|
-
scope: "generated_unapproved",
|
|
666
|
-
...(workspaceId ? { workspaceId } : {}),
|
|
667
|
-
}, requestOptions);
|
|
668
|
-
return { status: "executed_and_reread", result };
|
|
669
|
-
}
|
|
670
|
-
async function continueSignalDiscoverySource(action, workspaceId) {
|
|
671
|
-
const campaignOfferId = actionCampaignId(action);
|
|
672
|
-
const sourceLeadListId = actionSourceLeadListId(action);
|
|
673
|
-
const sourceFingerprint = actionSourceFingerprint(action);
|
|
674
|
-
const toolInput = actionToolInput(action);
|
|
675
|
-
const sourceRowLimit = Math.min(1500, Math.max(100, Math.floor(numberValue(toolInput.sourceRowLimit) ??
|
|
676
|
-
numberValue(toolInput.targetRows) ??
|
|
677
|
-
numberValue(action.targetRows) ??
|
|
678
|
-
100)));
|
|
679
|
-
const maxPostsToScrape = maxSignalDiscoveryPostsForSourceLimit(sourceRowLimit);
|
|
680
|
-
if (!campaignOfferId || !sourceLeadListId || !sourceFingerprint) {
|
|
681
|
-
return {
|
|
682
|
-
status: "refused",
|
|
683
|
-
refusalReason: "continue_signal_discovery_source action is missing campaignOfferId, sourceLeadListId, or sourceFingerprint",
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
const requestedProvider = actionLeadSourceProvider(action);
|
|
687
|
-
if (requestedProvider &&
|
|
688
|
-
requestedProvider !== "signal-discovery" &&
|
|
689
|
-
requestedProvider !== "campaign-tracked-post") {
|
|
690
|
-
return {
|
|
691
|
-
status: "refused",
|
|
692
|
-
refusalReason: "continue_signal_discovery_source can only run for Signal Discovery source families",
|
|
693
|
-
};
|
|
694
|
-
}
|
|
695
|
-
const api = getApi();
|
|
696
|
-
const requestOptions = workspaceRequestOptions(workspaceId);
|
|
697
|
-
const campaign = await api.get(`/api/v2/campaign-offers/${campaignOfferId}`, requestOptions);
|
|
698
|
-
if (campaign.leadSourceProvider !== "signal-discovery" &&
|
|
699
|
-
campaign.leadSourceProvider !== "campaign-tracked-post") {
|
|
700
|
-
return {
|
|
701
|
-
status: "refused",
|
|
702
|
-
refusalReason: "campaign leadSourceProvider is not Signal Discovery; refusing same-source continuation",
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
if (campaign.selectedLeadListId &&
|
|
706
|
-
campaign.selectedLeadListId !== sourceLeadListId) {
|
|
707
|
-
return {
|
|
708
|
-
status: "refused",
|
|
709
|
-
refusalReason: "campaign selectedLeadListId changed since the plan packet; rerun get_refill_target_plan before source continuation",
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
const sourceMeta = await api.get(`/api/v3/workflow-tables/${sourceLeadListId}?mode=meta`, requestOptions);
|
|
713
|
-
const sourceConfig = sourceMeta.table?.config ?? null;
|
|
714
|
-
const headlineICPCriteria = stringArray(sourceConfig?.headlineICPCriteria).length > 0
|
|
715
|
-
? stringArray(sourceConfig?.headlineICPCriteria)
|
|
716
|
-
: stringArray(sourceConfig?.rubricGuidelines);
|
|
717
|
-
const tabsResponse = await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`, requestOptions);
|
|
718
|
-
const signalTabs = tabsResponse.tabs ?? [];
|
|
719
|
-
const keywords = keywordsFromSignalTabs(signalTabs);
|
|
720
|
-
const excludedPostIds = selectedPostIdsFromSignalTabs(signalTabs);
|
|
721
|
-
if (keywords.length === 0) {
|
|
722
|
-
return {
|
|
723
|
-
status: "refused",
|
|
724
|
-
refusalReason: "no reusable Signal Discovery keywords were found on the campaign tabs",
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
markProviderPromptLoaded({
|
|
728
|
-
provider: "signal-discovery",
|
|
729
|
-
campaignOfferId,
|
|
730
|
-
});
|
|
731
|
-
const searchSummary = await searchSignals({
|
|
732
|
-
type: "keywords",
|
|
733
|
-
keywords: keywords.map((keyword) => ({
|
|
734
|
-
keyword,
|
|
735
|
-
source: "refill-sends-source-continuation",
|
|
736
|
-
})),
|
|
737
|
-
campaignOfferId,
|
|
738
|
-
currentStep: null,
|
|
739
|
-
headlineICPCriteria,
|
|
740
|
-
rubricGuidelines: headlineICPCriteria,
|
|
741
|
-
confirmed: true,
|
|
742
|
-
limit: 50,
|
|
743
|
-
...(workspaceId ? { workspaceId } : {}),
|
|
744
|
-
});
|
|
745
|
-
const selectedPostIds = postIdsFromSignalSearch(searchSummary, maxPostsToScrape, { excludePostIds: excludedPostIds });
|
|
746
|
-
if (selectedPostIds.length === 0) {
|
|
747
|
-
return {
|
|
748
|
-
status: "refused",
|
|
749
|
-
refusalReason: "Signal Discovery search returned no new recommended posts to continue the source",
|
|
750
|
-
result: { keywords, excludedPostIds, searchSummary },
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
const selectionResult = await selectPromisingPosts({
|
|
754
|
-
campaignOfferId,
|
|
755
|
-
selections: selectedPostIds.map((postId) => ({
|
|
756
|
-
postId,
|
|
757
|
-
reason: "refill_sends same-source continuation: recent post from the campaign's existing Signal Discovery keyword family",
|
|
758
|
-
})),
|
|
759
|
-
headlineICPCriteria,
|
|
760
|
-
currentStep: null,
|
|
761
|
-
selectionMode: "replace",
|
|
762
|
-
scrapePlanMode: "all-selected",
|
|
763
|
-
...(workspaceId ? { workspaceId } : {}),
|
|
764
|
-
});
|
|
765
|
-
if (selectionResult.success === false) {
|
|
766
|
-
return {
|
|
767
|
-
status: "refused",
|
|
768
|
-
refusalReason: stringValue(selectionResult.message) ??
|
|
769
|
-
"select_promising_posts did not select any posts",
|
|
770
|
-
result: { keywords, selectedPostIds, selectionResult },
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
const importResult = await importLeads({
|
|
774
|
-
campaignOfferId,
|
|
775
|
-
provider: "signal-discovery",
|
|
776
|
-
sourceLeadListId,
|
|
777
|
-
currentStep: null,
|
|
778
|
-
headlineICPCriteria,
|
|
779
|
-
rubricGuidelines: headlineICPCriteria,
|
|
780
|
-
confirmed: true,
|
|
781
|
-
maxPostsToScrape: selectedPostIds.length,
|
|
782
|
-
allowInvalidSignalPosts: true,
|
|
783
|
-
...(workspaceId ? { workspaceId } : {}),
|
|
784
|
-
});
|
|
785
|
-
const importRecord = recordValue(importResult);
|
|
786
|
-
if (importRecord?.error) {
|
|
787
|
-
return {
|
|
788
|
-
status: "refused",
|
|
789
|
-
refusalReason: stringValue(importRecord.message) ??
|
|
790
|
-
`Signal Discovery import returned ${String(importRecord.error)}`,
|
|
791
|
-
result: {
|
|
792
|
-
keywords,
|
|
793
|
-
excludedPostIds,
|
|
794
|
-
selectedPostIds,
|
|
795
|
-
selectionResult,
|
|
796
|
-
importResult,
|
|
797
|
-
},
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
return {
|
|
801
|
-
status: "executed_and_reread",
|
|
802
|
-
result: {
|
|
803
|
-
provider: "signal-discovery",
|
|
804
|
-
campaignOfferId,
|
|
805
|
-
previousSourceLeadListId: sourceLeadListId,
|
|
806
|
-
sourceFingerprint,
|
|
807
|
-
keywords,
|
|
808
|
-
excludedPostIds,
|
|
809
|
-
selectedPostIds,
|
|
810
|
-
sourceRowLimit,
|
|
811
|
-
maxPostsToScrape: selectedPostIds.length,
|
|
812
|
-
searchSummary,
|
|
813
|
-
selectionResult,
|
|
814
|
-
importResult,
|
|
815
|
-
},
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
async function refreshPaidInmailCreditsWithRetry(senderId, workspaceId) {
|
|
819
|
-
const errors = [];
|
|
820
|
-
for (let attempt = 1; attempt <= PAID_INMAIL_REFRESH_MAX_ATTEMPTS; attempt += 1) {
|
|
821
|
-
try {
|
|
822
|
-
const receipt = await refreshPaidInmailCredits({
|
|
823
|
-
senderId,
|
|
824
|
-
workspaceId,
|
|
825
|
-
});
|
|
826
|
-
return { receipt, attempts: attempt, errors };
|
|
827
|
-
}
|
|
828
|
-
catch (error) {
|
|
829
|
-
errors.push(error instanceof Error ? error.message : String(error));
|
|
830
|
-
if (attempt < PAID_INMAIL_REFRESH_MAX_ATTEMPTS) {
|
|
831
|
-
await sleep(PAID_INMAIL_REFRESH_RETRY_DELAY_MS);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
return {
|
|
836
|
-
receipt: null,
|
|
837
|
-
attempts: PAID_INMAIL_REFRESH_MAX_ATTEMPTS,
|
|
838
|
-
errors,
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
async function executeOneYoloPrimitive(action, workspaceId) {
|
|
842
|
-
if (!action)
|
|
843
|
-
return { status: "no_action" };
|
|
844
|
-
if (action.yoloEligible === false) {
|
|
845
|
-
return {
|
|
846
|
-
status: "refused",
|
|
847
|
-
refusalReason: "first global action is not yolo eligible",
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
switch (action.type) {
|
|
851
|
-
case "wait_for_scheduler":
|
|
852
|
-
case "wait_for_active_work":
|
|
853
|
-
case "wait_for_source_import":
|
|
854
|
-
return { status: "read_only_reread", result: { waited: true } };
|
|
855
|
-
case "prepare_messages": {
|
|
856
|
-
const campaignId = actionCampaignId(action);
|
|
857
|
-
const tableId = actionTableId(action) ?? undefined;
|
|
858
|
-
const toolInput = actionToolInput(action);
|
|
859
|
-
const targetPreparedMessages = numberValue(toolInput.targetPreparedMessages);
|
|
860
|
-
const rowSelector = prepareRowSelectorValue(toolInput.rowSelector);
|
|
861
|
-
const approvalMode = toolInput.approvalMode === "approve" ? "approve" : "mark_ready";
|
|
862
|
-
if (!campaignId ||
|
|
863
|
-
!targetPreparedMessages ||
|
|
864
|
-
targetPreparedMessages <= 0) {
|
|
865
|
-
return {
|
|
866
|
-
status: "refused",
|
|
867
|
-
refusalReason: "prepare_messages action is missing campaignId or bounded targetPreparedMessages",
|
|
868
|
-
};
|
|
869
|
-
}
|
|
870
|
-
const result = await startPrepareCampaignMessages({
|
|
871
|
-
campaignId,
|
|
872
|
-
tableId,
|
|
873
|
-
...(workspaceId ? { workspaceId } : {}),
|
|
874
|
-
targetPreparedMessages,
|
|
875
|
-
maxRowsToCheck: numberValue(toolInput.maxRowsToCheck) ?? 300,
|
|
876
|
-
approvalMode,
|
|
877
|
-
rowSelector,
|
|
878
|
-
autoContinue: true,
|
|
879
|
-
disableLowPassRateStop: true,
|
|
880
|
-
senderId: actionSenderId(action) ?? undefined,
|
|
881
|
-
actionType: actionActionType(action) ?? undefined,
|
|
882
|
-
requestHash: refillPrepareRequestHash({
|
|
883
|
-
action,
|
|
884
|
-
campaignId,
|
|
885
|
-
tableId,
|
|
886
|
-
approvalMode,
|
|
887
|
-
rowSelector,
|
|
888
|
-
}),
|
|
889
|
-
requestSource: "refill_sends",
|
|
890
|
-
});
|
|
891
|
-
return { status: "executed_and_reread", result };
|
|
892
|
-
}
|
|
893
|
-
case "copy_selected_source_rows": {
|
|
894
|
-
const campaignOfferId = actionCampaignId(action);
|
|
895
|
-
const sourceLeadListId = actionSourceLeadListId(action);
|
|
896
|
-
const toolInput = actionToolInput(action);
|
|
897
|
-
const sourceRowIds = stringArray(toolInput.sourceRowIds);
|
|
898
|
-
const sourceRowLimit = numberValue(toolInput.sourceRowLimit);
|
|
899
|
-
const sourceFingerprint = stringValue(toolInput.sourceFingerprint) ??
|
|
900
|
-
stringValue(actionIds(action).sourceFingerprint);
|
|
901
|
-
if (!campaignOfferId || !sourceLeadListId || !sourceFingerprint) {
|
|
902
|
-
return {
|
|
903
|
-
status: "refused",
|
|
904
|
-
refusalReason: "copy_selected_source_rows action is missing campaignOfferId, sourceLeadListId, or sourceFingerprint",
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
if (sourceRowIds.length === 0 &&
|
|
908
|
-
(!sourceRowLimit || sourceRowLimit <= 0)) {
|
|
909
|
-
return {
|
|
910
|
-
status: "refused",
|
|
911
|
-
refusalReason: "copy_selected_source_rows action is missing exact sourceRowIds or a bounded sourceRowLimit",
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
|
-
const copyInput = {
|
|
915
|
-
campaignOfferId,
|
|
916
|
-
sourceLeadListId,
|
|
917
|
-
currentStep: null,
|
|
918
|
-
confirmed: true,
|
|
919
|
-
...(workspaceId ? { workspaceId } : {}),
|
|
920
|
-
sourceRowIds: sourceRowIds.length > 0 ? sourceRowIds : undefined,
|
|
921
|
-
sourceRowLimit: sourceRowIds.length > 0 ? undefined : (sourceRowLimit ?? undefined),
|
|
922
|
-
reviewBatchLimit: sourceRowIds.length > 0
|
|
923
|
-
? sourceRowIds.length
|
|
924
|
-
: (sourceRowLimit ?? undefined),
|
|
925
|
-
};
|
|
926
|
-
let result;
|
|
927
|
-
try {
|
|
928
|
-
result = await confirmLeadList(copyInput);
|
|
929
|
-
}
|
|
930
|
-
catch (error) {
|
|
931
|
-
const rowLimitPayload = userAddedRowsLimitPayloadFromError(error);
|
|
932
|
-
if (!rowLimitPayload)
|
|
933
|
-
throw error;
|
|
934
|
-
const remainingRows = typeof rowLimitPayload.remainingRows === "number" &&
|
|
935
|
-
Number.isFinite(rowLimitPayload.remainingRows)
|
|
936
|
-
? Math.floor(rowLimitPayload.remainingRows)
|
|
937
|
-
: 0;
|
|
938
|
-
if (remainingRows <= 0) {
|
|
939
|
-
return {
|
|
940
|
-
status: "refused",
|
|
941
|
-
refusalReason: rowLimitPayload.error ??
|
|
942
|
-
"selected campaign table is at the workflow row limit",
|
|
943
|
-
};
|
|
944
|
-
}
|
|
945
|
-
const retrySourceRowIds = sourceRowIds.length > 0 ? sourceRowIds.slice(0, remainingRows) : [];
|
|
946
|
-
const retrySourceRowLimit = retrySourceRowIds.length > 0
|
|
947
|
-
? undefined
|
|
948
|
-
: Math.min(sourceRowLimit ?? remainingRows, remainingRows);
|
|
949
|
-
const retryReviewBatchLimit = retrySourceRowIds.length > 0
|
|
950
|
-
? retrySourceRowIds.length
|
|
951
|
-
: retrySourceRowLimit;
|
|
952
|
-
if (!retryReviewBatchLimit || retryReviewBatchLimit <= 0) {
|
|
953
|
-
return {
|
|
954
|
-
status: "refused",
|
|
955
|
-
refusalReason: rowLimitPayload.error ??
|
|
956
|
-
"selected campaign table cannot accept more workflow rows",
|
|
957
|
-
};
|
|
958
|
-
}
|
|
959
|
-
const retryResult = await confirmLeadList({
|
|
960
|
-
...copyInput,
|
|
961
|
-
sourceRowIds: retrySourceRowIds.length > 0 ? retrySourceRowIds : undefined,
|
|
962
|
-
sourceRowLimit: retrySourceRowIds.length > 0 ? undefined : retrySourceRowLimit,
|
|
963
|
-
reviewBatchLimit: retryReviewBatchLimit,
|
|
964
|
-
});
|
|
965
|
-
result =
|
|
966
|
-
retryResult && typeof retryResult === "object"
|
|
967
|
-
? {
|
|
968
|
-
...retryResult,
|
|
969
|
-
rowLimitRetry: {
|
|
970
|
-
code: rowLimitPayload.code,
|
|
971
|
-
maxRows: rowLimitPayload.maxRows,
|
|
972
|
-
currentRows: rowLimitPayload.currentRows,
|
|
973
|
-
requestedRows: rowLimitPayload.requestedRows,
|
|
974
|
-
remainingRows,
|
|
975
|
-
retriedRows: retryReviewBatchLimit,
|
|
976
|
-
},
|
|
977
|
-
}
|
|
978
|
-
: {
|
|
979
|
-
result: retryResult,
|
|
980
|
-
rowLimitRetry: {
|
|
981
|
-
code: rowLimitPayload.code,
|
|
982
|
-
remainingRows,
|
|
983
|
-
retriedRows: retryReviewBatchLimit,
|
|
984
|
-
},
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
return { status: "executed_and_reread", result };
|
|
988
|
-
}
|
|
989
|
-
case "continue_signal_discovery_source":
|
|
990
|
-
return continueSignalDiscoverySource(action, workspaceId);
|
|
991
|
-
case "approve_messages":
|
|
992
|
-
return approveGeneratedMessagesBatch(action, workspaceId);
|
|
993
|
-
default:
|
|
994
|
-
return {
|
|
995
|
-
status: "refused",
|
|
996
|
-
refusalReason: `action ${String(action.type)} is not a safe yolo primitive`,
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
345
|
export async function executeRefillSendsCommand(input = {}) {
|
|
1001
346
|
const yolo = input.yolo === true;
|
|
1002
347
|
const executionMode = input.executionMode ?? (yolo ? "yolo" : "manual");
|