@sellable/mcp 0.1.557 → 0.1.559

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 +2 -13
  2. package/agents/registry.json +2 -2
  3. package/dist/api.js +6 -3
  4. package/dist/auth.d.ts +6 -0
  5. package/dist/auth.js +44 -2
  6. package/dist/refill-contract.d.ts +157 -0
  7. package/dist/refill-contract.js +487 -0
  8. package/dist/refill-run-client.d.ts +10 -0
  9. package/dist/refill-run-client.js +22 -0
  10. package/dist/refill-run-loop.d.ts +14 -2
  11. package/dist/refill-run-loop.js +169 -18
  12. package/dist/server.js +0 -23
  13. package/dist/tools/auth.d.ts +5 -0
  14. package/dist/tools/auth.js +49 -12
  15. package/dist/tools/campaign-message-preparation.d.ts +62 -0
  16. package/dist/tools/campaign-message-preparation.js +41 -0
  17. package/dist/tools/campaigns.js +2 -2
  18. package/dist/tools/csv-dnc.js +2 -2
  19. package/dist/tools/evergreen-refill-plan.d.ts +3 -0
  20. package/dist/tools/evergreen-refill-plan.js +29 -7
  21. package/dist/tools/leads.d.ts +32 -317
  22. package/dist/tools/leads.js +10 -171
  23. package/dist/tools/model-quality.js +6 -4
  24. package/dist/tools/prompts.d.ts +3 -3
  25. package/dist/tools/prompts.js +15 -7
  26. package/dist/tools/provider-preflight.d.ts +2 -65
  27. package/dist/tools/provider-preflight.js +10 -97
  28. package/dist/tools/readiness.d.ts +5 -89
  29. package/dist/tools/readiness.js +0 -66
  30. package/dist/tools/refill-executors.d.ts +38 -0
  31. package/dist/tools/refill-executors.js +222 -3
  32. package/dist/tools/refill-sends-v2.d.ts +118 -1
  33. package/dist/tools/refill-sends-v2.js +312 -3
  34. package/dist/tools/refill-sends.d.ts +678 -32
  35. package/dist/tools/refill-sends.js +274 -13
  36. package/dist/tools/refill-target-plan.js +486 -14
  37. package/dist/tools/registry.d.ts +115 -330
  38. package/dist/tools/registry.js +1 -7
  39. package/dist/tools/scheduler-fill-capacity.js +1 -1
  40. package/dist/tools/scheduler-run.d.ts +71 -0
  41. package/dist/tools/scheduler-run.js +203 -1
  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 +8 -3
  45. package/dist/tools/workspace-export.js +2 -2
  46. package/dist/tools/workspaces.d.ts +48 -2
  47. package/dist/tools/workspaces.js +48 -5
  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 +630 -48
  53. package/skills/refill-sends/SKILL.md +94 -353
  54. package/skills/refill-sends-v2/SKILL.md +6 -6
  55. package/skills/refill-sends-v2-workflow/SKILL.md +5 -5
  56. package/skills/refill-sends-v2-workflow/core/flow.v1.json +8 -8
  57. package/skills/refill-sends-workflow/SKILL.md +100 -743
  58. package/skills/refill-sends-workflow/core/contract.v2.json +543 -0
  59. package/skills/refill-sends-workflow/core/flow.v1.json +185 -1
  60. package/dist/tools/find-leads-runs.d.ts +0 -151
  61. package/dist/tools/find-leads-runs.js +0 -98
  62. package/skills/find-leads-v2/SKILL.md +0 -70
  63. package/skills/find-leads-v2/core/flow.v1.json +0 -31
@@ -1,11 +1,34 @@
1
+ import { createHash } from "node:crypto";
2
+ import { REFILL_CONTRACT_CACHE_VERSION, REFILL_CONTRACT_HASH, REFILL_CONTRACT_VERSION, renderRefillSafetyGuidance, } from "../refill-contract.js";
1
3
  import { appendIndexLine, appendJournalEvent, createRunJournal, renderBootstrapSection, renderCrashedAbandonedSection, renderExecutionEventSection, renderPlanSection, renderTerminalSection, } from "../refill-journal.js";
2
4
  import { writeRefillWorkspaceState } from "../refill-local-state.js";
