@sellable/mcp 0.1.555 → 0.1.557

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.
Files changed (63) hide show
  1. package/README.md +13 -2
  2. package/agents/registry.json +2 -2
  3. package/dist/api.js +3 -6
  4. package/dist/auth.d.ts +0 -6
  5. package/dist/auth.js +2 -44
  6. package/dist/refill-run-client.d.ts +0 -5
  7. package/dist/refill-run-client.js +0 -15
  8. package/dist/refill-run-loop.d.ts +1 -12
  9. package/dist/refill-run-loop.js +13 -158
  10. package/dist/server.js +23 -0
  11. package/dist/tools/auth.d.ts +0 -5
  12. package/dist/tools/auth.js +12 -49
  13. package/dist/tools/campaign-message-preparation.d.ts +0 -62
  14. package/dist/tools/campaign-message-preparation.js +0 -41
  15. package/dist/tools/campaigns.js +2 -2
  16. package/dist/tools/csv-dnc.js +2 -2
  17. package/dist/tools/evergreen-refill-plan.d.ts +0 -3
  18. package/dist/tools/evergreen-refill-plan.js +7 -29
  19. package/dist/tools/find-leads-runs.d.ts +151 -0
  20. package/dist/tools/find-leads-runs.js +98 -0
  21. package/dist/tools/leads.d.ts +317 -32
  22. package/dist/tools/leads.js +171 -10
  23. package/dist/tools/model-quality.js +4 -6
  24. package/dist/tools/prompts.d.ts +3 -3
  25. package/dist/tools/prompts.js +7 -15
  26. package/dist/tools/provider-preflight.d.ts +65 -2
  27. package/dist/tools/provider-preflight.js +97 -10
  28. package/dist/tools/readiness.d.ts +89 -5
  29. package/dist/tools/readiness.js +66 -0
  30. package/dist/tools/refill-executors.d.ts +0 -38
  31. package/dist/tools/refill-executors.js +3 -222
  32. package/dist/tools/refill-sends-v2.d.ts +1 -112
  33. package/dist/tools/refill-sends-v2.js +2 -302
  34. package/dist/tools/refill-sends.d.ts +32 -678
  35. package/dist/tools/refill-sends.js +13 -274
  36. package/dist/tools/refill-target-plan.js +14 -486
  37. package/dist/tools/registry.d.ts +330 -115
  38. package/dist/tools/registry.js +7 -1
  39. package/dist/tools/scheduler-fill-capacity.js +1 -1
  40. package/dist/tools/scheduler-run.d.ts +0 -71
  41. package/dist/tools/scheduler-run.js +1 -203
  42. package/dist/tools/setup-evergreen-campaigns.js +1 -1
  43. package/dist/tools/workspace-context.d.ts +1 -1
  44. package/dist/tools/workspace-context.js +3 -8
  45. package/dist/tools/workspace-export.js +2 -2
  46. package/dist/tools/workspaces.d.ts +2 -48
  47. package/dist/tools/workspaces.js +5 -48
  48. package/package.json +1 -1
  49. package/skills/create-campaign/SKILL.md +3 -3
  50. package/skills/create-campaign-v2/SKILL.md +1 -1
  51. package/skills/create-evergreen-campaigns/SKILL.md +16 -16
  52. package/skills/find-leads/SKILL.md +48 -630
  53. package/skills/find-leads-v2/SKILL.md +70 -0
  54. package/skills/find-leads-v2/core/flow.v1.json +31 -0
  55. package/skills/refill-sends/SKILL.md +353 -91
  56. package/skills/refill-sends-v2/SKILL.md +6 -6
  57. package/skills/refill-sends-v2-workflow/SKILL.md +5 -5
  58. package/skills/refill-sends-v2-workflow/core/flow.v1.json +8 -8
  59. package/skills/refill-sends-workflow/SKILL.md +743 -100
  60. package/skills/refill-sends-workflow/core/flow.v1.json +1 -185
  61. package/dist/refill-contract.d.ts +0 -157
  62. package/dist/refill-contract.js +0 -487
  63. package/skills/refill-sends-workflow/core/contract.v2.json +0 -543
@@ -1,6 +1,6 @@
1
1
  import * as fs from "fs";
