@sellable/mcp 0.1.614 → 0.1.615-wip.121.2
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/refill-contract.d.ts +157 -0
- package/dist/refill-contract.js +487 -0
- package/dist/refill-run-loop.js +182 -31
- package/dist/tools/refill-executors.d.ts +46 -25
- package/dist/tools/refill-executors.js +458 -199
- package/dist/tools/refill-target-plan.js +75 -8
- package/package.json +1 -1
|
@@ -44,6 +44,51 @@ const USABLE_PAID_INMAIL_REFRESH_RECEIPT_STATUSES = new Set([
|
|
|
44
44
|
"refreshed",
|
|
45
45
|
"below_threshold",
|
|
46
46
|
]);
|
|
47
|
+
const PROVIDER_REFERENCE_ORIGINS = new Set([
|
|
48
|
+
"config_import_job",
|
|
49
|
+
"exact_table_job",
|
|
50
|
+
"find_leads_run",
|
|
51
|
+
]);
|
|
52
|
+
/**
|
|
53
|
+
* The sole compact provider-lineage decoder used at both the MCP packet and
|
|
54
|
+
* executor boundaries. Legacy loose searchId/provider fields never satisfy
|
|
55
|
+
* this contract.
|
|
56
|
+
*/
|
|
57
|
+
export function sanitizeProviderSearchReference(value) {
|
|
58
|
+
const reference = recordValue(value);
|
|
59
|
+
const owner = recordValue(reference?.owner);
|
|
60
|
+
const workspaceId = stringValue(reference?.workspaceId);
|
|
61
|
+
const provider = stringValue(reference?.provider);
|
|
62
|
+
const sourceTableId = stringValue(reference?.sourceTableId);
|
|
63
|
+
const searchId = stringValue(reference?.searchId);
|
|
64
|
+
const resolutionOrigin = stringValue(reference?.resolutionOrigin);
|
|
65
|
+
const ownerKind = stringValue(owner?.kind);
|
|
66
|
+
const ownerId = stringValue(owner?.id);
|
|
67
|
+
const basisFingerprint = stringValue(reference?.basisFingerprint);
|
|
68
|
+
if (!workspaceId ||
|
|
69
|
+
(provider !== "sales-nav" && provider !== "prospeo") ||
|
|
70
|
+
!sourceTableId ||
|
|
71
|
+
!searchId ||
|
|
72
|
+
!resolutionOrigin ||
|
|
73
|
+
!PROVIDER_REFERENCE_ORIGINS.has(resolutionOrigin) ||
|
|
74
|
+
(ownerKind !== "campaign" && ownerKind !== "find_leads_run") ||
|
|
75
|
+
!ownerId ||
|
|
76
|
+
!basisFingerprint) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
workspaceId,
|
|
81
|
+
provider,
|
|
82
|
+
sourceTableId,
|
|
83
|
+
searchId,
|
|
84
|
+
resolutionOrigin: resolutionOrigin,
|
|
85
|
+
owner: {
|
|
86
|
+
kind: ownerKind,
|
|
87
|
+
id: ownerId,
|
|
88
|
+
},
|
|
89
|
+
basisFingerprint,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
47
92
|
export function normalizeStrings(values) {
|
|
48
93
|
if (!Array.isArray(values))
|
|
49
94
|
return [];
|
|
@@ -1687,242 +1732,456 @@ export async function broadenSignalSearch(action, workspaceId) {
|
|
|
1687
1732
|
},
|
|
1688
1733
|
};
|
|
1689
1734
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
*/
|
|
1702
|
-
export async function continueProviderSource(action, workspaceId) {
|
|
1703
|
-
const type = stringValue(action.type);
|
|
1704
|
-
const campaignOfferId = actionCampaignId(action);
|
|
1705
|
-
const sourceLeadListId = actionSourceLeadListId(action);
|
|
1706
|
-
const sourceFingerprint = actionSourceFingerprint(action);
|
|
1735
|
+
function providerOutcomeAttempt(outcome, result = {}) {
|
|
1736
|
+
return {
|
|
1737
|
+
status: outcome.class === "executed" ? "executed_and_reread" : "refused",
|
|
1738
|
+
...(outcome.class === "executed"
|
|
1739
|
+
? {}
|
|
1740
|
+
: { refusalReason: `${outcome.code}: ${outcome.detail}` }),
|
|
1741
|
+
providerOutcome: outcome,
|
|
1742
|
+
result: { ...result, providerOutcome: outcome },
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
function providerActionIdentity(action) {
|
|
1707
1746
|
const toolInput = actionToolInput(action);
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
if (
|
|
1720
|
-
return
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
:
|
|
1738
|
-
|
|
1747
|
+
return {
|
|
1748
|
+
actionKey: stringValue(action.actionKey),
|
|
1749
|
+
effectId: stringValue(action.effectId) ?? stringValue(toolInput.effectId),
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
function exactProviderReferenceFromAction(action) {
|
|
1753
|
+
const rawReferences = [
|
|
1754
|
+
action.providerSearchReference,
|
|
1755
|
+
actionToolInput(action).providerSearchReference,
|
|
1756
|
+
actionIds(action).providerSearchReference,
|
|
1757
|
+
].filter((value) => value !== undefined && value !== null);
|
|
1758
|
+
if (rawReferences.length === 0)
|
|
1759
|
+
return null;
|
|
1760
|
+
const references = rawReferences.map(sanitizeProviderSearchReference);
|
|
1761
|
+
if (references.some((reference) => !reference))
|
|
1762
|
+
return null;
|
|
1763
|
+
const [first, ...rest] = references;
|
|
1764
|
+
const fingerprint = JSON.stringify(first);
|
|
1765
|
+
return rest.every((reference) => JSON.stringify(reference) === fingerprint)
|
|
1766
|
+
? first
|
|
1767
|
+
: null;
|
|
1768
|
+
}
|
|
1769
|
+
function providerFacts(params) {
|
|
1770
|
+
return {
|
|
1771
|
+
provider: params.provider,
|
|
1772
|
+
workspaceId: params.workspaceId,
|
|
1773
|
+
campaignOfferId: params.campaignOfferId,
|
|
1774
|
+
sourceLeadListId: params.sourceLeadListId,
|
|
1775
|
+
searchId: params.reference?.searchId ?? null,
|
|
1776
|
+
resolutionOrigin: params.reference?.resolutionOrigin ?? null,
|
|
1777
|
+
owner: params.reference?.owner ?? null,
|
|
1778
|
+
basisFingerprint: params.reference?.basisFingerprint ?? null,
|
|
1779
|
+
actionKey: params.actionKey,
|
|
1780
|
+
effectId: params.effectId,
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
function authorizationOutcome(status, facts) {
|
|
1784
|
+
return {
|
|
1785
|
+
class: "authorization_blocker",
|
|
1786
|
+
code: status === 401
|
|
1787
|
+
? "provider_authorization_global"
|
|
1788
|
+
: "provider_authorization_workspace",
|
|
1789
|
+
detail: status === 401
|
|
1790
|
+
? "Provider authorization is unavailable globally."
|
|
1791
|
+
: "Provider authorization is unavailable for this workspace scope.",
|
|
1792
|
+
scope: status === 401 ? "global" : "workspace",
|
|
1793
|
+
status,
|
|
1794
|
+
facts,
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
function numericFact(record, key) {
|
|
1798
|
+
const value = numberValue(record[key]);
|
|
1799
|
+
return value === null ? null : Math.max(0, value);
|
|
1800
|
+
}
|
|
1801
|
+
function classifyReconciledProviderJob(params) {
|
|
1802
|
+
const status = params.status.toUpperCase();
|
|
1803
|
+
const jobFacts = {
|
|
1804
|
+
...params.facts,
|
|
1805
|
+
jobId: params.jobId,
|
|
1806
|
+
jobStatus: status,
|
|
1807
|
+
totalPages: numericFact(params.job, "totalPages"),
|
|
1808
|
+
fetchedPages: numericFact(params.job, "fetchedPages"),
|
|
1809
|
+
totalLeads: numericFact(params.job, "totalLeads"),
|
|
1810
|
+
fetchedLeads: numericFact(params.job, "fetchedLeads"),
|
|
1811
|
+
existingRows: numericFact(params.progress, "existingRows"),
|
|
1812
|
+
requestedRows: numericFact(params.progress, "requestedRows"),
|
|
1813
|
+
processed: numericFact(params.progress, "processed"),
|
|
1814
|
+
leadsImported: numericFact(params.progress, "leadsImported"),
|
|
1815
|
+
sourceRows: numericFact(params.progress, "sourceRows"),
|
|
1816
|
+
uniqueRows: numericFact(params.progress, "uniqueRows"),
|
|
1817
|
+
duplicateRowsSkipped: numericFact(params.progress, "duplicateRowsSkipped"),
|
|
1818
|
+
invalidRows: numericFact(params.progress, "invalidRows"),
|
|
1819
|
+
};
|
|
1820
|
+
if (["PENDING", "FETCHING", "PROCESSING", "QUEUED", "RUNNING"].includes(status)) {
|
|
1739
1821
|
return {
|
|
1740
|
-
|
|
1741
|
-
|
|
1822
|
+
class: "executed",
|
|
1823
|
+
code: "provider_job_reconciled_active",
|
|
1824
|
+
detail: "An exact active provider job is attached to this source table.",
|
|
1825
|
+
jobId: params.jobId,
|
|
1826
|
+
facts: jobFacts,
|
|
1742
1827
|
};
|
|
1743
1828
|
}
|
|
1744
|
-
if (
|
|
1745
|
-
campaign.selectedLeadListId !== sourceLeadListId) {
|
|
1829
|
+
if (["FAILED", "ERROR", "CANCELLED", "CANCELED"].includes(status)) {
|
|
1746
1830
|
return {
|
|
1747
|
-
|
|
1748
|
-
|
|
1831
|
+
class: "transient",
|
|
1832
|
+
code: "provider_job_terminal_failure",
|
|
1833
|
+
detail: "The exact provider job ended without a successful import receipt.",
|
|
1834
|
+
retryable: true,
|
|
1835
|
+
facts: jobFacts,
|
|
1749
1836
|
};
|
|
1750
1837
|
}
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
// cannot start autonomously.
|
|
1760
|
-
const searchId = stringValue(toolInput.searchId);
|
|
1761
|
-
if (!searchId) {
|
|
1838
|
+
const completed = [
|
|
1839
|
+
"COMPLETED",
|
|
1840
|
+
"COMPLETE",
|
|
1841
|
+
"SUCCESS",
|
|
1842
|
+
"SUCCEEDED",
|
|
1843
|
+
"DONE",
|
|
1844
|
+
].includes(status);
|
|
1845
|
+
if (!completed) {
|
|
1762
1846
|
return {
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
campaignOfferId,
|
|
1768
|
-
sourceLeadListId,
|
|
1769
|
-
sourceFingerprint,
|
|
1770
|
-
},
|
|
1847
|
+
class: "uncertain_effect",
|
|
1848
|
+
code: "provider_job_status_unknown",
|
|
1849
|
+
detail: "The exact provider job exists but its status is not recognized.",
|
|
1850
|
+
facts: jobFacts,
|
|
1771
1851
|
};
|
|
1772
1852
|
}
|
|
1773
|
-
|
|
1774
|
-
const
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
const
|
|
1785
|
-
|
|
1853
|
+
const totalPages = jobFacts.totalPages;
|
|
1854
|
+
const fetchedPages = jobFacts.fetchedPages;
|
|
1855
|
+
const totalLeads = jobFacts.totalLeads;
|
|
1856
|
+
const fetchedLeads = jobFacts.fetchedLeads;
|
|
1857
|
+
const existingRows = jobFacts.existingRows;
|
|
1858
|
+
const leadsImported = jobFacts.leadsImported;
|
|
1859
|
+
const processed = jobFacts.processed;
|
|
1860
|
+
const sourceRows = jobFacts.sourceRows;
|
|
1861
|
+
const uniqueRows = jobFacts.uniqueRows;
|
|
1862
|
+
const duplicates = jobFacts.duplicateRowsSkipped;
|
|
1863
|
+
const invalidRows = jobFacts.invalidRows;
|
|
1864
|
+
const netNew = existingRows !== null && leadsImported !== null
|
|
1865
|
+
? Math.max(0, leadsImported - existingRows)
|
|
1866
|
+
: null;
|
|
1867
|
+
const paginationComplete = totalPages !== null && fetchedPages !== null && fetchedPages >= totalPages;
|
|
1868
|
+
const processedNew = existingRows !== null && processed !== null
|
|
1869
|
+
? Math.max(0, processed - existingRows)
|
|
1870
|
+
: null;
|
|
1871
|
+
const zeroUniverse = totalLeads === 0 || fetchedLeads === 0;
|
|
1872
|
+
const allRejected = (sourceRows !== null && uniqueRows === 0) ||
|
|
1873
|
+
(processedNew !== null &&
|
|
1874
|
+
processedNew > 0 &&
|
|
1875
|
+
(duplicates ?? 0) + (invalidRows ?? 0) >= processedNew);
|
|
1876
|
+
const zeroEligibleNetNew = netNew === 0 || zeroUniverse || allRejected;
|
|
1877
|
+
if (paginationComplete && zeroEligibleNetNew) {
|
|
1786
1878
|
return {
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1879
|
+
class: "source_terminal",
|
|
1880
|
+
code: "provider_pagination_exhausted",
|
|
1881
|
+
detail: "Completed provider pagination proved zero remaining eligible net-new yield.",
|
|
1882
|
+
facts: { ...jobFacts, netNewRows: netNew ?? 0, paginationComplete: true },
|
|
1791
1883
|
};
|
|
1792
1884
|
}
|
|
1793
1885
|
return {
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
},
|
|
1886
|
+
class: "executed",
|
|
1887
|
+
code: netNew !== null && netNew > 0
|
|
1888
|
+
? "provider_job_partial_or_positive_yield"
|
|
1889
|
+
: "provider_job_completed_replan",
|
|
1890
|
+
detail: netNew !== null && netNew > 0
|
|
1891
|
+
? "The provider job produced net-new rows; replan from the refreshed source."
|
|
1892
|
+
: "The provider job completed without sufficient facts to prove source exhaustion; replan without consuming the source.",
|
|
1893
|
+
jobId: params.jobId,
|
|
1894
|
+
facts: { ...jobFacts, netNewRows: netNew, paginationComplete },
|
|
1804
1895
|
};
|
|
1805
1896
|
}
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1897
|
+
async function reconcileExactProviderJob(params) {
|
|
1898
|
+
const api = getApi();
|
|
1899
|
+
const requestOptions = workspaceRequestOptions(params.workspaceId);
|
|
1900
|
+
try {
|
|
1901
|
+
const meta = await api.get(`/api/v3/workflow-tables/${encodeURIComponent(params.sourceLeadListId)}?mode=meta`, requestOptions);
|
|
1902
|
+
const table = recordValue(meta.table);
|
|
1903
|
+
const tableWorkspaceId = stringValue(table?.workspaceId);
|
|
1904
|
+
const config = recordValue(table?.config) ?? {};
|
|
1905
|
+
const jobId = stringValue(config.importJobId);
|
|
1906
|
+
const importProvider = stringValue(config.importProvider);
|
|
1907
|
+
if ((tableWorkspaceId && tableWorkspaceId !== params.workspaceId) ||
|
|
1908
|
+
importProvider !== params.provider ||
|
|
1909
|
+
!jobId ||
|
|
1910
|
+
(params.candidateJobId && params.candidateJobId !== jobId)) {
|
|
1911
|
+
return null;
|
|
1912
|
+
}
|
|
1913
|
+
const path = params.provider === "sales-nav"
|
|
1914
|
+
? `/api/v3/sales-nav/export?jobId=${encodeURIComponent(jobId)}`
|
|
1915
|
+
: `/api/v3/lead-lists/${encodeURIComponent(params.sourceLeadListId)}/prospeo-import/status?jobId=${encodeURIComponent(jobId)}`;
|
|
1916
|
+
const response = await api.get(path, requestOptions);
|
|
1917
|
+
const job = recordValue(response.job) ?? response;
|
|
1918
|
+
if (stringValue(job.id) !== jobId)
|
|
1919
|
+
return null;
|
|
1920
|
+
const status = stringValue(job.status);
|
|
1921
|
+
if (!status)
|
|
1922
|
+
return null;
|
|
1923
|
+
if (params.activeOnly &&
|
|
1924
|
+
!["PENDING", "FETCHING", "PROCESSING", "QUEUED", "RUNNING"].includes(status.toUpperCase())) {
|
|
1925
|
+
return null;
|
|
1926
|
+
}
|
|
1927
|
+
return classifyReconciledProviderJob({
|
|
1928
|
+
jobId,
|
|
1929
|
+
status,
|
|
1930
|
+
job,
|
|
1931
|
+
progress: recordValue(config.importProgress) ?? {},
|
|
1932
|
+
facts: params.facts,
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
catch {
|
|
1936
|
+
return null;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
function candidateJobIdFromError(error) {
|
|
1940
|
+
const body = apiErrorBody(error);
|
|
1941
|
+
if (!body)
|
|
1942
|
+
return null;
|
|
1943
|
+
try {
|
|
1944
|
+
return stringValue(recordValue(JSON.parse(body))?.jobId);
|
|
1945
|
+
}
|
|
1946
|
+
catch {
|
|
1947
|
+
return null;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
/** Shared continuation/broadening saved-search executor and classifier. */
|
|
1951
|
+
export async function executeProviderSavedSearch(action, workspaceId, mode) {
|
|
1952
|
+
const type = stringValue(action.type);
|
|
1823
1953
|
const toolInput = actionToolInput(action);
|
|
1824
1954
|
const packetProvider = stringValue(toolInput.provider);
|
|
1825
|
-
|
|
1826
|
-
// the cold-continuation authority. apollo-sourced campaigns stay manual.
|
|
1827
|
-
const provider = packetProvider === "sales-nav"
|
|
1955
|
+
const provider = type === "continue_sales_nav_source" || packetProvider === "sales-nav"
|
|
1828
1956
|
? "sales-nav"
|
|
1829
|
-
: packetProvider === "prospeo"
|
|
1957
|
+
: type === "continue_prospeo_source" || packetProvider === "prospeo"
|
|
1830
1958
|
? "prospeo"
|
|
1831
1959
|
: null;
|
|
1832
1960
|
if (!provider) {
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1961
|
+
const outcome = {
|
|
1962
|
+
class: "source_terminal",
|
|
1963
|
+
code: "provider_search_lineage_unusable",
|
|
1964
|
+
detail: "The action does not identify a supported saved-search provider.",
|
|
1965
|
+
facts: { actionType: type, provider: packetProvider },
|
|
1836
1966
|
};
|
|
1967
|
+
return providerOutcomeAttempt(outcome);
|
|
1837
1968
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1969
|
+
const campaignOfferId = actionCampaignId(action);
|
|
1970
|
+
const sourceLeadListId = actionSourceLeadListId(action);
|
|
1971
|
+
const reference = exactProviderReferenceFromAction(action);
|
|
1972
|
+
const identity = providerActionIdentity(action);
|
|
1973
|
+
const facts = providerFacts({
|
|
1974
|
+
provider,
|
|
1975
|
+
workspaceId: workspaceId ?? null,
|
|
1976
|
+
campaignOfferId,
|
|
1977
|
+
sourceLeadListId,
|
|
1978
|
+
reference,
|
|
1979
|
+
...identity,
|
|
1980
|
+
});
|
|
1981
|
+
const ownerMatches = reference
|
|
1982
|
+
? reference.owner.kind === "campaign"
|
|
1983
|
+
? reference.owner.id === campaignOfferId
|
|
1984
|
+
: reference.owner.id === sourceLeadListId
|
|
1985
|
+
: false;
|
|
1986
|
+
if (!workspaceId ||
|
|
1987
|
+
!campaignOfferId ||
|
|
1988
|
+
!sourceLeadListId ||
|
|
1989
|
+
!reference ||
|
|
1990
|
+
!identity.actionKey ||
|
|
1991
|
+
!identity.effectId ||
|
|
1992
|
+
reference.provider !== provider ||
|
|
1993
|
+
reference.sourceTableId !== sourceLeadListId ||
|
|
1994
|
+
!ownerMatches) {
|
|
1995
|
+
return providerOutcomeAttempt({
|
|
1996
|
+
class: "source_terminal",
|
|
1997
|
+
code: "provider_search_lineage_unusable",
|
|
1998
|
+
detail: "Exact workspace/provider/source/search/owner lineage and actionKey/effectId are required before provider execution.",
|
|
1999
|
+
facts,
|
|
2000
|
+
});
|
|
1843
2001
|
}
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
result: {
|
|
1852
|
-
provider,
|
|
1853
|
-
campaignOfferId,
|
|
1854
|
-
sourceLeadListId,
|
|
1855
|
-
sourceFingerprint,
|
|
1856
|
-
},
|
|
1857
|
-
};
|
|
2002
|
+
if (reference.workspaceId !== workspaceId) {
|
|
2003
|
+
return providerOutcomeAttempt({
|
|
2004
|
+
class: "stale_packet_replan",
|
|
2005
|
+
code: "provider_workspace_changed",
|
|
2006
|
+
detail: "The saved-search reference belongs to a different workspace.",
|
|
2007
|
+
facts,
|
|
2008
|
+
});
|
|
1858
2009
|
}
|
|
1859
2010
|
const api = getApi();
|
|
1860
2011
|
const requestOptions = workspaceRequestOptions(workspaceId);
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
return {
|
|
1865
|
-
status: "refused",
|
|
1866
|
-
refusalReason: `campaign leadSourceProvider (${String(campaignProvider)}) is not ${provider}; refusing search broadening`,
|
|
1867
|
-
};
|
|
2012
|
+
let campaign;
|
|
2013
|
+
try {
|
|
2014
|
+
campaign = await api.get(`/api/v2/campaign-offers/${campaignOfferId}`, requestOptions);
|
|
1868
2015
|
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
status
|
|
1873
|
-
|
|
1874
|
-
|
|
2016
|
+
catch (error) {
|
|
2017
|
+
const status = apiErrorStatus(error);
|
|
2018
|
+
if (status === 401 || status === 403) {
|
|
2019
|
+
return providerOutcomeAttempt(authorizationOutcome(status, facts));
|
|
2020
|
+
}
|
|
2021
|
+
if (status === 404) {
|
|
2022
|
+
return providerOutcomeAttempt({
|
|
2023
|
+
class: "stale_packet_replan",
|
|
2024
|
+
code: "provider_campaign_missing",
|
|
2025
|
+
detail: "The planned campaign no longer exists or is no longer visible.",
|
|
2026
|
+
facts,
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
return providerOutcomeAttempt({
|
|
2030
|
+
class: "transient",
|
|
2031
|
+
code: "provider_preflight_unavailable",
|
|
2032
|
+
detail: "Provider preflight could not read the current campaign.",
|
|
2033
|
+
retryable: true,
|
|
2034
|
+
facts: { ...facts, status },
|
|
2035
|
+
});
|
|
1875
2036
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2037
|
+
if (campaign.leadSourceProvider !== provider ||
|
|
2038
|
+
(campaign.selectedLeadListId &&
|
|
2039
|
+
campaign.selectedLeadListId !== sourceLeadListId)) {
|
|
2040
|
+
return providerOutcomeAttempt({
|
|
2041
|
+
class: "stale_packet_replan",
|
|
2042
|
+
code: "provider_source_changed",
|
|
2043
|
+
detail: "The campaign provider or selected source changed after planning.",
|
|
2044
|
+
facts: {
|
|
2045
|
+
...facts,
|
|
2046
|
+
currentProvider: campaign.leadSourceProvider ?? null,
|
|
2047
|
+
currentSourceLeadListId: campaign.selectedLeadListId ?? null,
|
|
2048
|
+
},
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
const existing = await reconcileExactProviderJob({
|
|
1889
2052
|
provider,
|
|
2053
|
+
workspaceId,
|
|
1890
2054
|
sourceLeadListId,
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
targetLeadCount: broadenedTargetLeadCount,
|
|
1894
|
-
confirmed: true,
|
|
1895
|
-
...(workspaceId ? { workspaceId } : {}),
|
|
2055
|
+
facts,
|
|
2056
|
+
activeOnly: true,
|
|
1896
2057
|
});
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
2058
|
+
if (existing)
|
|
2059
|
+
return providerOutcomeAttempt(existing);
|
|
2060
|
+
const baseTarget = Math.max(1, Math.floor(numberValue(toolInput.sourceRowLimit) ??
|
|
2061
|
+
numberValue(toolInput.targetLeadCount) ??
|
|
2062
|
+
numberValue(toolInput.targetRows) ??
|
|
2063
|
+
numberValue(action.targetRows) ??
|
|
2064
|
+
PROVIDER_SOURCE_CONTINUATION_DEFAULT_ROWS));
|
|
2065
|
+
const priorDepth = Math.max(1, Math.floor(numberValue(toolInput.priorSearchDepth) ?? 1));
|
|
2066
|
+
const targetLeadCount = mode === "broadening"
|
|
2067
|
+
? Math.min(PROVIDER_SEARCH_BROADEN_MAX_ROWS, baseTarget * (priorDepth + 1))
|
|
2068
|
+
: Math.min(PROVIDER_SOURCE_CONTINUATION_MAX_ROWS, baseTarget);
|
|
2069
|
+
markProviderPromptLoaded({ provider, campaignOfferId });
|
|
2070
|
+
let importResult;
|
|
2071
|
+
try {
|
|
2072
|
+
importResult = await importLeads({
|
|
2073
|
+
campaignOfferId,
|
|
2074
|
+
provider,
|
|
2075
|
+
sourceLeadListId,
|
|
2076
|
+
searchId: reference.searchId,
|
|
2077
|
+
mode: "add",
|
|
2078
|
+
targetLeadCount,
|
|
2079
|
+
confirmed: true,
|
|
2080
|
+
workspaceId,
|
|
2081
|
+
});
|
|
1905
2082
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
2083
|
+
catch (error) {
|
|
2084
|
+
const status = apiErrorStatus(error);
|
|
2085
|
+
if (status === 401 || status === 403) {
|
|
2086
|
+
return providerOutcomeAttempt(authorizationOutcome(status, facts));
|
|
2087
|
+
}
|
|
2088
|
+
if (userAddedRowsLimitPayloadFromError(error)) {
|
|
2089
|
+
return providerOutcomeAttempt({
|
|
2090
|
+
class: "capacity",
|
|
2091
|
+
code: "provider_table_capacity",
|
|
2092
|
+
detail: "The selected source table cannot accept the bounded add.",
|
|
2093
|
+
facts: {
|
|
2094
|
+
...facts,
|
|
2095
|
+
capacity: userAddedRowsLimitPayloadFromError(error),
|
|
2096
|
+
},
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
if (status === 404) {
|
|
2100
|
+
return providerOutcomeAttempt({
|
|
2101
|
+
class: "stale_packet_replan",
|
|
2102
|
+
code: "provider_search_or_source_changed",
|
|
2103
|
+
detail: "The saved search or source table changed after planning.",
|
|
2104
|
+
facts,
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
if (status === 429) {
|
|
2108
|
+
return providerOutcomeAttempt({
|
|
2109
|
+
class: "transient",
|
|
2110
|
+
code: "provider_rate_limited",
|
|
2111
|
+
detail: "The provider rejected this bounded attempt due to rate limiting.",
|
|
2112
|
+
retryable: true,
|
|
2113
|
+
facts: { ...facts, status },
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
const candidateJobId = candidateJobIdFromError(error);
|
|
2117
|
+
const reconciled = await reconcileExactProviderJob({
|
|
1911
2118
|
provider,
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
2119
|
+
workspaceId,
|
|
2120
|
+
sourceLeadListId,
|
|
2121
|
+
candidateJobId,
|
|
2122
|
+
facts,
|
|
2123
|
+
});
|
|
2124
|
+
if (reconciled)
|
|
2125
|
+
return providerOutcomeAttempt(reconciled);
|
|
2126
|
+
if (status !== null && status >= 500) {
|
|
2127
|
+
return providerOutcomeAttempt({
|
|
2128
|
+
class: "transient",
|
|
2129
|
+
code: "provider_outage",
|
|
2130
|
+
detail: "The provider returned a server failure and no exact active job was found during reconciliation.",
|
|
2131
|
+
retryable: true,
|
|
2132
|
+
facts: { ...facts, status },
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
return providerOutcomeAttempt({
|
|
2136
|
+
class: "uncertain_effect",
|
|
2137
|
+
code: status === 409
|
|
2138
|
+
? "provider_conflict_unreconciled"
|
|
2139
|
+
: "provider_timeout_or_crash_unreconciled",
|
|
2140
|
+
detail: "The mutation may have started, but exact active-job reconciliation did not prove its effect.",
|
|
2141
|
+
facts: { ...facts, status, candidateJobId },
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
const importRecord = recordValue(importResult) ?? {};
|
|
2145
|
+
const jobId = stringValue(importRecord.jobId);
|
|
2146
|
+
if (!jobId) {
|
|
2147
|
+
const reconciled = await reconcileExactProviderJob({
|
|
2148
|
+
provider,
|
|
2149
|
+
workspaceId,
|
|
2150
|
+
sourceLeadListId,
|
|
2151
|
+
facts,
|
|
2152
|
+
});
|
|
2153
|
+
if (reconciled)
|
|
2154
|
+
return providerOutcomeAttempt(reconciled, { importResult });
|
|
2155
|
+
return providerOutcomeAttempt({
|
|
2156
|
+
class: "uncertain_effect",
|
|
2157
|
+
code: "provider_import_not_started",
|
|
2158
|
+
detail: "The provider returned without a jobId and exact active-job reconciliation found no attributable job.",
|
|
2159
|
+
facts,
|
|
2160
|
+
}, { importResult });
|
|
2161
|
+
}
|
|
2162
|
+
const rowsGained = numberValue(recordValue(importRecord.counts)?.rows);
|
|
2163
|
+
const outcome = {
|
|
2164
|
+
class: "executed",
|
|
2165
|
+
code: rowsGained !== null && rowsGained > 0
|
|
2166
|
+
? "provider_job_partial_or_positive_yield"
|
|
2167
|
+
: "provider_job_started",
|
|
2168
|
+
detail: "The provider returned a confirmed started jobId; replan from the refreshed source receipt.",
|
|
2169
|
+
jobId,
|
|
2170
|
+
facts: {
|
|
2171
|
+
...facts,
|
|
2172
|
+
targetLeadCount,
|
|
2173
|
+
priorSearchDepth: mode === "broadening" ? priorDepth : null,
|
|
2174
|
+
newSearchDepth: mode === "broadening" ? priorDepth + 1 : null,
|
|
2175
|
+
netNewRows: rowsGained,
|
|
1924
2176
|
},
|
|
1925
2177
|
};
|
|
2178
|
+
return providerOutcomeAttempt(outcome, { importResult });
|
|
2179
|
+
}
|
|
2180
|
+
export async function continueProviderSource(action, workspaceId) {
|
|
2181
|
+
return executeProviderSavedSearch(action, workspaceId, "continuation");
|
|
2182
|
+
}
|
|
2183
|
+
export async function broadenProviderSearch(action, workspaceId) {
|
|
2184
|
+
return executeProviderSavedSearch(action, workspaceId, "broadening");
|
|
1926
2185
|
}
|
|
1927
2186
|
/**
|
|
1928
2187
|
* feat(110-23b): rung 4 of the automatic escalation ladder — bounded, reversible
|