@sellable/mcp 0.1.520 → 0.1.521
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.
|
@@ -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);
|
|
@@ -673,7 +746,7 @@ export async function executeOneYoloPrimitive(action, workspaceId) {
|
|
|
673
746
|
case "wait_for_scheduler":
|
|
674
747
|
case "wait_for_active_work":
|
|
675
748
|
case "wait_for_source_import":
|
|
676
|
-
return { status: "read_only_reread", result:
|
|
749
|
+
return { status: "read_only_reread", result: waitResultForAction(action) };
|
|
677
750
|
case "prepare_messages": {
|
|
678
751
|
const campaignId = actionCampaignId(action);
|
|
679
752
|
const tableId = actionTableId(action) ?? undefined;
|
|
@@ -712,7 +785,10 @@ export async function executeOneYoloPrimitive(action, workspaceId) {
|
|
|
712
785
|
}),
|
|
713
786
|
requestSource: "refill_sends",
|
|
714
787
|
});
|
|
715
|
-
return {
|
|
788
|
+
return {
|
|
789
|
+
status: "executed_and_reread",
|
|
790
|
+
result: normalizePrepareMessagesResult(result),
|
|
791
|
+
};
|
|
716
792
|
}
|
|
717
793
|
case "copy_selected_source_rows": {
|
|
718
794
|
const campaignOfferId = actionCampaignId(action);
|
|
@@ -50,7 +50,7 @@ function normalizeTargetDate(value) {
|
|
|
50
50
|
export const refillSendsToolDefinitions = [
|
|
51
51
|
{
|
|
52
52
|
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
|
|
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 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
54
|
inputSchema: {
|
|
55
55
|
type: "object",
|
|
56
56
|
properties: {
|
|
@@ -209,6 +209,7 @@ export function refillSendsCommand(input = {}) {
|
|
|
209
209
|
: `, horizonSendDays: ${horizonSendDays}`}, approvalMode: "${approvalMode}" }) before any import, prep, approval, start, or schedule-affecting action.`,
|
|
210
210
|
"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
211
|
"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.",
|
|
212
|
+
"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
213
|
"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
214
|
"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
215
|
"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 +229,17 @@ export function refillSendsCommand(input = {}) {
|
|
|
228
229
|
"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
230
|
"Fresh reread get_campaign_refill_state immediately before any import, prep, approval, or horizon-fill mutation.",
|
|
230
231
|
"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.",
|
|
232
|
+
"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
233
|
"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
234
|
"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.",
|
|
235
|
+
"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
236
|
"Do not present paid-credit refresh as the next operator action after refill_sends returns autoPaidInmailRefresh and the post-refresh targetPlan.",
|
|
234
237
|
"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
238
|
"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
239
|
"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
240
|
],
|
|
238
241
|
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,
|
|
242
|
+
? "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
243
|
: hasSenderSelectors
|
|
241
244
|
? "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
245
|
: "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 +252,7 @@ export function refillSendsCommand(input = {}) {
|
|
|
249
252
|
"direct scheduler writes",
|
|
250
253
|
"sender reassignment",
|
|
251
254
|
"campaigns outside the rendered eligible sender packet",
|
|
252
|
-
"on-demand or unrelated active-campaign fallback unless the refill workflow
|
|
255
|
+
"on-demand or unrelated active-campaign fallback unless the refill workflow shows receipt-proven exhaustion for the selected same-sender lane",
|
|
253
256
|
],
|
|
254
257
|
hostExamples: {
|
|
255
258
|
claude: [
|
|
@@ -79,6 +79,11 @@ function stringValue(value) {
|
|
|
79
79
|
function booleanValue(value) {
|
|
80
80
|
return typeof value === "boolean" ? value : undefined;
|
|
81
81
|
}
|
|
82
|
+
function numericRecord(value) {
|
|
83
|
+
if (!isRecord(value))
|
|
84
|
+
return {};
|
|
85
|
+
return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "number" && Number.isFinite(entry[1])));
|
|
86
|
+
}
|
|
82
87
|
function campaignClassificationValue(value) {
|
|
83
88
|
return value === "sales_nav_cascade" ? "sales_nav_cascade" : undefined;
|
|
84
89
|
}
|
|
@@ -190,6 +195,8 @@ function sanitizeActionCandidate(candidate) {
|
|
|
190
195
|
columnRole: stringValue(candidate.columnRole),
|
|
191
196
|
rowSelector: sanitizeRowSelector(candidate.rowSelector),
|
|
192
197
|
actionKey: stringValue(candidate.actionKey),
|
|
198
|
+
wait: sanitizeWait(candidate.wait),
|
|
199
|
+
refillReceipt: sanitizeRefillReceipt(candidate.refillReceipt),
|
|
193
200
|
rereadAfter: candidate.rereadAfter === "get_refill_target_plan"
|
|
194
201
|
? "get_refill_target_plan"
|
|
195
202
|
: undefined,
|
|
@@ -217,6 +224,98 @@ function sanitizeRowSelector(value) {
|
|
|
217
224
|
: {}),
|
|
218
225
|
};
|
|
219
226
|
}
|
|
227
|
+
function sanitizeWait(value) {
|
|
228
|
+
if (!isRecord(value))
|
|
229
|
+
return undefined;
|
|
230
|
+
const deadlineAt = stringValue(value.deadlineAt);
|
|
231
|
+
const onExpiry = stringValue(value.onExpiry);
|
|
232
|
+
if (!deadlineAt && !onExpiry)
|
|
233
|
+
return undefined;
|
|
234
|
+
return { deadlineAt, onExpiry };
|
|
235
|
+
}
|
|
236
|
+
function sanitizeStuckActiveCells(value) {
|
|
237
|
+
if (!Array.isArray(value))
|
|
238
|
+
return [];
|
|
239
|
+
return value
|
|
240
|
+
.filter((cell) => isRecord(cell))
|
|
241
|
+
.map((cell) => ({
|
|
242
|
+
cellId: stringValue(cell.cellId),
|
|
243
|
+
rowId: stringValue(cell.rowId),
|
|
244
|
+
status: stringValue(cell.status),
|
|
245
|
+
ageMs: typeof cell.ageMs === "number" ? cell.ageMs : undefined,
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
function sanitizeApprovedNotDispatched(value) {
|
|
249
|
+
if (!Array.isArray(value))
|
|
250
|
+
return [];
|
|
251
|
+
return value
|
|
252
|
+
.filter((row) => isRecord(row))
|
|
253
|
+
.map((row) => ({
|
|
254
|
+
rowId: stringValue(row.rowId),
|
|
255
|
+
attempts: typeof row.attempts === "number" ? row.attempts : undefined,
|
|
256
|
+
reason: stringValue(row.reason),
|
|
257
|
+
terminal: booleanValue(row.terminal),
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
function sanitizeExistingRowFrontier(value) {
|
|
261
|
+
if (!isRecord(value))
|
|
262
|
+
return undefined;
|
|
263
|
+
return {
|
|
264
|
+
proof: stringValue(value.proof),
|
|
265
|
+
batchStop: stringValue(value.batchStop),
|
|
266
|
+
hasMoreFrontierRows: booleanValue(value.hasMoreFrontierRows),
|
|
267
|
+
stageCounts: numericRecord(value.stageCounts),
|
|
268
|
+
activeCellCount: typeof value.activeCellCount === "number"
|
|
269
|
+
? value.activeCellCount
|
|
270
|
+
: undefined,
|
|
271
|
+
approvalCandidates: typeof value.approvalCandidates === "number"
|
|
272
|
+
? value.approvalCandidates
|
|
273
|
+
: undefined,
|
|
274
|
+
brokenByReason: numericRecord(value.brokenByReason),
|
|
275
|
+
stuckActiveCells: sanitizeStuckActiveCells(value.stuckActiveCells),
|
|
276
|
+
approvedNotDispatched: sanitizeApprovedNotDispatched(value.approvedNotDispatched),
|
|
277
|
+
wait: sanitizeWait(value.wait),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function sanitizeSkippedRungs(value) {
|
|
281
|
+
if (!Array.isArray(value))
|
|
282
|
+
return [];
|
|
283
|
+
return value
|
|
284
|
+
.filter((rung) => isRecord(rung))
|
|
285
|
+
.map((rung) => ({
|
|
286
|
+
rung: stringValue(rung.rung),
|
|
287
|
+
reason: stringValue(rung.reason),
|
|
288
|
+
detail: stringValue(rung.detail),
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
function sanitizeRefillReceipt(value) {
|
|
292
|
+
if (!isRecord(value))
|
|
293
|
+
return undefined;
|
|
294
|
+
return {
|
|
295
|
+
summary: stringValue(value.summary),
|
|
296
|
+
schedulableGap: typeof value.schedulableGap === "number"
|
|
297
|
+
? value.schedulableGap
|
|
298
|
+
: undefined,
|
|
299
|
+
schedulerFillableGap: typeof value.schedulerFillableGap === "number"
|
|
300
|
+
? value.schedulerFillableGap
|
|
301
|
+
: undefined,
|
|
302
|
+
selectedCampaignId: stringValue(value.selectedCampaignId),
|
|
303
|
+
selectedCampaignName: stringValue(value.selectedCampaignName),
|
|
304
|
+
selectedTableId: stringValue(value.selectedTableId),
|
|
305
|
+
senderId: stringValue(value.senderId),
|
|
306
|
+
senderName: stringValue(value.senderName),
|
|
307
|
+
actionType: allowedActionType(value.actionType),
|
|
308
|
+
dateWindow: isRecord(value.dateWindow)
|
|
309
|
+
? { selectedDates: stringArray(value.dateWindow.selectedDates) }
|
|
310
|
+
: undefined,
|
|
311
|
+
readyBuffer: typeof value.readyBuffer === "number" ? value.readyBuffer : undefined,
|
|
312
|
+
approvalCandidates: typeof value.approvalCandidates === "number"
|
|
313
|
+
? value.approvalCandidates
|
|
314
|
+
: undefined,
|
|
315
|
+
existingRowFrontier: sanitizeExistingRowFrontier(value.existingRowFrontier),
|
|
316
|
+
skippedRungs: sanitizeSkippedRungs(value.skippedRungs),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
220
319
|
function sanitizeRerunErroredCellsOperations(value) {
|
|
221
320
|
if (!Array.isArray(value))
|
|
222
321
|
return [];
|
|
@@ -285,6 +384,8 @@ function sanitizeToolInput(value) {
|
|
|
285
384
|
: undefined,
|
|
286
385
|
threshold: typeof value.threshold === "number" ? value.threshold : undefined,
|
|
287
386
|
operations: sanitizeRerunErroredCellsOperations(value.operations),
|
|
387
|
+
wait: sanitizeWait(value.wait),
|
|
388
|
+
refillReceipt: sanitizeRefillReceipt(value.refillReceipt),
|
|
288
389
|
};
|
|
289
390
|
}
|
|
290
391
|
function sanitizePacketIds(value) {
|
|
@@ -328,6 +429,7 @@ function sanitizeStructuredAction(action, selectedBySender, mode, rank) {
|
|
|
328
429
|
toolInput: sanitizeToolInput(action.toolInput),
|
|
329
430
|
inputSummary: stringValue(action.inputSummary) ?? "",
|
|
330
431
|
reason: stringValue(action.reason) ?? "",
|
|
432
|
+
refillReceipt: sanitizeRefillReceipt(action.refillReceipt),
|
|
331
433
|
prerequisites: stringArray(action.prerequisites),
|
|
332
434
|
rereadAfter: action.rereadAfter === "get_refill_target_plan"
|
|
333
435
|
? "get_refill_target_plan"
|
|
@@ -514,6 +616,7 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
|
|
|
514
616
|
campaignRanking: sanitizeCampaignRanking(plan.campaignRanking),
|
|
515
617
|
paidInmail: sanitizePaidInmail(plan.paidInmail),
|
|
516
618
|
sourcePlan: sanitizeSourcePlan(plan.sourcePlan),
|
|
619
|
+
refillReceipt: sanitizeRefillReceipt(plan.refillReceipt),
|
|
517
620
|
nextActions,
|
|
518
621
|
manualAlternates,
|
|
519
622
|
...(isRecord(plan.emptyState)
|
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
|