2
2
  import { getApi, SellableApiError } from "../api.js";
3
- import { getConfig, getConfigPath, getEffectiveConfiguredWorkspaceId, getLockedWorkspaceId, getResolvedConfigsDir, isWorkspaceLockRequired, workspaceLockRequiredMessage, } from "../auth.js";
3
+ import { getConfig, getConfigPath, getResolvedConfigsDir } from "../auth.js";
4
4
  import { checkForUpdates } from "../update-check.js";
5
5
  function maskToken(token) {
6
6
  if (token.length <= 10)
@@ -38,8 +38,6 @@ export async function getAuthStatus() {
38
38
  const checkedAt = new Date().toISOString();
39
39
  const update = await getUpdateStatus();
40
40
  let tokenPresent = false;
41
- const workspaceLockRequired = isWorkspaceLockRequired();
42
- const lockedWorkspaceId = getLockedWorkspaceId();
43
41
  const base = {
44
42
  ok: false,
45
43
  configPath,
@@ -52,47 +50,27 @@ export async function getAuthStatus() {
52
50
  tokenPresent: false,
53
51
  tokenPrefix: null,
54
52
  workspacesCount: null,
55
- workspaceLock: {
56
- enabled: Boolean(lockedWorkspaceId),
57
- workspaceId: lockedWorkspaceId,
58
- required: workspaceLockRequired,
59
- },
60
53
  _userNotice: appendUpdateNotice(null, update),
61
54
  update,
62
55
  checkedAt,
63
56
  };
64
- if (workspaceLockRequired && !lockedWorkspaceId) {
65
- return {
66
- ...base,
67
- error: {
68
- type: "workspace",
69
- message: "Customer profile workspace lock is missing.",
70
- guidance: workspaceLockRequiredMessage("get_auth_status"),
71
- },
72
- };
73
- }
74
57
  try {
75
58
  const config = getConfig();
76
59
  tokenPresent = Boolean(config.token);
77
60
  base.activeEnvName = config.activeEnvName || null;
78
61
  const api = getApi();
79
62
  const { workspaces } = await api.get("/api/v3/workspaces");
80
- const activeWorkspaceId = getEffectiveConfiguredWorkspaceId(config);
63
+ const activeWorkspaceId = config.activeWorkspaceId || config.workspaceId || null;
81
64
  const activeWorkspace = activeWorkspaceId
82
65
  ? workspaces.find((ws) => ws.id === activeWorkspaceId)
83
66
  : null;
84
- const visibleWorkspacesCount = lockedWorkspaceId
85
- ? activeWorkspace
86
- ? 1
87
- : 0
88
- : workspaces.length;
89
67
  if (!activeWorkspaceId) {
90
68
  return {
91
69
  ...base,
92
70
  apiUrl: config.apiUrl || null,
93
71
  tokenPresent,
94
72
  tokenPrefix: maskToken(config.token),
95
- workspacesCount: visibleWorkspacesCount,
73
+ workspacesCount: workspaces.length,
96
74
  error: {
97
75
  type: "workspace",
98
76
  message: "No active workspace selected.",
@@ -109,26 +87,22 @@ export async function getAuthStatus() {
109
87
  apiUrl: config.apiUrl || null,
110
88
  tokenPresent,
111
89
  tokenPrefix: maskToken(config.token),
112
- workspacesCount: visibleWorkspacesCount,
90
+ workspacesCount: workspaces.length,
113
91
  error: {
114
92
  type: "workspace",
115
93
  message: `Active workspace ${activeWorkspaceId} not found in access list.`,
116
- guidance: lockedWorkspaceId
117
- ? "This profile is locked to a workspace that the configured Sellable token cannot access. Re-provision the customer profile from sellable-admin with the correct workspace token."
118
- : available
119
- ? `Available: ${available}. Run set_active_workspace with a valid ID.`
120
- : "Run list_workspaces to view available workspaces, then set_active_workspace.",
94
+ guidance: available
95
+ ? `Available: ${available}. Run set_active_workspace with a valid ID.`
96
+ : "Run list_workspaces to view available workspaces, then set_active_workspace.",
121
97
  },
122
98
  };
123
99
  }
124
100
  const workspaceName = activeWorkspace.name || config.activeWorkspaceName || null;
125
101
  const envLabel = config.activeEnvName || null;
126
102
  const wsLabel = workspaceName || activeWorkspaceId;
127
- const noticePrefix = lockedWorkspaceId
128
- ? `You're locked to ${wsLabel}.`
129
- : envLabel
130
- ? `You're in ${wsLabel} (${envLabel}).`
131
- : `You're in ${wsLabel}.`;
103
+ const notice = envLabel
104
+ ? `You're in ${wsLabel} (${envLabel}).`
105
+ : `You're in ${wsLabel}.`;
132
106
  return {
133
107
  ok: true,
134
108
  configPath,
@@ -140,19 +114,8 @@ export async function getAuthStatus() {
140
114
  activeWorkspaceName: workspaceName,
141
115
  tokenPresent,
142
116
  tokenPrefix: maskToken(config.token),
143
- workspacesCount: visibleWorkspacesCount,
144
- workspaceLock: lockedWorkspaceId
145
- ? {
146
- enabled: true,
147
- workspaceId: lockedWorkspaceId,
148
- required: workspaceLockRequired,
149
- }
150
- : {
151
- enabled: false,
152
- workspaceId: null,
153
- required: workspaceLockRequired,
154
- },
155
- _userNotice: appendUpdateNotice(noticePrefix, update),
117
+ workspacesCount: workspaces.length,
118
+ _userNotice: appendUpdateNotice(notice, update),
156
119
  update,
157
120
  checkedAt,
158
121
  error: null,
@@ -36,16 +36,9 @@ type StartPrepareMessagesInput = PrepareMessagesBaseInput & {
36
36
  requestSource?: string;
37
37
  senderId?: string;
38
38
  actionType?: string;
39
- targetDate?: string;
40
- refillLaneKey?: string;
41
- sourceEpoch?: string;
42
39
  };
43
40
  type StatusPrepareMessagesInput = PrepareMessagesBaseInput;
44
41
  type CancelPrepareMessagesInput = PrepareMessagesBaseInput;
45
- type StopSatisfiedPrepareMessagesInput = PrepareMessagesBaseInput & {
46
- jobId: string;
47
- expectedUpdatedAt?: string;
48
- };
49
42
  export declare const campaignMessagePreparationToolDefinitions: ({
50
43
  name: string;
51
44
  description: string;
@@ -124,18 +117,6 @@ export declare const campaignMessagePreparationToolDefinitions: ({
124
117
  enum: string[];
125
118
  description: string;
126
119
  };
127
- targetDate: {
128
- type: string;
129
- description: string;
130
- };
131
- refillLaneKey: {
132
- type: string;
133
- description: string;
134
- };
135
- sourceEpoch: {
136
- type: string;
137
- description: string;
138
- };
139
120
  disableLowPassRateStop: {
140
121
  type: string;
141
122
  description: string;
@@ -145,7 +126,6 @@ export declare const campaignMessagePreparationToolDefinitions: ({
145
126
  description: string;
146
127
  };
147
128
  jobId?: undefined;
148
- expectedUpdatedAt?: undefined;
149
129
  };
150
130
  required: string[];
151
131
  additionalProperties: boolean;
@@ -180,55 +160,13 @@ export declare const campaignMessagePreparationToolDefinitions: ({
180
160
  autoContinue?: undefined;
181
161
  senderId?: undefined;
182
162
  actionType?: undefined;
183
- targetDate?: undefined;
184
- refillLaneKey?: undefined;
185
- sourceEpoch?: undefined;
186
163
  disableLowPassRateStop?: undefined;
187
- expectedUpdatedAt?: undefined;
188
164
  };
189
165
  additionalProperties: boolean;
190
166
  required?: undefined;
191
167
  };
192
- } | {
193
- name: string;
194
- description: string;
195
- inputSchema: {
196
- type: string;
197
- properties: {
198
- jobId: {
199
- type: string;
200
- };
201
- expectedUpdatedAt: {
202
- type: string;
203
- description: string;
204
- };
205
- workspaceId: {
206
- type: string;
207
- description?: undefined;
208
- };
209
- campaignId?: undefined;
210
- tableId?: undefined;
211
- targetPreparedMessages?: undefined;
212
- maxRowsToCheck?: undefined;
213
- batchSize?: undefined;
214
- maxBatchRows?: undefined;
215
- approvalMode?: undefined;
216
- rowSelector?: undefined;
217
- excludeRowIds?: undefined;
218
- autoContinue?: undefined;
219
- senderId?: undefined;
220
- actionType?: undefined;
221
- targetDate?: undefined;
222
- refillLaneKey?: undefined;
223
- sourceEpoch?: undefined;
224
- disableLowPassRateStop?: undefined;
225
- };
226
- required: string[];
227
- additionalProperties: boolean;
228
- };
229
168
  })[];
230
169
  export declare function startPrepareCampaignMessages(input: StartPrepareMessagesInput): Promise<unknown>;
231
170
  export declare function getPrepareCampaignMessagesStatus(input: StatusPrepareMessagesInput): Promise<unknown>;
232
171
  export declare function cancelPrepareCampaignMessages(input: CancelPrepareMessagesInput): Promise<unknown>;
233
- export declare function stopSatisfiedPrepareCampaignMessages(input: StopSatisfiedPrepareMessagesInput): Promise<unknown>;
234
172
  export {};
@@ -84,18 +84,6 @@ export const campaignMessagePreparationToolDefinitions = [
84
84
  ],
85
85
  description: "Optional refill-scope send action. InMail scopes treat open and paid InMail as one contact-reuse family.",
86
86
  },
87
- targetDate: {
88
- type: "string",
89
- description: "Exact sender-local YYYY-MM-DD refill target date.",
90
- },
91
- refillLaneKey: {
92
- type: "string",
93
- description: "Normalized refill lane identity from the canonical target plan.",
94
- },
95
- sourceEpoch: {
96
- type: "string",
97
- description: "Stable source/provider epoch; only a material epoch change resets zero-yield recurrence.",
98
- },
99
87
  disableLowPassRateStop: {
100
88
  type: "boolean",
101
89
  description: "Operator override for explicit fill/exhaust requests. When true, keep processing eligible rows even if the enriched sample pass rate is low. Still bounded by targetPreparedMessages and maxRowsToCheck.",
@@ -123,23 +111,6 @@ export const campaignMessagePreparationToolDefinitions = [
123
111
  additionalProperties: false,
124
112
  },
125
113
  },
126
- {
127
- name: "stop_satisfied_campaign_message_preparation",
128
- description: "Cooperatively stop exactly one preparation job after the server rereads its persisted refill scope and proves its allocatable preparation deficit is zero. This never cancels table-wide workflow cells.",
129
- inputSchema: {
130
- type: "object",
131
- properties: {
132
- jobId: { type: "string" },
133
- expectedUpdatedAt: {
134
- type: "string",
135
- description: "Optional optimistic job updatedAt revision from status.",
136
- },
137
- workspaceId: { type: "string" },
138
- },
139
- required: ["jobId"],
140
- additionalProperties: false,
141
- },
142
- },
143
114
  {
144
115
  name: "cancel_campaign_message_preparation",
145
116
  description: "Cancel an active campaign message preparation job and cancel the same pending workflow-table cells that the UI Cancel Pending Cells button would cancel. Launch remains a separate explicit start_campaign step.",
@@ -174,9 +145,6 @@ export function startPrepareCampaignMessages(input) {
174
145
  requestSource: input.requestSource,
175
146
  senderId: input.senderId,
176
147
  actionType: input.actionType,
177
- targetDate: input.targetDate,
178
- refillLaneKey: input.refillLaneKey,
179
- sourceEpoch: input.sourceEpoch,
180
148
  ...(workspaceId ? { workspaceId } : {}),
181
149
  }, workspaceId ?? undefined);
182
150
  }
@@ -200,12 +168,3 @@ export function cancelPrepareCampaignMessages(input) {
200
168
  ...(workspaceId ? { workspaceId } : {}),
201
169
  }, workspaceId ?? undefined);
202
170
  }
203
- export function stopSatisfiedPrepareCampaignMessages(input) {
204
- const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
205
- return postPrepareMessages({
206
- action: "stop_if_target_satisfied",
207
- jobId: input.jobId,
208
- expectedUpdatedAt: input.expectedUpdatedAt,
209
- ...(workspaceId ? { workspaceId } : {}),
210
- }, workspaceId ?? undefined);
211
- }
@@ -1,5 +1,5 @@
1
1
  import { getApi } from "../api.js";
2
- import { getConfig, getEffectiveConfiguredWorkspaceId } from "../auth.js";
2
+ import { getConfig } from "../auth.js";
3
3
  import { assertCreateCampaignPromptLoaded, assertNetNewCreateCampaignResearchReady, } from "./flow-preflight.js";
4
4
  import { setCampaignInteractionMode, } from "./interaction-mode.js";
5
5
  import { isLinkedInProfileInput, normalizeLinkedInProfileInput, } from "./linkedin-url.js";
@@ -114,7 +114,7 @@ function normalizeLeadSourceProvider(input) {
114
114
  : null;
115
115
  }
116
116
  export function buildWatchUrl(config, path) {
117
- const workspaceId = getEffectiveConfiguredWorkspaceId(config);
117
+ const workspaceId = config.activeWorkspaceId || config.workspaceId;
118
118
  const url = new URL(path, config.apiUrl);
119
119
  if (workspaceId && !url.searchParams.has("workspaceId")) {
120
120
  url.searchParams.set("workspaceId", workspaceId);
@@ -3,7 +3,7 @@ import { createHash, createHmac, randomBytes } from "node:crypto";
3
3
  import { readFileSync, statSync } from "node:fs";
4
4
  import { dirname, isAbsolute, resolve } from "node:path";
5
5
  import { getApi } from "../api.js";
6
- import { getConfig, getEffectiveConfiguredWorkspaceId } from "../auth.js";
6
+ import { getConfig } from "../auth.js";
7
7
  import { resolveWorkspaceRoot } from "../utils/workspace-root.js";
8
8
  import { sanitizeCsvDomainCandidate } from "./csv-domains.js";
9
9
  import { validateAndNormalizeLinkedInUrl } from "./csv-linkedin.js";
@@ -324,7 +324,7 @@ function dedupeCandidates(candidates) {
324
324
  }
325
325
  async function getVerifiedActiveWorkspace() {
326
326
  const config = getConfig();
327
- const activeWorkspaceId = getEffectiveConfiguredWorkspaceId(config);
327
+ const activeWorkspaceId = config.activeWorkspaceId || config.workspaceId;
328
328
  if (!activeWorkspaceId) {
329
329
  throw new Error("No active workspace selected. Run list_workspaces then set_active_workspace for the exact workspace whose Sellable DNC list should be read or updated.");
330
330
  }
@@ -2,9 +2,6 @@ type GetRefillPlanV2Input = {
2
2
  workspaceId?: string;
3
3
  intent?: "auto" | "evergreen" | "plain" | "active";
4
4
  senderIds?: string[];
5
- horizonSendDays?: number;
6
- untilDate?: string;
7
- targetDate?: string;
8
5
  runState?: Record<string, unknown>;
9
6
  journal?: boolean;
10
7
  journalNote?: string;
@@ -2,7 +2,6 @@ import * as path from "node:path";
2
2
  import { getApi, SellableApiError } from "../api.js";
3
3
  import { appendIndexLine, appendJournalEvent, createRunJournal, renderBootstrapSection, renderPlanSection, renderTerminalSection, } from "../refill-journal.js";
4
4
  import { readRefillWorkspaceState } from "../refill-local-state.js";
5
- import { getRefillTargetPlan } from "./refill-target-plan.js";
6
5
  import { normalizeExplicitWorkspaceId, workspaceRequestOptions, } from "./workspace-context.js";
7
6
  const KNOWN_TOP_LEVEL_FIELDS = [
8
7
  "readOnly",
@@ -200,24 +199,12 @@ export async function getRefillPlanV2(input) {
200
199
  : buildRunStateFromLocalHints(workspaceId);
201
200
  let raw;
202
201
  try {
203
- const hasExplicitDateSelector = input.targetDate !== undefined ||
204
- input.untilDate !== undefined ||
205
- input.horizonSendDays !== undefined;
206
- raw = hasExplicitDateSelector
207
- ? await getRefillTargetPlan({
208
- workspaceId,
209
- intent: input.intent === "auto" ? undefined : (input.intent ?? undefined),
210
- senderIds: input.senderIds,
211
- targetDate: input.targetDate,
212
- untilDate: input.untilDate,
213
- horizonSendDays: input.horizonSendDays,
214
- })
215
- : await postRefillPlanV2({
216
- workspaceId,
217
- intent: input.intent ?? "auto",
218
- senderIds: input.senderIds,
219
- runState,
220
- }, workspaceId);
202
+ raw = await postRefillPlanV2({
203
+ workspaceId,
204
+ intent: input.intent ?? "auto",
205
+ senderIds: input.senderIds,
206
+ runState,
207
+ }, workspaceId);
221
208
  }
222
209
  catch (error) {
223
210
  if (!isWorkspaceAccessError(error))
@@ -251,16 +238,7 @@ export async function getRefillPlanV2(input) {
251
238
  workspaceResolution: "explicit",
252
239
  };
253
240
  }
254
- const rawRecord = isRecord(raw) ? raw : {};
255
- const target = isRecord(rawRecord.target) ? rawRecord.target : null;
256
- const sanitized = sanitizeEvergreenRefillPlanResult(target
257
- ? {
258
- ...rawRecord,
259
- plans: target.senderRefillPlans,
260
- globalActionQueue: target.globalActionQueue,
261
- packet: rawRecord,
262
- }
263
- : rawRecord);
241
+ const sanitized = sanitizeEvergreenRefillPlanResult(raw);
264
242
  const warnings = Array.isArray(sanitized.warnings)
265
243
  ? [...sanitized.warnings]
266
244
  : [];
@@ -0,0 +1,151 @@
1
+ type FindLeadsSnapshot = {
2
+ runId: string;
3
+ leadListId?: string;
4
+ version?: number;
5
+ status: string;
6
+ terminal?: boolean;
7
+ cancelled?: boolean;
8
+ [key: string]: unknown;
9
+ };
10
+ export declare const findLeadsRunToolDefinitions: readonly [{
11
+ readonly name: "bootstrap_find_leads";
12
+ readonly description: "Create or resume a durable campaignless Find Leads run and its lead list. No campaign or workingCampaignId is required.";
13
+ readonly inputSchema: {
14
+ readonly type: "object";
15
+ readonly properties: {
16
+ readonly bootstrapKey: {
17
+ readonly type: "string";
18
+ };
19
+ readonly name: {
20
+ readonly type: "string";
21
+ };
22
+ };
23
+ readonly required: readonly ["bootstrapKey"];
24
+ readonly additionalProperties: false;
25
+ };
26
+ }, {
27
+ readonly name: "get_find_leads_run";
28
+ readonly description: "Read the authoritative durable Find Leads snapshot.";
29
+ readonly inputSchema: {
30
+ readonly type: "object";
31
+ readonly properties: {
32
+ readonly runId: {
33
+ readonly type: "string";
34
+ };
35
+ };
36
+ readonly required: readonly ["runId"];
37
+ readonly additionalProperties: false;
38
+ };
39
+ }, {
40
+ readonly name: "update_find_leads_run";
41
+ readonly description: "Persist a provider/source/import approval receipt using optimistic concurrency.";
42
+ readonly inputSchema: {
43
+ readonly type: "object";
44
+ readonly properties: {
45
+ readonly runId: {
46
+ readonly type: "string";
47
+ };
48
+ readonly version: {
49
+ readonly type: "number";
50
+ };
51
+ readonly action: {
52
+ readonly type: "string";
53
+ readonly enum: readonly ["approve_provider", "approve_source_plan", "approve_import"];
54
+ };
55
+ readonly provider: {
56
+ readonly type: "string";
57
+ readonly enum: readonly ["sales-nav", "prospeo", "signal-discovery"];
58
+ };
59
+ readonly receipt: {
60
+ readonly type: "object";
61
+ };
62
+ };
63
+ readonly required: readonly ["runId", "version", "action", "receipt"];
64
+ readonly additionalProperties: false;
65
+ };
66
+ }, {
67
+ readonly name: "wait_for_find_leads_run";
68
+ readonly description: "Poll durable Find Leads state until it changes or reaches a terminal state. Progress notifications are optional hints only.";
69
+ readonly inputSchema: {
70
+ readonly type: "object";
71
+ readonly properties: {
72
+ readonly runId: {
73
+ readonly type: "string";
74
+ };
75
+ readonly timeoutMs: {
76
+ readonly type: "number";
77
+ };
78
+ readonly intervalMs: {
79
+ readonly type: "number";
80
+ };
81
+ };
82
+ readonly required: readonly ["runId"];
83
+ readonly additionalProperties: false;
84
+ };
85
+ }, {
86
+ readonly name: "cancel_find_leads";
87
+ readonly description: "Cancel a mutable Find Leads run with its current version.";
88
+ readonly inputSchema: {
89
+ readonly type: "object";
90
+ readonly properties: {
91
+ readonly runId: {
92
+ readonly type: "string";
93
+ };
94
+ readonly version: {
95
+ readonly type: "number";
96
+ };
97
+ };
98
+ readonly required: readonly ["runId", "version"];
99
+ readonly additionalProperties: false;
100
+ };
101
+ }, {
102
+ readonly name: "reissue_find_leads_watch_link";
103
+ readonly description: "Rotate and return a fresh one-time live Find Leads watch handoff. Old links are revoked and cannot be reconstructed.";
104
+ readonly inputSchema: {
105
+ readonly type: "object";
106
+ readonly properties: {
107
+ readonly runId: {
108
+ readonly type: "string";
109
+ };
110
+ readonly mode: {
111
+ readonly type: "string";
112
+ readonly enum: readonly ["claude", "codex", "hermes"];
113
+ };
114
+ };
115
+ readonly required: readonly ["runId"];
116
+ readonly additionalProperties: false;
117
+ };
118
+ }];
119
+ export declare function bootstrapFindLeads(input: {
120
+ bootstrapKey: string;
121
+ name?: string;
122
+ }): Promise<FindLeadsSnapshot>;
123
+ export declare function getFindLeadsRun(input: {
124
+ runId: string;
125
+ }): Promise<FindLeadsSnapshot>;
126
+ export declare function updateFindLeadsRun(input: {
127
+ runId: string;
128
+ version: number;
129
+ action: "approve_provider" | "approve_source_plan" | "approve_import";
130
+ provider?: "sales-nav" | "prospeo" | "signal-discovery";
131
+ receipt: Record<string, unknown>;
132
+ }): Promise<FindLeadsSnapshot>;
133
+ export declare function cancelFindLeads(input: {
134
+ runId: string;
135
+ version: number;
136
+ }): Promise<FindLeadsSnapshot>;
137
+ export declare function reissueFindLeadsWatchLink(input: {
138
+ runId: string;
139
+ mode?: "claude" | "codex" | "hermes";
140
+ }): Promise<{
141
+ runId: string;
142
+ leadListId: string;
143
+ watchUrl: string;
144
+ expiresAt: string;
145
+ }>;
146
+ export declare function waitForFindLeadsRun(input: {
147
+ runId: string;
148
+ timeoutMs?: number;
149
+ intervalMs?: number;
150
+ }): Promise<FindLeadsSnapshot>;
151
+ export {};
@@ -0,0 +1,98 @@
1
+ import { getApi } from "../api.js";
2
+ import { getConfig } from "../auth.js";
3
+ function requestOptions() {
4
+ const config = getConfig();
5
+ const workspaceId = config.activeWorkspaceId || config.workspaceId || null;
6
+ if (!workspaceId) {
7
+ throw new Error("No active Sellable workspace. Run list_workspaces then set_active_workspace and retry Find Leads.");
8
+ }
9
+ return { workspaceId };
10
+ }
11
+ export const findLeadsRunToolDefinitions = [
12
+ {
13
+ name: "bootstrap_find_leads",
14
+ description: "Create or resume a durable campaignless Find Leads run and its lead list. No campaign or workingCampaignId is required.",
15
+ inputSchema: {
16
+ type: "object",
17
+ properties: { bootstrapKey: { type: "string" }, name: { type: "string" } },
18
+ required: ["bootstrapKey"],
19
+ additionalProperties: false,
20
+ },
21
+ },
22
+ {
23
+ name: "get_find_leads_run",
24
+ description: "Read the authoritative durable Find Leads snapshot.",
25
+ inputSchema: { type: "object", properties: { runId: { type: "string" } }, required: ["runId"], additionalProperties: false },
26
+ },
27
+ {
28
+ name: "update_find_leads_run",
29
+ description: "Persist a provider/source/import approval receipt using optimistic concurrency.",
30
+ inputSchema: {
31
+ type: "object",
32
+ properties: {
33
+ runId: { type: "string" }, version: { type: "number" },
34
+ action: { type: "string", enum: ["approve_provider", "approve_source_plan", "approve_import"] },
35
+ provider: { type: "string", enum: ["sales-nav", "prospeo", "signal-discovery"] },
36
+ receipt: { type: "object" },
37
+ },
38
+ required: ["runId", "version", "action", "receipt"],
39
+ additionalProperties: false,
40
+ },
41
+ },
42
+ {
43
+ name: "wait_for_find_leads_run",
44
+ description: "Poll durable Find Leads state until it changes or reaches a terminal state. Progress notifications are optional hints only.",
45
+ inputSchema: {
46
+ type: "object",
47
+ properties: { runId: { type: "string" }, timeoutMs: { type: "number" }, intervalMs: { type: "number" } },
48
+ required: ["runId"], additionalProperties: false,
49
+ },
50
+ },
51
+ {
52
+ name: "cancel_find_leads",
53
+ description: "Cancel a mutable Find Leads run with its current version.",
54
+ inputSchema: { type: "object", properties: { runId: { type: "string" }, version: { type: "number" } }, required: ["runId", "version"], additionalProperties: false },
55
+ },
56
+ {
57
+ name: "reissue_find_leads_watch_link",
58
+ description: "Rotate and return a fresh one-time live Find Leads watch handoff. Old links are revoked and cannot be reconstructed.",
59
+ inputSchema: { type: "object", properties: { runId: { type: "string" }, mode: { type: "string", enum: ["claude", "codex", "hermes"] } }, required: ["runId"], additionalProperties: false },
60
+ },
61
+ ];
62
+ export async function bootstrapFindLeads(input) {
63
+ return getApi().post("/api/v3/find-leads/runs", input, requestOptions());
64
+ }
65
+ export async function getFindLeadsRun(input) {
66
+ return getApi().get(`/api/v3/find-leads/runs/${encodeURIComponent(input.runId)}`, requestOptions());
67
+ }
68
+ export async function updateFindLeadsRun(input) {
69
+ const kind = input.action === "approve_provider"
70
+ ? "provider_prompt"
71
+ : input.action === "approve_source_plan"
72
+ ? "source_plan"
73
+ : "import_approval";
74
+ const provider = input.provider?.replace(/-/g, "_");
75
+ return getApi().patch(`/api/v3/find-leads/runs/${encodeURIComponent(input.runId)}`, { action: "record_receipt", version: input.version, kind, receipt: input.receipt, ...(provider ? { provider } : {}) }, requestOptions());
76
+ }
77
+ export async function cancelFindLeads(input) {
78
+ return getApi().patch(`/api/v3/find-leads/runs/${encodeURIComponent(input.runId)}`, { action: "cancel", version: input.version }, requestOptions());
79
+ }
80
+ export async function reissueFindLeadsWatchLink(input) {
81
+ return getApi().post(`/api/v3/find-leads/runs/${encodeURIComponent(input.runId)}/watch-token`, { mode: input.mode ?? (process.env.CODEX_HOME ? "codex" : "claude") }, requestOptions());
82
+ }
83
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
84
+ export async function waitForFindLeadsRun(input) {
85
+ const timeoutMs = Math.min(Math.max(input.timeoutMs ?? 55_000, 25), 55_000);
86
+ const intervalMs = Math.min(Math.max(input.intervalMs ?? 2_000, 1), 10_000);
87
+ const startedAt = Date.now();
88
+ let previousVersion;
89
+ let snapshot = await getFindLeadsRun(input);
90
+ while (!snapshot.terminal && !snapshot.cancelled && Date.now() - startedAt < timeoutMs) {
91
+ if (previousVersion !== undefined && snapshot.version !== previousVersion)
92
+ return snapshot;
93
+ previousVersion = snapshot.version;
94
+ await sleep(intervalMs);
95
+ snapshot = await getFindLeadsRun(input);
96
+ }
97
+ return snapshot;
98
+ }