@sellable/mcp 0.1.508 → 0.1.509

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/server.js CHANGED
@@ -305,7 +305,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
305
305
  }
306
306
  break;
307
307
  case "start_campaign":
308
- result = await startCampaign(args?.campaignId);
308
+ result = await startCampaign({
309
+ campaignId: args?.campaignId,
310
+ workspaceId: args?.workspaceId,
311
+ });
309
312
  if (args?.campaignId && result?.success) {
310
313
  markCampaignContextDirty(args.campaignId, "start_campaign");
311
314
  }
@@ -185,6 +185,7 @@ export declare const campaignToolDefinitions: ({
185
185
  useMessagingTemplate?: undefined;
186
186
  rubric?: undefined;
187
187
  flowVersion?: undefined;
188
+ workspaceId?: undefined;
188
189
  };
189
190
  required: string[];
190
191
  additionalProperties: boolean;
@@ -223,6 +224,7 @@ export declare const campaignToolDefinitions: ({
223
224
  useMessagingTemplate?: undefined;
224
225
  rubric?: undefined;
225
226
  flowVersion?: undefined;
227
+ workspaceId?: undefined;
226
228
  };
227
229
  required: never[];
228
230
  additionalProperties?: undefined;
@@ -261,6 +263,7 @@ export declare const campaignToolDefinitions: ({
261
263
  useMessagingTemplate?: undefined;
262
264
  rubric?: undefined;
263
265
  flowVersion?: undefined;
266
+ workspaceId?: undefined;
264
267
  };
265
268
  required: string[];
266
269
  additionalProperties?: undefined;
@@ -342,6 +345,7 @@ export declare const campaignToolDefinitions: ({
342
345
  useMessagingTemplate?: undefined;
343
346
  rubric?: undefined;
344
347
  flowVersion?: undefined;
348
+ workspaceId?: undefined;
345
349
  };
346
350
  required: string[];
347
351
  additionalProperties: boolean;
@@ -536,6 +540,7 @@ export declare const campaignToolDefinitions: ({
536
540
  useMessagingTemplate?: undefined;
537
541
  rubric?: undefined;
538
542
  flowVersion?: undefined;
543
+ workspaceId?: undefined;
539
544
  };
540
545
  required: never[];
541
546
  additionalProperties?: undefined;
@@ -741,6 +746,49 @@ export declare const campaignToolDefinitions: ({
741
746
  clientProspectId?: undefined;
742
747
  senderLinkedinUrl?: undefined;
743
748
  messageGenerationMode?: undefined;
749
+ workspaceId?: undefined;
750
+ };
751
+ required: string[];
752
+ additionalProperties?: undefined;
753
+ };
754
+ } | {
755
+ name: string;
756
+ description: string;
757
+ inputSchema: {
758
+ type: string;
759
+ properties: {
760
+ campaignId: {
761
+ type: string;
762
+ description: string;
763
+ };
764
+ workspaceId: {
765
+ type: string;
766
+ description: string;
767
+ };
768
+ limit?: undefined;
769
+ tableId?: undefined;
770
+ leadLimit?: undefined;
771
+ page?: undefined;
772
+ filters?: undefined;
773
+ name?: undefined;
774
+ clientProspectId?: undefined;
775
+ senderLinkedinUrl?: undefined;
776
+ offerPositioning?: undefined;
777
+ campaignBrief?: undefined;
778
+ messageGenerationMode?: undefined;
779
+ currentStep?: undefined;
780
+ watchNarration?: undefined;
781
+ leadSourceType?: undefined;
782
+ leadSourceProvider?: undefined;
783
+ selectedLeadListId?: undefined;
784
+ senderIds?: undefined;
785
+ currentStepTransition?: undefined;
786
+ clearCurrentStepIfMatches?: undefined;
787
+ interactionMode?: undefined;
788
+ enableICPFilters?: undefined;
789
+ useMessagingTemplate?: undefined;
790
+ rubric?: undefined;
791
+ flowVersion?: undefined;
744
792
  };
745
793
  required: string[];
746
794
  additionalProperties?: undefined;
@@ -782,6 +830,7 @@ export declare const campaignToolDefinitions: ({
782
830
  useMessagingTemplate?: undefined;
783
831
  rubric?: undefined;
784
832
  flowVersion?: undefined;
833
+ workspaceId?: undefined;
785
834
  };
786
835
  required: string[];
787
836
  additionalProperties?: undefined;
@@ -821,7 +870,11 @@ export interface UpdateCampaignResult {
821
870
  _campaign?: CampaignOfferNavigation;
822
871
  }
823
872
  export declare function updateCampaign(campaignId: string, input: UpdateCampaignInput): Promise<UpdateCampaignResult>;
824
- export declare function startCampaign(campaignId: string): Promise<{
873
+ type StartCampaignInput = string | {
874
+ campaignId?: string | null;
875
+ workspaceId?: string | null;
876
+ };
877
+ export declare function startCampaign(input: StartCampaignInput): Promise<{
825
878
  success: boolean;
826
879
  }>;
827
880
  export declare function pauseCampaign(campaignId: string): Promise<{
@@ -4,6 +4,7 @@ import { assertCreateCampaignPromptLoaded, assertNetNewCreateCampaignResearchRea
4
4
  import { setCampaignInteractionMode, } from "./interaction-mode.js";
5
5
  import { isLinkedInProfileInput, normalizeLinkedInProfileInput, } from "./linkedin-url.js";
6
6
  import { fetchCampaignRubrics } from "./processing.js";
7
+ import { normalizeExplicitWorkspaceId, workspaceRequestOptions, } from "./workspace-context.js";
7
8
  const LEAD_SOURCE_PROVIDERS = {
8
9
  APOLLO: "apollo-ai",
9
10
  SALES_NAV: "sales-nav",
@@ -456,7 +457,7 @@ export const campaignToolDefinitions = [
456
457
  },
457
458
  {
458
459
  name: "start_campaign",
459
- description: "Start a paused campaign, enabling the sweeper to send messages. This is an explicit human launch/start action and must not be used as part of fill-send-horizon, message preparation, or scheduling-proof flows.",
460
+ description: "Start a paused campaign, enabling the sweeper to send messages. This is an explicit human launch/start action. It is also allowed as the exact selected start_paused_campaign primitive from a bounded refill_sends target packet, using request-scoped workspaceId.",
460
461
  inputSchema: {
461
462
  type: "object",
462
463
  properties: {
@@ -464,6 +465,10 @@ export const campaignToolDefinitions = [
464
465
  type: "string",
465
466
  description: "Campaign ID to start",
466
467
  },
468
+ workspaceId: {
469
+ type: "string",
470
+ description: "Explicit request-scoped workspace id for scheduled/yolo refill automation.",
471
+ },
467
472
  },
468
473
  required: ["campaignId"],
469
474
  },
@@ -1014,9 +1019,19 @@ export async function updateCampaign(campaignId, input) {
1014
1019
  _campaign: result,
1015
1020
  };
1016
1021
  }
1017
- export async function startCampaign(campaignId) {
1022
+ export async function startCampaign(input) {
1023
+ const campaignId = typeof input === "string" ? input : input.campaignId?.trim();
1024
+ if (!campaignId) {
1025
+ throw new Error("start_campaign requires campaignId");
1026
+ }
1027
+ const workspaceId = typeof input === "string"
1028
+ ? null
1029
+ : normalizeExplicitWorkspaceId(input.workspaceId);
1030
+ const requestOptions = workspaceRequestOptions(workspaceId);
1018
1031
  const api = getApi();
1019
- return api.post(`/api/v3/campaigns/${campaignId}/start`);
1032
+ return requestOptions
1033
+ ? api.post(`/api/v3/campaigns/${campaignId}/start`, { workspaceId }, requestOptions)
1034
+ : api.post(`/api/v3/campaigns/${campaignId}/start`);
1020
1035
  }
1021
1036
  export async function pauseCampaign(campaignId) {
1022
1037
  const api = getApi();
@@ -127,6 +127,7 @@ export type LookupSalesNavFilterInput = {
127
127
  };
128
128
  export type SignalSearchInput = {
129
129
  type?: "keywords" | "profile" | "post" | "company";
130
+ workspaceId?: string;
130
131
  keywords?: Array<{
131
132
  keyword: string;
132
133
  id?: string;
@@ -192,6 +193,7 @@ export type ConfirmLeadListInput = {
192
193
  };
193
194
  export type SelectPromisingPostsInput = {
194
195
  campaignOfferId: string;
196
+ workspaceId?: string;
195
197
  selections: Array<{
196
198
  postId: string;
197
199
  reason: string;
@@ -1259,7 +1259,7 @@ function serializeInvalidSignalPosts(posts) {
1259
1259
  reason: post.reason,
1260
1260
  }));
1261
1261
  }
1262
- async function validateSelectedSignalPostsForImport(api, posts) {
1262
+ async function validateSelectedSignalPostsForImport(api, posts, requestOptions) {
1263
1263
  const results = await Promise.all(posts.map(async (post) => {
1264
1264
  if (!post.url.trim()) {
1265
1265
  return {
@@ -1269,10 +1269,15 @@ async function validateSelectedSignalPostsForImport(api, posts) {
1269
1269
  };
1270
1270
  }
1271
1271
  try {
1272
- const response = await api.post("/api/v1/signal-discovery/search-signals", {
1273
- type: "post",
1274
- postUrl: post.url,
1275
- });
1272
+ const response = requestOptions
1273
+ ? await api.post("/api/v1/signal-discovery/search-signals", {
1274
+ type: "post",
1275
+ postUrl: post.url,
1276
+ }, requestOptions)
1277
+ : await api.post("/api/v1/signal-discovery/search-signals", {
1278
+ type: "post",
1279
+ postUrl: post.url,
1280
+ });
1276
1281
  const valid = response.success !== false &&
1277
1282
  Array.isArray(response.posts) &&
1278
1283
  response.posts.some(hasUsableSignalValidationPost);
@@ -3633,6 +3638,8 @@ export async function searchSignals(input) {
3633
3638
  campaignOfferId: input?.campaignOfferId,
3634
3639
  });
3635
3640
  const api = getApi();
3641
+ const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
3642
+ const requestOptions = workspaceRequestOptions(workspaceId);
3636
3643
  const { campaignOfferId, headlineICPCriteria, rubricGuidelines, currentStepTransition, } = input;
3637
3644
  const searchRequest = { ...input };
3638
3645
  delete searchRequest.currentStepTransition;
@@ -3643,36 +3650,61 @@ export async function searchSignals(input) {
3643
3650
  ? headlineICPCriteria
3644
3651
  : rubricGuidelines;
3645
3652
  if (campaignOfferId && searchCurrentStep) {
3646
- await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
3653
+ const body = {
3647
3654
  leadSourceType: "new",
3648
3655
  leadSourceProvider: "signal-discovery",
3649
3656
  currentStep: searchCurrentStep,
3650
3657
  ...(currentStepTransition ? { currentStepTransition } : {}),
3651
3658
  watchNarration: buildSignalDiscoverySearchWatchNarration(),
3652
- });
3659
+ ...(workspaceId ? { workspaceId } : {}),
3660
+ };
3661
+ if (requestOptions) {
3662
+ await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, body, requestOptions);
3663
+ }
3664
+ else {
3665
+ await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, body);
3666
+ }
3653
3667
  }
3654
3668
  // Persist criteria early so user take-over has filters ready.
3655
3669
  if (campaignOfferId && effectiveHeadlineICPCriteria?.length) {
3656
- await api.patch(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/posts`, {
3670
+ const body = {
3657
3671
  selectedIds: [],
3658
3672
  unselectedIds: [],
3659
3673
  headlineICPCriteria: effectiveHeadlineICPCriteria,
3660
3674
  rubricGuidelines: effectiveHeadlineICPCriteria,
3661
- });
3675
+ ...(workspaceId ? { workspaceId } : {}),
3676
+ };
3677
+ if (requestOptions) {
3678
+ await api.patch(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/posts`, body, requestOptions);
3679
+ }
3680
+ else {
3681
+ await api.patch(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/posts`, body);
3682
+ }
3662
3683
  }
3663
- const response = await api.post(`/api/v1/signal-discovery/search-signals`, searchRequest);
3684
+ const response = requestOptions
3685
+ ? await api.post(`/api/v1/signal-discovery/search-signals`, searchRequest, requestOptions)
3686
+ : await api.post(`/api/v1/signal-discovery/search-signals`, searchRequest);
3664
3687
  const summary = summarizeSignalSearchResponse(response);
3665
3688
  if (campaignOfferId && searchCurrentStep) {
3666
- await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
3689
+ const body = {
3667
3690
  currentStep: searchCurrentStep,
3668
3691
  ...(currentStepTransition ? { currentStepTransition } : {}),
3669
3692
  watchNarration: buildSignalDiscoveryResultsWatchNarration(summary.postsReturned),
3670
- });
3693
+ ...(workspaceId ? { workspaceId } : {}),
3694
+ };
3695
+ if (requestOptions) {
3696
+ await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, body, requestOptions);
3697
+ }
3698
+ else {
3699
+ await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, body);
3700
+ }
3671
3701
  }
3672
3702
  return summary;
3673
3703
  }
3674
3704
  export async function importLeads(input) {
3675
3705
  const api = getApi();
3706
+ const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
3707
+ const requestOptions = workspaceRequestOptions(workspaceId);
3676
3708
  const { campaignOfferId, currentStep, sourceLeadListId: inputSourceLeadListId, searchId, targetLeadCount, mode, searchName, leadListName, headlineICPCriteria, targetEngagerCount, maxPostsToScrape, allowInvalidSignalPosts, rubricGuidelines, confirmed, } = input;
3677
3709
  assertInteractionApproval({
3678
3710
  campaignId: campaignOfferId,
@@ -3687,7 +3719,9 @@ export async function importLeads(input) {
3687
3719
  let campaignSelectedLeadListId;
3688
3720
  if (!provider || currentStep === undefined || mode === undefined) {
3689
3721
  // Pull campaign once when we need provider or to determine default step behavior.
3690
- const campaign = await api.get(`/api/v2/campaign-offers/${campaignOfferId}`);
3722
+ const campaign = requestOptions
3723
+ ? await api.get(`/api/v2/campaign-offers/${campaignOfferId}`, requestOptions)
3724
+ : await api.get(`/api/v2/campaign-offers/${campaignOfferId}`);
3691
3725
  if (!provider) {
3692
3726
  provider = normalizeImportProvider(campaign.leadSourceProvider);
3693
3727
  }
@@ -3809,7 +3843,9 @@ export async function importLeads(input) {
3809
3843
  const effectiveTargetEngagerCount = normalizePositiveInteger(targetEngagerCount) ?? null;
3810
3844
  // Get selected posts from the campaign's signal search tabs
3811
3845
  // Note: API returns flat fields (postUrl, postContent, authorName, etc.)
3812
- const tabsResponse = await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`);
3846
+ const tabsResponse = requestOptions
3847
+ ? await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`, requestOptions)
3848
+ : await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`);
3813
3849
  // Collect all selected posts, mapping from API format to signal-leads/create format
3814
3850
  const selectedPosts = [];
3815
3851
  for (const tab of tabsResponse.tabs || []) {
@@ -3864,7 +3900,7 @@ export async function importLeads(input) {
3864
3900
  : " Select/promote more right-content posts, run another narrow Signal Discovery search, or switch to Sales Nav recent activity if the lane cannot produce enough source candidates.";
3865
3901
  throw new Error(`Signal Discovery selected posts only cover about ${importSelection.estimatedEngagers.toLocaleString("en-US")} people to check, below the approved ${importSelection.targetEngagerCount.toLocaleString("en-US")} source-candidate target. Do not scrape this under-capacity post set.${capClause}`);
3866
3902
  }
3867
- const postValidation = await validateSelectedSignalPostsForImport(api, uniqueSelectedPosts);
3903
+ const postValidation = await validateSelectedSignalPostsForImport(api, uniqueSelectedPosts, requestOptions);
3868
3904
  const plannedPostKeys = new Set(postsToScrape.map(signalImportPostKey));
3869
3905
  const invalidPlannedPosts = postValidation.invalidPosts.filter((post) => plannedPostKeys.has(signalImportPostKey(post)));
3870
3906
  const skippedInvalidPostWarning = postValidation.invalidPosts.length > 0
@@ -3935,7 +3971,7 @@ export async function importLeads(input) {
3935
3971
  ? headlineICPCriteria
3936
3972
  : rubricGuidelines;
3937
3973
  // Start the scrape job
3938
- const result = await api.post(`/api/v3/campaigns/${campaignOfferId}/signal-leads/create`, {
3974
+ const createBody = {
3939
3975
  posts: postsToScrape,
3940
3976
  targetEngagerCount: importSelection.targetEngagerCount ?? undefined,
3941
3977
  ...(effectiveHeadlineICPCriteria &&
@@ -3945,10 +3981,14 @@ export async function importLeads(input) {
3945
3981
  rubricGuidelines: effectiveHeadlineICPCriteria,
3946
3982
  }
3947
3983
  : {}),
3948
- });
3984
+ ...(workspaceId ? { workspaceId } : {}),
3985
+ };
3986
+ const result = requestOptions
3987
+ ? await api.post(`/api/v3/campaigns/${campaignOfferId}/signal-leads/create`, createBody, requestOptions)
3988
+ : await api.post(`/api/v3/campaigns/${campaignOfferId}/signal-leads/create`, createBody);
3949
3989
  // CRITICAL: Update selectedLeadListId so UI subscribes to correct table
3950
3990
  // This enables realtime updates in LeadListCanvas
3951
- await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
3991
+ const updateBody = {
3952
3992
  selectedLeadListId: result.tableId,
3953
3993
  ...(shouldSetCurrentStep ? { currentStep: postImportCurrentStep } : {}),
3954
3994
  ...(shouldSetCurrentStep
@@ -3961,7 +4001,14 @@ export async function importLeads(input) {
3961
4001
  }),
3962
4002
  }
3963
4003
  : {}),
3964
- });
4004
+ ...(workspaceId ? { workspaceId } : {}),
4005
+ };
4006
+ if (requestOptions) {
4007
+ await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, updateBody, requestOptions);
4008
+ }
4009
+ else {
4010
+ await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, updateBody);
4011
+ }
3965
4012
  return {
3966
4013
  provider: "signal-discovery",
3967
4014
  leadListId: result.tableId,
@@ -3992,10 +4039,14 @@ export async function importLeads(input) {
3992
4039
  const fallbackName = leadListName ||
3993
4040
  (searchName ? `${providerLabel} - ${searchName}` : undefined) ||
3994
4041
  `${providerLabel} Import ${new Date().toISOString().slice(0, 10)}`;
3995
- const createResult = await api.post(`/api/v3/lead-lists`, {
4042
+ const createBody = {
3996
4043
  name: fallbackName,
3997
4044
  templateType: "lead_list",
3998
- });
4045
+ ...(workspaceId ? { workspaceId } : {}),
4046
+ };
4047
+ const createResult = requestOptions
4048
+ ? await api.post(`/api/v3/lead-lists`, createBody, requestOptions)
4049
+ : await api.post(`/api/v3/lead-lists`, createBody);
3999
4050
  leadListId = createResult.leadList?.id;
4000
4051
  createdLeadList = createResult.leadList;
4001
4052
  }
@@ -4009,21 +4060,29 @@ export async function importLeads(input) {
4009
4060
  const startImport = async () => {
4010
4061
  if (provider === "sales-nav") {
4011
4062
  // Sales Nav export flow
4012
- return api.post(`/api/v3/sales-nav/export`, {
4063
+ const body = {
4013
4064
  searchId,
4014
4065
  workflowTableId: leadListId,
4015
4066
  campaignOfferId,
4016
4067
  targetLeadCount: cappedTargetLeadCount ?? defaultProviderSourceListTarget,
4017
4068
  ...(normalizedMode ? { mode: normalizedMode } : {}),
4018
- });
4069
+ ...(workspaceId ? { workspaceId } : {}),
4070
+ };
4071
+ return requestOptions
4072
+ ? api.post(`/api/v3/sales-nav/export`, body, requestOptions)
4073
+ : api.post(`/api/v3/sales-nav/export`, body);
4019
4074
  }
4020
4075
  if (provider === "prospeo") {
4021
- return api.post(`/api/v3/lead-lists/${leadListId}/prospeo-import/start`, {
4076
+ const body = {
4022
4077
  searchId,
4023
4078
  campaignOfferId,
4024
4079
  targetLeadCount: cappedTargetLeadCount ?? defaultProviderSourceListTarget,
4025
4080
  ...(normalizedMode ? { mode: normalizedMode } : {}),
4026
- });
4081
+ ...(workspaceId ? { workspaceId } : {}),
4082
+ };
4083
+ return requestOptions
4084
+ ? api.post(`/api/v3/lead-lists/${leadListId}/prospeo-import/start`, body, requestOptions)
4085
+ : api.post(`/api/v3/lead-lists/${leadListId}/prospeo-import/start`, body);
4027
4086
  }
4028
4087
  throw new Error(`Unsupported import provider ${provider}. Choose sales-nav, prospeo, or signal-discovery.`);
4029
4088
  };
@@ -4071,7 +4130,7 @@ export async function importLeads(input) {
4071
4130
  }
4072
4131
  // Update selectedLeadListId so UI subscribes to the source lead list
4073
4132
  // This enables realtime updates while the import job runs.
4074
- await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
4133
+ const updateBody = {
4075
4134
  selectedLeadListId: leadListId,
4076
4135
  ...(shouldSetCurrentStep ? { currentStep: effectiveCurrentStep } : {}),
4077
4136
  ...(shouldSetCurrentStep
@@ -4082,7 +4141,14 @@ export async function importLeads(input) {
4082
4141
  }),
4083
4142
  }
4084
4143
  : {}),
4085
- });
4144
+ ...(workspaceId ? { workspaceId } : {}),
4145
+ };
4146
+ if (requestOptions) {
4147
+ await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, updateBody, requestOptions);
4148
+ }
4149
+ else {
4150
+ await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, updateBody);
4151
+ }
4086
4152
  return {
4087
4153
  provider,
4088
4154
  leadListId,
@@ -4582,6 +4648,8 @@ export function getProviderPrompt(input) {
4582
4648
  }
4583
4649
  export async function selectPromisingPosts(input) {
4584
4650
  const api = getApi();
4651
+ const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
4652
+ const requestOptions = workspaceRequestOptions(workspaceId);
4585
4653
  const { campaignOfferId, selections, headlineICPCriteria, currentStep, selectionMode, mode, scrapePlanMode, targetEngagerCount, maxPostsToScrape, } = input;
4586
4654
  const effectiveMode = selectionMode ?? mode ?? "replace";
4587
4655
  const effectiveScrapePlanMode = scrapePlanMode ??
@@ -4601,19 +4669,25 @@ export async function selectPromisingPosts(input) {
4601
4669
  const postIds = selections.map((s) => s.postId);
4602
4670
  let unselectedIds = [];
4603
4671
  if (effectiveMode === "replace") {
4604
- const existing = await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/posts?selected=true`);
4672
+ const existing = requestOptions
4673
+ ? await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/posts?selected=true`, requestOptions)
4674
+ : await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/posts?selected=true`);
4605
4675
  const existingIds = existing?.posts
4606
4676
  ?.map((post) => post.id)
4607
4677
  .filter((id) => Boolean(id)) ?? [];
4608
4678
  const selectedSet = new Set(postIds);
4609
4679
  unselectedIds = existingIds.filter((id) => !selectedSet.has(id));
4610
4680
  }
4611
- const selectionResult = await api.patch(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/posts`, {
4681
+ const selectionBody = {
4612
4682
  selectedIds: postIds,
4613
4683
  unselectedIds,
4614
4684
  headlineICPCriteria,
4615
4685
  rubricGuidelines: headlineICPCriteria,
4616
- });
4686
+ ...(workspaceId ? { workspaceId } : {}),
4687
+ };
4688
+ const selectionResult = requestOptions
4689
+ ? await api.patch(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/posts`, selectionBody, requestOptions)
4690
+ : await api.patch(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/posts`, selectionBody);
4617
4691
  if (selectionResult.selectedCount <= 0) {
4618
4692
  return {
4619
4693
  success: false,
@@ -4627,7 +4701,9 @@ export async function selectPromisingPosts(input) {
4627
4701
  let recommendedPostCount = selectionResult.selectedCount;
4628
4702
  let recommendationTargetEngagerCount = null;
4629
4703
  try {
4630
- const tabsResponse = await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`);
4704
+ const tabsResponse = requestOptions
4705
+ ? await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`, requestOptions)
4706
+ : await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`);
4631
4707
  const reasonsByPostId = new Map(selections.map((selection) => [selection.postId, selection.reason]));
4632
4708
  const selectedByUrl = new Map();
4633
4709
  for (const tab of tabsResponse.tabs ?? []) {
@@ -4684,10 +4760,17 @@ Approval card should say:
4684
4760
  }
4685
4761
  const selectionCurrentStep = currentStep === null ? undefined : (currentStep ?? "signal-discovery");
4686
4762
  if (selectionCurrentStep) {
4687
- await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
4763
+ const body = {
4688
4764
  currentStep: selectionCurrentStep,
4689
4765
  watchNarration: buildSelectedPostApprovalWatchNarration(selectionResult.selectedCount, recommendedPostCount, recommendationTargetEngagerCount),
4690
- });
4766
+ ...(workspaceId ? { workspaceId } : {}),
4767
+ };
4768
+ if (requestOptions) {
4769
+ await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, body, requestOptions);
4770
+ }
4771
+ else {
4772
+ await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, body);
4773
+ }
4691
4774
  }
4692
4775
  return {
4693
4776
  success: true,
@@ -17,6 +17,34 @@ type RefillSendsCommandInput = {
17
17
  intent?: RefillSendsIntent;
18
18
  approvalMode?: RefillSendsApprovalMode;
19
19
  };
20
+ type YoloLoopStatus = "complete" | "no_action" | "read_only_reread" | "executed_and_reread" | "refused" | "max_actions_reached" | "target_shape_drift";
21
+ type YoloLoopStopReason = "target_complete" | "no_global_action" | "primitive_refused" | "max_actions_reached" | "target_shape_drift";
22
+ type YoloPlanSummary = {
23
+ status?: string | null;
24
+ targetShapeRevision?: string | null;
25
+ stateRevision?: string | null;
26
+ grossTarget?: number | null;
27
+ sent?: number | null;
28
+ scheduled?: number | null;
29
+ projected?: number | null;
30
+ readyBuffer?: number | null;
31
+ remainingProjectedGap?: number | null;
32
+ remainingReadyOrProjectedGap?: number | null;
33
+ actionType?: string | null;
34
+ };
35
+ type YoloActionReceipt = {
36
+ attempt: number;
37
+ selectedAction: Record<string, unknown> | null;
38
+ before: YoloPlanSummary;
39
+ primitive: YoloPrimitiveAttempt;
40
+ after: YoloPlanSummary | null;
41
+ postActionFirstAction?: Record<string, unknown> | null;
42
+ };
43
+ type YoloPrimitiveAttempt = {
44
+ status: "no_action" | "read_only_reread" | "executed_and_reread" | "refused";
45
+ result?: unknown;
46
+ refusalReason?: string;
47
+ };
20
48
  export declare const refillSendsToolDefinitions: {
21
49
  name: string;
22
50
  description: string;
@@ -187,9 +215,6 @@ export declare function executeRefillSendsCommand(input?: RefillSendsCommandInpu
187
215
  status: "not_run_without_yolo";
188
216
  selectedAction: null;
189
217
  targetPlanReread: false;
190
- result?: undefined;
191
- refusalReason?: undefined;
192
- postActionFirstAction?: undefined;
193
218
  };
194
219
  command: string;
195
220
  tool: string;
@@ -287,20 +312,20 @@ export declare function executeRefillSendsCommand(input?: RefillSendsCommandInpu
287
312
  };
288
313
  yoloExecution: {
289
314
  enabled: true;
290
- status: "skipped_after_paid_refresh";
291
- selectedAction: null;
292
- targetPlanReread: true;
293
- result?: undefined;
294
- refusalReason?: undefined;
295
- postActionFirstAction?: undefined;
296
- } | {
297
- enabled: true;
298
- status: "no_action" | "read_only_reread" | "executed_and_reread" | "refused";
315
+ status: YoloLoopStatus;
316
+ stopReason: YoloLoopStopReason;
299
317
  selectedAction: Record<string, unknown> | null;
300
- result: unknown;
318
+ actions: YoloActionReceipt[];
319
+ actionCount: number;
320
+ result?: unknown;
301
321
  targetPlanReread: boolean;
302
- refusalReason: string | undefined;
303
- postActionFirstAction: Record<string, unknown> | null;
322
+ targetPlanRereads: number;
323
+ initialTargetShapeRevision?: string | null;
324
+ finalTargetShapeRevision?: string | null;
325
+ finalStateRevision?: string | null;
326
+ maxActions: number;
327
+ refusalReason?: string;
328
+ postActionFirstAction?: unknown;
304
329
  };
305
330
  command: string;
306
331
  tool: string;
@@ -1,16 +1,19 @@
1
1
  import { getApi, SellableApiError } from "../api.js";
2
2
  import { startPrepareCampaignMessages } from "./campaign-message-preparation.js";
3
+ import { startCampaign } from "./campaigns.js";
3
4
  import { confirmLeadList, importLeads, searchSignals, selectPromisingPosts, } from "./leads.js";
4
5
  import { markProviderPromptLoaded } from "./provider-preflight.js";
5
6
  import { getRefillTargetPlan } from "./refill-target-plan.js";
6
7
  import { refreshPaidInmailCredits } from "./senders.js";
7
- import { createWorkspaceContext, normalizeExplicitWorkspaceId, } from "./workspace-context.js";
8
+ import { createWorkspaceContext, normalizeExplicitWorkspaceId, workspaceRequestOptions, } from "./workspace-context.js";
8
9
  const DEFAULT_SCHEDULER_FORWARD_HOURS = 48;
9
10
  const PAID_INMAIL_REFRESH_MAX_ATTEMPTS = 3;
10
11
  const PAID_INMAIL_REFRESH_RETRY_DELAY_MS = Number(process.env.SELLABLE_MCP_PAID_REFRESH_RETRY_DELAY_MS ?? "1000");
11
12
  const SIGNAL_DISCOVERY_MIN_REFILL_POSTS = 3;
12
13
  const SIGNAL_DISCOVERY_MAX_REFILL_POSTS = 10;
13
14
  const SIGNAL_DISCOVERY_TARGET_ROWS_PER_POST = 150;
15
+ const YOLO_MAX_ACTIONS = Math.max(1, Math.floor(Number(process.env.SELLABLE_MCP_REFILL_YOLO_MAX_ACTIONS ?? "24")));
16
+ const YOLO_SCHEDULER_POLL_DELAY_MS = Math.max(0, Math.floor(Number(process.env.SELLABLE_MCP_REFILL_YOLO_POLL_DELAY_MS ?? "0")));
14
17
  function normalizeStrings(values) {
15
18
  if (!Array.isArray(values))
16
19
  return [];
@@ -87,7 +90,7 @@ function userAddedRowsLimitPayloadFromError(error) {
87
90
  export const refillSendsToolDefinitions = [
88
91
  {
89
92
  name: "refill_sends",
90
- description: "Typed command entrypoint for Sellable refill sends. Accepts --yolo semantics and optional sender selectors, then returns the bounded execution contract for the skill-led refill workflow. In --yolo, it may refresh stale paid InMail credit cache facts once, or execute exactly one safe primitive from target.globalActionQueue[0] (bounded existing-row preparation, bounded same-source copy, bounded generated-message approval, or read-only wait) and reread. It does not run unbounded approval, lower paid InMail thresholds, switch source families, create campaigns, schedule sends, launch, archive, delete, or write scheduler rows.",
93
+ description: "Typed command entrypoint for Sellable refill sends. Accepts --yolo semantics and optional sender selectors, then returns the bounded execution contract for the skill-led refill workflow. In --yolo, it may refresh stale paid InMail credit cache facts once, then run a bounded loop of safe primitives from target.globalActionQueue[0] (start exact selected paused campaign, bounded existing-row preparation, bounded same-source copy, bounded generated-message approval, safe Signal Discovery continuation, or read-only scheduler wait) with a fresh target-plan reread after each primitive. It does not run unbounded approval, lower paid InMail thresholds, switch source families outside the selected packet, create campaigns, schedule sends, archive, delete, or write scheduler rows.",
91
94
  inputSchema: {
92
95
  type: "object",
93
96
  properties: {
@@ -264,10 +267,11 @@ export function refillSendsCommand(input = {}) {
264
267
  "Do not present paid-credit refresh as the next operator action after refill_sends returns autoPaidInmailRefresh and the post-refresh targetPlan.",
265
268
  "If paid InMail feasibility remains below threshold after that automatic refresh, report the exact sender/campaign/table/column threshold action or same-campaign connection fallback; --yolo must not lower paid InMail thresholds or create campaigns.",
266
269
  "In --yolo or after one Accept, continue through every safe selected sender/campaign action covered by the rendered target packet; sent/scheduled-count progress changes stateRevision and should continue while targetShapeRevision is stable.",
270
+ "A start_paused_campaign action is safe in --yolo only when it is target.globalActionQueue[0] for the exact selected PAUSED campaign in the current refill target plan and the request has explicit workspaceId.",
267
271
  "Do not complete a fill/schedule request until a final get_refill_target_plan or refill-state reread proves projected coverage (sent + scheduled) fills the scheduler-forward target window. Treat awaiting_scheduler_after_ready_buffer as loaded, awaiting scheduler when ready buffer covers the gap; do not import/prep more rows in that state, and keep polling unless Christian stops/statuses the run or a concrete non-scheduler blocker such as paid_inmail_below_threshold appears.",
268
272
  ],
269
273
  approvalContract: yolo
270
- ? "Auto-accept only the rendered bounded refill target packet after get_refill_target_plan and fresh reread. Execute one globally ranked primitive from target.globalActionQueue at a time, then rerun get_refill_target_plan before taking another action. Continue through every safe prep/source-copy/bounded-approval/read-only wait action inside that packet. Unbounded approval, start_campaign, source-family switches, threshold changes, and campaign creation require their own explicit gates. After each safe action, rerun get_refill_target_plan; keep going while targetShapeRevision is stable and projected coverage (sent + scheduled) progresses toward the target, even though stateRevision changes. Ready-to-schedule rows are buffer, not completion. If ready buffer covers the projected gap, use only persistent read-only scheduler wait/reread and keep the run open until projected coverage fills or Christian stops/statuses it. Stop immediately if targetShapeRevision changes because sender set, route, ids, caps, dates, blockers, action class, paid InMail threshold feasibility, or side-effect class drifts. --yolo does not authorize lowering paid InMail thresholds or creating connection campaigns."
274
+ ? "Auto-accept only the rendered bounded refill target packet after get_refill_target_plan and fresh reread. Execute one globally ranked primitive from target.globalActionQueue at a time, then rerun get_refill_target_plan before taking another action. Continue through every safe prep/source-copy/bounded-approval/start exact selected paused campaign/safe same-source continuation/read-only wait action inside that packet. Unbounded approval, source-family switches outside the selected packet, threshold changes, and campaign creation require their own explicit gates. After each safe action, rerun get_refill_target_plan; keep going while targetShapeRevision is stable and projected coverage (sent + scheduled) progresses toward the target, even though stateRevision changes. Ready-to-schedule rows are buffer, not completion. If ready buffer covers the projected gap, use only persistent read-only scheduler wait/reread and keep the run open until projected coverage fills or Christian stops/statuses it. Stop immediately if targetShapeRevision changes because sender set, route, ids, caps, dates, blockers, action class, paid InMail threshold feasibility, or side-effect class drifts. --yolo does not authorize lowering paid InMail thresholds or creating connection campaigns."
271
275
  : hasSenderSelectors
272
276
  ? "Before mutation, post the full bounded refill packet in normal chat as Markdown, including workspace, sender scope, campaign table, exact ids, caps/dates, gross target, sent count, scheduled count, projected count, ready buffer, remaining projected gap, paid InMail threshold feasibility, targetShapeRevision/stateRevision, side effects, forbidden actions, and stop condition. Then ask the final Accept/Decline structured approval question with a compact body that refers back to the posted packet instead of duplicating it. Only ask when get_refill_target_plan reports a positive remaining projected/ready gap. If target is complete by projected coverage, no-op without approval. If ready buffer covers the projected gap, run only persistent read-only scheduler wait/reread and keep the run open until projected coverage fills or Christian stops/statuses it. Threshold changes and campaign creation need separate explicit approval."
273
277
  : "Ask which eligible enrolled senders to refill first, then, before mutation, post the full bounded refill packet in normal chat as Markdown and ask the final Accept/Decline structured approval question with a compact body that refers back to the posted packet.",
@@ -447,6 +451,65 @@ function firstGlobalAction(plan) {
447
451
  const [first] = globalActionQueue;
448
452
  return recordValue(first);
449
453
  }
454
+ function planTarget(plan) {
455
+ return recordValue(recordValue(plan)?.target);
456
+ }
457
+ function planStatus(plan) {
458
+ return stringValue(recordValue(plan)?.status);
459
+ }
460
+ function planTargetShapeRevision(plan) {
461
+ const root = recordValue(plan);
462
+ return (stringValue(root?.targetShapeRevision) ?? stringValue(root?.targetRevision));
463
+ }
464
+ function planStateRevision(plan) {
465
+ return stringValue(recordValue(plan)?.stateRevision);
466
+ }
467
+ function planSummary(plan) {
468
+ const target = planTarget(plan);
469
+ const senderPlans = Array.isArray(target?.senderRefillPlans)
470
+ ? target.senderRefillPlans
471
+ : [];
472
+ const firstSenderPlan = recordValue(senderPlans[0]);
473
+ return {
474
+ status: planStatus(plan),
475
+ targetShapeRevision: planTargetShapeRevision(plan),
476
+ stateRevision: planStateRevision(plan),
477
+ grossTarget: numberValue(target?.grossTarget),
478
+ sent: numberValue(target?.sent),
479
+ scheduled: numberValue(target?.scheduled),
480
+ projected: numberValue(target?.projected),
481
+ readyBuffer: numberValue(target?.readyBuffer),
482
+ remainingProjectedGap: numberValue(target?.remainingProjectedGap),
483
+ remainingReadyOrProjectedGap: numberValue(target?.remainingReadyOrProjectedGap),
484
+ actionType: stringValue(firstSenderPlan?.actionType),
485
+ };
486
+ }
487
+ function planIsComplete(plan) {
488
+ const summary = planSummary(plan);
489
+ return summary.status === "complete" || summary.remainingProjectedGap === 0;
490
+ }
491
+ function yoloLoopExecution(params) {
492
+ const lastAction = params.actions[params.actions.length - 1];
493
+ return {
494
+ enabled: true,
495
+ status: params.status,
496
+ stopReason: params.stopReason,
497
+ selectedAction: params.selectedAction ??
498
+ params.actions[0]?.selectedAction ??
499
+ firstGlobalAction(params.initialPlan),
500
+ actions: params.actions,
501
+ actionCount: params.actions.length,
502
+ result: params.result ?? lastAction?.primitive.result,
503
+ targetPlanReread: params.targetPlanRereads > 0,
504
+ targetPlanRereads: params.targetPlanRereads,
505
+ initialTargetShapeRevision: planTargetShapeRevision(params.initialPlan),
506
+ finalTargetShapeRevision: planTargetShapeRevision(params.finalPlan),
507
+ finalStateRevision: planStateRevision(params.finalPlan),
508
+ maxActions: YOLO_MAX_ACTIONS,
509
+ refusalReason: params.refusalReason ?? lastAction?.primitive.refusalReason,
510
+ postActionFirstAction: firstGlobalAction(params.finalPlan),
511
+ };
512
+ }
450
513
  function actionIds(action) {
451
514
  return recordValue(action.ids) ?? {};
452
515
  }
@@ -581,7 +644,7 @@ function boundedApprovalLimit(action) {
581
644
  return null;
582
645
  return Math.floor(limit);
583
646
  }
584
- async function approveGeneratedMessagesBatch(action) {
647
+ async function approveGeneratedMessagesBatch(action, workspaceId) {
585
648
  const tableId = actionTableId(action);
586
649
  const toolInput = actionToolInput(action);
587
650
  const columnId = stringValue(toolInput.columnId) ?? stringValue(actionIds(action).columnId);
@@ -593,19 +656,25 @@ async function approveGeneratedMessagesBatch(action) {
593
656
  };
594
657
  }
595
658
  const api = getApi();
596
- const result = await api.post("/api/v3/workflow-tables/cells/approve-batch", {
659
+ const requestOptions = workspaceRequestOptions(workspaceId);
660
+ const body = {
597
661
  tableId,
598
662
  ...(columnId ? { columnId } : {}),
599
663
  limit,
600
664
  scope: "generated_unapproved",
601
- });
665
+ ...(workspaceId ? { workspaceId } : {}),
666
+ };
667
+ const result = requestOptions
668
+ ? await api.post("/api/v3/workflow-tables/cells/approve-batch", body, requestOptions)
669
+ : await api.post("/api/v3/workflow-tables/cells/approve-batch", body);
602
670
  return { status: "executed_and_reread", result };
603
671
  }
604
- async function continueSignalDiscoverySource(action) {
672
+ async function continueSignalDiscoverySource(action, workspaceId) {
605
673
  const campaignOfferId = actionCampaignId(action);
606
674
  const sourceLeadListId = actionSourceLeadListId(action);
607
675
  const sourceFingerprint = actionSourceFingerprint(action);
608
676
  const toolInput = actionToolInput(action);
677
+ const requestOptions = workspaceRequestOptions(workspaceId);
609
678
  const sourceRowLimit = Math.min(1500, Math.max(100, Math.floor(numberValue(toolInput.sourceRowLimit) ??
610
679
  numberValue(toolInput.targetRows) ??
611
680
  numberValue(action.targetRows) ??
@@ -627,7 +696,9 @@ async function continueSignalDiscoverySource(action) {
627
696
  };
628
697
  }
629
698
  const api = getApi();
630
- const campaign = await api.get(`/api/v2/campaign-offers/${campaignOfferId}`);
699
+ const campaign = requestOptions
700
+ ? await api.get(`/api/v2/campaign-offers/${campaignOfferId}`, requestOptions)
701
+ : await api.get(`/api/v2/campaign-offers/${campaignOfferId}`);
631
702
  if (campaign.leadSourceProvider !== "signal-discovery" &&
632
703
  campaign.leadSourceProvider !== "campaign-tracked-post") {
633
704
  return {
@@ -642,12 +713,16 @@ async function continueSignalDiscoverySource(action) {
642
713
  refusalReason: "campaign selectedLeadListId changed since the plan packet; rerun get_refill_target_plan before source continuation",
643
714
  };
644
715
  }
645
- const sourceMeta = await api.get(`/api/v3/workflow-tables/${sourceLeadListId}?mode=meta`);
716
+ const sourceMeta = requestOptions
717
+ ? await api.get(`/api/v3/workflow-tables/${sourceLeadListId}?mode=meta`, requestOptions)
718
+ : await api.get(`/api/v3/workflow-tables/${sourceLeadListId}?mode=meta`);
646
719
  const sourceConfig = sourceMeta.table?.config ?? null;
647
720
  const headlineICPCriteria = stringArray(sourceConfig?.headlineICPCriteria).length > 0
648
721
  ? stringArray(sourceConfig?.headlineICPCriteria)
649
722
  : stringArray(sourceConfig?.rubricGuidelines);
650
- const tabsResponse = await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`);
723
+ const tabsResponse = requestOptions
724
+ ? await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`, requestOptions)
725
+ : await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`);
651
726
  const signalTabs = tabsResponse.tabs ?? [];
652
727
  const keywords = keywordsFromSignalTabs(signalTabs);
653
728
  const excludedPostIds = selectedPostIdsFromSignalTabs(signalTabs);
@@ -663,6 +738,7 @@ async function continueSignalDiscoverySource(action) {
663
738
  });
664
739
  const searchSummary = await searchSignals({
665
740
  type: "keywords",
741
+ ...(workspaceId ? { workspaceId } : {}),
666
742
  keywords: keywords.map((keyword) => ({
667
743
  keyword,
668
744
  source: "refill-sends-source-continuation",
@@ -684,6 +760,7 @@ async function continueSignalDiscoverySource(action) {
684
760
  }
685
761
  const selectionResult = await selectPromisingPosts({
686
762
  campaignOfferId,
763
+ ...(workspaceId ? { workspaceId } : {}),
687
764
  selections: selectedPostIds.map((postId) => ({
688
765
  postId,
689
766
  reason: "refill_sends same-source continuation: recent post from the campaign's existing Signal Discovery keyword family",
@@ -703,6 +780,7 @@ async function continueSignalDiscoverySource(action) {
703
780
  }
704
781
  const importResult = await importLeads({
705
782
  campaignOfferId,
783
+ ...(workspaceId ? { workspaceId } : {}),
706
784
  provider: "signal-discovery",
707
785
  sourceLeadListId,
708
786
  currentStep: null,
@@ -768,7 +846,7 @@ async function refreshPaidInmailCreditsWithRetry(senderId, workspaceId) {
768
846
  errors,
769
847
  };
770
848
  }
771
- async function executeOneYoloPrimitive(action) {
849
+ async function executeOneYoloPrimitive(action, context = {}) {
772
850
  if (!action)
773
851
  return { status: "no_action" };
774
852
  if (action.yoloEligible === false) {
@@ -800,6 +878,7 @@ async function executeOneYoloPrimitive(action) {
800
878
  const result = await startPrepareCampaignMessages({
801
879
  campaignId,
802
880
  tableId,
881
+ workspaceId: context.workspaceId,
803
882
  targetPreparedMessages,
804
883
  maxRowsToCheck: numberValue(toolInput.maxRowsToCheck) ?? 300,
805
884
  approvalMode,
@@ -842,6 +921,7 @@ async function executeOneYoloPrimitive(action) {
842
921
  }
843
922
  const copyInput = {
844
923
  campaignOfferId,
924
+ workspaceId: context.workspaceId,
845
925
  sourceLeadListId,
846
926
  currentStep: null,
847
927
  confirmed: true,
@@ -915,9 +995,23 @@ async function executeOneYoloPrimitive(action) {
915
995
  return { status: "executed_and_reread", result };
916
996
  }
917
997
  case "continue_signal_discovery_source":
918
- return continueSignalDiscoverySource(action);
998
+ return continueSignalDiscoverySource(action, context.workspaceId);
919
999
  case "approve_messages":
920
- return approveGeneratedMessagesBatch(action);
1000
+ return approveGeneratedMessagesBatch(action, context.workspaceId);
1001
+ case "start_paused_campaign": {
1002
+ const campaignId = actionCampaignId(action);
1003
+ if (!campaignId || !context.workspaceId) {
1004
+ return {
1005
+ status: "refused",
1006
+ refusalReason: "start_paused_campaign action is missing campaignId or explicit workspaceId",
1007
+ };
1008
+ }
1009
+ const result = await startCampaign({
1010
+ campaignId,
1011
+ workspaceId: context.workspaceId,
1012
+ });
1013
+ return { status: "executed_and_reread", result };
1014
+ }
921
1015
  default:
922
1016
  return {
923
1017
  status: "refused",
@@ -925,6 +1019,154 @@ async function executeOneYoloPrimitive(action) {
925
1019
  };
926
1020
  }
927
1021
  }
1022
+ async function executeYoloPrimitiveLoop(params) {
1023
+ let currentPlan = params.initialPlan;
1024
+ const actions = [];
1025
+ let targetPlanRereads = 0;
1026
+ let targetPlanBeforeYoloPrimitive = null;
1027
+ const initialTargetShapeRevision = planTargetShapeRevision(currentPlan);
1028
+ if (planIsComplete(currentPlan)) {
1029
+ return {
1030
+ finalTargetPlan: currentPlan,
1031
+ targetPlanBeforeYoloPrimitive: null,
1032
+ execution: yoloLoopExecution({
1033
+ status: "complete",
1034
+ stopReason: "target_complete",
1035
+ actions,
1036
+ targetPlanRereads,
1037
+ initialPlan: params.initialPlan,
1038
+ finalPlan: currentPlan,
1039
+ }),
1040
+ };
1041
+ }
1042
+ for (let attempt = 1; attempt <= YOLO_MAX_ACTIONS; attempt += 1) {
1043
+ const selectedAction = firstGlobalAction(currentPlan);
1044
+ if (!selectedAction) {
1045
+ return {
1046
+ finalTargetPlan: currentPlan,
1047
+ targetPlanBeforeYoloPrimitive,
1048
+ execution: yoloLoopExecution({
1049
+ status: "no_action",
1050
+ stopReason: "no_global_action",
1051
+ actions,
1052
+ targetPlanRereads,
1053
+ initialPlan: params.initialPlan,
1054
+ finalPlan: currentPlan,
1055
+ selectedAction: null,
1056
+ }),
1057
+ };
1058
+ }
1059
+ const before = planSummary(currentPlan);
1060
+ if (!targetPlanBeforeYoloPrimitive) {
1061
+ targetPlanBeforeYoloPrimitive = currentPlan;
1062
+ }
1063
+ if (selectedAction.type === "wait_for_scheduler" &&
1064
+ YOLO_SCHEDULER_POLL_DELAY_MS > 0) {
1065
+ await sleep(YOLO_SCHEDULER_POLL_DELAY_MS);
1066
+ }
1067
+ const primitive = await executeOneYoloPrimitive(selectedAction, {
1068
+ workspaceId: params.workspaceId,
1069
+ });
1070
+ const shouldReread = primitive.status === "executed_and_reread" ||
1071
+ primitive.status === "read_only_reread";
1072
+ const nextPlan = shouldReread
1073
+ ? await getRefillTargetPlan(params.targetPlanInput)
1074
+ : null;
1075
+ if (shouldReread)
1076
+ targetPlanRereads += 1;
1077
+ const receipt = {
1078
+ attempt,
1079
+ selectedAction,
1080
+ before,
1081
+ primitive,
1082
+ after: nextPlan ? planSummary(nextPlan) : null,
1083
+ postActionFirstAction: nextPlan ? firstGlobalAction(nextPlan) : null,
1084
+ };
1085
+ actions.push(receipt);
1086
+ if (primitive.status === "refused") {
1087
+ return {
1088
+ finalTargetPlan: currentPlan,
1089
+ targetPlanBeforeYoloPrimitive,
1090
+ execution: yoloLoopExecution({
1091
+ status: "refused",
1092
+ stopReason: "primitive_refused",
1093
+ actions,
1094
+ targetPlanRereads,
1095
+ initialPlan: params.initialPlan,
1096
+ finalPlan: currentPlan,
1097
+ selectedAction,
1098
+ result: primitive.result,
1099
+ refusalReason: primitive.refusalReason,
1100
+ }),
1101
+ };
1102
+ }
1103
+ if (!nextPlan) {
1104
+ return {
1105
+ finalTargetPlan: currentPlan,
1106
+ targetPlanBeforeYoloPrimitive,
1107
+ execution: yoloLoopExecution({
1108
+ status: primitive.status,
1109
+ stopReason: "no_global_action",
1110
+ actions,
1111
+ targetPlanRereads,
1112
+ initialPlan: params.initialPlan,
1113
+ finalPlan: currentPlan,
1114
+ selectedAction,
1115
+ result: primitive.result,
1116
+ }),
1117
+ };
1118
+ }
1119
+ currentPlan = nextPlan;
1120
+ const nextTargetShapeRevision = planTargetShapeRevision(currentPlan);
1121
+ if (initialTargetShapeRevision &&
1122
+ nextTargetShapeRevision &&
1123
+ nextTargetShapeRevision !== initialTargetShapeRevision &&
1124
+ !planIsComplete(currentPlan)) {
1125
+ return {
1126
+ finalTargetPlan: currentPlan,
1127
+ targetPlanBeforeYoloPrimitive,
1128
+ execution: yoloLoopExecution({
1129
+ status: "target_shape_drift",
1130
+ stopReason: "target_shape_drift",
1131
+ actions,
1132
+ targetPlanRereads,
1133
+ initialPlan: params.initialPlan,
1134
+ finalPlan: currentPlan,
1135
+ selectedAction,
1136
+ result: primitive.result,
1137
+ }),
1138
+ };
1139
+ }
1140
+ if (planIsComplete(currentPlan)) {
1141
+ return {
1142
+ finalTargetPlan: currentPlan,
1143
+ targetPlanBeforeYoloPrimitive,
1144
+ execution: yoloLoopExecution({
1145
+ status: "complete",
1146
+ stopReason: "target_complete",
1147
+ actions,
1148
+ targetPlanRereads,
1149
+ initialPlan: params.initialPlan,
1150
+ finalPlan: currentPlan,
1151
+ selectedAction,
1152
+ result: primitive.result,
1153
+ }),
1154
+ };
1155
+ }
1156
+ }
1157
+ return {
1158
+ finalTargetPlan: currentPlan,
1159
+ targetPlanBeforeYoloPrimitive,
1160
+ execution: yoloLoopExecution({
1161
+ status: "max_actions_reached",
1162
+ stopReason: "max_actions_reached",
1163
+ actions,
1164
+ targetPlanRereads,
1165
+ initialPlan: params.initialPlan,
1166
+ finalPlan: currentPlan,
1167
+ }),
1168
+ };
1169
+ }
928
1170
  export async function executeRefillSendsCommand(input = {}) {
929
1171
  const yolo = input.yolo === true;
930
1172
  const executionMode = input.executionMode ?? (yolo ? "yolo" : "manual");
@@ -995,21 +1237,17 @@ export async function executeRefillSendsCommand(input = {}) {
995
1237
  const targetPlan = refreshActions.length > 0
996
1238
  ? await getRefillTargetPlan(targetPlanInput)
997
1239
  : targetPlanBeforePaidRefresh;
998
- const selectedAction = refreshActions.length > 0 ? null : firstGlobalAction(targetPlan);
999
- const primitiveAttempt = refreshActions.length > 0
1000
- ? null
1001
- : await executeOneYoloPrimitive(selectedAction);
1002
- const shouldRereadAfterPrimitive = primitiveAttempt?.status === "executed_and_reread" ||
1003
- primitiveAttempt?.status === "read_only_reread";
1004
- const postActionTargetPlan = shouldRereadAfterPrimitive
1005
- ? await getRefillTargetPlan(targetPlanInput)
1006
- : null;
1007
- const finalTargetPlan = postActionTargetPlan ?? targetPlan;
1240
+ const yoloLoop = await executeYoloPrimitiveLoop({
1241
+ initialPlan: targetPlan,
1242
+ targetPlanInput,
1243
+ workspaceId: workspaceId ?? undefined,
1244
+ });
1245
+ const finalTargetPlan = yoloLoop.finalTargetPlan;
1008
1246
  return {
1009
1247
  ...command,
1010
1248
  targetPlan: finalTargetPlan,
1011
1249
  targetPlanBeforePaidRefresh: refreshActions.length > 0 ? targetPlanBeforePaidRefresh : null,
1012
- targetPlanBeforeYoloPrimitive: refreshActions.length === 0 && postActionTargetPlan ? targetPlan : null,
1250
+ targetPlanBeforeYoloPrimitive: yoloLoop.targetPlanBeforeYoloPrimitive,
1013
1251
  autoPaidInmailRefresh: {
1014
1252
  enabled: true,
1015
1253
  status: refreshActions.length === 0
@@ -1025,26 +1263,9 @@ export async function executeRefillSendsCommand(input = {}) {
1025
1263
  workspaceId,
1026
1264
  workspaceResolution: workspaceId ? "explicit" : "active_config",
1027
1265
  note: refreshActions.length > 0
1028
- ? "refill_sends refreshed stale paid InMail credit facts internally with bounded retries, once per sender, then returned the post-refresh targetPlan."
1266
+ ? "refill_sends refreshed stale paid InMail credit facts internally with bounded retries, once per sender, reran the target plan, then continued the bounded yolo primitive loop from the post-refresh targetPlan."
1029
1267
  : "No stale or missing paid InMail credit refresh candidates were present in the initial target plan.",
1030
1268
  },
1031
- yoloExecution: refreshActions.length > 0
1032
- ? {
1033
- enabled: true,
1034
- status: "skipped_after_paid_refresh",
1035
- selectedAction: null,
1036
- targetPlanReread: true,
1037
- }
1038
- : {
1039
- enabled: true,
1040
- status: primitiveAttempt?.status ?? "no_action",
1041
- selectedAction,
1042
- result: primitiveAttempt?.result,
1043
- targetPlanReread: shouldRereadAfterPrimitive,
1044
- refusalReason: primitiveAttempt?.refusalReason,
1045
- postActionFirstAction: postActionTargetPlan
1046
- ? firstGlobalAction(postActionTargetPlan)
1047
- : null,
1048
- },
1269
+ yoloExecution: yoloLoop.execution,
1049
1270
  };
1050
1271
  }
@@ -1079,6 +1079,7 @@ export declare const allTools: ({
1079
1079
  useMessagingTemplate?: undefined;
1080
1080
  rubric?: undefined;
1081
1081
  flowVersion?: undefined;
1082
+ workspaceId?: undefined;
1082
1083
  };
1083
1084
  required: string[];
1084
1085
  additionalProperties: boolean;
@@ -1117,6 +1118,7 @@ export declare const allTools: ({
1117
1118
  useMessagingTemplate?: undefined;
1118
1119
  rubric?: undefined;
1119
1120
  flowVersion?: undefined;
1121
+ workspaceId?: undefined;
1120
1122
  };
1121
1123
  required: never[];
1122
1124
  additionalProperties?: undefined;
@@ -1155,6 +1157,7 @@ export declare const allTools: ({
1155
1157
  useMessagingTemplate?: undefined;
1156
1158
  rubric?: undefined;
1157
1159
  flowVersion?: undefined;
1160
+ workspaceId?: undefined;
1158
1161
  };
1159
1162
  required: string[];
1160
1163
  additionalProperties?: undefined;
@@ -1236,6 +1239,7 @@ export declare const allTools: ({
1236
1239
  useMessagingTemplate?: undefined;
1237
1240
  rubric?: undefined;
1238
1241
  flowVersion?: undefined;
1242
+ workspaceId?: undefined;
1239
1243
  };
1240
1244
  required: string[];
1241
1245
  additionalProperties: boolean;
@@ -1430,6 +1434,7 @@ export declare const allTools: ({
1430
1434
  useMessagingTemplate?: undefined;
1431
1435
  rubric?: undefined;
1432
1436
  flowVersion?: undefined;
1437
+ workspaceId?: undefined;
1433
1438
  };
1434
1439
  required: never[];
1435
1440
  additionalProperties?: undefined;
@@ -1635,6 +1640,49 @@ export declare const allTools: ({
1635
1640
  clientProspectId?: undefined;
1636
1641
  senderLinkedinUrl?: undefined;
1637
1642
  messageGenerationMode?: undefined;
1643
+ workspaceId?: undefined;
1644
+ };
1645
+ required: string[];
1646
+ additionalProperties?: undefined;
1647
+ };
1648
+ } | {
1649
+ name: string;
1650
+ description: string;
1651
+ inputSchema: {
1652
+ type: string;
1653
+ properties: {
1654
+ campaignId: {
1655
+ type: string;
1656
+ description: string;
1657
+ };
1658
+ workspaceId: {
1659
+ type: string;
1660
+ description: string;
1661
+ };
1662
+ limit?: undefined;
1663
+ tableId?: undefined;
1664
+ leadLimit?: undefined;
1665
+ page?: undefined;
1666
+ filters?: undefined;
1667
+ name?: undefined;
1668
+ clientProspectId?: undefined;
1669
+ senderLinkedinUrl?: undefined;
1670
+ offerPositioning?: undefined;
1671
+ campaignBrief?: undefined;
1672
+ messageGenerationMode?: undefined;
1673
+ currentStep?: undefined;
1674
+ watchNarration?: undefined;
1675
+ leadSourceType?: undefined;
1676
+ leadSourceProvider?: undefined;
1677
+ selectedLeadListId?: undefined;
1678
+ senderIds?: undefined;
1679
+ currentStepTransition?: undefined;
1680
+ clearCurrentStepIfMatches?: undefined;
1681
+ interactionMode?: undefined;
1682
+ enableICPFilters?: undefined;
1683
+ useMessagingTemplate?: undefined;
1684
+ rubric?: undefined;
1685
+ flowVersion?: undefined;
1638
1686
  };
1639
1687
  required: string[];
1640
1688
  additionalProperties?: undefined;
@@ -1676,6 +1724,7 @@ export declare const allTools: ({
1676
1724
  useMessagingTemplate?: undefined;
1677
1725
  rubric?: undefined;
1678
1726
  flowVersion?: undefined;
1727
+ workspaceId?: undefined;
1679
1728
  };
1680
1729
  required: string[];
1681
1730
  additionalProperties?: undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.508",
3
+ "version": "0.1.509",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",