@sellable/mcp 0.1.520 → 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.
- package/dist/tools/refill-executors.d.ts +10 -1
- package/dist/tools/refill-executors.js +80 -3
- package/dist/tools/refill-sends.js +24 -7
- package/dist/tools/refill-target-plan.js +193 -11
- package/dist/tools/registry.d.ts +98 -0
- package/dist/tools/senders.d.ts +67 -0
- package/dist/tools/senders.js +60 -1
- package/package.json +1 -1
- package/skills/refill-sends/SKILL.md +20 -3
- package/skills/refill-sends-workflow/SKILL.md +16 -1
|
@@ -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[];
|
|
@@ -365,6 +365,79 @@ export function refillPrepareRequestHash(params) {
|
|
|
365
365
|
: "no-row-selector",
|
|
366
366
|
].join(":");
|
|
367
367
|
}
|
|
368
|
+
function prepReceiptFromResult(result) {
|
|
369
|
+
const root = recordValue(result);
|
|
370
|
+
if (!root)
|
|
371
|
+
return null;
|
|
372
|
+
const status = recordValue(root.status);
|
|
373
|
+
const progress = recordValue(root.progress) ?? recordValue(status?.progress);
|
|
374
|
+
return (recordValue(root.prepReceipt) ??
|
|
375
|
+
recordValue(progress?.receipt) ??
|
|
376
|
+
recordValue(root.receipt));
|
|
377
|
+
}
|
|
378
|
+
function normalizePrepareMessagesResult(result) {
|
|
379
|
+
const receipt = prepReceiptFromResult(result);
|
|
380
|
+
const root = recordValue(result);
|
|
381
|
+
if (!receipt || !root)
|
|
382
|
+
return result;
|
|
383
|
+
const world = recordValue(receipt.world);
|
|
384
|
+
const batchStop = stringValue(receipt.batchStop);
|
|
385
|
+
const brokenByReason = recordValue(world?.brokenByReason) ?? {};
|
|
386
|
+
const approvalCandidates = numberValue(world?.approvalCandidates) ?? 0;
|
|
387
|
+
const hasMoreFrontierRows = world?.hasMoreFrontierRows === true;
|
|
388
|
+
const stuckActiveCells = Array.isArray(world?.stuckActiveCells)
|
|
389
|
+
? world.stuckActiveCells
|
|
390
|
+
: [];
|
|
391
|
+
const approvedNotDispatched = Array.isArray(world?.approvedNotDispatched)
|
|
392
|
+
? world.approvedNotDispatched
|
|
393
|
+
: [];
|
|
394
|
+
return {
|
|
395
|
+
...root,
|
|
396
|
+
prepReceipt: receipt,
|
|
397
|
+
exhaustionEvidence: {
|
|
398
|
+
clean: batchStop === "frontier_exhausted" &&
|
|
399
|
+
hasMoreFrontierRows === false &&
|
|
400
|
+
stuckActiveCells.length === 0 &&
|
|
401
|
+
approvedNotDispatched.every((entry) => {
|
|
402
|
+
const row = recordValue(entry);
|
|
403
|
+
return row?.terminal === true || approvedNotDispatched.length === 0;
|
|
404
|
+
}),
|
|
405
|
+
batchStop,
|
|
406
|
+
hasMoreFrontierRows,
|
|
407
|
+
approvalCandidates,
|
|
408
|
+
brokenByReason,
|
|
409
|
+
stuckActiveCells,
|
|
410
|
+
approvedNotDispatched,
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function waitReceiptFromAction(action) {
|
|
415
|
+
const toolInput = actionToolInput(action);
|
|
416
|
+
const wait = recordValue(toolInput.wait) ??
|
|
417
|
+
recordValue(action.wait) ??
|
|
418
|
+
recordValue(recordValue(toolInput.refillReceipt)?.wait);
|
|
419
|
+
if (!wait)
|
|
420
|
+
return null;
|
|
421
|
+
const deadlineAt = stringValue(wait.deadlineAt);
|
|
422
|
+
const onExpiry = stringValue(wait.onExpiry);
|
|
423
|
+
if (!deadlineAt || !onExpiry)
|
|
424
|
+
return null;
|
|
425
|
+
return { deadlineAt, onExpiry };
|
|
426
|
+
}
|
|
427
|
+
function waitResultForAction(action) {
|
|
428
|
+
const wait = waitReceiptFromAction(action);
|
|
429
|
+
if (!wait)
|
|
430
|
+
return { waited: true };
|
|
431
|
+
const deadlineMs = Date.parse(wait.deadlineAt);
|
|
432
|
+
if (Number.isFinite(deadlineMs) && deadlineMs <= Date.now()) {
|
|
433
|
+
return {
|
|
434
|
+
waited: false,
|
|
435
|
+
reason: "wait_deadline_expired",
|
|
436
|
+
wait,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
return { waited: true, wait };
|
|
440
|
+
}
|
|
368
441
|
export function boundedApprovalLimit(action) {
|
|
369
442
|
const toolInput = actionToolInput(action);
|
|
370
443
|
const rowSelector = recordValue(toolInput.rowSelector);
|
|
@@ -545,13 +618,14 @@ export async function continueSignalDiscoverySource(action, workspaceId) {
|
|
|
545
618
|
},
|
|
546
619
|
};
|
|
547
620
|
}
|
|
548
|
-
export async function refreshPaidInmailCreditsWithRetry(senderId, workspaceId) {
|
|
621
|
+
export async function refreshPaidInmailCreditsWithRetry(senderId, workspaceId, options = {}) {
|
|
549
622
|
const errors = [];
|
|
550
623
|
for (let attempt = 1; attempt <= PAID_INMAIL_REFRESH_MAX_ATTEMPTS; attempt += 1) {
|
|
551
624
|
try {
|
|
552
625
|
const receipt = await refreshPaidInmailCredits({
|
|
553
626
|
senderId,
|
|
554
627
|
workspaceId,
|
|
628
|
+
...options,
|
|
555
629
|
});
|
|
556
630
|
return { receipt, attempts: attempt, errors };
|
|
557
631
|
}
|
|
@@ -673,7 +747,7 @@ export async function executeOneYoloPrimitive(action, workspaceId) {
|
|
|
673
747
|
case "wait_for_scheduler":
|
|
674
748
|
case "wait_for_active_work":
|
|
675
749
|
case "wait_for_source_import":
|
|
676
|
-
return { status: "read_only_reread", result:
|
|
750
|
+
return { status: "read_only_reread", result: waitResultForAction(action) };
|
|
677
751
|
case "prepare_messages": {
|
|
678
752
|
const campaignId = actionCampaignId(action);
|
|
679
753
|
const tableId = actionTableId(action) ?? undefined;
|
|
@@ -712,7 +786,10 @@ export async function executeOneYoloPrimitive(action, workspaceId) {
|
|
|
712
786
|
}),
|
|
713
787
|
requestSource: "refill_sends",
|
|
714
788
|
});
|
|
715
|
-
return {
|
|
789
|
+
return {
|
|
790
|
+
status: "executed_and_reread",
|
|
791
|
+
result: normalizePrepareMessagesResult(result),
|
|
792
|
+
};
|
|
716
793
|
}
|
|
717
794
|
case "copy_selected_source_rows": {
|
|
718
795
|
const campaignOfferId = actionCampaignId(action);
|
|
@@ -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;
|
|
@@ -50,7 +51,7 @@ function normalizeTargetDate(value) {
|
|
|
50
51
|
export const refillSendsToolDefinitions = [
|
|
51
52
|
{
|
|
52
53
|
name: "refill_sends",
|
|
53
|
-
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
|
|
54
|
+
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 generated-message approval, receipt-proven same-source copy, or read-only wait) and reread. Same-source copy/source fallback is only safe after receipt-proven exhaustion: hasMoreFrontierRows:false, zero approvalCandidates, no stuckActiveCells, and no non-terminal approvedNotDispatched work. Wait primitives are gates, not competing goals, and carry absolute wait.deadlineAt when present. It does not run unbounded approval, lower paid InMail thresholds, switch source families, create campaigns, schedule sends, launch, archive, delete, or write scheduler rows.",
|
|
54
55
|
inputSchema: {
|
|
55
56
|
type: "object",
|
|
56
57
|
properties: {
|
|
@@ -209,6 +210,7 @@ export function refillSendsCommand(input = {}) {
|
|
|
209
210
|
: `, horizonSendDays: ${horizonSendDays}`}, approvalMode: "${approvalMode}" }) before any import, prep, approval, start, or schedule-affecting action.`,
|
|
210
211
|
"Render target.eligibleSenderLedger and target.senderRefillPlans before mutation. Preserve the coverage labels Need to prepare, Goal, Already sent, Scheduled, Ready and waiting to be scheduled, and Still need.",
|
|
211
212
|
"Use target.globalActionQueue as the only cross-sender yolo queue: execute only target.globalActionQueue[0], one globally ranked primitive, then rerun get_refill_target_plan before choosing another action.",
|
|
213
|
+
"Read target.senderRefillPlans[].refillReceipt as the public ladder receipt: it carries the selected campaign/sender/lane summary, skippedRungs, existingRowFrontier, and any absolute wait.deadlineAt. Do not choose source/copy/fallback work until that receipt proves the earlier ready/prep/approval rungs are exhausted.",
|
|
212
214
|
"If get_refill_target_plan returns status complete, report eligible sender ledger, target.senderRefillPlans, gross target, selected days, sent count, scheduled count, projected count, campaign ids, targetShapeRevision, and no-op proof without asking for approval.",
|
|
213
215
|
"Refill target lanes are connection invites (send_invite), standalone paid InMails (send_inmail_closed), or unified Sales Nav cascades represented publicly as send_inmail_closed with campaign classification sales_nav_cascade. For a Sales Nav cascade, refill the selected campaign first; its sequence can route prospects to Open InMail, paid InMail while fresh credits are >= 5, or same-campaign connection fallback without asking for separate open/paid/connection campaigns. Do not count send_dm as horizon target capacity.",
|
|
214
216
|
"If remainingReadyOrProjectedGap is 0 but remainingProjectedGap is positive, run only a persistent read-only scheduler wait/reread loop; do not ask for prep/import/approval and do not close out while scheduler pickup is the only remaining state.",
|
|
@@ -228,15 +230,17 @@ export function refillSendsCommand(input = {}) {
|
|
|
228
230
|
"Treat current dashboard-active PAUSED campaign-backed sequence campaigns as start-eligible candidates: read refill state before deciding whether to prep, approve, start, or skip.",
|
|
229
231
|
"Fresh reread get_campaign_refill_state immediately before any import, prep, approval, or horizon-fill mutation.",
|
|
230
232
|
"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.",
|
|
233
|
+
"Source/fallback primitives require receipt-proven exhaustion: existingRowFrontier.hasMoreFrontierRows:false, approvalCandidates:0, no fresh active prep, no stuckActiveCells, and no non-terminal approvedNotDispatched rows. Treat anomalies, stuckActiveCells, and non-terminal approvedNotDispatched as diagnose-and-report gates, not exhaustion. Terminal approvedNotDispatched blockers may be reported and then the ladder can proceed.",
|
|
231
234
|
"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.",
|
|
232
235
|
"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.",
|
|
236
|
+
"For wait_for_active_work and wait_for_scheduler, honor the receipt's absolute wait.deadlineAt when present. If the deadline is expired on this call, escalate to diagnostics with the receipt evidence instead of issuing another blind wait.",
|
|
233
237
|
"Do not present paid-credit refresh as the next operator action after refill_sends returns autoPaidInmailRefresh and the post-refresh targetPlan.",
|
|
234
238
|
"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.",
|
|
235
239
|
"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.",
|
|
236
240
|
"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.",
|
|
237
241
|
],
|
|
238
242
|
approvalContract: yolo
|
|
239
|
-
? "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,
|
|
243
|
+
? "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, but source-copy/fallback requires receipt-proven exhaustion of earlier ready/prep/approval rungs. 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, side-effect class, or receipt exhaustion proof drifts. --yolo does not authorize lowering paid InMail thresholds or creating connection campaigns."
|
|
240
244
|
: hasSenderSelectors
|
|
241
245
|
? "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."
|
|
242
246
|
: "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.",
|
|
@@ -249,7 +253,7 @@ export function refillSendsCommand(input = {}) {
|
|
|
249
253
|
"direct scheduler writes",
|
|
250
254
|
"sender reassignment",
|
|
251
255
|
"campaigns outside the rendered eligible sender packet",
|
|
252
|
-
"on-demand or unrelated active-campaign fallback unless the refill workflow
|
|
256
|
+
"on-demand or unrelated active-campaign fallback unless the refill workflow shows receipt-proven exhaustion for the selected same-sender lane",
|
|
253
257
|
],
|
|
254
258
|
hostExamples: {
|
|
255
259
|
claude: [
|
|
@@ -326,6 +330,9 @@ function paidRefreshApprovalPacket(params) {
|
|
|
326
330
|
const action = params.action.action ?? {};
|
|
327
331
|
const ids = actionIds(action);
|
|
328
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);
|
|
329
336
|
return {
|
|
330
337
|
approvalSource: "explicit_yolo_flag",
|
|
331
338
|
workspaceId: params.workspaceId ?? null,
|
|
@@ -336,12 +343,22 @@ function paidRefreshApprovalPacket(params) {
|
|
|
336
343
|
tableId: stringValue(ids.tableId) ?? stringValue(action.tableId) ?? null,
|
|
337
344
|
columnId: stringValue(ids.columnId) ?? stringValue(action.columnId) ?? null,
|
|
338
345
|
threshold: threshold ?? null,
|
|
339
|
-
maxStalenessSeconds
|
|
340
|
-
numberValue(action.paidInmailCreditsMaxStalenessSeconds) ??
|
|
341
|
-
null,
|
|
346
|
+
maxStalenessSeconds,
|
|
342
347
|
targetPlanFingerprint: targetPlanFingerprint(params.targetPlan),
|
|
343
348
|
};
|
|
344
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
|
+
}
|
|
345
362
|
export async function executeRefillSendsCommand(input = {}) {
|
|
346
363
|
const yolo = input.yolo === true;
|
|
347
364
|
const executionMode = input.executionMode ?? (yolo ? "yolo" : "manual");
|
|
@@ -404,7 +421,7 @@ export async function executeRefillSendsCommand(input = {}) {
|
|
|
404
421
|
});
|
|
405
422
|
continue;
|
|
406
423
|
}
|
|
407
|
-
const refreshResult = await refreshPaidInmailCreditsWithRetry(action.senderId, workspaceId);
|
|
424
|
+
const refreshResult = await refreshPaidInmailCreditsWithRetry(action.senderId, workspaceId, paidRefreshOptionsFromApprovalPacket(approvalPacket));
|
|
408
425
|
if (refreshResult.receipt) {
|
|
409
426
|
refreshedPaidInmailSenderIds.push(action.senderId);
|
|
410
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,15 +78,37 @@ 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
|
}
|
|
79
87
|
function booleanValue(value) {
|
|
80
88
|
return typeof value === "boolean" ? value : undefined;
|
|
81
89
|
}
|
|
90
|
+
function numericRecord(value) {
|
|
91
|
+
if (!isRecord(value))
|
|
92
|
+
return {};
|
|
93
|
+
return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "number" && Number.isFinite(entry[1])));
|
|
94
|
+
}
|
|
82
95
|
function campaignClassificationValue(value) {
|
|
83
96
|
return value === "sales_nav_cascade" ? "sales_nav_cascade" : undefined;
|
|
84
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
|
+
}
|
|
85
112
|
function stringArray(value) {
|
|
86
113
|
if (!Array.isArray(value))
|
|
87
114
|
return [];
|
|
@@ -133,7 +160,7 @@ function sanitizeCounts(counts, selectedKeys) {
|
|
|
133
160
|
return Boolean(key && selectedKeys.has(key));
|
|
134
161
|
});
|
|
135
162
|
}
|
|
136
|
-
function sanitizeActionCandidates(candidates, selectedKeys, status, remainingReadyOrProjectedGap, remainingProjectedGap) {
|
|
163
|
+
function sanitizeActionCandidates(candidates, selectedKeys, paidRefreshNeededSenderIds, status, remainingReadyOrProjectedGap, remainingProjectedGap) {
|
|
137
164
|
const filtered = Array.isArray(candidates)
|
|
138
165
|
? candidates
|
|
139
166
|
.filter((item) => {
|
|
@@ -144,6 +171,13 @@ function sanitizeActionCandidates(candidates, selectedKeys, status, remainingRea
|
|
|
144
171
|
return false;
|
|
145
172
|
}
|
|
146
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
|
+
}
|
|
147
181
|
if (actionType === undefined)
|
|
148
182
|
return item.type === "wait_for_scheduler";
|
|
149
183
|
const allowed = allowedActionType(actionType);
|
|
@@ -190,6 +224,8 @@ function sanitizeActionCandidate(candidate) {
|
|
|
190
224
|
columnRole: stringValue(candidate.columnRole),
|
|
191
225
|
rowSelector: sanitizeRowSelector(candidate.rowSelector),
|
|
192
226
|
actionKey: stringValue(candidate.actionKey),
|
|
227
|
+
wait: sanitizeWait(candidate.wait),
|
|
228
|
+
refillReceipt: sanitizeRefillReceipt(candidate.refillReceipt),
|
|
193
229
|
rereadAfter: candidate.rereadAfter === "get_refill_target_plan"
|
|
194
230
|
? "get_refill_target_plan"
|
|
195
231
|
: undefined,
|
|
@@ -217,6 +253,98 @@ function sanitizeRowSelector(value) {
|
|
|
217
253
|
: {}),
|
|
218
254
|
};
|
|
219
255
|
}
|
|
256
|
+
function sanitizeWait(value) {
|
|
257
|
+
if (!isRecord(value))
|
|
258
|
+
return undefined;
|
|
259
|
+
const deadlineAt = stringValue(value.deadlineAt);
|
|
260
|
+
const onExpiry = stringValue(value.onExpiry);
|
|
261
|
+
if (!deadlineAt && !onExpiry)
|
|
262
|
+
return undefined;
|
|
263
|
+
return { deadlineAt, onExpiry };
|
|
264
|
+
}
|
|
265
|
+
function sanitizeStuckActiveCells(value) {
|
|
266
|
+
if (!Array.isArray(value))
|
|
267
|
+
return [];
|
|
268
|
+
return value
|
|
269
|
+
.filter((cell) => isRecord(cell))
|
|
270
|
+
.map((cell) => ({
|
|
271
|
+
cellId: stringValue(cell.cellId),
|
|
272
|
+
rowId: stringValue(cell.rowId),
|
|
273
|
+
status: stringValue(cell.status),
|
|
274
|
+
ageMs: typeof cell.ageMs === "number" ? cell.ageMs : undefined,
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
function sanitizeApprovedNotDispatched(value) {
|
|
278
|
+
if (!Array.isArray(value))
|
|
279
|
+
return [];
|
|
280
|
+
return value
|
|
281
|
+
.filter((row) => isRecord(row))
|
|
282
|
+
.map((row) => ({
|
|
283
|
+
rowId: stringValue(row.rowId),
|
|
284
|
+
attempts: typeof row.attempts === "number" ? row.attempts : undefined,
|
|
285
|
+
reason: stringValue(row.reason),
|
|
286
|
+
terminal: booleanValue(row.terminal),
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
function sanitizeExistingRowFrontier(value) {
|
|
290
|
+
if (!isRecord(value))
|
|
291
|
+
return undefined;
|
|
292
|
+
return {
|
|
293
|
+
proof: stringValue(value.proof),
|
|
294
|
+
batchStop: stringValue(value.batchStop),
|
|
295
|
+
hasMoreFrontierRows: booleanValue(value.hasMoreFrontierRows),
|
|
296
|
+
stageCounts: numericRecord(value.stageCounts),
|
|
297
|
+
activeCellCount: typeof value.activeCellCount === "number"
|
|
298
|
+
? value.activeCellCount
|
|
299
|
+
: undefined,
|
|
300
|
+
approvalCandidates: typeof value.approvalCandidates === "number"
|
|
301
|
+
? value.approvalCandidates
|
|
302
|
+
: undefined,
|
|
303
|
+
brokenByReason: numericRecord(value.brokenByReason),
|
|
304
|
+
stuckActiveCells: sanitizeStuckActiveCells(value.stuckActiveCells),
|
|
305
|
+
approvedNotDispatched: sanitizeApprovedNotDispatched(value.approvedNotDispatched),
|
|
306
|
+
wait: sanitizeWait(value.wait),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function sanitizeSkippedRungs(value) {
|
|
310
|
+
if (!Array.isArray(value))
|
|
311
|
+
return [];
|
|
312
|
+
return value
|
|
313
|
+
.filter((rung) => isRecord(rung))
|
|
314
|
+
.map((rung) => ({
|
|
315
|
+
rung: stringValue(rung.rung),
|
|
316
|
+
reason: stringValue(rung.reason),
|
|
317
|
+
detail: stringValue(rung.detail),
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
function sanitizeRefillReceipt(value) {
|
|
321
|
+
if (!isRecord(value))
|
|
322
|
+
return undefined;
|
|
323
|
+
return {
|
|
324
|
+
summary: stringValue(value.summary),
|
|
325
|
+
schedulableGap: typeof value.schedulableGap === "number"
|
|
326
|
+
? value.schedulableGap
|
|
327
|
+
: undefined,
|
|
328
|
+
schedulerFillableGap: typeof value.schedulerFillableGap === "number"
|
|
329
|
+
? value.schedulerFillableGap
|
|
330
|
+
: undefined,
|
|
331
|
+
selectedCampaignId: stringValue(value.selectedCampaignId),
|
|
332
|
+
selectedCampaignName: stringValue(value.selectedCampaignName),
|
|
333
|
+
selectedTableId: stringValue(value.selectedTableId),
|
|
334
|
+
senderId: stringValue(value.senderId),
|
|
335
|
+
senderName: stringValue(value.senderName),
|
|
336
|
+
actionType: allowedActionType(value.actionType),
|
|
337
|
+
dateWindow: isRecord(value.dateWindow)
|
|
338
|
+
? { selectedDates: stringArray(value.dateWindow.selectedDates) }
|
|
339
|
+
: undefined,
|
|
340
|
+
readyBuffer: typeof value.readyBuffer === "number" ? value.readyBuffer : undefined,
|
|
341
|
+
approvalCandidates: typeof value.approvalCandidates === "number"
|
|
342
|
+
? value.approvalCandidates
|
|
343
|
+
: undefined,
|
|
344
|
+
existingRowFrontier: sanitizeExistingRowFrontier(value.existingRowFrontier),
|
|
345
|
+
skippedRungs: sanitizeSkippedRungs(value.skippedRungs),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
220
348
|
function sanitizeRerunErroredCellsOperations(value) {
|
|
221
349
|
if (!Array.isArray(value))
|
|
222
350
|
return [];
|
|
@@ -285,6 +413,8 @@ function sanitizeToolInput(value) {
|
|
|
285
413
|
: undefined,
|
|
286
414
|
threshold: typeof value.threshold === "number" ? value.threshold : undefined,
|
|
287
415
|
operations: sanitizeRerunErroredCellsOperations(value.operations),
|
|
416
|
+
wait: sanitizeWait(value.wait),
|
|
417
|
+
refillReceipt: sanitizeRefillReceipt(value.refillReceipt),
|
|
288
418
|
};
|
|
289
419
|
}
|
|
290
420
|
function sanitizePacketIds(value) {
|
|
@@ -328,6 +458,7 @@ function sanitizeStructuredAction(action, selectedBySender, mode, rank) {
|
|
|
328
458
|
toolInput: sanitizeToolInput(action.toolInput),
|
|
329
459
|
inputSummary: stringValue(action.inputSummary) ?? "",
|
|
330
460
|
reason: stringValue(action.reason) ?? "",
|
|
461
|
+
refillReceipt: sanitizeRefillReceipt(action.refillReceipt),
|
|
331
462
|
prerequisites: stringArray(action.prerequisites),
|
|
332
463
|
rereadAfter: action.rereadAfter === "get_refill_target_plan"
|
|
333
464
|
? "get_refill_target_plan"
|
|
@@ -430,20 +561,41 @@ function sanitizeSourcePlan(value) {
|
|
|
430
561
|
function sanitizePaidInmail(value) {
|
|
431
562
|
if (!isRecord(value))
|
|
432
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
|
+
}
|
|
433
585
|
return {
|
|
434
|
-
status
|
|
435
|
-
reason
|
|
436
|
-
checkedAt
|
|
586
|
+
status,
|
|
587
|
+
reason,
|
|
588
|
+
checkedAt,
|
|
437
589
|
balance: typeof value.balance === "number" ? value.balance : null,
|
|
438
590
|
sentSince: numberValue(value.sentSince),
|
|
439
|
-
availableCredits
|
|
440
|
-
threshold
|
|
591
|
+
availableCredits,
|
|
592
|
+
threshold,
|
|
441
593
|
campaignId: stringValue(value.campaignId) ?? null,
|
|
442
594
|
campaignName: stringValue(value.campaignName) ?? null,
|
|
443
595
|
tableId: stringValue(value.tableId) ?? null,
|
|
444
596
|
tableName: stringValue(value.tableName) ?? null,
|
|
445
597
|
columnId: stringValue(value.columnId) ?? null,
|
|
446
|
-
maxStalenessSeconds
|
|
598
|
+
maxStalenessSeconds,
|
|
447
599
|
campaignClassification: campaignClassificationValue(value.campaignClassification) ?? null,
|
|
448
600
|
connectionFallbackAvailable: booleanValue(value.connectionFallbackAvailable) ?? false,
|
|
449
601
|
connectionFallbackCampaignId: stringValue(value.connectionFallbackCampaignId) ?? null,
|
|
@@ -451,8 +603,19 @@ function sanitizePaidInmail(value) {
|
|
|
451
603
|
suggestedThreshold: typeof value.suggestedThreshold === "number"
|
|
452
604
|
? value.suggestedThreshold
|
|
453
605
|
: null,
|
|
606
|
+
...(freshnessPolicy ? { freshnessPolicy } : {}),
|
|
454
607
|
};
|
|
455
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
|
+
}
|
|
456
619
|
function sanitizeStructuredSenderPlans(value, selectedBySender) {
|
|
457
620
|
if (!Array.isArray(value))
|
|
458
621
|
return [];
|
|
@@ -464,10 +627,12 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
|
|
|
464
627
|
return Boolean(selectedLane && selectedBySender.get(plan.senderId) === selectedLane);
|
|
465
628
|
})
|
|
466
629
|
.map((plan) => {
|
|
630
|
+
const paidInmail = sanitizePaidInmail(plan.paidInmail);
|
|
467
631
|
const nextActions = Array.isArray(plan.nextActions)
|
|
468
632
|
? plan.nextActions
|
|
469
633
|
.map((action, index) => sanitizeStructuredAction(action, selectedBySender, "next", index + 1))
|
|
470
634
|
.filter((action) => Boolean(action))
|
|
635
|
+
.filter((action) => filterFreshPaidInmailRefreshAction(action, paidInmail))
|
|
471
636
|
: [];
|
|
472
637
|
const manualAlternates = Array.isArray(plan.manualAlternates)
|
|
473
638
|
? plan.manualAlternates
|
|
@@ -512,8 +677,9 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
|
|
|
512
677
|
: {},
|
|
513
678
|
coverageDisplay: stringArray(plan.coverageDisplay),
|
|
514
679
|
campaignRanking: sanitizeCampaignRanking(plan.campaignRanking),
|
|
515
|
-
paidInmail
|
|
680
|
+
paidInmail,
|
|
516
681
|
sourcePlan: sanitizeSourcePlan(plan.sourcePlan),
|
|
682
|
+
refillReceipt: sanitizeRefillReceipt(plan.refillReceipt),
|
|
517
683
|
nextActions,
|
|
518
684
|
manualAlternates,
|
|
519
685
|
...(isRecord(plan.emptyState)
|
|
@@ -537,6 +703,17 @@ function buildSanitizedGlobalActionQueue(senderRefillPlans) {
|
|
|
537
703
|
rank: index + 1,
|
|
538
704
|
}));
|
|
539
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
|
+
}
|
|
540
717
|
function emptyUnsupportedResult(result) {
|
|
541
718
|
if (!isRecord(result) || !isRecord(result.target))
|
|
542
719
|
return result;
|
|
@@ -579,7 +756,7 @@ function emptyUnsupportedResult(result) {
|
|
|
579
756
|
mcpSanitizedRefillLanes: true,
|
|
580
757
|
};
|
|
581
758
|
}
|
|
582
|
-
function sanitizeBlockers(blockers, selectedKeys) {
|
|
759
|
+
function sanitizeBlockers(blockers, selectedKeys, paidRefreshNeededSenderIds) {
|
|
583
760
|
if (!Array.isArray(blockers))
|
|
584
761
|
return [];
|
|
585
762
|
return blockers.filter((item) => {
|
|
@@ -591,6 +768,10 @@ function sanitizeBlockers(blockers, selectedKeys) {
|
|
|
591
768
|
return true;
|
|
592
769
|
const detailSenderId = detail.split(":")[0];
|
|
593
770
|
if (code.startsWith("paid_inmail_")) {
|
|
771
|
+
if (code === "paid_inmail_credit_stale" &&
|
|
772
|
+
!paidRefreshNeededSenderIds.has(detailSenderId)) {
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
594
775
|
return selectedKeys.has(`${detailSenderId}:send_inmail_closed`);
|
|
595
776
|
}
|
|
596
777
|
if (detail.includes(":send_dm") || detail.includes(":send_inmail_open")) {
|
|
@@ -679,9 +860,10 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
679
860
|
const remainingScheduledGap = senderPlans.reduce((sum, plan) => sum + numberValue(plan.remainingScheduledGap), 0);
|
|
680
861
|
const remainingProjectedGap = senderPlans.reduce((sum, plan) => sum + numberValue(plan.remainingProjectedGap), 0);
|
|
681
862
|
const remainingReadyOrProjectedGap = senderPlans.reduce((sum, plan) => sum + numberValue(plan.remainingReadyOrProjectedGap), 0);
|
|
682
|
-
const blockers = sanitizeBlockers(result.blockers, selectedKeys);
|
|
683
863
|
const eligibleSenderLedger = sanitizeEligibleSenderLedger(target.eligibleSenderLedger, selectedBySender);
|
|
684
864
|
const senderRefillPlans = sanitizeStructuredSenderPlans(target.senderRefillPlans, selectedBySender);
|
|
865
|
+
const paidRefreshNeededSenderIdSet = paidRefreshNeededSenderIds(senderRefillPlans);
|
|
866
|
+
const blockers = sanitizeBlockers(result.blockers, selectedKeys, paidRefreshNeededSenderIdSet);
|
|
685
867
|
const globalActionQueue = buildSanitizedGlobalActionQueue(senderRefillPlans);
|
|
686
868
|
const hasTerminalNoActionBlocker = globalActionQueue.length === 0 &&
|
|
687
869
|
blockers.some((blocker) => TERMINAL_NO_ACTION_BLOCKER_CODES.has(String(blocker.code)));
|
|
@@ -728,7 +910,7 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
728
910
|
readyCounts: sanitizeCounts(result.coverage.readyCounts, selectedKeys),
|
|
729
911
|
}
|
|
730
912
|
: result.coverage,
|
|
731
|
-
actionCandidates: sanitizeActionCandidates(result.actionCandidates, selectedKeys, status, remainingReadyOrProjectedGap, remainingProjectedGap),
|
|
913
|
+
actionCandidates: sanitizeActionCandidates(result.actionCandidates, selectedKeys, paidRefreshNeededSenderIdSet, status, remainingReadyOrProjectedGap, remainingProjectedGap),
|
|
732
914
|
blockers,
|
|
733
915
|
mcpSanitizedRefillLanes: true,
|
|
734
916
|
};
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -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;
|
package/dist/tools/senders.d.ts
CHANGED
|
@@ -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;
|
package/dist/tools/senders.js
CHANGED
|
@@ -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
|
|
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
|
@@ -97,8 +97,11 @@ That command helper normalizes arguments and returns the execution contract. In
|
|
|
97
97
|
non-yolo mode it does not mutate. In `--yolo`, it may execute exactly one safe
|
|
98
98
|
bounded primitive from the fresh `target.globalActionQueue[0]`, then reread and
|
|
99
99
|
return the new target plan; currently safe primitives are paid-credit refresh,
|
|
100
|
-
existing-row message preparation,
|
|
101
|
-
|
|
100
|
+
existing-row message preparation, generated-message approval, receipt-proven
|
|
101
|
+
same-source row copy, and read-only wait rereads. Same-source copy/source
|
|
102
|
+
fallback is safe only after receipt-proven exhaustion:
|
|
103
|
+
`hasMoreFrontierRows:false`, zero `approvalCandidates`, no `stuckActiveCells`,
|
|
104
|
+
and no non-terminal `approvedNotDispatched` work. It does not run unbounded approval, lower
|
|
102
105
|
paid-InMail thresholds, switch source families, create campaigns, launch, send,
|
|
103
106
|
or write scheduler rows. Continue with the workflow below for route selection,
|
|
104
107
|
state rereads, approval gating, source import, preparation, and bounded
|
|
@@ -198,7 +201,10 @@ Structured planner packet:
|
|
|
198
201
|
- `target.senderRefillPlans[]` is the canonical sender-level packet; read and
|
|
199
202
|
display it before mutation.
|
|
200
203
|
- Each sender packet includes `campaignRanking.options`, `sourcePlan`,
|
|
201
|
-
`nextActions`, and `manualAlternates`.
|
|
204
|
+
`refillReceipt`, `nextActions`, and `manualAlternates`.
|
|
205
|
+
- `refillReceipt` is the public ladder receipt. It carries the selected
|
|
206
|
+
campaign/sender/lane summary, skipped rungs, existing-row frontier proof, and
|
|
207
|
+
any absolute `wait.deadlineAt`.
|
|
202
208
|
- Preserve these coverage labels exactly: `Need to prepare`, `Goal`,
|
|
203
209
|
`Already sent`, `Scheduled`, `Ready and waiting to be scheduled`, and
|
|
204
210
|
`Still need`.
|
|
@@ -217,6 +223,13 @@ from the selected source (`selectedLeadListId`, provider, and source
|
|
|
217
223
|
fingerprint preserved), then use provider-aligned source-more. A new source or
|
|
218
224
|
provider switch changes the reply-rate baseline and is a manual alternate, not a
|
|
219
225
|
`--yolo` side effect.
|
|
226
|
+
Source/copy/fallback requires receipt-proven exhaustion of earlier rungs:
|
|
227
|
+
`existingRowFrontier.hasMoreFrontierRows:false`, zero `approvalCandidates`, no
|
|
228
|
+
fresh active prep, no `stuckActiveCells`, and no non-terminal
|
|
229
|
+
`approvedNotDispatched` rows. Treat anomalies, `stuckActiveCells`, and
|
|
230
|
+
non-terminal `approvedNotDispatched` as diagnose-and-report gates, not
|
|
231
|
+
exhaustion. Terminal `approvedNotDispatched` blockers may be reported, then the
|
|
232
|
+
ladder can proceed.
|
|
220
233
|
|
|
221
234
|
Run-local paid-credit guard: in `--yolo`, the `refill_sends` MCP command
|
|
222
235
|
automatically maintains a `refreshedPaidInmailSenderIds` set for the current
|
|
@@ -266,6 +279,10 @@ interval, until projected coverage fills the target, a concrete non-scheduler
|
|
|
266
279
|
blocker appears, or Christian explicitly asks to stop or only receive a status
|
|
267
280
|
report. Treat `awaiting_scheduler_after_ready_buffer` as an in-progress wait
|
|
268
281
|
state, not a close-out condition.
|
|
282
|
+
Wait actions are gates, not competing goals. When `wait_for_active_work` or
|
|
283
|
+
`wait_for_scheduler` includes receipt `wait.deadlineAt`, honor that absolute
|
|
284
|
+
deadline; if it is expired on this call, escalate to diagnostics with the
|
|
285
|
+
receipt evidence instead of issuing another blind wait.
|
|
269
286
|
If paid InMail credit facts are stale or missing and the first target plan
|
|
270
287
|
contains `refresh_paid_inmail_credits`, do not present that as the operator's
|
|
271
288
|
next action in `--yolo`. Do not present paid-credit refresh as the next operator
|
|
@@ -119,7 +119,10 @@ files or memory.
|
|
|
119
119
|
- `target.senderRefillPlans[]` is the canonical sender-level packet; read and
|
|
120
120
|
display it before mutation.
|
|
121
121
|
- Each sender packet includes `campaignRanking.options`, `sourcePlan`,
|
|
122
|
-
`nextActions`, and `manualAlternates`.
|
|
122
|
+
`refillReceipt`, `nextActions`, and `manualAlternates`.
|
|
123
|
+
- `refillReceipt` is the public ladder receipt. It carries the selected
|
|
124
|
+
campaign/sender/lane summary, skipped rungs, existing-row frontier proof,
|
|
125
|
+
and any absolute `wait.deadlineAt`.
|
|
123
126
|
- Preserve these coverage labels exactly: `Need to prepare`, `Goal`,
|
|
124
127
|
`Already sent`, `Scheduled`, `Ready and waiting to be scheduled`, and
|
|
125
128
|
`Still need`.
|
|
@@ -138,6 +141,14 @@ files or memory.
|
|
|
138
141
|
provider, and source fingerprint preserved), then use provider-aligned
|
|
139
142
|
source-more. A new source or provider switch changes the reply-rate baseline
|
|
140
143
|
and is a manual alternate, not a `--yolo` side effect.
|
|
144
|
+
Source/copy/fallback requires receipt-proven exhaustion of earlier rungs:
|
|
145
|
+
`existingRowFrontier.hasMoreFrontierRows:false`, zero
|
|
146
|
+
`approvalCandidates`, no fresh active prep, no `stuckActiveCells`, and no
|
|
147
|
+
non-terminal `approvedNotDispatched` rows. Treat anomalies,
|
|
148
|
+
`stuckActiveCells`, and non-terminal `approvedNotDispatched` as
|
|
149
|
+
diagnose-and-report gates, not exhaustion. Terminal
|
|
150
|
+
`approvedNotDispatched` blockers may be reported, then the ladder can
|
|
151
|
+
proceed.
|
|
141
152
|
Run-local paid-credit guard: in `--yolo`, the `refill_sends` MCP command
|
|
142
153
|
automatically maintains a `refreshedPaidInmailSenderIds` set for the current
|
|
143
154
|
command call. If its first target plan has stale/missing paid-InMail credit
|
|
@@ -187,6 +198,10 @@ files or memory.
|
|
|
187
198
|
stop or only receive a status report. Treat
|
|
188
199
|
`awaiting_scheduler_after_ready_buffer` as an in-progress wait state, not a
|
|
189
200
|
close-out condition.
|
|
201
|
+
Wait actions are gates, not competing goals. When `wait_for_active_work` or
|
|
202
|
+
`wait_for_scheduler` includes receipt `wait.deadlineAt`, honor that absolute
|
|
203
|
+
deadline; if it is expired on this call, escalate to diagnostics with the
|
|
204
|
+
receipt evidence instead of issuing another blind wait.
|
|
190
205
|
If paid InMail credit facts are stale or missing and the first target plan
|
|
191
206
|
contains `refresh_paid_inmail_credits`, do not present that as the operator's
|
|
192
207
|
next action in `--yolo`. Do not present paid-credit refresh as the next
|