@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
@@ -59,6 +59,16 @@ const SCHEDULER_RUN_NEXT_ACTIONS = new Set([
59
59
  function isRecord(value) {
60
60
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
61
61
  }
62
+ function planScopeInput(input) {
63
+ return {
64
+ ...(input.targetDate ? { targetDate: input.targetDate } : {}),
65
+ ...(input.untilDate ? { untilDate: input.untilDate } : {}),
66
+ ...(input.horizonSendDays !== undefined
67
+ ? { horizonSendDays: input.horizonSendDays }
68
+ : {}),
69
+ ...(input.scopeHash ? { scopeHash: input.scopeHash } : {}),
70
+ };
71
+ }
62
72
  function stringValue(value) {
63
73
  return typeof value === "string" && value.trim() ? value.trim() : null;
64
74
  }
@@ -256,10 +266,8 @@ function sanitizeSchedulerRunReceipt(raw) {
256
266
  const prefilteredCells = numberValue(rawReceipt.prefilteredCells) ?? 0;
257
267
  const tablesFilteredForSenderMismatch = numberValue(rawReceipt.tablesFilteredForSenderMismatch) ?? 0;
258
268
  dominantSkipReason = dominantSchedulerRunSkipReason(skipReasons);
259
- dominantPrefilterReason =
260
- dominantSchedulerRunCountReason(prefilterReasons);
261
- dominantDeferredReason =
262
- dominantSchedulerRunSkipReason(deferredReasons);
269
+ dominantPrefilterReason = dominantSchedulerRunCountReason(prefilterReasons);
270
+ dominantDeferredReason = dominantSchedulerRunSkipReason(deferredReasons);
263
271
  recommendedNextAction = schedulerRunRecommendedAction({
264
272
  rawAction: stringValue(rawReceipt.nextAction),
265
273
  readyCellsFound,
@@ -353,11 +361,17 @@ function actionSenderId(action) {
353
361
  }
354
362
  function fingerprintFromPlan(plan) {
355
363
  const packet = recordValue(plan.packet) ?? {};
364
+ const targetShapeRevision = stringValue(packet.targetShapeRevision) ??
365
+ stringValue(packet.targetRevision) ??
366
+ stringValue(plan.targetShapeRevision) ??
367
+ stringValue(plan.targetRevision);
356
368
  return {
357
- planRevision: stringValue(plan.planRevision),
369
+ // Evergreen packets carry their own semantic plan revision. Regular
370
+ // active-campaign packets intentionally do not, so their immutable target
371
+ // shape is the durable planning revision for fencing and recovery.
372
+ planRevision: stringValue(plan.planRevision) ?? targetShapeRevision,
358
373
  stateRevision: stringValue(plan.stateRevision) ?? stringValue(packet.stateRevision),
359
- targetShapeRevision: stringValue(packet.targetShapeRevision) ??
360
- stringValue(packet.targetRevision),
374
+ targetShapeRevision,
361
375
  };
362
376
  }
363
377
  function fingerprintChanged(expected, actual) {
@@ -429,6 +443,43 @@ function normalizeDoneReason(value) {
429
443
  function runStateProgress(ctx) {
430
444
  return recordValue(ctx.runState.progress) ?? {};
431
445
  }
446
+ function prepCircuitFrom(value) {
447
+ const root = recordValue(value);
448
+ const result = recordValue(root?.result);
449
+ const progress = recordValue(root?.progress);
450
+ return (recordValue(root?.circuit) ??
451
+ recordValue(result?.circuit) ??
452
+ recordValue(progress?.circuit) ??
453
+ null);
454
+ }
455
+ function openCircuitWaits(circuit, now) {
456
+ if (stringValue(circuit?.state) !== "open")
457
+ return false;
458
+ const nextProbeAt = stringValue(circuit?.nextProbeAt);
459
+ return Boolean(nextProbeAt && Date.parse(nextProbeAt) > now.getTime());
460
+ }
461
+ function activePreparationScopeSatisfied(plan, action) {
462
+ const packet = recordValue(plan.packet) ?? plan;
463
+ const target = recordValue(packet.target) ?? recordValue(plan.target) ?? {};
464
+ const senderId = actionSenderId(action);
465
+ const toolInput = actionToolInput(action);
466
+ const targetDate = stringValue(toolInput.targetDate);
467
+ const refillLaneKey = stringValue(toolInput.refillLaneKey);
468
+ const senderPlans = arrayValue(target.senderRefillPlans).filter(isRecord);
469
+ const ledgers = senderPlans
470
+ .filter((senderPlan) => !senderId || stringValue(senderPlan.senderId) === senderId)
471
+ .flatMap((senderPlan) => arrayValue(senderPlan.laneLedgers).filter(isRecord));
472
+ const matching = ledgers.filter((ledger) => (!senderId || stringValue(ledger.senderId) === senderId) &&
473
+ (!targetDate || stringValue(ledger.targetDate) === targetDate) &&
474
+ (!refillLaneKey || stringValue(ledger.refillLaneKey) === refillLaneKey));
475
+ return (matching.length > 0 &&
476
+ matching.every((ledger) => {
477
+ const checks = recordValue(ledger.identityChecks);
478
+ return (numberValue(ledger.initialAllocatablePrepDeficit) === 0 &&
479
+ checks != null &&
480
+ Object.values(checks).every((value) => value === true));
481
+ }));
482
+ }
432
483
  function mergeProgress(ctx, updates) {
433
484
  return {
434
485
  ...runStateProgress(ctx),
@@ -672,14 +723,28 @@ async function createJournal(input, deps, runId) {
672
723
  }
673
724
  async function startOrResume(input, deps) {
674
725
  const initialRunState = buildRunStateFromLocalHints(input.workspaceId);
726
+ const scopedInitialRunState = input.scope
727
+ ? {
728
+ ...initialRunState,
729
+ scope: input.scope,
730
+ scopeHash: input.scopeHash,
731
+ approval: input.approval,
732
+ }
733
+ : initialRunState;
675
734
  const started = await deps.runClient.startRefillRunRemote({
676
735
  workspaceId: input.workspaceId,
677
- config: {
678
- intent: input.intent ?? "auto",
679
- senderIds: input.senderIds,
680
- approvalMode: input.approvalMode ?? "approve",
681
- },
682
- runState: initialRunState,
736
+ config: input.scope
737
+ ? {
738
+ scope: input.scope,
739
+ scopeHash: input.scopeHash,
740
+ approval: input.approval,
741
+ }
742
+ : {
743
+ intent: input.intent ?? "auto",
744
+ senderIds: input.senderIds,
745
+ approvalMode: input.approvalMode ?? "approve",
746
+ },
747
+ runState: scopedInitialRunState,
683
748
  resume: input.resume,
684
749
  });
685
750
  if (isLeaseLost(started)) {
@@ -712,7 +777,7 @@ async function startOrResume(input, deps) {
712
777
  const gate = (stringValue(result.gate) ?? "g0_bootstrap");
713
778
  const gateSeq = numberValue(result.gateSeq) ?? 0;
714
779
  const runState = recordValue(result.runState) ??
715
- recordValue(initialRunState) ??
780
+ recordValue(scopedInitialRunState) ??
716
781
  {
717
782
  version: 1,
718
783
  senderCursors: [],
@@ -755,6 +820,7 @@ async function gateBootstrap(input, deps, ctx) {
755
820
  senderIds: input.senderIds,
756
821
  runState: ctx.runState,
757
822
  journal: false,
823
+ ...planScopeInput(input),
758
824
  });
759
825
  const paidCredit = arrayValue(recordValue(plan.bootstrap)?.paidCredit).filter(isRecord);
760
826
  const refreshedSenderIds = new Set(arrayValue(recordValue(ctx.runState.creditTrust)?.senderIds)
@@ -901,6 +967,7 @@ async function gatePlan(input, deps, ctx) {
901
967
  senderIds: input.senderIds,
902
968
  runState: ctx.runState,
903
969
  journal: false,
970
+ ...planScopeInput(input),
904
971
  });
905
972
  if (plan.blocker === "workspace_access") {
906
973
  return {
@@ -960,6 +1027,17 @@ async function gatePlan(input, deps, ctx) {
960
1027
  fallbackSummary: line.fallbackSummary,
961
1028
  }));
962
1029
  const fingerprint = fingerprintFromPlan(plan);
1030
+ const persistedCircuit = recordValue(runStateProgress(ctx).prepCircuit);
1031
+ if (stringValue(action.type) === "prepare_messages" &&
1032
+ openCircuitWaits(persistedCircuit, deps.now?.() ?? new Date())) {
1033
+ const heartbeat = await heartbeatAndContinue(input, deps, ctx);
1034
+ if (heartbeat.status !== "in_progress")
1035
+ return heartbeat;
1036
+ return {
1037
+ ...heartbeat,
1038
+ guidance: `Preparation circuit remains open until ${String(persistedCircuit?.nextProbeAt)}; re-invoke with this handle after the probe deadline.`,
1039
+ };
1040
+ }
963
1041
  const noProgress = nextNoProgressState(ctx, plan, action);
964
1042
  if (noProgress.repeatCount >= 3) {
965
1043
  return completeTerminal(input, deps, ctx, "blocked", {
@@ -970,10 +1048,17 @@ async function gatePlan(input, deps, ctx) {
970
1048
  guidance: "The same refill rung repeated three times with no coverage, frontier, or lane movement.",
971
1049
  });
972
1050
  }
1051
+ const circuitProgress = stringValue(action.type) === "prepare_messages" &&
1052
+ stringValue(persistedCircuit?.state) === "open"
1053
+ ? { ...persistedCircuit, state: "half_open" }
1054
+ : persistedCircuit;
973
1055
  const nextRunState = mergeRunState(ctx.runState, {
974
1056
  lastPlan: fingerprint,
975
1057
  headAction: action,
976
- progress: mergeProgress(ctx, { noProgress }),
1058
+ progress: mergeProgress(ctx, {
1059
+ noProgress,
1060
+ ...(circuitProgress ? { prepCircuit: circuitProgress } : {}),
1061
+ }),
977
1062
  });
978
1063
  const advanced = await deps.runClient.advanceRefillRunGateRemote({
979
1064
  workspaceId: input.workspaceId,
@@ -1010,6 +1095,7 @@ async function verifyRecoveryPoint(input, deps, ctx) {
1010
1095
  senderIds: input.senderIds,
1011
1096
  runState: ctx.runState,
1012
1097
  journal: false,
1098
+ ...planScopeInput(input),
1013
1099
  });
1014
1100
  const changed = fingerprintChanged(expected, fingerprintFromPlan(fresh));
1015
1101
  if (changed.length > 0) {
@@ -1079,6 +1165,9 @@ async function gateExecute(input, deps, ctx) {
1079
1165
  dispatchKey: stringValue(action.type),
1080
1166
  evergreenRung: stringValue(action.evergreenRung),
1081
1167
  sideEffectClass: stringValue(action.sideEffectClass),
1168
+ scopeHash: input.scopeHash ?? null,
1169
+ expectedTargetsHash: stringValue(input.scope?.expectedTargetsHash) ?? null,
1170
+ dateSelector: recordValue(input.scope?.dateSelector),
1082
1171
  };
1083
1172
  const plannedResult = await deps.runClient.recordRunPlanned({
1084
1173
  workspaceId: input.workspaceId,
@@ -1172,6 +1261,12 @@ async function advanceBackToPlan(input, deps, ctx, runStateUpdates = {}) {
1172
1261
  async function verifyPrepareAction(input, deps, ctx, action, budgets) {
1173
1262
  const getStatus = deps.executors.getPrepareCampaignMessagesStatus;
1174
1263
  const jobId = activeJobIdFromOutcome(ctx.runState.lastOutcome);
1264
+ const executionCircuit = prepCircuitFrom(ctx.runState.lastOutcome);
1265
+ if (!jobId && executionCircuit) {
1266
+ return advanceBackToPlan(input, deps, ctx, {
1267
+ progress: mergeProgress(ctx, { prepCircuit: executionCircuit }),
1268
+ });
1269
+ }
1175
1270
  if (!getStatus || !jobId) {
1176
1271
  return {
1177
1272
  status: "in_progress",
@@ -1211,6 +1306,58 @@ async function verifyPrepareAction(input, deps, ctx, action, budgets) {
1211
1306
  }),
1212
1307
  });
1213
1308
  }
1309
+ if (ownJob &&
1310
+ !isTerminalJobStatus(latestStatus) &&
1311
+ deps.executors.stopSatisfiedPrepareCampaignMessages) {
1312
+ const fresh = await deps.readPlan({
1313
+ workspaceId: input.workspaceId,
1314
+ intent: input.intent,
1315
+ senderIds: input.senderIds,
1316
+ runState: ctx.runState,
1317
+ journal: false,
1318
+ ...planScopeInput(input),
1319
+ });
1320
+ ctx.plan = fresh;
1321
+ if (activePreparationScopeSatisfied(fresh, action)) {
1322
+ const plannedStop = await deps.runClient.recordRunPlanned({
1323
+ workspaceId: input.workspaceId,
1324
+ runId: ctx.runId,
1325
+ fence: ctx.fence,
1326
+ gate: ctx.gate,
1327
+ gateSeq: ctx.gateSeq,
1328
+ planned: {
1329
+ toolName: "stop_satisfied_campaign_message_preparation",
1330
+ toolInput: { jobId },
1331
+ dispatchKey: `stop_satisfied:${jobId}`,
1332
+ sideEffectClass: "message_preparation_stop",
1333
+ scopeHash: input.scopeHash ?? null,
1334
+ },
1335
+ });
1336
+ if (isLeaseLost(plannedStop))
1337
+ return leaseLostResult(ctx);
1338
+ const stopEventId = stringValue(recordValue(plannedStop)?.eventId) ??
1339
+ `${ctx.runId}:${ctx.gateSeq}:stop`;
1340
+ const stopReceipt = await deps.executors.stopSatisfiedPrepareCampaignMessages({
1341
+ workspaceId: input.workspaceId,
1342
+ jobId,
1343
+ expectedUpdatedAt: stringValue(statusRecord.updatedAt) ?? undefined,
1344
+ });
1345
+ await deps.runClient.recordRunOutcome({
1346
+ workspaceId: input.workspaceId,
1347
+ runId: ctx.runId,
1348
+ fence: ctx.fence,
1349
+ eventId: stopEventId,
1350
+ did: { jobId },
1351
+ outcome: stopReceipt,
1352
+ });
1353
+ return advanceBackToPlan(input, deps, ctx, {
1354
+ progress: mergeProgress(ctx, {
1355
+ prepStopReceipt: stopReceipt,
1356
+ prepStoppedAt: (deps.now?.() ?? new Date()).toISOString(),
1357
+ }),
1358
+ });
1359
+ }
1360
+ }
1214
1361
  if (isTerminalJobStatus(latestStatus)) {
1215
1362
  const preparedCount = preparedCountFromStatus(latestStatus);
1216
1363
  const checkedRows = checkedRowsFromStatus(latestStatus);
@@ -1278,6 +1425,7 @@ async function verifyReadOnlyWait(input, deps, ctx, budgets, waitKind) {
1278
1425
  senderIds: input.senderIds,
1279
1426
  runState: ctx.runState,
1280
1427
  journal: false,
1428
+ ...planScopeInput(input),
1281
1429
  });
1282
1430
  ctx.plan = fresh;
1283
1431
  const doneReason = doneReasonFromPlan(fresh);
@@ -1360,6 +1508,7 @@ async function verifySchedulerWait(input, deps, ctx, action, budgets) {
1360
1508
  senderIds: input.senderIds,
1361
1509
  runState: ctx.runState,
1362
1510
  journal: false,
1511
+ ...planScopeInput(input),
1363
1512
  });
1364
1513
  ctx.plan = fresh;
1365
1514
  const doneReason = normalizeDoneReason(doneReasonFromPlan(fresh));
@@ -1422,6 +1571,7 @@ async function gateVerify(input, deps, ctx, budgets) {
1422
1571
  senderIds: input.senderIds,
1423
1572
  runState: ctx.runState,
1424
1573
  journal: false,
1574
+ ...planScopeInput(input),
1425
1575
  });
1426
1576
  ctx.plan = fresh;
1427
1577
  const doneReason = doneReasonFromPlan(fresh);
@@ -1474,19 +1624,19 @@ function classifyCaughtError(error, ctx) {
1474
1624
  };
1475
1625
  }
1476
1626
  async function heartbeatAndContinue(input, deps, ctx) {
1477
- const heartbeat = await deps.runClient.heartbeatRefillRun({
1627
+ const yielded = await deps.runClient.yieldRefillRun({
1478
1628
  workspaceId: input.workspaceId,
1479
1629
  runId: ctx.runId,
1480
1630
  fence: ctx.fence,
1481
1631
  });
1482
- if (isLeaseLost(heartbeat))
1632
+ if (isLeaseLost(yielded))
1483
1633
  return leaseLostResult(ctx);
1484
1634
  return {
1485
1635
  status: "in_progress",
1486
1636
  runId: ctx.runId,
1487
1637
  fence: ctx.fence,
1488
1638
  gate: ctx.gate,
1489
- guidance: "Per-invocation gate budget reached. Re-invoke refill_sends_v2 with this runId/fence to continue.",
1639
+ guidance: "Per-invocation gate budget reached. Re-invoke public refill_sends with this durable run handle to continue.",
1490
1640
  journalPath: ctx.journalPath,
1491
1641
  };
1492
1642
  }
@@ -1500,6 +1650,7 @@ export async function runRefillV2Loop(input, deps) {
1500
1650
  intent: input.intent,
1501
1651
  senderIds: input.senderIds,
1502
1652
  journal: true,
1653
+ ...planScopeInput(input),
1503
1654
  });
1504
1655
  return { status: "dry_run", plan };
1505
1656
  }
package/dist/server.js CHANGED
@@ -29,7 +29,6 @@ import { getEngageStateTool, setEngageStateTool, } from "./tools/engage-state.js
29
29
  import { bulkEnrichWithProspeo, enrichWithProspeo, getProspeoCredits, } from "./tools/enrichment.js";
30
30
  import { getRefillPlanV2 } from "./tools/evergreen-refill-plan.js";
31
31
  import { getCampaignFramework } from "./tools/framework.js";
32
- import { bootstrapFindLeads, cancelFindLeads, getFindLeadsRun, reissueFindLeadsWatchLink, updateFindLeadsRun, waitForFindLeadsRun, } from "./tools/find-leads-runs.js";
33
32
  import { confirmHarvestJobCompanies, searchHarvestJobs, } from "./tools/harvest-jobs.js";
34
33
  import { checkInboxReplyEligibility, getInboxThread, searchInboxThreads, sendInboxDraft, sendInboxManualReply, updateInboxDraft, } from "./tools/inbox.js";
35
34
  import { cancelLeadImport, confirmLeadList, confirmProspeoCompanyAccounts, getProviderPrompt, importLeads, listDncEntriesTool, loadCsvDncEntriesTool, loadCsvDomains, loadCsvLinkedinLeads, lookupSalesNavFilter, saveDomainFilters, searchApollo, searchProspeo, searchProspeoCompanies, searchSalesNav, searchSignals, selectPromisingPosts, setHeadlineICPCriteria, } from "./tools/leads.js";
@@ -37,7 +36,6 @@ import { fetchCompany, fetchCompanyPosts, fetchLinkedInPosts, fetchLinkedInProfi
37
36
  import { getCampaignNavigationState } from "./tools/navigation.js";
38
37
  import { addOnDemandLeads, createOnDemandCampaign, createOnDemandTable, initOnDemandSequence, pauseOnDemandCampaign, startOnDemandCampaign, } from "./tools/one-off.js";
39
38
  import { upsertRubric } from "./tools/processing.js";
40
- import { preflightFindLeadsProvider } from "./tools/provider-preflight.js";
41
39
  import { completeSenderResearch, getPostFindLeadsScoutRegistry, getSourceScoutRegistry, getSubskillAsset, getSubskillPrompt, listSubskillPrompts, searchSubskillPrompts, } from "./tools/prompts.js";
42
40
  import { waitForCampaignTableReady, waitForLeadListReady, } from "./tools/readiness.js";
43
41
  import { refreshSenderEngagementCommand } from "./tools/refresh-sender-engagement.js";
@@ -148,27 +146,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
148
146
  case "wait_for_cli_login":
149
147
  result = await handleWaitForCliLogin(args);
150
148
  break;
151
- case "bootstrap_find_leads":
152
- result = await bootstrapFindLeads(args);
153
- break;
154
- case "get_find_leads_run":
155
- result = await getFindLeadsRun(args);
156
- break;
157
- case "update_find_leads_run":
158
- result = await updateFindLeadsRun(args);
159
- break;
160
- case "wait_for_find_leads_run":
161
- result = await waitForFindLeadsRun(args);
162
- break;
163
- case "cancel_find_leads":
164
- result = await cancelFindLeads(args);
165
- break;
166
- case "reissue_find_leads_watch_link":
167
- result = await reissueFindLeadsWatchLink(args);
168
- break;
169
- case "preflight_find_leads_provider":
170
- result = await preflightFindLeadsProvider(args);
171
- break;
172
149
  case "bootstrap_engage":
173
150
  result = await bootstrapEngage(args);
174
151
  break;
@@ -11,6 +11,11 @@ export type AuthStatus = {
11
11
  tokenPresent: boolean;
12
12
  tokenPrefix: string | null;
13
13
  workspacesCount: number | null;
14
+ workspaceLock: {
15
+ enabled: boolean;
16
+ workspaceId: string | null;
17
+ required: boolean;
18
+ };
14
19
  checkedAt: string;
15
20
  /** Short message the LLM MUST show the user after auth succeeds. */
16
21
  _userNotice: string | null;
@@ -1,6 +1,6 @@
1
1
  import * as fs from "fs";
2
2
  import { getApi, SellableApiError } from "../api.js";
3
- import { getConfig, getConfigPath, getResolvedConfigsDir } from "../auth.js";
3
+ import { getConfig, getConfigPath, getEffectiveConfiguredWorkspaceId, getLockedWorkspaceId, getResolvedConfigsDir, isWorkspaceLockRequired, workspaceLockRequiredMessage, } from "../auth.js";
4
4
  import { checkForUpdates } from "../update-check.js";
5
5
  function maskToken(token) {
6
6
  if (token.length <= 10)
@@ -38,6 +38,8 @@ 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();
41
43
  const base = {
42
44
  ok: false,
43
45
  configPath,
@@ -50,27 +52,47 @@ export async function getAuthStatus() {
50
52
  tokenPresent: false,
51
53
  tokenPrefix: null,
52
54
  workspacesCount: null,
55
+ workspaceLock: {
56
+ enabled: Boolean(lockedWorkspaceId),
57
+ workspaceId: lockedWorkspaceId,
58
+ required: workspaceLockRequired,
59
+ },
53
60
  _userNotice: appendUpdateNotice(null, update),
54
61
  update,
55
62
  checkedAt,
56
63
  };
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
+ }
57
74
  try {
58
75
  const config = getConfig();
59
76
  tokenPresent = Boolean(config.token);
60
77
  base.activeEnvName = config.activeEnvName || null;
61
78
  const api = getApi();
62
79
  const { workspaces } = await api.get("/api/v3/workspaces");
63
- const activeWorkspaceId = config.activeWorkspaceId || config.workspaceId || null;
80
+ const activeWorkspaceId = getEffectiveConfiguredWorkspaceId(config);
64
81
  const activeWorkspace = activeWorkspaceId
65
82
  ? workspaces.find((ws) => ws.id === activeWorkspaceId)
66
83
  : null;
84
+ const visibleWorkspacesCount = lockedWorkspaceId
85
+ ? activeWorkspace
86
+ ? 1
87
+ : 0
88
+ : workspaces.length;
67
89
  if (!activeWorkspaceId) {
68
90
  return {
69
91
  ...base,
70
92
  apiUrl: config.apiUrl || null,
71
93
  tokenPresent,
72
94
  tokenPrefix: maskToken(config.token),
73
- workspacesCount: workspaces.length,
95
+ workspacesCount: visibleWorkspacesCount,
74
96
  error: {
75
97
  type: "workspace",
76
98
  message: "No active workspace selected.",
@@ -87,22 +109,26 @@ export async function getAuthStatus() {
87
109
  apiUrl: config.apiUrl || null,
88
110
  tokenPresent,
89
111
  tokenPrefix: maskToken(config.token),
90
- workspacesCount: workspaces.length,
112
+ workspacesCount: visibleWorkspacesCount,
91
113
  error: {
92
114
  type: "workspace",
93
115
  message: `Active workspace ${activeWorkspaceId} not found in access list.`,
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.",
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.",
97
121
  },
98
122
  };
99
123
  }
100
124
  const workspaceName = activeWorkspace.name || config.activeWorkspaceName || null;
101
125
  const envLabel = config.activeEnvName || null;
102
126
  const wsLabel = workspaceName || activeWorkspaceId;
103
- const notice = envLabel
104
- ? `You're in ${wsLabel} (${envLabel}).`
105
- : `You're in ${wsLabel}.`;
127
+ const noticePrefix = lockedWorkspaceId
128
+ ? `You're locked to ${wsLabel}.`
129
+ : envLabel
130
+ ? `You're in ${wsLabel} (${envLabel}).`
131
+ : `You're in ${wsLabel}.`;
106
132
  return {
107
133
  ok: true,
108
134
  configPath,
@@ -114,8 +140,19 @@ export async function getAuthStatus() {
114
140
  activeWorkspaceName: workspaceName,
115
141
  tokenPresent,
116
142
  tokenPrefix: maskToken(config.token),
117
- workspacesCount: workspaces.length,
118
- _userNotice: appendUpdateNotice(notice, update),
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),
119
156
  update,
120
157
  checkedAt,
121
158
  error: null,
@@ -36,9 +36,16 @@ type StartPrepareMessagesInput = PrepareMessagesBaseInput & {
36
36
  requestSource?: string;
37
37
  senderId?: string;
38
38
  actionType?: string;
39
+ targetDate?: string;
40
+ refillLaneKey?: string;
41
+ sourceEpoch?: string;
39
42
  };
40
43
  type StatusPrepareMessagesInput = PrepareMessagesBaseInput;
41
44
  type CancelPrepareMessagesInput = PrepareMessagesBaseInput;
45
+ type StopSatisfiedPrepareMessagesInput = PrepareMessagesBaseInput & {
46
+ jobId: string;
47
+ expectedUpdatedAt?: string;
48
+ };
42
49
  export declare const campaignMessagePreparationToolDefinitions: ({
43
50
  name: string;
44
51
  description: string;
@@ -117,6 +124,18 @@ export declare const campaignMessagePreparationToolDefinitions: ({
117
124
  enum: string[];
118
125
  description: string;
119
126
  };
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
+ };
120
139
  disableLowPassRateStop: {
121
140
  type: string;
122
141
  description: string;
@@ -126,6 +145,7 @@ export declare const campaignMessagePreparationToolDefinitions: ({
126
145
  description: string;
127
146
  };
128
147
  jobId?: undefined;
148
+ expectedUpdatedAt?: undefined;
129
149
  };
130
150
  required: string[];
131
151
  additionalProperties: boolean;
@@ -160,13 +180,55 @@ export declare const campaignMessagePreparationToolDefinitions: ({
160
180
  autoContinue?: undefined;
161
181
  senderId?: undefined;
162
182
  actionType?: undefined;
183
+ targetDate?: undefined;
184
+ refillLaneKey?: undefined;
185
+ sourceEpoch?: undefined;
163
186
  disableLowPassRateStop?: undefined;
187
+ expectedUpdatedAt?: undefined;
164
188
  };
165
189
  additionalProperties: boolean;
166
190
  required?: undefined;
167
191
  };
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
+ };
168
229
  })[];
169
230
  export declare function startPrepareCampaignMessages(input: StartPrepareMessagesInput): Promise<unknown>;
170
231
  export declare function getPrepareCampaignMessagesStatus(input: StatusPrepareMessagesInput): Promise<unknown>;
171
232
  export declare function cancelPrepareCampaignMessages(input: CancelPrepareMessagesInput): Promise<unknown>;
233
+ export declare function stopSatisfiedPrepareCampaignMessages(input: StopSatisfiedPrepareMessagesInput): Promise<unknown>;
172
234
  export {};
@@ -84,6 +84,18 @@ 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
+ },
87
99
  disableLowPassRateStop: {
88
100
  type: "boolean",
89
101
  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.",
@@ -111,6 +123,23 @@ export const campaignMessagePreparationToolDefinitions = [
111
123
  additionalProperties: false,
112
124
  },
113
125
  },
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
+ },
114
143
  {
115
144
  name: "cancel_campaign_message_preparation",
116
145
  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.",
@@ -145,6 +174,9 @@ export function startPrepareCampaignMessages(input) {
145
174
  requestSource: input.requestSource,
146
175
  senderId: input.senderId,
147
176
  actionType: input.actionType,
177
+ targetDate: input.targetDate,
178
+ refillLaneKey: input.refillLaneKey,
179
+ sourceEpoch: input.sourceEpoch,
148
180
  ...(workspaceId ? { workspaceId } : {}),
149
181
  }, workspaceId ?? undefined);
150
182
  }
@@ -168,3 +200,12 @@ export function cancelPrepareCampaignMessages(input) {
168
200
  ...(workspaceId ? { workspaceId } : {}),
169
201
  }, workspaceId ?? undefined);
170
202
  }
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
+ }