@sellable/mcp 0.1.521 → 0.1.522

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.
@@ -5,6 +5,15 @@ export type PaidInmailRefreshAction = {
5
5
  actionKey?: string | null;
6
6
  action?: Record<string, unknown>;
7
7
  };
8
+ export type PaidInmailCreditRefreshOptions = {
9
+ actionType?: string | null;
10
+ campaignId?: string | null;
11
+ tableId?: string | null;
12
+ columnId?: string | null;
13
+ threshold?: number | null;
14
+ maxStalenessSeconds?: number | null;
15
+ paidInmailCreditsMaxStalenessSeconds?: number | null;
16
+ };
8
17
  export type YoloPrimitiveExecution = {
9
18
  enabled: true;
10
19
  status: "no_action" | "read_only_reread" | "executed_and_reread" | "refused" | "skipped_after_paid_refresh";
@@ -161,7 +170,7 @@ export declare function approveGeneratedMessagesBatch(action: Record<string, unk
161
170
  refusalReason?: undefined;
162
171
  }>;
163
172
  export declare function continueSignalDiscoverySource(action: Record<string, unknown>, workspaceId?: string): Promise<YoloPrimitiveAttempt>;
164
- export declare function refreshPaidInmailCreditsWithRetry(senderId: string, workspaceId: string): Promise<{
173
+ export declare function refreshPaidInmailCreditsWithRetry(senderId: string, workspaceId: string, options?: PaidInmailCreditRefreshOptions): Promise<{
165
174
  receipt: import("./senders.js").RefreshPaidInmailCreditsResponse;
166
175
  attempts: number;
167
176
  errors: string[];
@@ -618,13 +618,14 @@ export async function continueSignalDiscoverySource(action, workspaceId) {
618
618
  },
619
619
  };
620
620
  }
621
- export async function refreshPaidInmailCreditsWithRetry(senderId, workspaceId) {
621
+ export async function refreshPaidInmailCreditsWithRetry(senderId, workspaceId, options = {}) {
622
622
  const errors = [];
623
623
  for (let attempt = 1; attempt <= PAID_INMAIL_REFRESH_MAX_ATTEMPTS; attempt += 1) {
624
624
  try {
625
625
  const receipt = await refreshPaidInmailCredits({
626
626
  senderId,
627
627
  workspaceId,
628
+ ...options,
628
629
  });
629
630
  return { receipt, attempts: attempt, errors };
630
631
  }
@@ -2,6 +2,7 @@ import { actionIds, collectPaidInmailRefreshActions, executeOneYoloPrimitive, fi
2
2
  import { getRefillTargetPlan } from "./refill-target-plan.js";
3
3
  import { createWorkspaceContext, normalizeExplicitWorkspaceId, } from "./workspace-context.js";
4
4
  const DEFAULT_SCHEDULER_FORWARD_HOURS = 48;
5
+ const MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS = 4 * 60 * 60;
5
6
  function normalizeHorizonSendDays(value) {
6
7
  if (value === undefined || value === null)
7
8
  return null;
@@ -329,6 +330,9 @@ function paidRefreshApprovalPacket(params) {
329
330
  const action = params.action.action ?? {};
330
331
  const ids = actionIds(action);
331
332
  const threshold = numberValue(action.oldThreshold) ?? numberValue(action.threshold);
333
+ const maxStalenessSeconds = Math.max(numberValue(action.maxStalenessSeconds) ??
334
+ numberValue(action.paidInmailCreditsMaxStalenessSeconds) ??
335
+ MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS, MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS);
332
336
  return {
333
337
  approvalSource: "explicit_yolo_flag",
334
338
  workspaceId: params.workspaceId ?? null,
@@ -339,12 +343,22 @@ function paidRefreshApprovalPacket(params) {
339
343
  tableId: stringValue(ids.tableId) ?? stringValue(action.tableId) ?? null,
340
344
  columnId: stringValue(ids.columnId) ?? stringValue(action.columnId) ?? null,
341
345
  threshold: threshold ?? null,
342
- maxStalenessSeconds: numberValue(action.maxStalenessSeconds) ??
343
- numberValue(action.paidInmailCreditsMaxStalenessSeconds) ??
344
- null,
346
+ maxStalenessSeconds,
345
347
  targetPlanFingerprint: targetPlanFingerprint(params.targetPlan),
346
348
  };
347
349
  }
350
+ function paidRefreshOptionsFromApprovalPacket(packet) {
351
+ return {
352
+ ...(packet.actionType ? { actionType: packet.actionType } : {}),
353
+ ...(packet.campaignId ? { campaignId: packet.campaignId } : {}),
354
+ ...(packet.tableId ? { tableId: packet.tableId } : {}),
355
+ ...(packet.columnId ? { columnId: packet.columnId } : {}),
356
+ ...(typeof packet.threshold === "number"
357
+ ? { threshold: packet.threshold }
358
+ : {}),
359
+ maxStalenessSeconds: packet.maxStalenessSeconds,
360
+ };
361
+ }
348
362
  export async function executeRefillSendsCommand(input = {}) {
349
363
  const yolo = input.yolo === true;
350
364
  const executionMode = input.executionMode ?? (yolo ? "yolo" : "manual");
@@ -407,7 +421,7 @@ export async function executeRefillSendsCommand(input = {}) {
407
421
  });
408
422
  continue;
409
423
  }
410
- const refreshResult = await refreshPaidInmailCreditsWithRetry(action.senderId, workspaceId);
424
+ const refreshResult = await refreshPaidInmailCreditsWithRetry(action.senderId, workspaceId, paidRefreshOptionsFromApprovalPacket(approvalPacket));
411
425
  if (refreshResult.receipt) {
412
426
  refreshedPaidInmailSenderIds.push(action.senderId);
413
427
  refreshReceipts.push({
@@ -36,6 +36,11 @@ const TERMINAL_NO_ACTION_BLOCKER_CODES = new Set([
36
36
  "paid_inmail_credit_missing",
37
37
  "paid_inmail_credit_stale",
38
38
  ]);
39
+ const MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS = 4 * 60 * 60;
40
+ const PAID_INMAIL_REFRESH_STATUSES = new Set([
41
+ "missing_credit_facts",
42
+ "stale_credit_facts",
43
+ ]);
39
44
  const SOURCE_PLAN_STATES = new Set([
40
45
  "not_needed",
41
46
  "use_existing_rows",
@@ -73,6 +78,9 @@ function actionTypesFrom(value) {
73
78
  function numberValue(value) {
74
79
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
75
80
  }
81
+ function optionalNumberValue(value) {
82
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
83
+ }
76
84
  function stringValue(value) {
77
85
  return typeof value === "string" ? value : undefined;
78
86
  }
@@ -87,6 +95,20 @@ function numericRecord(value) {
87
95
  function campaignClassificationValue(value) {
88
96
  return value === "sales_nav_cascade" ? "sales_nav_cascade" : undefined;
89
97
  }
98
+ function normalizePaidInmailMaxStalenessSeconds(value) {
99
+ const numeric = optionalNumberValue(value);
100
+ return numeric !== null && numeric > 0
101
+ ? Math.max(Math.trunc(numeric), MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS)
102
+ : MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS;
103
+ }
104
+ function isFreshWithinMcpPaidInmailWindow(checkedAt, maxStalenessSeconds) {
105
+ if (!checkedAt)
106
+ return false;
107
+ const checkedAtMs = new Date(checkedAt).getTime();
108
+ if (!Number.isFinite(checkedAtMs))
109
+ return false;
110
+ return Date.now() - checkedAtMs <= maxStalenessSeconds * 1000;
111
+ }
90
112
  function stringArray(value) {
91
113
  if (!Array.isArray(value))
92
114
  return [];
@@ -138,7 +160,7 @@ function sanitizeCounts(counts, selectedKeys) {
138
160
  return Boolean(key && selectedKeys.has(key));
139
161
  });
140
162
  }
141
- function sanitizeActionCandidates(candidates, selectedKeys, status, remainingReadyOrProjectedGap, remainingProjectedGap) {
163
+ function sanitizeActionCandidates(candidates, selectedKeys, paidRefreshNeededSenderIds, status, remainingReadyOrProjectedGap, remainingProjectedGap) {
142
164
  const filtered = Array.isArray(candidates)
143
165
  ? candidates
144
166
  .filter((item) => {
@@ -149,6 +171,13 @@ function sanitizeActionCandidates(candidates, selectedKeys, status, remainingRea
149
171
  return false;
150
172
  }
151
173
  const actionType = item.actionType;
174
+ if (item.type === "refresh_paid_inmail_credits") {
175
+ const ids = isRecord(item.ids) ? item.ids : {};
176
+ const senderId = stringValue(item.senderId) ?? stringValue(ids.senderId);
177
+ return Boolean(senderId &&
178
+ paidRefreshNeededSenderIds.has(senderId) &&
179
+ selectedKeys.has(`${senderId}:send_inmail_closed`));
180
+ }
152
181
  if (actionType === undefined)
153
182
  return item.type === "wait_for_scheduler";
154
183
  const allowed = allowedActionType(actionType);
@@ -532,20 +561,41 @@ function sanitizeSourcePlan(value) {
532
561
  function sanitizePaidInmail(value) {
533
562
  if (!isRecord(value))
534
563
  return null;
564
+ const originalMaxStalenessSeconds = optionalNumberValue(value.maxStalenessSeconds);
565
+ const maxStalenessSeconds = normalizePaidInmailMaxStalenessSeconds(value.maxStalenessSeconds);
566
+ const checkedAt = stringValue(value.checkedAt) ?? null;
567
+ const availableCredits = numberValue(value.availableCredits);
568
+ const threshold = numberValue(value.threshold);
569
+ let status = stringValue(value.status) ?? "missing_credit_facts";
570
+ let reason = stringValue(value.reason) ?? "";
571
+ let freshnessPolicy;
572
+ if (status === "stale_credit_facts" &&
573
+ isFreshWithinMcpPaidInmailWindow(checkedAt, maxStalenessSeconds)) {
574
+ status = availableCredits < threshold ? "below_threshold" : "ok";
575
+ reason =
576
+ status === "below_threshold"
577
+ ? "MCP normalized paid InMail freshness to 4h; cached credits are fresh enough but below threshold."
578
+ : "MCP normalized paid InMail freshness to 4h; cached credits are fresh enough.";
579
+ freshnessPolicy = {
580
+ normalizedBy: "mcp",
581
+ minMaxStalenessSeconds: MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS,
582
+ originalMaxStalenessSeconds,
583
+ };
584
+ }
535
585
  return {
536
- status: stringValue(value.status) ?? "missing_credit_facts",
537
- reason: stringValue(value.reason) ?? "",
538
- checkedAt: stringValue(value.checkedAt) ?? null,
586
+ status,
587
+ reason,
588
+ checkedAt,
539
589
  balance: typeof value.balance === "number" ? value.balance : null,
540
590
  sentSince: numberValue(value.sentSince),
541
- availableCredits: numberValue(value.availableCredits),
542
- threshold: numberValue(value.threshold),
591
+ availableCredits,
592
+ threshold,
543
593
  campaignId: stringValue(value.campaignId) ?? null,
544
594
  campaignName: stringValue(value.campaignName) ?? null,
545
595
  tableId: stringValue(value.tableId) ?? null,
546
596
  tableName: stringValue(value.tableName) ?? null,
547
597
  columnId: stringValue(value.columnId) ?? null,
548
- maxStalenessSeconds: numberValue(value.maxStalenessSeconds),
598
+ maxStalenessSeconds,
549
599
  campaignClassification: campaignClassificationValue(value.campaignClassification) ?? null,
550
600
  connectionFallbackAvailable: booleanValue(value.connectionFallbackAvailable) ?? false,
551
601
  connectionFallbackCampaignId: stringValue(value.connectionFallbackCampaignId) ?? null,
@@ -553,8 +603,19 @@ function sanitizePaidInmail(value) {
553
603
  suggestedThreshold: typeof value.suggestedThreshold === "number"
554
604
  ? value.suggestedThreshold
555
605
  : null,
606
+ ...(freshnessPolicy ? { freshnessPolicy } : {}),
556
607
  };
557
608
  }
609
+ function paidInmailNeedsRefresh(value) {
610
+ if (!isRecord(value))
611
+ return false;
612
+ const status = stringValue(value.status);
613
+ return Boolean(status && PAID_INMAIL_REFRESH_STATUSES.has(status));
614
+ }
615
+ function filterFreshPaidInmailRefreshAction(action, paidInmail) {
616
+ return (action.type !== "refresh_paid_inmail_credits" ||
617
+ paidInmailNeedsRefresh(paidInmail));
618
+ }
558
619
  function sanitizeStructuredSenderPlans(value, selectedBySender) {
559
620
  if (!Array.isArray(value))
560
621
  return [];
@@ -566,10 +627,12 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
566
627
  return Boolean(selectedLane && selectedBySender.get(plan.senderId) === selectedLane);
567
628
  })
568
629
  .map((plan) => {
630
+ const paidInmail = sanitizePaidInmail(plan.paidInmail);
569
631
  const nextActions = Array.isArray(plan.nextActions)
570
632
  ? plan.nextActions
571
633
  .map((action, index) => sanitizeStructuredAction(action, selectedBySender, "next", index + 1))
572
634
  .filter((action) => Boolean(action))
635
+ .filter((action) => filterFreshPaidInmailRefreshAction(action, paidInmail))
573
636
  : [];
574
637
  const manualAlternates = Array.isArray(plan.manualAlternates)
575
638
  ? plan.manualAlternates
@@ -614,7 +677,7 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
614
677
  : {},
615
678
  coverageDisplay: stringArray(plan.coverageDisplay),
616
679
  campaignRanking: sanitizeCampaignRanking(plan.campaignRanking),
617
- paidInmail: sanitizePaidInmail(plan.paidInmail),
680
+ paidInmail,
618
681
  sourcePlan: sanitizeSourcePlan(plan.sourcePlan),
619
682
  refillReceipt: sanitizeRefillReceipt(plan.refillReceipt),
620
683
  nextActions,
@@ -640,6 +703,17 @@ function buildSanitizedGlobalActionQueue(senderRefillPlans) {
640
703
  rank: index + 1,
641
704
  }));
642
705
  }
706
+ function paidRefreshNeededSenderIds(senderRefillPlans) {
707
+ const senderIds = new Set();
708
+ for (const plan of senderRefillPlans) {
709
+ if (!paidInmailNeedsRefresh(plan.paidInmail))
710
+ continue;
711
+ const senderId = stringValue(plan.senderId);
712
+ if (senderId)
713
+ senderIds.add(senderId);
714
+ }
715
+ return senderIds;
716
+ }
643
717
  function emptyUnsupportedResult(result) {
644
718
  if (!isRecord(result) || !isRecord(result.target))
645
719
  return result;
@@ -682,7 +756,7 @@ function emptyUnsupportedResult(result) {
682
756
  mcpSanitizedRefillLanes: true,
683
757
  };
684
758
  }
685
- function sanitizeBlockers(blockers, selectedKeys) {
759
+ function sanitizeBlockers(blockers, selectedKeys, paidRefreshNeededSenderIds) {
686
760
  if (!Array.isArray(blockers))
687
761
  return [];
688
762
  return blockers.filter((item) => {
@@ -694,6 +768,10 @@ function sanitizeBlockers(blockers, selectedKeys) {
694
768
  return true;
695
769
  const detailSenderId = detail.split(":")[0];
696
770
  if (code.startsWith("paid_inmail_")) {
771
+ if (code === "paid_inmail_credit_stale" &&
772
+ !paidRefreshNeededSenderIds.has(detailSenderId)) {
773
+ return false;
774
+ }
697
775
  return selectedKeys.has(`${detailSenderId}:send_inmail_closed`);
698
776
  }
699
777
  if (detail.includes(":send_dm") || detail.includes(":send_inmail_open")) {
@@ -782,9 +860,10 @@ function sanitizeRefillTargetPlanResult(result) {
782
860
  const remainingScheduledGap = senderPlans.reduce((sum, plan) => sum + numberValue(plan.remainingScheduledGap), 0);
783
861
  const remainingProjectedGap = senderPlans.reduce((sum, plan) => sum + numberValue(plan.remainingProjectedGap), 0);
784
862
  const remainingReadyOrProjectedGap = senderPlans.reduce((sum, plan) => sum + numberValue(plan.remainingReadyOrProjectedGap), 0);
785
- const blockers = sanitizeBlockers(result.blockers, selectedKeys);
786
863
  const eligibleSenderLedger = sanitizeEligibleSenderLedger(target.eligibleSenderLedger, selectedBySender);
787
864
  const senderRefillPlans = sanitizeStructuredSenderPlans(target.senderRefillPlans, selectedBySender);
865
+ const paidRefreshNeededSenderIdSet = paidRefreshNeededSenderIds(senderRefillPlans);
866
+ const blockers = sanitizeBlockers(result.blockers, selectedKeys, paidRefreshNeededSenderIdSet);
788
867
  const globalActionQueue = buildSanitizedGlobalActionQueue(senderRefillPlans);
789
868
  const hasTerminalNoActionBlocker = globalActionQueue.length === 0 &&
790
869
  blockers.some((blocker) => TERMINAL_NO_ACTION_BLOCKER_CODES.has(String(blocker.code)));
@@ -831,7 +910,7 @@ function sanitizeRefillTargetPlanResult(result) {
831
910
  readyCounts: sanitizeCounts(result.coverage.readyCounts, selectedKeys),
832
911
  }
833
912
  : result.coverage,
834
- actionCandidates: sanitizeActionCandidates(result.actionCandidates, selectedKeys, status, remainingReadyOrProjectedGap, remainingProjectedGap),
913
+ actionCandidates: sanitizeActionCandidates(result.actionCandidates, selectedKeys, paidRefreshNeededSenderIdSet, status, remainingReadyOrProjectedGap, remainingProjectedGap),
835
914
  blockers,
836
915
  mcpSanitizedRefillLanes: true,
837
916
  };
@@ -584,6 +584,97 @@ export declare const allTools: ({
584
584
  required: string[];
585
585
  additionalProperties: boolean;
586
586
  };
587
+ } | {
588
+ name: string;
589
+ description: string;
590
+ inputSchema: {
591
+ type: string;
592
+ properties: {
593
+ campaignId: {
594
+ type: string;
595
+ description: string;
596
+ };
597
+ tableId: {
598
+ type: string;
599
+ };
600
+ targetPreparedMessages: {
601
+ type: string;
602
+ minimum: number;
603
+ maximum: number;
604
+ };
605
+ maxRowsToCheck: {
606
+ type: string;
607
+ minimum: number;
608
+ maximum: number;
609
+ description: string;
610
+ };
611
+ batchSize: {
612
+ type: string;
613
+ minimum: number;
614
+ maximum: number;
615
+ description: string;
616
+ };
617
+ maxBatchRows: {
618
+ type: string;
619
+ minimum: number;
620
+ maximum: number;
621
+ description: string;
622
+ };
623
+ approvalMode: {
624
+ type: string;
625
+ enum: string[];
626
+ description: string;
627
+ };
628
+ rowSelector: {
629
+ type: string;
630
+ description: string;
631
+ properties: {
632
+ type: {
633
+ type: string;
634
+ enum: string[];
635
+ };
636
+ limit: {
637
+ type: string;
638
+ minimum: number;
639
+ maximum: number;
640
+ };
641
+ };
642
+ required: string[];
643
+ additionalProperties: boolean;
644
+ };
645
+ excludeRowIds: {
646
+ type: string;
647
+ items: {
648
+ type: string;
649
+ };
650
+ maxItems: number;
651
+ description: string;
652
+ };
653
+ autoContinue: {
654
+ type: string;
655
+ };
656
+ senderId: {
657
+ type: string;
658
+ description: string;
659
+ };
660
+ actionType: {
661
+ type: string;
662
+ enum: string[];
663
+ description: string;
664
+ };
665
+ disableLowPassRateStop: {
666
+ type: string;
667
+ description: string;
668
+ };
669
+ workspaceId: {
670
+ type: string;
671
+ description: string;
672
+ };
673
+ jobId?: undefined;
674
+ };
675
+ required: string[];
676
+ additionalProperties: boolean;
677
+ };
587
678
  } | {
588
679
  name: string;
589
680
  description: string;
@@ -2343,6 +2434,13 @@ export declare const allTools: ({
2343
2434
  type: string;
2344
2435
  description: string;
2345
2436
  };
2437
+ actionType?: undefined;
2438
+ campaignId?: undefined;
2439
+ tableId?: undefined;
2440
+ columnId?: undefined;
2441
+ threshold?: undefined;
2442
+ maxStalenessSeconds?: undefined;
2443
+ paidInmailCreditsMaxStalenessSeconds?: undefined;
2346
2444
  };
2347
2445
  required: string[];
2348
2446
  additionalProperties: boolean;
@@ -35,6 +35,13 @@ export type SenderDetailResponse = {
35
35
  export type RefreshPaidInmailCreditsInput = {
36
36
  senderId: string;
37
37
  workspaceId: string;
38
+ actionType?: string | null;
39
+ campaignId?: string | null;
40
+ tableId?: string | null;
41
+ columnId?: string | null;
42
+ threshold?: number | null;
43
+ maxStalenessSeconds?: number | null;
44
+ paidInmailCreditsMaxStalenessSeconds?: number | null;
38
45
  };
39
46
  export type PaidInmailCreditStatus = {
40
47
  available: number;
@@ -68,6 +75,13 @@ export declare const senderToolDefinitions: ({
68
75
  description: string;
69
76
  };
70
77
  senderId?: undefined;
78
+ actionType?: undefined;
79
+ campaignId?: undefined;
80
+ tableId?: undefined;
81
+ columnId?: undefined;
82
+ threshold?: undefined;
83
+ maxStalenessSeconds?: undefined;
84
+ paidInmailCreditsMaxStalenessSeconds?: undefined;
71
85
  };
72
86
  required: never[];
73
87
  additionalProperties: boolean;
@@ -86,6 +100,59 @@ export declare const senderToolDefinitions: ({
86
100
  type: string;
87
101
  description: string;
88
102
  };
103
+ actionType?: undefined;
104
+ campaignId?: undefined;
105
+ tableId?: undefined;
106
+ columnId?: undefined;
107
+ threshold?: undefined;
108
+ maxStalenessSeconds?: undefined;
109
+ paidInmailCreditsMaxStalenessSeconds?: undefined;
110
+ };
111
+ required: string[];
112
+ additionalProperties: boolean;
113
+ };
114
+ } | {
115
+ name: string;
116
+ description: string;
117
+ inputSchema: {
118
+ type: string;
119
+ properties: {
120
+ senderId: {
121
+ type: string;
122
+ description: string;
123
+ };
124
+ workspaceId: {
125
+ type: string;
126
+ description: string;
127
+ };
128
+ actionType: {
129
+ type: string;
130
+ description: string;
131
+ };
132
+ campaignId: {
133
+ type: string;
134
+ description: string;
135
+ };
136
+ tableId: {
137
+ type: string;
138
+ description: string;
139
+ };
140
+ columnId: {
141
+ type: string;
142
+ description: string;
143
+ };
144
+ threshold: {
145
+ type: string;
146
+ description: string;
147
+ };
148
+ maxStalenessSeconds: {
149
+ type: string;
150
+ description: string;
151
+ };
152
+ paidInmailCreditsMaxStalenessSeconds: {
153
+ type: string;
154
+ description: string;
155
+ };
89
156
  };
90
157
  required: string[];
91
158
  additionalProperties: boolean;
@@ -11,6 +11,19 @@ function pickString(...values) {
11
11
  }
12
12
  return null;
13
13
  }
14
+ const DEFAULT_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS = 4 * 60 * 60;
15
+ function numberOrNull(value) {
16
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
17
+ }
18
+ function normalizePaidInmailCreditsMaxStalenessSeconds(...values) {
19
+ for (const value of values) {
20
+ const numeric = numberOrNull(value);
21
+ if (numeric === null || numeric <= 0)
22
+ continue;
23
+ return Math.max(Math.trunc(numeric), DEFAULT_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS);
24
+ }
25
+ return DEFAULT_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS;
26
+ }
14
27
  function computeDisplayName(sender) {
15
28
  const profile = sender?.unipileAccount?.profileData;
16
29
  const name = pickString(profile?.name);
@@ -148,6 +161,34 @@ export const senderToolDefinitions = [
148
161
  type: "string",
149
162
  description: "Explicit request-scoped workspace id for scheduled/yolo refill automation. Pass this instead of switching the shared active workspace.",
150
163
  },
164
+ actionType: {
165
+ type: "string",
166
+ description: "Optional action type context for the credit freshness check. Defaults to send_inmail_closed.",
167
+ },
168
+ campaignId: {
169
+ type: "string",
170
+ description: "Optional campaign context for the refresh receipt.",
171
+ },
172
+ tableId: {
173
+ type: "string",
174
+ description: "Optional workflow table context for the refresh receipt.",
175
+ },
176
+ columnId: {
177
+ type: "string",
178
+ description: "Optional workflow column context for the refresh receipt.",
179
+ },
180
+ threshold: {
181
+ type: "number",
182
+ description: "Optional paid InMail threshold used by the planner for this sender.",
183
+ },
184
+ maxStalenessSeconds: {
185
+ type: "number",
186
+ description: "Optional cache freshness window in seconds. Values below 4h are clamped to 4h.",
187
+ },
188
+ paidInmailCreditsMaxStalenessSeconds: {
189
+ type: "number",
190
+ description: "Optional legacy alias for maxStalenessSeconds. Values below 4h are clamped to 4h.",
191
+ },
151
192
  },
152
193
  required: ["senderId", "workspaceId"],
153
194
  additionalProperties: false,
@@ -213,7 +254,25 @@ export async function refreshPaidInmailCredits(input) {
213
254
  throw new Error("workspaceId is required.");
214
255
  }
215
256
  const requestOptions = workspaceRequestOptions(workspaceId);
216
- const body = { workspaceId };
257
+ const threshold = numberOrNull(input.threshold);
258
+ const body = {
259
+ workspaceId,
260
+ maxStalenessSeconds: normalizePaidInmailCreditsMaxStalenessSeconds(input.maxStalenessSeconds, input.paidInmailCreditsMaxStalenessSeconds),
261
+ };
262
+ const actionType = pickString(input.actionType);
263
+ const campaignId = pickString(input.campaignId);
264
+ const tableId = pickString(input.tableId);
265
+ const columnId = pickString(input.columnId);
266
+ if (actionType)
267
+ body.actionType = actionType;
268
+ if (campaignId)
269
+ body.campaignId = campaignId;
270
+ if (tableId)
271
+ body.tableId = tableId;
272
+ if (columnId)
273
+ body.columnId = columnId;
274
+ if (threshold !== null)
275
+ body.threshold = threshold;
217
276
  const result = await api.post(`/api/v3/mcp/senders/${encodeURIComponent(senderId)}/refresh-inmail-credits`, body, requestOptions);
218
277
  return {
219
278
  senderId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.521",
3
+ "version": "0.1.522",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",