@sellable/mcp 0.1.553 → 0.1.555
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/refill-contract.d.ts +157 -0
- package/dist/refill-contract.js +487 -0
- package/dist/refill-run-client.d.ts +5 -0
- package/dist/refill-run-client.js +15 -0
- package/dist/refill-run-loop.d.ts +12 -1
- package/dist/refill-run-loop.js +158 -13
- package/dist/tools/campaign-message-preparation.d.ts +62 -0
- package/dist/tools/campaign-message-preparation.js +41 -0
- package/dist/tools/evergreen-refill-plan.d.ts +3 -0
- package/dist/tools/evergreen-refill-plan.js +29 -7
- package/dist/tools/prompts.js +9 -0
- package/dist/tools/refill-executors.d.ts +9 -1
- package/dist/tools/refill-executors.js +101 -8
- package/dist/tools/refill-sends-v2.d.ts +112 -1
- package/dist/tools/refill-sends-v2.js +302 -2
- package/dist/tools/refill-sends.d.ts +506 -2
- package/dist/tools/refill-sends.js +134 -1
- package/dist/tools/refill-target-plan.js +317 -23
- package/dist/tools/registry.d.ts +94 -33
- package/dist/tools/registry.js +1 -3
- package/dist/tools/scheduler-run.d.ts +61 -0
- package/dist/tools/scheduler-run.js +183 -1
- package/package.json +1 -1
- package/skills/refill-sends/SKILL.md +91 -388
- package/skills/refill-sends-v2/SKILL.md +6 -6
- package/skills/refill-sends-v2-workflow/SKILL.md +5 -5
- package/skills/refill-sends-v2-workflow/core/flow.v1.json +8 -8
- package/skills/refill-sends-workflow/SKILL.md +100 -781
- package/skills/refill-sends-workflow/core/contract.v2.json +543 -0
- package/skills/refill-sends-workflow/core/flow.v1.json +122 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { actionIds, classifyPaidInmailRefreshReceipt, collectPaidInmailRefreshActions, executeOneYoloPrimitive, firstGlobalAction, normalizeStrings, numberValue, recordValue, refreshPaidInmailCreditsWithRetry, stringValue, } from "./refill-executors.js";
|
|
2
|
+
import { prepareRefillSendsV2Approval, refillSendsV2Command, } from "./refill-sends-v2.js";
|
|
3
|
+
import { REFILL_CONTRACT_CACHE_VERSION, REFILL_CONTRACT_HASH, REFILL_CONTRACT_VERSION, renderRefillBootstrapGuidance, renderRefillSafetyGuidance, renderRefillTerminalGuidance, } from "../refill-contract.js";
|
|
2
4
|
import { getRefillTargetPlan } from "./refill-target-plan.js";
|
|
3
5
|
import { createWorkspaceContext, normalizeExplicitWorkspaceId, } from "./workspace-context.js";
|
|
4
6
|
const REFILL_SEND_ACTION_TYPES = ["send_invite", "send_inmail_closed"];
|
|
@@ -94,7 +96,16 @@ function normalizeRefillSendsInput(input) {
|
|
|
94
96
|
export const refillSendsToolDefinitions = [
|
|
95
97
|
{
|
|
96
98
|
name: "refill_sends",
|
|
97
|
-
description:
|
|
99
|
+
description: [
|
|
100
|
+
"Public durable refill entrypoint. It preserves explicit workspace/date/selectors, renders the normal bounded packet even under yolo, executes one approved primitive, records a typed receipt, rereads, and replans.",
|
|
101
|
+
"An exact-date request-scoped run_scheduler_sweep may schedule eligible workspace cells through product gates; it never sends directly or raw-writes scheduler fields.",
|
|
102
|
+
`contractVersion=${REFILL_CONTRACT_VERSION}`,
|
|
103
|
+
`cacheVersion=${REFILL_CONTRACT_CACHE_VERSION}`,
|
|
104
|
+
`contractHash=${REFILL_CONTRACT_HASH}`,
|
|
105
|
+
renderRefillBootstrapGuidance(),
|
|
106
|
+
renderRefillSafetyGuidance(),
|
|
107
|
+
renderRefillTerminalGuidance(),
|
|
108
|
+
].join(" "),
|
|
98
109
|
inputSchema: {
|
|
99
110
|
type: "object",
|
|
100
111
|
properties: {
|
|
@@ -180,6 +191,14 @@ export const refillSendsToolDefinitions = [
|
|
|
180
191
|
type: "string",
|
|
181
192
|
description: "Optional actionKey from the approved packet's first global action. A mismatch returns approval_scope_changed before any refresh or mutation.",
|
|
182
193
|
},
|
|
194
|
+
runId: { type: "string" },
|
|
195
|
+
fence: { type: "number" },
|
|
196
|
+
scopeHash: { type: "string" },
|
|
197
|
+
targetShapeRevision: { type: "string" },
|
|
198
|
+
actionFingerprint: { type: "string" },
|
|
199
|
+
expectedTargetsHash: { type: "string" },
|
|
200
|
+
approvalExpiresAt: { type: "string" },
|
|
201
|
+
approvalFingerprint: { type: "string" },
|
|
183
202
|
},
|
|
184
203
|
required: [],
|
|
185
204
|
additionalProperties: false,
|
|
@@ -450,6 +469,51 @@ function paidRefreshOptionsFromApprovalPacket(packet) {
|
|
|
450
469
|
maxStalenessSeconds: packet.maxStalenessSeconds,
|
|
451
470
|
};
|
|
452
471
|
}
|
|
472
|
+
function durableV2PlanEligible(plan) {
|
|
473
|
+
const root = recordValue(plan);
|
|
474
|
+
const target = recordValue(root?.target);
|
|
475
|
+
const senderPlans = Array.isArray(target?.senderRefillPlans)
|
|
476
|
+
? target.senderRefillPlans
|
|
477
|
+
: [];
|
|
478
|
+
return Boolean(stringValue(root?.targetShapeRevision) &&
|
|
479
|
+
senderPlans.some((entry) => {
|
|
480
|
+
const senderPlan = recordValue(entry);
|
|
481
|
+
return Array.isArray(senderPlan?.laneLedgers);
|
|
482
|
+
}));
|
|
483
|
+
}
|
|
484
|
+
function durableApprovalEcho(input) {
|
|
485
|
+
const values = {
|
|
486
|
+
scopeHash: stringValue(input.scopeHash),
|
|
487
|
+
targetShapeRevision: stringValue(input.targetShapeRevision),
|
|
488
|
+
actionFingerprint: stringValue(input.actionFingerprint),
|
|
489
|
+
expectedTargetsHash: stringValue(input.expectedTargetsHash),
|
|
490
|
+
approvalExpiresAt: stringValue(input.approvalExpiresAt),
|
|
491
|
+
approvalFingerprint: stringValue(input.approvalFingerprint),
|
|
492
|
+
};
|
|
493
|
+
return Object.values(values).every(Boolean)
|
|
494
|
+
? values
|
|
495
|
+
: null;
|
|
496
|
+
}
|
|
497
|
+
function durableYoloBaseResult(params) {
|
|
498
|
+
return {
|
|
499
|
+
...params.command,
|
|
500
|
+
targetPlan: params.targetPlan,
|
|
501
|
+
targetPlanBeforePaidRefresh: null,
|
|
502
|
+
targetPlanBeforeYoloPrimitive: params.targetPlan,
|
|
503
|
+
autoPaidInmailRefresh: {
|
|
504
|
+
enabled: false,
|
|
505
|
+
status: "skipped_awaiting_durable_approval",
|
|
506
|
+
refreshedPaidInmailSenderIds: [],
|
|
507
|
+
failedPaidInmailRefreshes: [],
|
|
508
|
+
refreshReceipts: [],
|
|
509
|
+
attemptedSenderIds: [],
|
|
510
|
+
targetPlanReread: false,
|
|
511
|
+
workspaceId: params.workspaceId,
|
|
512
|
+
workspaceResolution: "explicit",
|
|
513
|
+
note: "Durable v2 binds approval before credit refresh or any refill primitive.",
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
}
|
|
453
517
|
export async function executeRefillSendsCommand(input = {}) {
|
|
454
518
|
const normalizedInput = normalizeRefillSendsInput(input);
|
|
455
519
|
const yolo = normalizedInput.yolo === true;
|
|
@@ -486,6 +550,7 @@ export async function executeRefillSendsCommand(input = {}) {
|
|
|
486
550
|
status: "not_run_without_yolo",
|
|
487
551
|
refreshedPaidInmailSenderIds: [],
|
|
488
552
|
failedPaidInmailRefreshes: [],
|
|
553
|
+
refreshReceipts: [],
|
|
489
554
|
note: "Automatic paid InMail credit refresh requires --yolo, scheduled mode, or an explicit workspaceId so the request can refresh exact sender facts safely.",
|
|
490
555
|
},
|
|
491
556
|
yoloExecution: {
|
|
@@ -513,6 +578,7 @@ export async function executeRefillSendsCommand(input = {}) {
|
|
|
513
578
|
status: "skipped_approval_scope_changed",
|
|
514
579
|
refreshedPaidInmailSenderIds: [],
|
|
515
580
|
failedPaidInmailRefreshes: [],
|
|
581
|
+
refreshReceipts: [],
|
|
516
582
|
attemptedSenderIds: [],
|
|
517
583
|
targetPlanReread: false,
|
|
518
584
|
workspaceId,
|
|
@@ -527,6 +593,73 @@ export async function executeRefillSendsCommand(input = {}) {
|
|
|
527
593
|
},
|
|
528
594
|
};
|
|
529
595
|
}
|
|
596
|
+
if (yolo &&
|
|
597
|
+
workspaceId &&
|
|
598
|
+
durableV2PlanEligible(targetPlanBeforePaidRefresh)) {
|
|
599
|
+
const base = durableYoloBaseResult({
|
|
600
|
+
command,
|
|
601
|
+
targetPlan: targetPlanBeforePaidRefresh,
|
|
602
|
+
workspaceId,
|
|
603
|
+
});
|
|
604
|
+
const echo = durableApprovalEcho(scopedInput);
|
|
605
|
+
if (!scopedInput.approvalFingerprint) {
|
|
606
|
+
const approval = await prepareRefillSendsV2Approval({
|
|
607
|
+
workspaceId,
|
|
608
|
+
intent: scopedInput.intent,
|
|
609
|
+
senderIds: normalizeStrings(scopedInput.senderIds),
|
|
610
|
+
approvalMode: scopedInput.approvalMode ?? "approve",
|
|
611
|
+
targetDate: scopedInput.targetDate ?? undefined,
|
|
612
|
+
untilDate: scopedInput.untilDate ?? undefined,
|
|
613
|
+
horizonSendDays: scopedInput.horizonSendDays,
|
|
614
|
+
targetPlan: targetPlanBeforePaidRefresh,
|
|
615
|
+
});
|
|
616
|
+
return {
|
|
617
|
+
...base,
|
|
618
|
+
yoloExecution: {
|
|
619
|
+
enabled: true,
|
|
620
|
+
...approval,
|
|
621
|
+
selectedAction: firstGlobalAction(targetPlanBeforePaidRefresh),
|
|
622
|
+
targetPlanReread: false,
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
if (!echo || !scopedInput.runId || typeof scopedInput.fence !== "number") {
|
|
627
|
+
return {
|
|
628
|
+
...base,
|
|
629
|
+
ok: false,
|
|
630
|
+
code: "approval_scope_changed",
|
|
631
|
+
yoloExecution: {
|
|
632
|
+
enabled: true,
|
|
633
|
+
status: "approval_scope_changed",
|
|
634
|
+
blocker: "approval_echo_incomplete",
|
|
635
|
+
selectedAction: firstGlobalAction(targetPlanBeforePaidRefresh),
|
|
636
|
+
targetPlanReread: false,
|
|
637
|
+
},
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
const durable = await refillSendsV2Command({
|
|
641
|
+
workspaceId,
|
|
642
|
+
runId: scopedInput.runId,
|
|
643
|
+
fence: scopedInput.fence,
|
|
644
|
+
intent: scopedInput.intent,
|
|
645
|
+
senderIds: normalizeStrings(scopedInput.senderIds),
|
|
646
|
+
approvalMode: scopedInput.approvalMode ?? "approve",
|
|
647
|
+
targetDate: scopedInput.targetDate ?? undefined,
|
|
648
|
+
untilDate: scopedInput.untilDate ?? undefined,
|
|
649
|
+
horizonSendDays: scopedInput.horizonSendDays,
|
|
650
|
+
targetPlan: targetPlanBeforePaidRefresh,
|
|
651
|
+
approvalEcho: echo,
|
|
652
|
+
});
|
|
653
|
+
return {
|
|
654
|
+
...base,
|
|
655
|
+
yoloExecution: {
|
|
656
|
+
enabled: true,
|
|
657
|
+
...durable,
|
|
658
|
+
selectedAction: firstGlobalAction(targetPlanBeforePaidRefresh),
|
|
659
|
+
targetPlanReread: true,
|
|
660
|
+
},
|
|
661
|
+
};
|
|
662
|
+
}
|
|
530
663
|
const refreshActions = collectPaidInmailRefreshActions(targetPlanBeforePaidRefresh);
|
|
531
664
|
const refreshedPaidInmailSenderIds = [];
|
|
532
665
|
const failedPaidInmailRefreshes = [];
|
|
@@ -11,6 +11,7 @@ const SAFE_PACKET_ACTION_TYPES = new Set([
|
|
|
11
11
|
"start_paused_campaign",
|
|
12
12
|
"rerun_errored_cells",
|
|
13
13
|
"run_scheduler_sweep",
|
|
14
|
+
"scheduler_first_settle",
|
|
14
15
|
"wait_for_scheduler",
|
|
15
16
|
"wait_for_active_work",
|
|
16
17
|
"wait_for_source_import",
|
|
@@ -84,11 +85,7 @@ const SCHEDULER_SWEEP_FORBIDDEN_SIDE_EFFECTS = [
|
|
|
84
85
|
"change sender limits",
|
|
85
86
|
"change source selection",
|
|
86
87
|
];
|
|
87
|
-
const SCHEDULER_SWEEP_RECEIPT_GROUPS = [
|
|
88
|
-
"campaignId",
|
|
89
|
-
"tableId",
|
|
90
|
-
"actionType",
|
|
91
|
-
];
|
|
88
|
+
const SCHEDULER_SWEEP_RECEIPT_GROUPS = ["campaignId", "tableId", "actionType"];
|
|
92
89
|
async function postRefillTargetPlan(body, workspaceId) {
|
|
93
90
|
const api = getApi();
|
|
94
91
|
const requestOptions = workspaceRequestOptions(workspaceId);
|
|
@@ -215,14 +212,289 @@ function senderActionKey(senderId, actionType) {
|
|
|
215
212
|
function sanitizeCounts(counts, selectedKeys) {
|
|
216
213
|
if (!Array.isArray(counts))
|
|
217
214
|
return [];
|
|
218
|
-
return counts
|
|
215
|
+
return counts
|
|
216
|
+
.filter((item) => {
|
|
219
217
|
if (!isRecord(item))
|
|
220
218
|
return false;
|
|
221
219
|
const actionType = allowedActionType(item.actionType);
|
|
222
220
|
const key = senderActionKey(item.senderId, actionType);
|
|
223
221
|
return Boolean(key && selectedKeys.has(key));
|
|
222
|
+
})
|
|
223
|
+
.map((item) => ({
|
|
224
|
+
senderId: stringValue(item.senderId),
|
|
225
|
+
actionType: allowedActionType(item.actionType),
|
|
226
|
+
refillLaneKey: refillLaneKeyValue(item.refillLaneKey),
|
|
227
|
+
branchActionType: branchActionTypeValue(item.branchActionType),
|
|
228
|
+
date: stringValue(item.date),
|
|
229
|
+
count: nonNegativeNumber(item.count),
|
|
230
|
+
campaignId: stringValue(item.campaignId),
|
|
231
|
+
tableId: stringValue(item.tableId),
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
const REFILL_BRANCH_ACTION_TYPES = new Set([
|
|
235
|
+
"send_invite",
|
|
236
|
+
"send_dm",
|
|
237
|
+
"send_inmail_open",
|
|
238
|
+
"send_inmail_closed",
|
|
239
|
+
"react_and_comment",
|
|
240
|
+
]);
|
|
241
|
+
function nonNegativeNumber(value) {
|
|
242
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
243
|
+
? Math.max(0, value)
|
|
244
|
+
: 0;
|
|
245
|
+
}
|
|
246
|
+
function refillLaneKeyValue(value) {
|
|
247
|
+
if (value === "sales_nav_cascade")
|
|
248
|
+
return value;
|
|
249
|
+
return allowedActionType(value) ?? undefined;
|
|
250
|
+
}
|
|
251
|
+
function branchActionTypeValue(value) {
|
|
252
|
+
return typeof value === "string" && REFILL_BRANCH_ACTION_TYPES.has(value)
|
|
253
|
+
? value
|
|
254
|
+
: undefined;
|
|
255
|
+
}
|
|
256
|
+
function sanitizeBranchMetrics(value) {
|
|
257
|
+
if (!Array.isArray(value))
|
|
258
|
+
return [];
|
|
259
|
+
return value
|
|
260
|
+
.filter((metric) => isRecord(metric))
|
|
261
|
+
.map((metric) => ({
|
|
262
|
+
actionType: branchActionTypeValue(metric.actionType),
|
|
263
|
+
count: nonNegativeNumber(metric.count),
|
|
264
|
+
}))
|
|
265
|
+
.filter((metric) => Boolean(metric.actionType));
|
|
266
|
+
}
|
|
267
|
+
function sanitizeBranchMetricGroups(value) {
|
|
268
|
+
if (!isRecord(value)) {
|
|
269
|
+
return { sentScheduled: [], assignedReady: [], sharedReady: [] };
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
sentScheduled: sanitizeBranchMetrics(value.sentScheduled),
|
|
273
|
+
assignedReady: sanitizeBranchMetrics(value.assignedReady),
|
|
274
|
+
sharedReady: sanitizeBranchMetrics(value.sharedReady),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function sanitizeLedgerViews(value) {
|
|
278
|
+
const views = isRecord(value) ? value : {};
|
|
279
|
+
const raw = isRecord(views.raw) ? views.raw : {};
|
|
280
|
+
const goalCapped = isRecord(views.goalCapped) ? views.goalCapped : {};
|
|
281
|
+
const allocatable = isRecord(views.allocatable) ? views.allocatable : {};
|
|
282
|
+
const unallocatable = isRecord(views.unallocatable)
|
|
283
|
+
? views.unallocatable
|
|
284
|
+
: {};
|
|
285
|
+
return {
|
|
286
|
+
raw: {
|
|
287
|
+
sentScheduled: nonNegativeNumber(raw.sentScheduled),
|
|
288
|
+
assignedReady: nonNegativeNumber(raw.assignedReady),
|
|
289
|
+
sharedReady: nonNegativeNumber(raw.sharedReady),
|
|
290
|
+
},
|
|
291
|
+
goalCapped: {
|
|
292
|
+
projected: nonNegativeNumber(goalCapped.projected),
|
|
293
|
+
ready: nonNegativeNumber(goalCapped.ready),
|
|
294
|
+
remaining: nonNegativeNumber(goalCapped.remaining),
|
|
295
|
+
},
|
|
296
|
+
allocatable: {
|
|
297
|
+
demand: nonNegativeNumber(allocatable.demand),
|
|
298
|
+
ready: nonNegativeNumber(allocatable.ready),
|
|
299
|
+
prepDeficit: nonNegativeNumber(allocatable.prepDeficit),
|
|
300
|
+
},
|
|
301
|
+
unallocatable: {
|
|
302
|
+
assignedReady: nonNegativeNumber(unallocatable.assignedReady),
|
|
303
|
+
sharedReady: nonNegativeNumber(unallocatable.sharedReady),
|
|
304
|
+
goalDeficit: nonNegativeNumber(unallocatable.goalDeficit),
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function sanitizeIdentityChecks(value) {
|
|
309
|
+
const checks = isRecord(value) ? value : {};
|
|
310
|
+
return {
|
|
311
|
+
assignedConserved: checks.assignedConserved === true,
|
|
312
|
+
sharedConserved: checks.sharedConserved === true,
|
|
313
|
+
readyCapped: checks.readyCapped === true,
|
|
314
|
+
deficitConserved: checks.deficitConserved === true,
|
|
315
|
+
nonNegative: checks.nonNegative === true,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const LEDGER_NUMBER_FIELDS = [
|
|
319
|
+
"goal",
|
|
320
|
+
"sentScheduledRawUnique",
|
|
321
|
+
"goalAfterSentScheduled",
|
|
322
|
+
"schedulerFillableSlotsFromCandidateSimulation",
|
|
323
|
+
"preReadyAllocatableDemand",
|
|
324
|
+
"assignedRawReady",
|
|
325
|
+
"assignedCreditedReady",
|
|
326
|
+
"assignedUnallocatableReady",
|
|
327
|
+
"sharedRawReady",
|
|
328
|
+
"sharedAllocatedReady",
|
|
329
|
+
"sharedUnallocatableReady",
|
|
330
|
+
"readyGoalCredited",
|
|
331
|
+
"goalCappedProjected",
|
|
332
|
+
"remainingGoalGap",
|
|
333
|
+
"initialAllocatablePrepDeficit",
|
|
334
|
+
"unallocatableGoalDeficit",
|
|
335
|
+
"matchingCardinality",
|
|
336
|
+
];
|
|
337
|
+
function sanitizeLaneLedger(value, requireScope) {
|
|
338
|
+
if (!isRecord(value))
|
|
339
|
+
return null;
|
|
340
|
+
const senderId = stringValue(value.senderId);
|
|
341
|
+
const refillLaneKey = refillLaneKeyValue(value.refillLaneKey);
|
|
342
|
+
const targetDate = stringValue(value.targetDate);
|
|
343
|
+
if (requireScope && (!senderId || !refillLaneKey || !targetDate))
|
|
344
|
+
return null;
|
|
345
|
+
const numbers = Object.fromEntries(LEDGER_NUMBER_FIELDS.map((field) => [
|
|
346
|
+
field,
|
|
347
|
+
nonNegativeNumber(value[field]),
|
|
348
|
+
]));
|
|
349
|
+
return {
|
|
350
|
+
...(senderId ? { senderId } : {}),
|
|
351
|
+
...(refillLaneKey ? { refillLaneKey } : {}),
|
|
352
|
+
...(targetDate ? { targetDate } : {}),
|
|
353
|
+
...numbers,
|
|
354
|
+
unmatchedReadyReasons: numericRecord(value.unmatchedReadyReasons),
|
|
355
|
+
branchMetrics: sanitizeBranchMetricGroups(value.branchMetrics),
|
|
356
|
+
views: sanitizeLedgerViews(value.views),
|
|
357
|
+
identityChecks: sanitizeIdentityChecks(value.identityChecks),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function sanitizeLaneLedgers(value) {
|
|
361
|
+
if (!Array.isArray(value))
|
|
362
|
+
return [];
|
|
363
|
+
return value.flatMap((ledger) => {
|
|
364
|
+
const sanitized = sanitizeLaneLedger(ledger, true);
|
|
365
|
+
return sanitized ? [sanitized] : [];
|
|
224
366
|
});
|
|
225
367
|
}
|
|
368
|
+
function sanitizePhase98RefillContract(value) {
|
|
369
|
+
if (!isRecord(value))
|
|
370
|
+
return {};
|
|
371
|
+
const refillLaneKey = refillLaneKeyValue(value.refillLaneKey);
|
|
372
|
+
const laneLedgers = sanitizeLaneLedgers(value.laneLedgers);
|
|
373
|
+
const laneLedgerTotals = sanitizeLaneLedger(value.laneLedgerTotals, false);
|
|
374
|
+
const settleKey = stringValue(value.settleKey);
|
|
375
|
+
const settleDeadline = stringValue(value.settleDeadline);
|
|
376
|
+
const settleBasisRevision = stringValue(value.settleBasisRevision);
|
|
377
|
+
return {
|
|
378
|
+
...(refillLaneKey ? { refillLaneKey } : {}),
|
|
379
|
+
...(isRecord(value.branchMetrics)
|
|
380
|
+
? { branchMetrics: sanitizeBranchMetricGroups(value.branchMetrics) }
|
|
381
|
+
: {}),
|
|
382
|
+
...(Array.isArray(value.laneLedgers) ? { laneLedgers } : {}),
|
|
383
|
+
...(laneLedgerTotals ? { laneLedgerTotals } : {}),
|
|
384
|
+
...(settleKey?.startsWith("scheduler_first_settle:") ? { settleKey } : {}),
|
|
385
|
+
...(settleDeadline ? { settleDeadline } : {}),
|
|
386
|
+
...(settleBasisRevision ? { settleBasisRevision } : {}),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function sanitizeSelectedDay(value) {
|
|
390
|
+
if (!isRecord(value))
|
|
391
|
+
return null;
|
|
392
|
+
const senderId = stringValue(value.senderId);
|
|
393
|
+
const actionType = allowedActionType(value.actionType);
|
|
394
|
+
if (!senderId || !actionType)
|
|
395
|
+
return null;
|
|
396
|
+
return {
|
|
397
|
+
senderId,
|
|
398
|
+
senderName: stringValue(value.senderName) ?? senderId,
|
|
399
|
+
actionType,
|
|
400
|
+
refillLaneKey: refillLaneKeyValue(value.refillLaneKey),
|
|
401
|
+
date: stringValue(value.date),
|
|
402
|
+
timeZone: stringValue(value.timeZone),
|
|
403
|
+
startUtc: stringValue(value.startUtc),
|
|
404
|
+
endUtc: stringValue(value.endUtc),
|
|
405
|
+
limit: nonNegativeNumber(value.limit),
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function sanitizeSenderPlan(value) {
|
|
409
|
+
if (!isRecord(value))
|
|
410
|
+
return null;
|
|
411
|
+
const senderId = stringValue(value.senderId);
|
|
412
|
+
const actionType = allowedActionType(value.actionType);
|
|
413
|
+
if (!senderId || !actionType)
|
|
414
|
+
return null;
|
|
415
|
+
const numericFields = [
|
|
416
|
+
"requestedTarget",
|
|
417
|
+
"grossTarget",
|
|
418
|
+
"effectiveTarget",
|
|
419
|
+
"sent",
|
|
420
|
+
"scheduled",
|
|
421
|
+
"projected",
|
|
422
|
+
"readyBuffer",
|
|
423
|
+
"remainingScheduledGap",
|
|
424
|
+
"remainingProjectedGap",
|
|
425
|
+
"remainingReadyOrProjectedGap",
|
|
426
|
+
"schedulerFillableSlots",
|
|
427
|
+
"schedulerCapacityTarget",
|
|
428
|
+
"remainingSchedulerFillableGap",
|
|
429
|
+
];
|
|
430
|
+
const numbers = Object.fromEntries(numericFields.map((field) => [field, nonNegativeNumber(value[field])]));
|
|
431
|
+
const campaigns = Array.isArray(value.campaigns)
|
|
432
|
+
? value.campaigns
|
|
433
|
+
.filter((campaign) => isRecord(campaign))
|
|
434
|
+
.map((campaign) => ({
|
|
435
|
+
campaignId: stringValue(campaign.campaignId),
|
|
436
|
+
campaignName: stringValue(campaign.campaignName) ?? "",
|
|
437
|
+
tableId: stringValue(campaign.tableId),
|
|
438
|
+
tableName: stringValue(campaign.tableName) ?? null,
|
|
439
|
+
campaignStatus: stringValue(campaign.campaignStatus) ?? null,
|
|
440
|
+
classification: campaignClassificationValue(campaign.classification),
|
|
441
|
+
selectedLeadListId: stringValue(campaign.selectedLeadListId) ?? null,
|
|
442
|
+
leadSourceProvider: stringValue(campaign.leadSourceProvider) ?? null,
|
|
443
|
+
sourceFingerprint: stringValue(campaign.sourceFingerprint) ?? null,
|
|
444
|
+
}))
|
|
445
|
+
: [];
|
|
446
|
+
return {
|
|
447
|
+
senderId,
|
|
448
|
+
senderName: stringValue(value.senderName) ?? senderId,
|
|
449
|
+
actionType,
|
|
450
|
+
...sanitizePhase98RefillContract(value),
|
|
451
|
+
selectedDays: Array.isArray(value.selectedDays)
|
|
452
|
+
? value.selectedDays
|
|
453
|
+
.map(sanitizeSelectedDay)
|
|
454
|
+
.filter((day) => Boolean(day))
|
|
455
|
+
: [],
|
|
456
|
+
campaigns,
|
|
457
|
+
...numbers,
|
|
458
|
+
schedulerFillableSlotsKnown: booleanValue(value.schedulerFillableSlotsKnown) ?? undefined,
|
|
459
|
+
schedulerCapacityMode: stringValue(value.schedulerCapacityMode),
|
|
460
|
+
allocationScope: stringValue(value.allocationScope),
|
|
461
|
+
schedulerWarnings: stringArray(value.schedulerWarnings),
|
|
462
|
+
paidInmail: sanitizePaidInmail(value.paidInmail),
|
|
463
|
+
refillReceipt: sanitizeRefillReceipt(value.refillReceipt),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function sanitizeActionSelection(value) {
|
|
467
|
+
if (!isRecord(value))
|
|
468
|
+
return null;
|
|
469
|
+
const senderId = stringValue(value.senderId);
|
|
470
|
+
if (!senderId)
|
|
471
|
+
return null;
|
|
472
|
+
const evidence = Array.isArray(value.evidence)
|
|
473
|
+
? value.evidence
|
|
474
|
+
.filter((item) => isRecord(item))
|
|
475
|
+
.map((item) => ({
|
|
476
|
+
senderId: stringValue(item.senderId),
|
|
477
|
+
actionType: allowedActionType(item.actionType),
|
|
478
|
+
refillLaneKey: refillLaneKeyValue(item.refillLaneKey),
|
|
479
|
+
source: stringValue(item.source),
|
|
480
|
+
count: nonNegativeNumber(item.count),
|
|
481
|
+
latestAt: stringValue(item.latestAt),
|
|
482
|
+
campaignId: stringValue(item.campaignId),
|
|
483
|
+
tableId: stringValue(item.tableId),
|
|
484
|
+
}))
|
|
485
|
+
: [];
|
|
486
|
+
const refillLaneKeys = Array.isArray(value.refillLaneKeys)
|
|
487
|
+
? value.refillLaneKeys.map(refillLaneKeyValue).filter(Boolean)
|
|
488
|
+
: [];
|
|
489
|
+
return {
|
|
490
|
+
senderId,
|
|
491
|
+
senderName: stringValue(value.senderName) ?? senderId,
|
|
492
|
+
actionTypes: actionTypesFrom(value.actionTypes),
|
|
493
|
+
...(refillLaneKeys.length > 0 ? { refillLaneKeys } : {}),
|
|
494
|
+
source: stringValue(value.source),
|
|
495
|
+
evidence,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
226
498
|
function sanitizeActionCandidates(candidates, selectedKeys, paidRefreshNeededSenderIds, status, remainingReadyOrProjectedGap, remainingProjectedGap) {
|
|
227
499
|
const filtered = Array.isArray(candidates)
|
|
228
500
|
? candidates
|
|
@@ -293,6 +565,7 @@ function sanitizeActionCandidate(candidate) {
|
|
|
293
565
|
? "get_refill_target_plan"
|
|
294
566
|
: undefined,
|
|
295
567
|
reason: stringValue(candidate.reason),
|
|
568
|
+
...sanitizePhase98RefillContract(candidate),
|
|
296
569
|
};
|
|
297
570
|
}
|
|
298
571
|
function sanitizeRowSelector(value) {
|
|
@@ -481,6 +754,16 @@ function sanitizeToolInput(value) {
|
|
|
481
754
|
wait: sanitizeWait(value.wait),
|
|
482
755
|
refillReceipt: sanitizeRefillReceipt(value.refillReceipt),
|
|
483
756
|
capSaturated: booleanValue(value.capSaturated) === true ? true : undefined,
|
|
757
|
+
settleKey: stringValue(value.settleKey),
|
|
758
|
+
settleDeadline: stringValue(value.settleDeadline),
|
|
759
|
+
settleBasisRevision: stringValue(value.settleBasisRevision),
|
|
760
|
+
calibration: isRecord(value.calibration)
|
|
761
|
+
? {
|
|
762
|
+
trusted: value.calibration.trusted === true,
|
|
763
|
+
passRate: optionalNumberValue(value.calibration.passRate),
|
|
764
|
+
minimumRows: nonNegativeNumber(value.calibration.minimumRows),
|
|
765
|
+
}
|
|
766
|
+
: undefined,
|
|
484
767
|
};
|
|
485
768
|
}
|
|
486
769
|
function sanitizePacketIds(value) {
|
|
@@ -534,6 +817,7 @@ function sanitizeStructuredAction(action, selectedBySender, mode, rank) {
|
|
|
534
817
|
actionKey: stringValue(action.actionKey),
|
|
535
818
|
capSaturated: booleanValue(action.capSaturated) === true ? true : undefined,
|
|
536
819
|
prepFailureDiagnosis: sanitizePrepFailureDiagnosis(action.prepFailureDiagnosis),
|
|
820
|
+
...sanitizePhase98RefillContract(action),
|
|
537
821
|
};
|
|
538
822
|
}
|
|
539
823
|
function sanitizeLedgerBlockers(value) {
|
|
@@ -726,7 +1010,12 @@ function paidInmailRefreshActionForSenderPlan(plan, paidInmail) {
|
|
|
726
1010
|
availableCredits: numberValue(paidInmail?.availableCredits),
|
|
727
1011
|
oldThreshold: numberValue(paidInmail?.threshold),
|
|
728
1012
|
maxStalenessSeconds: MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS,
|
|
729
|
-
actionKey: [
|
|
1013
|
+
actionKey: [
|
|
1014
|
+
"refresh_paid_inmail_credits",
|
|
1015
|
+
senderId,
|
|
1016
|
+
campaignId,
|
|
1017
|
+
columnId,
|
|
1018
|
+
].join(":"),
|
|
730
1019
|
rereadAfter: "get_refill_target_plan",
|
|
731
1020
|
reason: stringValue(paidInmail?.reason) ??
|
|
732
1021
|
"MCP paid InMail sender-credit facts require refresh.",
|
|
@@ -772,6 +1061,7 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
|
|
|
772
1061
|
senderName: stringValue(plan.senderName) ?? "",
|
|
773
1062
|
status: stringValue(plan.status) ?? "needs_refill",
|
|
774
1063
|
selectedLane: allowedActionType(plan.selectedLane),
|
|
1064
|
+
...sanitizePhase98RefillContract(plan),
|
|
775
1065
|
horizon: isRecord(plan.horizon)
|
|
776
1066
|
? {
|
|
777
1067
|
selectedDays: Array.isArray(plan.horizon.selectedDays)
|
|
@@ -848,15 +1138,10 @@ function sanitizeSchedulerSweepSourceScopes(value) {
|
|
|
848
1138
|
const tableId = stringValue(entry.tableId)?.trim();
|
|
849
1139
|
const sourceLeadListId = stringValue(entry.sourceLeadListId)?.trim();
|
|
850
1140
|
const leadSourceProvider = stringValue(entry.leadSourceProvider)?.trim();
|
|
851
|
-
if (!campaignId ||
|
|
852
|
-
!tableId ||
|
|
853
|
-
!sourceLeadListId ||
|
|
854
|
-
!leadSourceProvider) {
|
|
1141
|
+
if (!campaignId || !tableId || !sourceLeadListId || !leadSourceProvider) {
|
|
855
1142
|
return [];
|
|
856
1143
|
}
|
|
857
|
-
return [
|
|
858
|
-
{ campaignId, tableId, sourceLeadListId, leadSourceProvider },
|
|
859
|
-
];
|
|
1144
|
+
return [{ campaignId, tableId, sourceLeadListId, leadSourceProvider }];
|
|
860
1145
|
});
|
|
861
1146
|
const unique = new Map(scopes.map((scope) => [
|
|
862
1147
|
[
|
|
@@ -1100,11 +1385,17 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
1100
1385
|
const actionTypes = [...new Set(selectedBySender.values())];
|
|
1101
1386
|
if (selectedKeys.size === 0)
|
|
1102
1387
|
return emptyUnsupportedResult(result);
|
|
1103
|
-
const senderPlans = rawSenderPlans
|
|
1104
|
-
|
|
1388
|
+
const senderPlans = rawSenderPlans
|
|
1389
|
+
.filter((plan) => isRecord(plan) &&
|
|
1390
|
+
selectedKeys.has(`${String(plan.senderId)}:${String(plan.actionType)}`))
|
|
1391
|
+
.map(sanitizeSenderPlan)
|
|
1392
|
+
.filter((plan) => Boolean(plan));
|
|
1105
1393
|
const selectedDays = Array.isArray(target.selectedDays)
|
|
1106
|
-
? target.selectedDays
|
|
1394
|
+
? target.selectedDays
|
|
1395
|
+
.filter((day) => isRecord(day) &&
|
|
1107
1396
|
selectedKeys.has(`${String(day.senderId)}:${String(day.actionType)}`))
|
|
1397
|
+
.map(sanitizeSelectedDay)
|
|
1398
|
+
.filter((day) => Boolean(day))
|
|
1108
1399
|
: [];
|
|
1109
1400
|
const actionSelections = rawSelections
|
|
1110
1401
|
.filter((selection) => isRecord(selection) &&
|
|
@@ -1112,15 +1403,17 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
1112
1403
|
selectedBySender.has(selection.senderId))
|
|
1113
1404
|
.map((selection) => {
|
|
1114
1405
|
const actionType = selectedBySender.get(selection.senderId);
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
|
|
1406
|
+
const sanitized = sanitizeActionSelection(selection);
|
|
1407
|
+
if (!sanitized)
|
|
1408
|
+
return null;
|
|
1409
|
+
const evidence = sanitized.evidence.filter((item) => item.actionType === actionType);
|
|
1118
1410
|
return {
|
|
1119
|
-
...
|
|
1411
|
+
...sanitized,
|
|
1120
1412
|
actionTypes: actionType ? [actionType] : [],
|
|
1121
1413
|
evidence,
|
|
1122
1414
|
};
|
|
1123
|
-
})
|
|
1415
|
+
})
|
|
1416
|
+
.filter((selection) => Boolean(selection));
|
|
1124
1417
|
const grossTarget = senderPlans.reduce((sum, plan) => sum + numberValue(plan.grossTarget), 0);
|
|
1125
1418
|
const requestedTarget = senderPlans.reduce((sum, plan) => sum + numberValue(plan.requestedTarget), 0);
|
|
1126
1419
|
const effectiveTarget = senderPlans.reduce((sum, plan) => {
|
|
@@ -1190,6 +1483,7 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
1190
1483
|
remainingProjectedGap,
|
|
1191
1484
|
remainingReadyOrProjectedGap,
|
|
1192
1485
|
remainingReadyOrScheduledGap: remainingReadyOrProjectedGap,
|
|
1486
|
+
...sanitizePhase98RefillContract(target),
|
|
1193
1487
|
},
|
|
1194
1488
|
coverage: isRecord(result.coverage)
|
|
1195
1489
|
? {
|
|
@@ -1207,7 +1501,7 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
1207
1501
|
export const refillTargetPlanToolDefinitions = [
|
|
1208
1502
|
{
|
|
1209
1503
|
name: "get_refill_target_plan",
|
|
1210
|
-
description: "read-only refill target planner to call before any refill mutation. It identifies eligible senders and infers one implicit refill lane per sender. For unified Sales Nav cascade campaigns
|
|
1504
|
+
description: "read-only refill target planner to call before any refill mutation. It identifies eligible senders and infers one implicit refill lane per sender. For unified Sales Nav cascade campaigns with campaign classification sales_nav_cascade, Open InMail, paid InMail, and connection fallback satisfy one sales_nav_cascade goal while branch metrics remain visible; row routing still uses fresh paid credits >= 5 and reports same-campaign connection fallback availability. Campaign and sender names stay primary labels and stable IDs remain execution proof. Non-cascade lanes remain connection invites or paid InMails, chosen from future scheduled, recent scheduled, and ready-to-schedule evidence in active campaign-backed sequence campaigns before falling back to campaign sequences. DMs remain follow-up sequence actions, not refill target lanes. By default the planner computes the scheduler-forward 48-hour target window, selected sender-local days whose sending windows overlap that window, raw coverage, projected coverage (sent + scheduled), goal-capped projected coverage, ready buffer, remaining projected gap, allocatable preparation deficit, unallocatable supply/deficit, paid InMail credit/threshold/freshness feasibility, bounded action candidates, and targetRevision drift proof. A calibrated small gap may return one scheduler_first_settle read-only receipt with a stable key/deadline before bounded preparation. This tool does not create rows, import leads, prepare messages, approve messages, does not schedule sends, start campaigns, lower paid InMail thresholds, create campaigns, launch, spend InMail credits, or write scheduler fields. Complete projected targets no-op without approval; loaded, awaiting scheduler targets use read-only wait/reread until projected coverage fills.",
|
|
1211
1505
|
inputSchema: {
|
|
1212
1506
|
type: "object",
|
|
1213
1507
|
properties: {
|