@sellable/mcp 0.1.510 → 0.1.511

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.
@@ -1,6 +1,5 @@
1
1
  import { getApi, SellableApiError } from "../api.js";
2
2
  import { startPrepareCampaignMessages } from "./campaign-message-preparation.js";
3
- import { startCampaign } from "./campaigns.js";
4
3
  import { confirmLeadList, importLeads, searchSignals, selectPromisingPosts, } from "./leads.js";
5
4
  import { markProviderPromptLoaded } from "./provider-preflight.js";
6
5
  import { getRefillTargetPlan } from "./refill-target-plan.js";
@@ -12,8 +11,6 @@ const PAID_INMAIL_REFRESH_RETRY_DELAY_MS = Number(process.env.SELLABLE_MCP_PAID_
12
11
  const SIGNAL_DISCOVERY_MIN_REFILL_POSTS = 3;
13
12
  const SIGNAL_DISCOVERY_MAX_REFILL_POSTS = 10;
14
13
  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")));
17
14
  function normalizeStrings(values) {
18
15
  if (!Array.isArray(values))
19
16
  return [];
@@ -90,7 +87,7 @@ function userAddedRowsLimitPayloadFromError(error) {
90
87
  export const refillSendsToolDefinitions = [
91
88
  {
92
89
  name: "refill_sends",
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.",
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.",
94
91
  inputSchema: {
95
92
  type: "object",
96
93
  properties: {
@@ -191,6 +188,10 @@ export function refillSendsCommand(input = {}) {
191
188
  tool: "refill_sends",
192
189
  promptName: "refill-sends",
193
190
  workflowPromptName: "refill-sends-workflow",
191
+ workflowAsset: {
192
+ subskillName: "refill-sends-workflow",
193
+ assetPath: "core/flow.v1.json",
194
+ },
194
195
  yolo,
195
196
  executionMode,
196
197
  workspaceId,
@@ -232,6 +233,7 @@ export function refillSendsCommand(input = {}) {
232
233
  },
233
234
  firstOperationalSteps: [
234
235
  'Load get_subskill_prompt({ subskillName: "refill-sends-workflow" }) before any product operation.',
236
+ 'Load get_subskill_asset({ subskillName: "refill-sends-workflow", assetPath: "core/flow.v1.json" }) before any product operation; continue chunks until hasMore is false, parse JSON, and verify workflow:"refill-sends-workflow" with a v1 version.',
235
237
  "A skill cannot create or invoke /goal by itself. If this refill is already running inside an active Codex goal, keep that goal open until every selected sender lane is horizon-filled by projected coverage (sent + scheduled), Christian explicitly stops/statuses the run, or a concrete non-scheduler blocker appears.",
236
238
  `Call get_refill_target_plan({ intent: "${intent}"${workspaceId ? `, workspaceId: "${workspaceId}"` : ""}${input.campaignId ? `, campaignId: "${input.campaignId}"` : ""}${input.tableId ? `, tableId: "${input.tableId}"` : ""}${senderIds.length > 0 ? `, senderIds: ${JSON.stringify(senderIds)}` : ""}${senderNames.length > 0
237
239
  ? `, senderNames: ${JSON.stringify(senderNames)}`
@@ -264,14 +266,14 @@ export function refillSendsCommand(input = {}) {
264
266
  "Fresh reread get_campaign_refill_state immediately before any import, prep, approval, or horizon-fill mutation.",
265
267
  "Maintain a target-window saturation ledger per selected sender from get_refill_target_plan: selected days, gross capacity, actual sent counts, future scheduler-owned scheduled counts, projected counts, ready-to-schedule buffer, remaining projected gap, paid InMail threshold feasibility, targetShapeRevision, stateRevision, and next MCP primitive.",
266
268
  "In --yolo, this tool automatically maintains a run-local refreshedPaidInmailSenderIds set: when the first target plan returns refresh_paid_inmail_credits candidates for selected paid-InMail lanes, it refreshes each exact sender at most once, reruns get_refill_target_plan, and returns the post-refresh targetPlan before any prep/source-copy/bounded-approval/read-only wait action is chosen.",
269
+ "Freshness gate precedes scheduler wait: if any selected target.senderRefillPlans[].paidInmail.status is missing_credit_facts or stale_credit_facts, or the target plan contains refresh_paid_inmail_credits, do not enter wait_for_scheduler even when remainingReadyOrProjectedGap is 0; refresh exact sender credit facts once, reread get_refill_target_plan, then choose scheduler wait only if freshness is clean.",
267
270
  "Do not present paid-credit refresh as the next operator action after refill_sends returns autoPaidInmailRefresh and the post-refresh targetPlan.",
268
271
  "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.",
269
272
  "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.",
271
273
  "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.",
272
274
  ],
273
275
  approvalContract: yolo
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."
276
+ ? "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."
275
277
  : hasSenderSelectors
276
278
  ? "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."
277
279
  : "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.",
@@ -409,6 +411,35 @@ function paidRefreshActionFrom(value) {
409
411
  action,
410
412
  };
411
413
  }
414
+ function targetPlanFingerprint(plan) {
415
+ const root = recordValue(plan);
416
+ return {
417
+ targetRevision: stringValue(root?.targetRevision) ?? null,
418
+ targetShapeRevision: stringValue(root?.targetShapeRevision) ?? null,
419
+ stateRevision: stringValue(root?.stateRevision) ?? null,
420
+ generatedAt: stringValue(root?.generatedAt) ?? null,
421
+ };
422
+ }
423
+ function paidRefreshApprovalPacket(params) {
424
+ const action = params.action.action ?? {};
425
+ const ids = actionIds(action);
426
+ const threshold = numberValue(action.oldThreshold) ?? numberValue(action.threshold);
427
+ return {
428
+ approvalSource: "explicit_yolo_flag",
429
+ workspaceId: params.workspaceId ?? null,
430
+ senderId: params.action.senderId,
431
+ actionKey: params.action.actionKey ?? null,
432
+ actionType: stringValue(action.actionType),
433
+ campaignId: stringValue(ids.campaignId) ?? stringValue(action.campaignId) ?? null,
434
+ tableId: stringValue(ids.tableId) ?? stringValue(action.tableId) ?? null,
435
+ columnId: stringValue(ids.columnId) ?? stringValue(action.columnId) ?? null,
436
+ threshold: threshold ?? null,
437
+ maxStalenessSeconds: numberValue(action.maxStalenessSeconds) ??
438
+ numberValue(action.paidInmailCreditsMaxStalenessSeconds) ??
439
+ null,
440
+ targetPlanFingerprint: targetPlanFingerprint(params.targetPlan),
441
+ };
442
+ }
412
443
  function collectPaidInmailRefreshActions(plan) {
413
444
  const root = recordValue(plan);
414
445
  const target = recordValue(root?.target);
@@ -429,6 +460,35 @@ function collectPaidInmailRefreshActions(plan) {
429
460
  : [];
430
461
  for (const action of nextActions)
431
462
  add(action);
463
+ const paidInmail = recordValue(planRecord?.paidInmail);
464
+ const paidStatus = stringValue(paidInmail?.status);
465
+ const senderId = stringValue(planRecord?.senderId) ?? stringValue(paidInmail?.senderId);
466
+ if (senderId &&
467
+ (paidStatus === "missing_credit_facts" ||
468
+ paidStatus === "stale_credit_facts")) {
469
+ add({
470
+ type: "refresh_paid_inmail_credits",
471
+ actionKey: [
472
+ "refresh_paid_inmail_credits",
473
+ senderId,
474
+ stringValue(paidInmail?.campaignId) ?? "unknown",
475
+ stringValue(paidInmail?.columnId) ?? "unknown",
476
+ ].join(":"),
477
+ actionType: "send_inmail_closed",
478
+ senderId,
479
+ campaignId: stringValue(paidInmail?.campaignId) ?? undefined,
480
+ tableId: stringValue(paidInmail?.tableId) ?? undefined,
481
+ columnId: stringValue(paidInmail?.columnId) ?? undefined,
482
+ oldThreshold: numberValue(paidInmail?.threshold) ?? undefined,
483
+ maxStalenessSeconds: numberValue(paidInmail?.maxStalenessSeconds) ?? undefined,
484
+ ids: {
485
+ senderId,
486
+ campaignId: stringValue(paidInmail?.campaignId) ?? undefined,
487
+ tableId: stringValue(paidInmail?.tableId) ?? undefined,
488
+ columnId: stringValue(paidInmail?.columnId) ?? undefined,
489
+ },
490
+ });
491
+ }
432
492
  }
433
493
  const globalActionQueue = Array.isArray(target?.globalActionQueue)
434
494
  ? target.globalActionQueue
@@ -451,65 +511,6 @@ function firstGlobalAction(plan) {
451
511
  const [first] = globalActionQueue;
452
512
  return recordValue(first);
453
513
  }
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
- }
513
514
  function actionIds(action) {
514
515
  return recordValue(action.ids) ?? {};
515
516
  }
@@ -657,16 +658,13 @@ async function approveGeneratedMessagesBatch(action, workspaceId) {
657
658
  }
658
659
  const api = getApi();
659
660
  const requestOptions = workspaceRequestOptions(workspaceId);
660
- const body = {
661
+ const result = await api.post("/api/v3/workflow-tables/cells/approve-batch", {
661
662
  tableId,
662
663
  ...(columnId ? { columnId } : {}),
663
664
  limit,
664
665
  scope: "generated_unapproved",
665
666
  ...(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);
667
+ }, requestOptions);
670
668
  return { status: "executed_and_reread", result };
671
669
  }
672
670
  async function continueSignalDiscoverySource(action, workspaceId) {
@@ -674,7 +672,6 @@ async function continueSignalDiscoverySource(action, workspaceId) {
674
672
  const sourceLeadListId = actionSourceLeadListId(action);
675
673
  const sourceFingerprint = actionSourceFingerprint(action);
676
674
  const toolInput = actionToolInput(action);
677
- const requestOptions = workspaceRequestOptions(workspaceId);
678
675
  const sourceRowLimit = Math.min(1500, Math.max(100, Math.floor(numberValue(toolInput.sourceRowLimit) ??
679
676
  numberValue(toolInput.targetRows) ??
680
677
  numberValue(action.targetRows) ??
@@ -696,9 +693,8 @@ async function continueSignalDiscoverySource(action, workspaceId) {
696
693
  };
697
694
  }
698
695
  const api = getApi();
699
- const campaign = requestOptions
700
- ? await api.get(`/api/v2/campaign-offers/${campaignOfferId}`, requestOptions)
701
- : await api.get(`/api/v2/campaign-offers/${campaignOfferId}`);
696
+ const requestOptions = workspaceRequestOptions(workspaceId);
697
+ const campaign = await api.get(`/api/v2/campaign-offers/${campaignOfferId}`, requestOptions);
702
698
  if (campaign.leadSourceProvider !== "signal-discovery" &&
703
699
  campaign.leadSourceProvider !== "campaign-tracked-post") {
704
700
  return {
@@ -713,16 +709,12 @@ async function continueSignalDiscoverySource(action, workspaceId) {
713
709
  refusalReason: "campaign selectedLeadListId changed since the plan packet; rerun get_refill_target_plan before source continuation",
714
710
  };
715
711
  }
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`);
712
+ const sourceMeta = await api.get(`/api/v3/workflow-tables/${sourceLeadListId}?mode=meta`, requestOptions);
719
713
  const sourceConfig = sourceMeta.table?.config ?? null;
720
714
  const headlineICPCriteria = stringArray(sourceConfig?.headlineICPCriteria).length > 0
721
715
  ? stringArray(sourceConfig?.headlineICPCriteria)
722
716
  : stringArray(sourceConfig?.rubricGuidelines);
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`);
717
+ const tabsResponse = await api.get(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/tabs`, requestOptions);
726
718
  const signalTabs = tabsResponse.tabs ?? [];
727
719
  const keywords = keywordsFromSignalTabs(signalTabs);
728
720
  const excludedPostIds = selectedPostIdsFromSignalTabs(signalTabs);
@@ -738,7 +730,6 @@ async function continueSignalDiscoverySource(action, workspaceId) {
738
730
  });
739
731
  const searchSummary = await searchSignals({
740
732
  type: "keywords",
741
- ...(workspaceId ? { workspaceId } : {}),
742
733
  keywords: keywords.map((keyword) => ({
743
734
  keyword,
744
735
  source: "refill-sends-source-continuation",
@@ -749,6 +740,7 @@ async function continueSignalDiscoverySource(action, workspaceId) {
749
740
  rubricGuidelines: headlineICPCriteria,
750
741
  confirmed: true,
751
742
  limit: 50,
743
+ ...(workspaceId ? { workspaceId } : {}),
752
744
  });
753
745
  const selectedPostIds = postIdsFromSignalSearch(searchSummary, maxPostsToScrape, { excludePostIds: excludedPostIds });
754
746
  if (selectedPostIds.length === 0) {
@@ -760,7 +752,6 @@ async function continueSignalDiscoverySource(action, workspaceId) {
760
752
  }
761
753
  const selectionResult = await selectPromisingPosts({
762
754
  campaignOfferId,
763
- ...(workspaceId ? { workspaceId } : {}),
764
755
  selections: selectedPostIds.map((postId) => ({
765
756
  postId,
766
757
  reason: "refill_sends same-source continuation: recent post from the campaign's existing Signal Discovery keyword family",
@@ -769,6 +760,7 @@ async function continueSignalDiscoverySource(action, workspaceId) {
769
760
  currentStep: null,
770
761
  selectionMode: "replace",
771
762
  scrapePlanMode: "all-selected",
763
+ ...(workspaceId ? { workspaceId } : {}),
772
764
  });
773
765
  if (selectionResult.success === false) {
774
766
  return {
@@ -780,7 +772,6 @@ async function continueSignalDiscoverySource(action, workspaceId) {
780
772
  }
781
773
  const importResult = await importLeads({
782
774
  campaignOfferId,
783
- ...(workspaceId ? { workspaceId } : {}),
784
775
  provider: "signal-discovery",
785
776
  sourceLeadListId,
786
777
  currentStep: null,
@@ -789,6 +780,7 @@ async function continueSignalDiscoverySource(action, workspaceId) {
789
780
  confirmed: true,
790
781
  maxPostsToScrape: selectedPostIds.length,
791
782
  allowInvalidSignalPosts: true,
783
+ ...(workspaceId ? { workspaceId } : {}),
792
784
  });
793
785
  const importRecord = recordValue(importResult);
794
786
  if (importRecord?.error) {
@@ -829,7 +821,7 @@ async function refreshPaidInmailCreditsWithRetry(senderId, workspaceId) {
829
821
  try {
830
822
  const receipt = await refreshPaidInmailCredits({
831
823
  senderId,
832
- ...(workspaceId ? { workspaceId } : {}),
824
+ workspaceId,
833
825
  });
834
826
  return { receipt, attempts: attempt, errors };
835
827
  }
@@ -846,7 +838,7 @@ async function refreshPaidInmailCreditsWithRetry(senderId, workspaceId) {
846
838
  errors,
847
839
  };
848
840
  }
849
- async function executeOneYoloPrimitive(action, context = {}) {
841
+ async function executeOneYoloPrimitive(action, workspaceId) {
850
842
  if (!action)
851
843
  return { status: "no_action" };
852
844
  if (action.yoloEligible === false) {
@@ -878,7 +870,7 @@ async function executeOneYoloPrimitive(action, context = {}) {
878
870
  const result = await startPrepareCampaignMessages({
879
871
  campaignId,
880
872
  tableId,
881
- workspaceId: context.workspaceId,
873
+ ...(workspaceId ? { workspaceId } : {}),
882
874
  targetPreparedMessages,
883
875
  maxRowsToCheck: numberValue(toolInput.maxRowsToCheck) ?? 300,
884
876
  approvalMode,
@@ -921,10 +913,10 @@ async function executeOneYoloPrimitive(action, context = {}) {
921
913
  }
922
914
  const copyInput = {
923
915
  campaignOfferId,
924
- workspaceId: context.workspaceId,
925
916
  sourceLeadListId,
926
917
  currentStep: null,
927
918
  confirmed: true,
919
+ ...(workspaceId ? { workspaceId } : {}),
928
920
  sourceRowIds: sourceRowIds.length > 0 ? sourceRowIds : undefined,
929
921
  sourceRowLimit: sourceRowIds.length > 0 ? undefined : (sourceRowLimit ?? undefined),
930
922
  reviewBatchLimit: sourceRowIds.length > 0
@@ -995,23 +987,9 @@ async function executeOneYoloPrimitive(action, context = {}) {
995
987
  return { status: "executed_and_reread", result };
996
988
  }
997
989
  case "continue_signal_discovery_source":
998
- return continueSignalDiscoverySource(action, context.workspaceId);
990
+ return continueSignalDiscoverySource(action, workspaceId);
999
991
  case "approve_messages":
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
- }
992
+ return approveGeneratedMessagesBatch(action, workspaceId);
1015
993
  default:
1016
994
  return {
1017
995
  status: "refused",
@@ -1019,154 +997,6 @@ async function executeOneYoloPrimitive(action, context = {}) {
1019
997
  };
1020
998
  }
1021
999
  }
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
- }
1170
1000
  export async function executeRefillSendsCommand(input = {}) {
1171
1001
  const yolo = input.yolo === true;
1172
1002
  const executionMode = input.executionMode ?? (yolo ? "yolo" : "manual");
@@ -1213,7 +1043,23 @@ export async function executeRefillSendsCommand(input = {}) {
1213
1043
  const failedPaidInmailRefreshes = [];
1214
1044
  const refreshReceipts = [];
1215
1045
  for (const action of refreshActions) {
1216
- const refreshResult = await refreshPaidInmailCreditsWithRetry(action.senderId, workspaceId ?? undefined);
1046
+ const approvalPacket = paidRefreshApprovalPacket({
1047
+ action,
1048
+ workspaceId,
1049
+ targetPlan: targetPlanBeforePaidRefresh,
1050
+ });
1051
+ if (!workspaceId) {
1052
+ failedPaidInmailRefreshes.push({
1053
+ senderId: action.senderId,
1054
+ attempts: 0,
1055
+ error: "workspaceId is required before refreshing paid InMail credit facts.",
1056
+ errors: [
1057
+ "workspaceId is required before refreshing paid InMail credit facts.",
1058
+ ],
1059
+ });
1060
+ continue;
1061
+ }
1062
+ const refreshResult = await refreshPaidInmailCreditsWithRetry(action.senderId, workspaceId);
1217
1063
  if (refreshResult.receipt) {
1218
1064
  refreshedPaidInmailSenderIds.push(action.senderId);
1219
1065
  refreshReceipts.push({
@@ -1221,6 +1067,8 @@ export async function executeRefillSendsCommand(input = {}) {
1221
1067
  actionKey: action.actionKey ?? null,
1222
1068
  attempts: refreshResult.attempts,
1223
1069
  retryErrors: refreshResult.errors,
1070
+ approvalSource: "explicit_yolo_flag",
1071
+ approvalPacket,
1224
1072
  receipt: refreshResult.receipt,
1225
1073
  });
1226
1074
  }
@@ -1237,17 +1085,21 @@ export async function executeRefillSendsCommand(input = {}) {
1237
1085
  const targetPlan = refreshActions.length > 0
1238
1086
  ? await getRefillTargetPlan(targetPlanInput)
1239
1087
  : targetPlanBeforePaidRefresh;
1240
- const yoloLoop = await executeYoloPrimitiveLoop({
1241
- initialPlan: targetPlan,
1242
- targetPlanInput,
1243
- workspaceId: workspaceId ?? undefined,
1244
- });
1245
- const finalTargetPlan = yoloLoop.finalTargetPlan;
1088
+ const selectedAction = refreshActions.length > 0 ? null : firstGlobalAction(targetPlan);
1089
+ const primitiveAttempt = refreshActions.length > 0
1090
+ ? null
1091
+ : await executeOneYoloPrimitive(selectedAction, workspaceId ?? undefined);
1092
+ const shouldRereadAfterPrimitive = primitiveAttempt?.status === "executed_and_reread" ||
1093
+ primitiveAttempt?.status === "read_only_reread";
1094
+ const postActionTargetPlan = shouldRereadAfterPrimitive
1095
+ ? await getRefillTargetPlan(targetPlanInput)
1096
+ : null;
1097
+ const finalTargetPlan = postActionTargetPlan ?? targetPlan;
1246
1098
  return {
1247
1099
  ...command,
1248
1100
  targetPlan: finalTargetPlan,
1249
1101
  targetPlanBeforePaidRefresh: refreshActions.length > 0 ? targetPlanBeforePaidRefresh : null,
1250
- targetPlanBeforeYoloPrimitive: yoloLoop.targetPlanBeforeYoloPrimitive,
1102
+ targetPlanBeforeYoloPrimitive: refreshActions.length === 0 && postActionTargetPlan ? targetPlan : null,
1251
1103
  autoPaidInmailRefresh: {
1252
1104
  enabled: true,
1253
1105
  status: refreshActions.length === 0
@@ -1263,9 +1115,26 @@ export async function executeRefillSendsCommand(input = {}) {
1263
1115
  workspaceId,
1264
1116
  workspaceResolution: workspaceId ? "explicit" : "active_config",
1265
1117
  note: refreshActions.length > 0
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."
1118
+ ? "refill_sends refreshed stale paid InMail credit facts internally with bounded retries, once per sender, then returned the post-refresh targetPlan."
1267
1119
  : "No stale or missing paid InMail credit refresh candidates were present in the initial target plan.",
1268
1120
  },
1269
- yoloExecution: yoloLoop.execution,
1121
+ yoloExecution: refreshActions.length > 0
1122
+ ? {
1123
+ enabled: true,
1124
+ status: "skipped_after_paid_refresh",
1125
+ selectedAction: null,
1126
+ targetPlanReread: true,
1127
+ }
1128
+ : {
1129
+ enabled: true,
1130
+ status: primitiveAttempt?.status ?? "no_action",
1131
+ selectedAction,
1132
+ result: primitiveAttempt?.result,
1133
+ targetPlanReread: shouldRereadAfterPrimitive,
1134
+ refusalReason: primitiveAttempt?.refusalReason,
1135
+ postActionFirstAction: postActionTargetPlan
1136
+ ? firstGlobalAction(postActionTargetPlan)
1137
+ : null,
1138
+ },
1270
1139
  };
1271
1140
  }