@sellable/mcp 0.1.554 → 0.1.556
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/index-dev.js +0 -0
- package/dist/index.js +0 -0
- 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 +38 -0
- package/dist/tools/refill-executors.js +222 -3
- package/dist/tools/refill-sends-v2.d.ts +118 -1
- package/dist/tools/refill-sends-v2.js +310 -2
- package/dist/tools/refill-sends.d.ts +678 -32
- package/dist/tools/refill-sends.js +274 -13
- package/dist/tools/refill-target-plan.js +486 -14
- package/dist/tools/registry.d.ts +96 -27
- package/dist/tools/registry.js +1 -3
- package/dist/tools/scheduler-fill-capacity.js +1 -1
- package/dist/tools/scheduler-run.d.ts +71 -0
- package/dist/tools/scheduler-run.js +203 -1
- package/dist/tools/workspaces.d.ts +4 -6
- package/dist/tools/workspaces.js +11 -13
- package/package.json +1 -1
- package/skills/refill-sends/SKILL.md +91 -353
- 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 -743
- package/skills/refill-sends-workflow/core/contract.v2.json +543 -0
- package/skills/refill-sends-workflow/core/flow.v1.json +185 -1
- package/dist/refill-date-window.d.ts +0 -34
- package/dist/refill-date-window.js +0 -210
- package/dist/tools/refill-sends-evergreen.d.ts +0 -28
- package/dist/tools/refill-sends-evergreen.js +0 -47
- package/skills/research/config.json +0 -9
|
@@ -10,6 +10,8 @@ const SAFE_PACKET_ACTION_TYPES = new Set([
|
|
|
10
10
|
"approve_messages",
|
|
11
11
|
"start_paused_campaign",
|
|
12
12
|
"rerun_errored_cells",
|
|
13
|
+
"run_scheduler_sweep",
|
|
14
|
+
"scheduler_first_settle",
|
|
13
15
|
"wait_for_scheduler",
|
|
14
16
|
"wait_for_active_work",
|
|
15
17
|
"wait_for_source_import",
|
|
@@ -73,6 +75,17 @@ const PREP_FAILURE_RECOMMENDED_ACTIONS = new Set([
|
|
|
73
75
|
"approve_or_repair_approval_cells",
|
|
74
76
|
"inspect_rows",
|
|
75
77
|
]);
|
|
78
|
+
const SCHEDULER_SWEEP_ALLOWED_SIDE_EFFECTS = [
|
|
79
|
+
"schedule any eligible workspace cells on the approved date within scheduler gates",
|
|
80
|
+
];
|
|
81
|
+
const SCHEDULER_SWEEP_FORBIDDEN_SIDE_EFFECTS = [
|
|
82
|
+
"send directly",
|
|
83
|
+
"write scheduler fields directly",
|
|
84
|
+
"change campaign state",
|
|
85
|
+
"change sender limits",
|
|
86
|
+
"change source selection",
|
|
87
|
+
];
|
|
88
|
+
const SCHEDULER_SWEEP_RECEIPT_GROUPS = ["campaignId", "tableId", "actionType"];
|
|
76
89
|
async function postRefillTargetPlan(body, workspaceId) {
|
|
77
90
|
const api = getApi();
|
|
78
91
|
const requestOptions = workspaceRequestOptions(workspaceId);
|
|
@@ -199,14 +212,289 @@ function senderActionKey(senderId, actionType) {
|
|
|
199
212
|
function sanitizeCounts(counts, selectedKeys) {
|
|
200
213
|
if (!Array.isArray(counts))
|
|
201
214
|
return [];
|
|
202
|
-
return counts
|
|
215
|
+
return counts
|
|
216
|
+
.filter((item) => {
|
|
203
217
|
if (!isRecord(item))
|
|
204
218
|
return false;
|
|
205
219
|
const actionType = allowedActionType(item.actionType);
|
|
206
220
|
const key = senderActionKey(item.senderId, actionType);
|
|
207
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] : [];
|
|
208
366
|
});
|
|
209
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
|
+
}
|
|
210
498
|
function sanitizeActionCandidates(candidates, selectedKeys, paidRefreshNeededSenderIds, status, remainingReadyOrProjectedGap, remainingProjectedGap) {
|
|
211
499
|
const filtered = Array.isArray(candidates)
|
|
212
500
|
? candidates
|
|
@@ -277,6 +565,7 @@ function sanitizeActionCandidate(candidate) {
|
|
|
277
565
|
? "get_refill_target_plan"
|
|
278
566
|
: undefined,
|
|
279
567
|
reason: stringValue(candidate.reason),
|
|
568
|
+
...sanitizePhase98RefillContract(candidate),
|
|
280
569
|
};
|
|
281
570
|
}
|
|
282
571
|
function sanitizeRowSelector(value) {
|
|
@@ -465,6 +754,16 @@ function sanitizeToolInput(value) {
|
|
|
465
754
|
wait: sanitizeWait(value.wait),
|
|
466
755
|
refillReceipt: sanitizeRefillReceipt(value.refillReceipt),
|
|
467
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,
|
|
468
767
|
};
|
|
469
768
|
}
|
|
470
769
|
function sanitizePacketIds(value) {
|
|
@@ -515,8 +814,10 @@ function sanitizeStructuredAction(action, selectedBySender, mode, rank) {
|
|
|
515
814
|
: null,
|
|
516
815
|
stopCondition: stringValue(action.stopCondition) ?? "",
|
|
517
816
|
yoloEligible,
|
|
817
|
+
actionKey: stringValue(action.actionKey),
|
|
518
818
|
capSaturated: booleanValue(action.capSaturated) === true ? true : undefined,
|
|
519
819
|
prepFailureDiagnosis: sanitizePrepFailureDiagnosis(action.prepFailureDiagnosis),
|
|
820
|
+
...sanitizePhase98RefillContract(action),
|
|
520
821
|
};
|
|
521
822
|
}
|
|
522
823
|
function sanitizeLedgerBlockers(value) {
|
|
@@ -709,7 +1010,12 @@ function paidInmailRefreshActionForSenderPlan(plan, paidInmail) {
|
|
|
709
1010
|
availableCredits: numberValue(paidInmail?.availableCredits),
|
|
710
1011
|
oldThreshold: numberValue(paidInmail?.threshold),
|
|
711
1012
|
maxStalenessSeconds: MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS,
|
|
712
|
-
actionKey: [
|
|
1013
|
+
actionKey: [
|
|
1014
|
+
"refresh_paid_inmail_credits",
|
|
1015
|
+
senderId,
|
|
1016
|
+
campaignId,
|
|
1017
|
+
columnId,
|
|
1018
|
+
].join(":"),
|
|
713
1019
|
rereadAfter: "get_refill_target_plan",
|
|
714
1020
|
reason: stringValue(paidInmail?.reason) ??
|
|
715
1021
|
"MCP paid InMail sender-credit facts require refresh.",
|
|
@@ -755,6 +1061,7 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
|
|
|
755
1061
|
senderName: stringValue(plan.senderName) ?? "",
|
|
756
1062
|
status: stringValue(plan.status) ?? "needs_refill",
|
|
757
1063
|
selectedLane: allowedActionType(plan.selectedLane),
|
|
1064
|
+
...sanitizePhase98RefillContract(plan),
|
|
758
1065
|
horizon: isRecord(plan.horizon)
|
|
759
1066
|
? {
|
|
760
1067
|
selectedDays: Array.isArray(plan.horizon.selectedDays)
|
|
@@ -814,6 +1121,149 @@ function buildSanitizedGlobalActionQueue(senderRefillPlans) {
|
|
|
814
1121
|
rank: index + 1,
|
|
815
1122
|
}));
|
|
816
1123
|
}
|
|
1124
|
+
function sortedUniqueStrings(value) {
|
|
1125
|
+
return [...new Set(stringArray(value))].sort();
|
|
1126
|
+
}
|
|
1127
|
+
function sameStrings(left, right) {
|
|
1128
|
+
return (left.length === right.length &&
|
|
1129
|
+
left.every((value, index) => value === right[index]));
|
|
1130
|
+
}
|
|
1131
|
+
function sanitizeSchedulerSweepSourceScopes(value) {
|
|
1132
|
+
if (!Array.isArray(value))
|
|
1133
|
+
return [];
|
|
1134
|
+
const scopes = value.flatMap((entry) => {
|
|
1135
|
+
if (!isRecord(entry))
|
|
1136
|
+
return [];
|
|
1137
|
+
const campaignId = stringValue(entry.campaignId)?.trim();
|
|
1138
|
+
const tableId = stringValue(entry.tableId)?.trim();
|
|
1139
|
+
const sourceLeadListId = stringValue(entry.sourceLeadListId)?.trim();
|
|
1140
|
+
const leadSourceProvider = stringValue(entry.leadSourceProvider)?.trim();
|
|
1141
|
+
if (!campaignId || !tableId || !sourceLeadListId || !leadSourceProvider) {
|
|
1142
|
+
return [];
|
|
1143
|
+
}
|
|
1144
|
+
return [{ campaignId, tableId, sourceLeadListId, leadSourceProvider }];
|
|
1145
|
+
});
|
|
1146
|
+
const unique = new Map(scopes.map((scope) => [
|
|
1147
|
+
[
|
|
1148
|
+
scope.campaignId,
|
|
1149
|
+
scope.tableId,
|
|
1150
|
+
scope.sourceLeadListId,
|
|
1151
|
+
scope.leadSourceProvider,
|
|
1152
|
+
].join(":"),
|
|
1153
|
+
scope,
|
|
1154
|
+
]));
|
|
1155
|
+
return [...unique.values()].sort((a, b) => {
|
|
1156
|
+
if (a.campaignId !== b.campaignId) {
|
|
1157
|
+
return a.campaignId.localeCompare(b.campaignId);
|
|
1158
|
+
}
|
|
1159
|
+
return a.tableId.localeCompare(b.tableId);
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
function sanitizeTargetSchedulerSweep(params) {
|
|
1163
|
+
if (!Array.isArray(params.value) ||
|
|
1164
|
+
!params.expectedWorkspaceId ||
|
|
1165
|
+
!params.expectedTargetDate ||
|
|
1166
|
+
!/^\d{4}-\d{2}-\d{2}$/.test(params.expectedTargetDate) ||
|
|
1167
|
+
!params.expectedTargetShapeRevision ||
|
|
1168
|
+
!params.expectedStateRevision ||
|
|
1169
|
+
params.remainingProjectedGap <= 0 ||
|
|
1170
|
+
params.remainingReadyOrProjectedGap !== 0) {
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
const expectedSenderIds = [...params.selectedBySender.keys()].sort();
|
|
1174
|
+
const expectedActionTypes = [
|
|
1175
|
+
...new Set(params.selectedBySender.values()),
|
|
1176
|
+
].sort();
|
|
1177
|
+
if (expectedSenderIds.length === 0 || expectedActionTypes.length === 0) {
|
|
1178
|
+
return null;
|
|
1179
|
+
}
|
|
1180
|
+
for (const rawAction of params.value) {
|
|
1181
|
+
if (!isRecord(rawAction) || rawAction.type !== "run_scheduler_sweep") {
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
const input = isRecord(rawAction.toolInput) ? rawAction.toolInput : {};
|
|
1185
|
+
const actionKey = stringValue(rawAction.actionKey)?.trim();
|
|
1186
|
+
const requestKey = stringValue(input.requestKey)?.trim();
|
|
1187
|
+
const senderIds = sortedUniqueStrings(input.senderIds);
|
|
1188
|
+
const actionTypes = sortedUniqueStrings(input.actionTypes).filter((actionType) => allowedActionType(actionType) === actionType);
|
|
1189
|
+
const campaignIds = sortedUniqueStrings(input.campaignIds);
|
|
1190
|
+
const tableIds = sortedUniqueStrings(input.tableIds);
|
|
1191
|
+
const sourceScopes = sanitizeSchedulerSweepSourceScopes(input.sourceScopes);
|
|
1192
|
+
const receiptRequirements = isRecord(rawAction.receiptRequirements)
|
|
1193
|
+
? rawAction.receiptRequirements
|
|
1194
|
+
: {};
|
|
1195
|
+
const groupBy = stringArray(receiptRequirements.groupBy);
|
|
1196
|
+
const allowedSideEffects = stringArray(rawAction.allowedSideEffects);
|
|
1197
|
+
const forbiddenSideEffects = stringArray(rawAction.forbiddenSideEffects);
|
|
1198
|
+
const rawActionType = allowedActionType(rawAction.actionType);
|
|
1199
|
+
if (input.action !== "run" ||
|
|
1200
|
+
input.workspaceId !== params.expectedWorkspaceId ||
|
|
1201
|
+
input.targetDate !== params.expectedTargetDate ||
|
|
1202
|
+
input.targetDateKey !== params.expectedTargetDate ||
|
|
1203
|
+
input.targetShapeRevision !== params.expectedTargetShapeRevision ||
|
|
1204
|
+
input.stateRevision !== params.expectedStateRevision ||
|
|
1205
|
+
!actionKey ||
|
|
1206
|
+
requestKey !== actionKey ||
|
|
1207
|
+
!sameStrings(senderIds, expectedSenderIds) ||
|
|
1208
|
+
!sameStrings(actionTypes, expectedActionTypes) ||
|
|
1209
|
+
(expectedActionTypes.length === 1 &&
|
|
1210
|
+
rawActionType !== expectedActionTypes[0]) ||
|
|
1211
|
+
campaignIds.length === 0 ||
|
|
1212
|
+
tableIds.length === 0 ||
|
|
1213
|
+
sourceScopes.length === 0 ||
|
|
1214
|
+
sourceScopes.some((scope) => !campaignIds.includes(scope.campaignId) ||
|
|
1215
|
+
!tableIds.includes(scope.tableId)) ||
|
|
1216
|
+
rawAction.toolName !== "run_scheduler_sweep" ||
|
|
1217
|
+
rawAction.sideEffectClass !== "scheduler_placement" ||
|
|
1218
|
+
rawAction.workspaceWide !== true ||
|
|
1219
|
+
rawAction.yoloEligible !== true ||
|
|
1220
|
+
receiptRequirements.nonTruncated !== true ||
|
|
1221
|
+
!sameStrings(groupBy, SCHEDULER_SWEEP_RECEIPT_GROUPS) ||
|
|
1222
|
+
!sameStrings(allowedSideEffects, SCHEDULER_SWEEP_ALLOWED_SIDE_EFFECTS) ||
|
|
1223
|
+
!sameStrings(forbiddenSideEffects, SCHEDULER_SWEEP_FORBIDDEN_SIDE_EFFECTS)) {
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
return {
|
|
1227
|
+
rank: 1,
|
|
1228
|
+
type: "run_scheduler_sweep",
|
|
1229
|
+
actionType: rawActionType,
|
|
1230
|
+
toolName: "run_scheduler_sweep",
|
|
1231
|
+
sideEffectClass: "scheduler_placement",
|
|
1232
|
+
ids: {},
|
|
1233
|
+
toolInput: {
|
|
1234
|
+
action: "run",
|
|
1235
|
+
workspaceId: params.expectedWorkspaceId,
|
|
1236
|
+
targetDate: params.expectedTargetDate,
|
|
1237
|
+
targetDateKey: params.expectedTargetDate,
|
|
1238
|
+
senderIds,
|
|
1239
|
+
actionTypes,
|
|
1240
|
+
campaignIds,
|
|
1241
|
+
tableIds,
|
|
1242
|
+
sourceScopes,
|
|
1243
|
+
targetShapeRevision: params.expectedTargetShapeRevision,
|
|
1244
|
+
stateRevision: params.expectedStateRevision,
|
|
1245
|
+
requestKey,
|
|
1246
|
+
},
|
|
1247
|
+
inputSummary: stringValue(rawAction.inputSummary) ?? "",
|
|
1248
|
+
reason: stringValue(rawAction.reason) ?? "",
|
|
1249
|
+
prerequisites: stringArray(rawAction.prerequisites),
|
|
1250
|
+
rereadAfter: rawAction.rereadAfter === "get_refill_target_plan"
|
|
1251
|
+
? "get_refill_target_plan"
|
|
1252
|
+
: null,
|
|
1253
|
+
stopCondition: stringValue(rawAction.stopCondition) ?? "",
|
|
1254
|
+
yoloEligible: true,
|
|
1255
|
+
workspaceWide: true,
|
|
1256
|
+
allowedSideEffects: [...SCHEDULER_SWEEP_ALLOWED_SIDE_EFFECTS],
|
|
1257
|
+
forbiddenSideEffects: [...SCHEDULER_SWEEP_FORBIDDEN_SIDE_EFFECTS],
|
|
1258
|
+
receiptRequirements: {
|
|
1259
|
+
nonTruncated: true,
|
|
1260
|
+
groupBy: [...SCHEDULER_SWEEP_RECEIPT_GROUPS],
|
|
1261
|
+
},
|
|
1262
|
+
actionKey,
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
817
1267
|
function paidRefreshNeededSenderIds(senderRefillPlans) {
|
|
818
1268
|
const senderIds = new Set();
|
|
819
1269
|
for (const plan of senderRefillPlans) {
|
|
@@ -935,11 +1385,17 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
935
1385
|
const actionTypes = [...new Set(selectedBySender.values())];
|
|
936
1386
|
if (selectedKeys.size === 0)
|
|
937
1387
|
return emptyUnsupportedResult(result);
|
|
938
|
-
const senderPlans = rawSenderPlans
|
|
939
|
-
|
|
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));
|
|
940
1393
|
const selectedDays = Array.isArray(target.selectedDays)
|
|
941
|
-
? target.selectedDays
|
|
1394
|
+
? target.selectedDays
|
|
1395
|
+
.filter((day) => isRecord(day) &&
|
|
942
1396
|
selectedKeys.has(`${String(day.senderId)}:${String(day.actionType)}`))
|
|
1397
|
+
.map(sanitizeSelectedDay)
|
|
1398
|
+
.filter((day) => Boolean(day))
|
|
943
1399
|
: [];
|
|
944
1400
|
const actionSelections = rawSelections
|
|
945
1401
|
.filter((selection) => isRecord(selection) &&
|
|
@@ -947,20 +1403,22 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
947
1403
|
selectedBySender.has(selection.senderId))
|
|
948
1404
|
.map((selection) => {
|
|
949
1405
|
const actionType = selectedBySender.get(selection.senderId);
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
1406
|
+
const sanitized = sanitizeActionSelection(selection);
|
|
1407
|
+
if (!sanitized)
|
|
1408
|
+
return null;
|
|
1409
|
+
const evidence = sanitized.evidence.filter((item) => item.actionType === actionType);
|
|
953
1410
|
return {
|
|
954
|
-
...
|
|
1411
|
+
...sanitized,
|
|
955
1412
|
actionTypes: actionType ? [actionType] : [],
|
|
956
1413
|
evidence,
|
|
957
1414
|
};
|
|
958
|
-
})
|
|
1415
|
+
})
|
|
1416
|
+
.filter((selection) => Boolean(selection));
|
|
959
1417
|
const grossTarget = senderPlans.reduce((sum, plan) => sum + numberValue(plan.grossTarget), 0);
|
|
960
1418
|
const requestedTarget = senderPlans.reduce((sum, plan) => sum + numberValue(plan.requestedTarget), 0);
|
|
961
1419
|
const effectiveTarget = senderPlans.reduce((sum, plan) => {
|
|
962
|
-
const target = typeof plan.
|
|
963
|
-
? plan.
|
|
1420
|
+
const target = typeof plan.effectiveTarget === "number"
|
|
1421
|
+
? plan.effectiveTarget
|
|
964
1422
|
: numberValue(plan.grossTarget);
|
|
965
1423
|
return sum + target;
|
|
966
1424
|
}, 0);
|
|
@@ -975,7 +1433,20 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
975
1433
|
const senderRefillPlans = sanitizeStructuredSenderPlans(target.senderRefillPlans, selectedBySender);
|
|
976
1434
|
const paidRefreshNeededSenderIdSet = paidRefreshNeededSenderIds(senderRefillPlans);
|
|
977
1435
|
const blockers = sanitizeBlockers(result.blockers, selectedKeys, paidRefreshNeededSenderIdSet);
|
|
978
|
-
const
|
|
1436
|
+
const request = isRecord(result.request) ? result.request : {};
|
|
1437
|
+
const schedulerSweepAction = sanitizeTargetSchedulerSweep({
|
|
1438
|
+
value: target.globalActionQueue,
|
|
1439
|
+
selectedBySender,
|
|
1440
|
+
expectedWorkspaceId: stringValue(target.workspaceId),
|
|
1441
|
+
expectedTargetDate: stringValue(request.targetDate),
|
|
1442
|
+
expectedTargetShapeRevision: stringValue(result.targetShapeRevision),
|
|
1443
|
+
expectedStateRevision: stringValue(result.stateRevision),
|
|
1444
|
+
remainingProjectedGap,
|
|
1445
|
+
remainingReadyOrProjectedGap,
|
|
1446
|
+
});
|
|
1447
|
+
const globalActionQueue = schedulerSweepAction
|
|
1448
|
+
? [schedulerSweepAction]
|
|
1449
|
+
: buildSanitizedGlobalActionQueue(senderRefillPlans);
|
|
979
1450
|
const hasTerminalNoActionBlocker = globalActionQueue.length === 0 &&
|
|
980
1451
|
blockers.some((blocker) => TERMINAL_NO_ACTION_BLOCKER_CODES.has(String(blocker.code)));
|
|
981
1452
|
const status = remainingProjectedGap === 0
|
|
@@ -1012,6 +1483,7 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
1012
1483
|
remainingProjectedGap,
|
|
1013
1484
|
remainingReadyOrProjectedGap,
|
|
1014
1485
|
remainingReadyOrScheduledGap: remainingReadyOrProjectedGap,
|
|
1486
|
+
...sanitizePhase98RefillContract(target),
|
|
1015
1487
|
},
|
|
1016
1488
|
coverage: isRecord(result.coverage)
|
|
1017
1489
|
? {
|
|
@@ -1029,7 +1501,7 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
1029
1501
|
export const refillTargetPlanToolDefinitions = [
|
|
1030
1502
|
{
|
|
1031
1503
|
name: "get_refill_target_plan",
|
|
1032
|
-
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.",
|
|
1033
1505
|
inputSchema: {
|
|
1034
1506
|
type: "object",
|
|
1035
1507
|
properties: {
|