3
- import { advanceRefillRunGateRemote, completeRefillRun, heartbeatRefillRun, recordRunOutcome, recordRunPlanned, refillRunStatus, startRefillRunRemote, } from "../refill-run-client.js";
5
+ import { advanceRefillRunGateRemote, completeRefillRun, heartbeatRefillRun, recordRunOutcome, recordRunPlanned, refillRunStatus, startRefillRunRemote, yieldRefillRun, } from "../refill-run-client.js";
4
6
  import { runRefillV2Loop } from "../refill-run-loop.js";
5
- import { getPrepareCampaignMessagesStatus } from "./campaign-message-preparation.js";
7
+ import { getPrepareCampaignMessagesStatus, stopSatisfiedPrepareCampaignMessages, } from "./campaign-message-preparation.js";
6
8
  import { getRefillPlanV2 } from "./evergreen-refill-plan.js";
7
9
  import { executeOneYoloPrimitive, executeStartCampaignPrimitive, prepareRowSelectorValue, refillPrepareRequestHash, refreshPaidInmailCreditsWithRetry, } from "./refill-executors.js";
8
10
  import { runSchedulerSweep } from "./scheduler-run.js";
11
+ function installedIdentityEnv(env, name, fallback) {
12
+ const value = env[name]?.trim();
13
+ return value || fallback;
14
+ }
15
+ export function resolveRefillV2InstalledIdentity(env = process.env) {
16
+ const mcpVersion = installedIdentityEnv(env, "SELLABLE_MCP_PACKAGE_VERSION", "unresolved-mcp");
17
+ return {
18
+ mcpVersion,
19
+ installVersion: installedIdentityEnv(env, "SELLABLE_INSTALL_PACKAGE_VERSION", "unresolved-install"),
20
+ pluginVersion: installedIdentityEnv(env, "SELLABLE_CODEX_PLUGIN_VERSION", "unresolved-plugin"),
21
+ cacheIdentity: installedIdentityEnv(env, "SELLABLE_PLUGIN_CACHE_IDENTITY", `mcp-package:${mcpVersion}`),
22
+ };
23
+ }
24
+ export const REFILL_V2_RUNTIME_IDENTITY = {
25
+ contract: {
26
+ version: REFILL_CONTRACT_VERSION,
27
+ hash: REFILL_CONTRACT_HASH,
28
+ cacheKey: REFILL_CONTRACT_CACHE_VERSION,
29
+ },
30
+ installed: resolveRefillV2InstalledIdentity(),
31
+ };
9
32
  const FORBIDDEN_ACTIONS = [
10
33
  "Do not schedule sends.",
11
34
  "Do not send messages.",
@@ -18,7 +41,11 @@ const BOUNDED_AUTHORITY = "Within the refill run, bounded approvals, preparation
18
41
  export const refillSendsV2ToolDefinitions = [
19
42
  {
20
43
  name: "refill_sends_v2",
21
- description: "Execute the refill sends v2 loop with run-record fencing, packet re-planning, shared refill executors, and resumable dry-run/real-run modes.",
44
+ description: [
45
+ "Execute the server-internal refill loop behind public refill_sends; it is retained for dispatch compatibility but is not advertised.",
46
+ `contractHash=${REFILL_CONTRACT_HASH}`,
47
+ renderRefillSafetyGuidance(),
48
+ ].join(" "),
22
49
  inputSchema: {
23
50
  type: "object",
24
51
  properties: {
@@ -51,6 +78,18 @@ export const refillSendsV2ToolDefinitions = [
51
78
  type: "string",
52
79
  enum: ["auto", "evergreen", "plain", "active"],
53
80
  },
81
+ targetDate: {
82
+ type: "string",
83
+ description: "Exact sender-local YYYY-MM-DD date.",
84
+ },
85
+ untilDate: {
86
+ type: "string",
87
+ description: "Inclusive sender-local YYYY-MM-DD through-date.",
88
+ },
89
+ horizonSendDays: {
90
+ type: "number",
91
+ description: "Positive sender-local send-day horizon.",
92
+ },
54
93
  },
55
94
  required: ["workspaceId"],
56
95
  additionalProperties: false,
@@ -68,6 +107,234 @@ function resumeHandle(input) {
68
107
  ? { runId: input.runId, fence: input.fence }
69
108
  : undefined;
70
109
  }
110
+ function validDate(value, field) {
111
+ const trimmed = value.trim();
112
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(trimmed) ||
113
+ new Date(`${trimmed}T00:00:00.000Z`).toISOString().slice(0, 10) !== trimmed) {
114
+ throw new Error(`${field} must be a valid YYYY-MM-DD date.`);
115
+ }
116
+ return trimmed;
117
+ }
118
+ function normalizeDateSelector(input) {
119
+ const selected = [
120
+ input.targetDate !== undefined,
121
+ input.untilDate !== undefined,
122
+ input.horizonSendDays !== undefined,
123
+ ].filter(Boolean).length;
124
+ if (selected > 1) {
125
+ throw new Error("Provide exactly one refill date selector.");
126
+ }
127
+ if (input.targetDate !== undefined) {
128
+ const targetDate = validDate(input.targetDate, "targetDate");
129
+ return {
130
+ dateSelector: { kind: "target_date", targetDate },
131
+ targetDate,
132
+ };
133
+ }
134
+ if (input.untilDate !== undefined) {
135
+ const untilDate = validDate(input.untilDate, "untilDate");
136
+ return {
137
+ dateSelector: { kind: "until_date", untilDate },
138
+ untilDate,
139
+ };
140
+ }
141
+ if (input.horizonSendDays !== undefined) {
142
+ if (!Number.isInteger(input.horizonSendDays) ||
143
+ input.horizonSendDays <= 0) {
144
+ throw new Error("horizonSendDays must be a positive integer.");
145
+ }
146
+ return {
147
+ dateSelector: {
148
+ kind: "horizon_send_days",
149
+ horizonSendDays: input.horizonSendDays,
150
+ },
151
+ horizonSendDays: input.horizonSendDays,
152
+ };
153
+ }
154
+ return { dateSelector: { kind: "scheduler_default" } };
155
+ }
156
+ function record(value) {
157
+ return value && typeof value === "object" && !Array.isArray(value)
158
+ ? value
159
+ : null;
160
+ }
161
+ function text(value) {
162
+ return typeof value === "string" && value.trim() ? value.trim() : null;
163
+ }
164
+ function list(value) {
165
+ return Array.isArray(value) ? value : [];
166
+ }
167
+ function sortedUnique(values) {
168
+ return [
169
+ ...new Set(values.filter((value) => Boolean(value))),
170
+ ].sort((left, right) => left.localeCompare(right));
171
+ }
172
+ function sha256(value) {
173
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex");
174
+ }
175
+ function targetPlanParts(targetPlan) {
176
+ const target = record(targetPlan.target) ?? {};
177
+ const plans = list(target.senderRefillPlans)
178
+ .map(record)
179
+ .filter((value) => value != null);
180
+ const actions = list(target.globalActionQueue)
181
+ .map(record)
182
+ .filter((value) => value != null);
183
+ return { target, plans, actions };
184
+ }
185
+ function stableIdsFromTargetPlan(targetPlan, input) {
186
+ const { plans, actions } = targetPlanParts(targetPlan);
187
+ const actionRecords = actions.map((action) => ({
188
+ action,
189
+ ids: record(action.ids) ?? {},
190
+ toolInput: record(action.toolInput) ?? {},
191
+ }));
192
+ const senderIds = sortedUnique([
193
+ ...(input.senderIds ?? []),
194
+ ...plans.map((plan) => text(plan.senderId)),
195
+ ...actionRecords.map(({ ids, toolInput }) => text(ids.senderId) ?? text(toolInput.senderId)),
196
+ ]);
197
+ const campaignIds = sortedUnique(actionRecords.map(({ ids, toolInput }) => text(ids.campaignId) ?? text(toolInput.campaignId)));
198
+ const tableIds = sortedUnique(actionRecords.map(({ ids, toolInput }) => text(ids.tableId) ?? text(toolInput.tableId)));
199
+ const actionIds = sortedUnique(actionRecords.map(({ action }) => text(action.actionKey) ?? text(action.type)));
200
+ const senderTimezones = Object.fromEntries(plans
201
+ .flatMap((plan) => list(plan.selectedDays))
202
+ .map(record)
203
+ .filter((day) => day != null)
204
+ .map((day) => [
205
+ text(day.senderId),
206
+ text(day.senderTimeZone) ?? text(day.timeZone),
207
+ ])
208
+ .filter((entry) => entry[0] != null && entry[1] != null)
209
+ .sort(([left], [right]) => left.localeCompare(right)));
210
+ return {
211
+ senderIds,
212
+ campaignIds,
213
+ tableIds,
214
+ actionIds,
215
+ senderTimezones,
216
+ actions: actionRecords,
217
+ };
218
+ }
219
+ function buildApprovalEnvelope(input) {
220
+ const workspaceId = normalizeWorkspaceId(input.workspaceId);
221
+ const date = normalizeDateSelector(input);
222
+ const stable = stableIdsFromTargetPlan(input.targetPlan, input);
223
+ const schedulerAction = stable.actions.find(({ action }) => action.type === "run_scheduler_sweep");
224
+ const schedulerInput = schedulerAction?.toolInput ?? {};
225
+ const expectedTargets = list(schedulerInput.expectedTargets);
226
+ const expectedTargetsHash = text(schedulerInput.expectedTargetsHash) ?? sha256(expectedTargets);
227
+ const maxPlacements = typeof schedulerInput.maxPlacements === "number" &&
228
+ Number.isInteger(schedulerInput.maxPlacements) &&
229
+ schedulerInput.maxPlacements > 0
230
+ ? schedulerInput.maxPlacements
231
+ : 150;
232
+ const scope = {
233
+ version: 2,
234
+ workspaceId,
235
+ intent: input.intent ?? "auto",
236
+ senderIds: stable.senderIds,
237
+ campaignIds: stable.campaignIds,
238
+ tableIds: stable.tableIds,
239
+ actionIds: stable.actionIds,
240
+ approvalMode: input.approvalMode ?? "approve",
241
+ dateSelector: date.dateSelector,
242
+ senderTimezones: stable.senderTimezones,
243
+ expectedTargetsHash,
244
+ maxPlacements,
245
+ contract: { ...REFILL_V2_RUNTIME_IDENTITY.contract },
246
+ installed: { ...REFILL_V2_RUNTIME_IDENTITY.installed },
247
+ };
248
+ const scopeHash = sha256(scope);
249
+ const targetShapeRevision = text(input.targetPlan.targetShapeRevision) ??
250
+ text(record(input.targetPlan.target)?.targetShapeRevision) ??
251
+ "missing_target_shape_revision";
252
+ const actionFingerprint = text(stable.actions[0]?.action.actionKey) ??
253
+ text(stable.actions[0]?.action.type) ??
254
+ "no_action";
255
+ const sideEffectEnvelope = sortedUnique(stable.actions.map(({ action }) => text(action.type)));
256
+ const approvalExpiresAt = input.approvalExpiresAt ??
257
+ new Date((input.now ?? new Date()).getTime() + 15 * 60_000).toISOString();
258
+ const fingerprintFields = {
259
+ scopeHash,
260
+ targetShapeRevision,
261
+ actionFingerprint,
262
+ expectedTargetsHash,
263
+ sideEffectEnvelope,
264
+ maxPlacements,
265
+ approvalExpiresAt,
266
+ };
267
+ const approval = {
268
+ status: "awaiting_approval",
269
+ ...fingerprintFields,
270
+ approvalFingerprint: sha256(fingerprintFields),
271
+ };
272
+ return { scope, scopeHash, approval, date };
273
+ }
274
+ export async function prepareRefillSendsV2Approval(input) {
275
+ const envelope = buildApprovalEnvelope(input);
276
+ const started = await startRefillRunRemote({
277
+ workspaceId: envelope.scope.workspaceId,
278
+ config: {
279
+ scope: envelope.scope,
280
+ scopeHash: envelope.scopeHash,
281
+ approval: envelope.approval,
282
+ },
283
+ runState: {
284
+ version: 1,
285
+ scope: envelope.scope,
286
+ scopeHash: envelope.scopeHash,
287
+ approval: envelope.approval,
288
+ senderCursors: [],
289
+ laneCooldowns: [],
290
+ passRates: [],
291
+ },
292
+ });
293
+ const result = record(started) ?? {};
294
+ const runId = text(result.runId);
295
+ const fence = typeof result.fence === "number" && Number.isFinite(result.fence)
296
+ ? result.fence
297
+ : null;
298
+ if (!runId || fence == null) {
299
+ return {
300
+ status: "blocked",
301
+ blocker: text(result.blocker) ?? "approval_run_unavailable",
302
+ response: started,
303
+ };
304
+ }
305
+ const persistedConfig = record(result.config);
306
+ const persistedScope = record(persistedConfig?.scope) ?? envelope.scope;
307
+ const persistedApproval = record(persistedConfig?.approval) ??
308
+ envelope.approval;
309
+ return {
310
+ ...persistedApproval,
311
+ status: "awaiting_approval",
312
+ runId,
313
+ fence,
314
+ scope: persistedScope,
315
+ };
316
+ }
317
+ function validateApprovalEcho(envelope, echo, now = new Date()) {
318
+ const fields = [
319
+ "scopeHash",
320
+ "targetShapeRevision",
321
+ "actionFingerprint",
322
+ "expectedTargetsHash",
323
+ "approvalExpiresAt",
324
+ "approvalFingerprint",
325
+ ];
326
+ const changed = fields.filter((field) => echo[field] !== envelope.approval[field]);
327
+ if (Date.parse(envelope.approval.approvalExpiresAt) <= now.getTime()) {
328
+ return { ok: false, blocker: "approval_expired", changed };
329
+ }
330
+ return changed.length === 0
331
+ ? { ok: true }
332
+ : {
333
+ ok: false,
334
+ blocker: "approval_fingerprint_mismatch",
335
+ changed,
336
+ };
337
+ }
71
338
  async function maybeAddLostFenceGuidance(result, input) {
72
339
  if (result.status !== "blocked" ||
73
340
  result.blocker !== "lease_lost" ||
@@ -94,12 +361,45 @@ async function maybeAddLostFenceGuidance(result, input) {
94
361
  }
95
362
  export async function refillSendsV2Command(input) {
96
363
  const workspaceId = normalizeWorkspaceId(input.workspaceId);
364
+ const date = normalizeDateSelector(input);
365
+ let resolvedScope = input.scope;
366
+ let resolvedScopeHash = input.scopeHash;
367
+ let resolvedApproval = input.approval;
368
+ if (input.targetPlan && input.approvalEcho) {
369
+ const envelope = buildApprovalEnvelope({
370
+ workspaceId,
371
+ intent: input.intent,
372
+ senderIds: input.senderIds,
373
+ approvalMode: input.approvalMode,
374
+ targetDate: input.targetDate,
375
+ untilDate: input.untilDate,
376
+ horizonSendDays: input.horizonSendDays,
377
+ targetPlan: input.targetPlan,
378
+ approvalExpiresAt: input.approvalEcho.approvalExpiresAt,
379
+ });
380
+ const validation = validateApprovalEcho(envelope, input.approvalEcho);
381
+ if (!validation.ok) {
382
+ return {
383
+ status: "blocked",
384
+ blocker: validation.blocker,
385
+ guidance: "The durable refill approval no longer matches fresh scope; render and approve a new packet before mutation.",
386
+ report: { changed: validation.changed, fresh: envelope.approval },
387
+ readOnly: false,
388
+ forbiddenActions: FORBIDDEN_ACTIONS,
389
+ boundedAuthority: BOUNDED_AUTHORITY,
390
+ };
391
+ }
392
+ resolvedScope = envelope.scope;
393
+ resolvedScopeHash = envelope.scopeHash;
394
+ resolvedApproval = envelope.approval;
395
+ }
97
396
  if (input.dryRun) {
98
397
  const plan = await getRefillPlanV2({
99
398
  workspaceId,
100
399
  intent: input.intent,
101
400
  senderIds: input.senderIds,
102
401
  journal: true,
402
+ ...date,
103
403
  });
104
404
  return {
105
405
  ...plan,
@@ -115,11 +415,16 @@ export async function refillSendsV2Command(input) {
115
415
  senderIds: input.senderIds,
116
416
  approvalMode: input.approvalMode ?? "approve",
117
417
  resume: resumeHandle(input),
418
+ ...date,
419
+ scope: resolvedScope,
420
+ scopeHash: resolvedScopeHash,
421
+ approval: resolvedApproval,
118
422
  }, {
119
423
  runClient: {
120
424
  startRefillRunRemote,
121
425
  refillRunStatus,
122
426
  heartbeatRefillRun,
427
+ yieldRefillRun,
123
428
  advanceRefillRunGateRemote,
124
429
  recordRunPlanned,
125
430
  recordRunOutcome,
@@ -131,12 +436,16 @@ export async function refillSendsV2Command(input) {
131
436
  senderIds: planInput.senderIds,
132
437
  runState: planInput.runState,
133
438
  journal: planInput.journal,
439
+ targetDate: planInput.targetDate,
440
+ untilDate: planInput.untilDate,
441
+ horizonSendDays: planInput.horizonSendDays,
134
442
  }),
135
443
  executors: {
136
444
  executeOneYoloPrimitive,
137
445
  executeStartCampaignPrimitive,
138
446
  refreshPaidInmailCreditsWithRetry,
139
447
  getPrepareCampaignMessagesStatus,
448
+ stopSatisfiedPrepareCampaignMessages,
140
449
  refillPrepareRequestHash,
141
450
  prepareRowSelectorValue,
142
451
  },