@sellable/mcp 0.1.558 → 0.1.560
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-run-loop.js
CHANGED
|
@@ -361,11 +361,17 @@ function actionSenderId(action) {
|
|
|
361
361
|
}
|
|
362
362
|
function fingerprintFromPlan(plan) {
|
|
363
363
|
const packet = recordValue(plan.packet) ?? {};
|
|
364
|
+
const targetShapeRevision = stringValue(packet.targetShapeRevision) ??
|
|
365
|
+
stringValue(packet.targetRevision) ??
|
|
366
|
+
stringValue(plan.targetShapeRevision) ??
|
|
367
|
+
stringValue(plan.targetRevision);
|
|
364
368
|
return {
|
|
365
|
-
|
|
369
|
+
// Evergreen packets carry their own semantic plan revision. Regular
|
|
370
|
+
// active-campaign packets intentionally do not, so their immutable target
|
|
371
|
+
// shape is the durable planning revision for fencing and recovery.
|
|
372
|
+
planRevision: stringValue(plan.planRevision) ?? targetShapeRevision,
|
|
366
373
|
stateRevision: stringValue(plan.stateRevision) ?? stringValue(packet.stateRevision),
|
|
367
|
-
targetShapeRevision
|
|
368
|
-
stringValue(packet.targetRevision),
|
|
374
|
+
targetShapeRevision,
|
|
369
375
|
};
|
|
370
376
|
}
|
|
371
377
|
function fingerprintChanged(expected, actual) {
|
|
@@ -1021,6 +1027,23 @@ async function gatePlan(input, deps, ctx) {
|
|
|
1021
1027
|
fallbackSummary: line.fallbackSummary,
|
|
1022
1028
|
}));
|
|
1023
1029
|
const fingerprint = fingerprintFromPlan(plan);
|
|
1030
|
+
const approvedTargetShapeRevision = stringValue(input.approval?.targetShapeRevision);
|
|
1031
|
+
if (approvedTargetShapeRevision &&
|
|
1032
|
+
approvedTargetShapeRevision !== fingerprint.targetShapeRevision) {
|
|
1033
|
+
return {
|
|
1034
|
+
status: "blocked",
|
|
1035
|
+
blocker: "scope_drift",
|
|
1036
|
+
runId: ctx.runId,
|
|
1037
|
+
fence: ctx.fence,
|
|
1038
|
+
gate: ctx.gate,
|
|
1039
|
+
guidance: "The canonical refill target shape changed after approval; render a new packet before another primitive.",
|
|
1040
|
+
report: {
|
|
1041
|
+
approvedTargetShapeRevision,
|
|
1042
|
+
freshTargetShapeRevision: fingerprint.targetShapeRevision,
|
|
1043
|
+
},
|
|
1044
|
+
journalPath: ctx.journalPath,
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1024
1047
|
const persistedCircuit = recordValue(runStateProgress(ctx).prepCircuit);
|
|
1025
1048
|
if (stringValue(action.type) === "prepare_messages" &&
|
|
1026
1049
|
openCircuitWaits(persistedCircuit, deps.now?.() ?? new Date())) {
|
|
@@ -167,6 +167,17 @@ export declare function prepareRefillSendsV2Approval(input: RefillV2ApprovalPrep
|
|
|
167
167
|
response?: undefined;
|
|
168
168
|
}>;
|
|
169
169
|
export declare function refillSendsV2Command(input: RefillSendsV2Input): Promise<{
|
|
170
|
+
status: "blocked";
|
|
171
|
+
blocker: "approval_expired" | "approval_fingerprint_mismatch";
|
|
172
|
+
guidance: string;
|
|
173
|
+
report: {
|
|
174
|
+
fresh?: {} | null | undefined;
|
|
175
|
+
changed: string[];
|
|
176
|
+
};
|
|
177
|
+
readOnly: boolean;
|
|
178
|
+
forbiddenActions: string[];
|
|
179
|
+
boundedAuthority: string;
|
|
180
|
+
} | {
|
|
170
181
|
readOnly: boolean;
|
|
171
182
|
mode: string;
|
|
172
183
|
forbiddenActions: string[];
|
|
@@ -325,7 +325,11 @@ function validateApprovalEcho(envelope, echo, now = new Date()) {
|
|
|
325
325
|
];
|
|
326
326
|
const changed = fields.filter((field) => echo[field] !== envelope.approval[field]);
|
|
327
327
|
if (Date.parse(envelope.approval.approvalExpiresAt) <= now.getTime()) {
|
|
328
|
-
return {
|
|
328
|
+
return {
|
|
329
|
+
ok: false,
|
|
330
|
+
blocker: "approval_expired",
|
|
331
|
+
changed,
|
|
332
|
+
};
|
|
329
333
|
}
|
|
330
334
|
return changed.length === 0
|
|
331
335
|
? { ok: true }
|
|
@@ -335,6 +339,85 @@ function validateApprovalEcho(envelope, echo, now = new Date()) {
|
|
|
335
339
|
changed,
|
|
336
340
|
};
|
|
337
341
|
}
|
|
342
|
+
function blockedApprovalResult(params) {
|
|
343
|
+
return {
|
|
344
|
+
status: "blocked",
|
|
345
|
+
blocker: params.blocker,
|
|
346
|
+
guidance: "The durable refill approval no longer matches its persisted scope; render and approve a new packet before mutation.",
|
|
347
|
+
report: {
|
|
348
|
+
changed: params.changed,
|
|
349
|
+
...(params.fresh === undefined ? {} : { fresh: params.fresh }),
|
|
350
|
+
},
|
|
351
|
+
readOnly: false,
|
|
352
|
+
forbiddenActions: FORBIDDEN_ACTIONS,
|
|
353
|
+
boundedAuthority: BOUNDED_AUTHORITY,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function storedApprovalFromStatus(value) {
|
|
357
|
+
const status = record(value) ?? {};
|
|
358
|
+
const config = record(status.config);
|
|
359
|
+
const scope = record(config?.scope);
|
|
360
|
+
const approval = record(config?.approval);
|
|
361
|
+
const scopeHash = text(config?.scopeHash);
|
|
362
|
+
if (!config || !scope || !approval || !scopeHash)
|
|
363
|
+
return null;
|
|
364
|
+
return {
|
|
365
|
+
config,
|
|
366
|
+
scope,
|
|
367
|
+
scopeHash,
|
|
368
|
+
approval,
|
|
369
|
+
hasDispatched: status.hasDispatched === true,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function validateStoredApprovalEcho(params) {
|
|
373
|
+
const fields = [
|
|
374
|
+
"scopeHash",
|
|
375
|
+
"targetShapeRevision",
|
|
376
|
+
"actionFingerprint",
|
|
377
|
+
"expectedTargetsHash",
|
|
378
|
+
"approvalExpiresAt",
|
|
379
|
+
"approvalFingerprint",
|
|
380
|
+
];
|
|
381
|
+
const changed = fields.filter((field) => text(params.approval[field]) !== params.echo[field]);
|
|
382
|
+
const expiresAt = text(params.approval.approvalExpiresAt);
|
|
383
|
+
if (!params.allowExpired &&
|
|
384
|
+
(!expiresAt ||
|
|
385
|
+
Date.parse(expiresAt) <= (params.now ?? new Date()).getTime())) {
|
|
386
|
+
return {
|
|
387
|
+
ok: false,
|
|
388
|
+
blocker: "approval_expired",
|
|
389
|
+
changed,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return changed.length === 0
|
|
393
|
+
? { ok: true }
|
|
394
|
+
: {
|
|
395
|
+
ok: false,
|
|
396
|
+
blocker: "approval_fingerprint_mismatch",
|
|
397
|
+
changed,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function executionScopeFromStored(scope, fallback) {
|
|
401
|
+
const dateSelector = record(scope.dateSelector) ?? {};
|
|
402
|
+
const kind = text(dateSelector.kind);
|
|
403
|
+
const senderIds = list(scope.senderIds)
|
|
404
|
+
.map((value) => text(value))
|
|
405
|
+
.filter((value) => value != null);
|
|
406
|
+
const approvalMode = scope.approvalMode === "mark_ready" ? "mark_ready" : "approve";
|
|
407
|
+
return {
|
|
408
|
+
intent: text(scope.intent) ?? fallback.intent,
|
|
409
|
+
senderIds,
|
|
410
|
+
approvalMode,
|
|
411
|
+
...(kind === "target_date" && text(dateSelector.targetDate)
|
|
412
|
+
? { targetDate: text(dateSelector.targetDate) }
|
|
413
|
+
: kind === "until_date" && text(dateSelector.untilDate)
|
|
414
|
+
? { untilDate: text(dateSelector.untilDate) }
|
|
415
|
+
: kind === "horizon_send_days" &&
|
|
416
|
+
typeof dateSelector.horizonSendDays === "number"
|
|
417
|
+
? { horizonSendDays: dateSelector.horizonSendDays }
|
|
418
|
+
: {}),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
338
421
|
async function maybeAddLostFenceGuidance(result, input) {
|
|
339
422
|
if (result.status !== "blocked" ||
|
|
340
423
|
result.blocker !== "lease_lost" ||
|
|
@@ -365,7 +448,53 @@ export async function refillSendsV2Command(input) {
|
|
|
365
448
|
let resolvedScope = input.scope;
|
|
366
449
|
let resolvedScopeHash = input.scopeHash;
|
|
367
450
|
let resolvedApproval = input.approval;
|
|
368
|
-
if (input.
|
|
451
|
+
if (input.runId && input.approvalEcho) {
|
|
452
|
+
const status = await refillRunStatus({ workspaceId, runId: input.runId });
|
|
453
|
+
const stored = storedApprovalFromStatus(status);
|
|
454
|
+
if (!stored) {
|
|
455
|
+
return {
|
|
456
|
+
status: "blocked",
|
|
457
|
+
blocker: "approval_run_unavailable",
|
|
458
|
+
guidance: "The durable refill run no longer exposes a valid persisted approval packet.",
|
|
459
|
+
report: { status },
|
|
460
|
+
readOnly: false,
|
|
461
|
+
forbiddenActions: FORBIDDEN_ACTIONS,
|
|
462
|
+
boundedAuthority: BOUNDED_AUTHORITY,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const validation = validateStoredApprovalEcho({
|
|
466
|
+
approval: stored.approval,
|
|
467
|
+
echo: input.approvalEcho,
|
|
468
|
+
allowExpired: stored.hasDispatched,
|
|
469
|
+
});
|
|
470
|
+
if (!validation.ok) {
|
|
471
|
+
return blockedApprovalResult({
|
|
472
|
+
blocker: validation.blocker,
|
|
473
|
+
changed: validation.changed,
|
|
474
|
+
fresh: stored.approval,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
const freshTargetShapeRevision = input.targetPlan
|
|
478
|
+
? (text(input.targetPlan.targetShapeRevision) ??
|
|
479
|
+
text(record(input.targetPlan.target)?.targetShapeRevision))
|
|
480
|
+
: null;
|
|
481
|
+
const storedTargetShapeRevision = text(stored.approval.targetShapeRevision);
|
|
482
|
+
if (freshTargetShapeRevision &&
|
|
483
|
+
freshTargetShapeRevision !== storedTargetShapeRevision) {
|
|
484
|
+
return blockedApprovalResult({
|
|
485
|
+
blocker: "approval_fingerprint_mismatch",
|
|
486
|
+
changed: ["targetShapeRevision"],
|
|
487
|
+
fresh: {
|
|
488
|
+
storedTargetShapeRevision,
|
|
489
|
+
freshTargetShapeRevision,
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
resolvedScope = stored.scope;
|
|
494
|
+
resolvedScopeHash = stored.scopeHash;
|
|
495
|
+
resolvedApproval = stored.approval;
|
|
496
|
+
}
|
|
497
|
+
else if (input.targetPlan && input.approvalEcho) {
|
|
369
498
|
const envelope = buildApprovalEnvelope({
|
|
370
499
|
workspaceId,
|
|
371
500
|
intent: input.intent,
|
|
@@ -379,15 +508,11 @@ export async function refillSendsV2Command(input) {
|
|
|
379
508
|
});
|
|
380
509
|
const validation = validateApprovalEcho(envelope, input.approvalEcho);
|
|
381
510
|
if (!validation.ok) {
|
|
382
|
-
return {
|
|
383
|
-
status: "blocked",
|
|
511
|
+
return blockedApprovalResult({
|
|
384
512
|
blocker: validation.blocker,
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
forbiddenActions: FORBIDDEN_ACTIONS,
|
|
389
|
-
boundedAuthority: BOUNDED_AUTHORITY,
|
|
390
|
-
};
|
|
513
|
+
changed: validation.changed,
|
|
514
|
+
fresh: envelope.approval,
|
|
515
|
+
});
|
|
391
516
|
}
|
|
392
517
|
resolvedScope = envelope.scope;
|
|
393
518
|
resolvedScopeHash = envelope.scopeHash;
|
|
@@ -409,13 +534,18 @@ export async function refillSendsV2Command(input) {
|
|
|
409
534
|
boundedAuthority: BOUNDED_AUTHORITY,
|
|
410
535
|
};
|
|
411
536
|
}
|
|
537
|
+
const executionScope = resolvedScope
|
|
538
|
+
? executionScopeFromStored(resolvedScope, input)
|
|
539
|
+
: {
|
|
540
|
+
intent: input.intent,
|
|
541
|
+
senderIds: input.senderIds,
|
|
542
|
+
approvalMode: input.approvalMode ?? "approve",
|
|
543
|
+
...date,
|
|
544
|
+
};
|
|
412
545
|
const result = await runRefillV2Loop({
|
|
413
546
|
workspaceId,
|
|
414
|
-
|
|
415
|
-
senderIds: input.senderIds,
|
|
416
|
-
approvalMode: input.approvalMode ?? "approve",
|
|
547
|
+
...executionScope,
|
|
417
548
|
resume: resumeHandle(input),
|
|
418
|
-
...date,
|
|
419
549
|
scope: resolvedScope,
|
|
420
550
|
scopeHash: resolvedScopeHash,
|
|
421
551
|
approval: resolvedApproval,
|
|
@@ -725,6 +725,23 @@ export declare function executeRefillSendsCommand(input?: RefillSendsCommandInpu
|
|
|
725
725
|
intent: RefillSendsIntent;
|
|
726
726
|
} | {
|
|
727
727
|
yoloExecution: {
|
|
728
|
+
selectedAction: Record<string, unknown> | null;
|
|
729
|
+
targetPlanReread: boolean;
|
|
730
|
+
status: "blocked";
|
|
731
|
+
blocker: "approval_expired" | "approval_fingerprint_mismatch";
|
|
732
|
+
guidance: string;
|
|
733
|
+
report: {
|
|
734
|
+
fresh?: {} | null | undefined;
|
|
735
|
+
changed: string[];
|
|
736
|
+
};
|
|
737
|
+
readOnly: boolean;
|
|
738
|
+
forbiddenActions: string[];
|
|
739
|
+
boundedAuthority: string;
|
|
740
|
+
enabled: boolean;
|
|
741
|
+
result?: undefined;
|
|
742
|
+
refusalReason?: undefined;
|
|
743
|
+
postActionFirstAction?: undefined;
|
|
744
|
+
} | {
|
|
728
745
|
selectedAction: Record<string, unknown> | null;
|
|
729
746
|
targetPlanReread: boolean;
|
|
730
747
|
readOnly: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { REFILL_CONTRACT_CACHE_VERSION, REFILL_CONTRACT_HASH, REFILL_CONTRACT_VERSION, renderRefillBootstrapGuidance, renderRefillSafetyGuidance, renderRefillTerminalGuidance, } from "../refill-contract.js";
|
|
1
2
|
import { actionIds, classifyPaidInmailRefreshReceipt, collectPaidInmailRefreshActions, executeOneYoloPrimitive, firstGlobalAction, normalizeStrings, numberValue, recordValue, refreshPaidInmailCreditsWithRetry, stringValue, } from "./refill-executors.js";
|
|
2
3
|
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";
|
|
4
4
|
import { getRefillTargetPlan } from "./refill-target-plan.js";
|
|
5
5
|
import { createWorkspaceContext, normalizeExplicitWorkspaceId, } from "./workspace-context.js";
|
|
6
6
|
const REFILL_SEND_ACTION_TYPES = ["send_invite", "send_inmail_closed"];
|
|
@@ -189,7 +189,7 @@ export const refillSendsToolDefinitions = [
|
|
|
189
189
|
},
|
|
190
190
|
expectedActionKey: {
|
|
191
191
|
type: "string",
|
|
192
|
-
description: "Optional actionKey from the approved packet's first global action. A mismatch returns approval_scope_changed before any refresh or mutation.",
|
|
192
|
+
description: "Optional actual non-empty actionKey from the approved packet's first global action. Never infer it from type, toolName, or actionFingerprint. A mismatch returns approval_scope_changed before any refresh or mutation.",
|
|
193
193
|
},
|
|
194
194
|
runId: { type: "string" },
|
|
195
195
|
fence: { type: "number" },
|
|
@@ -293,7 +293,7 @@ function buildRefillSendsCommand(input) {
|
|
|
293
293
|
: `, horizonSendDays: ${horizonSendDays}`}, approvalMode: "${approvalMode}" }) before any import, prep, approval, start, or schedule-affecting action.`,
|
|
294
294
|
"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.",
|
|
295
295
|
"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.",
|
|
296
|
-
"When expectedTargetShapeRevision or expectedActionKey is supplied from an already rendered packet, compare both against the first fresh target plan before paid-credit refresh or any primitive. Return approval_scope_changed with the fresh packet on mismatch.",
|
|
296
|
+
"When expectedTargetShapeRevision or expectedActionKey is supplied from an already rendered packet, compare both against the first fresh target plan before paid-credit refresh or any primitive. Pass expectedActionKey only when the rendered action has an actual non-empty actionKey; never infer it from type, toolName, or actionFingerprint. Return approval_scope_changed with the fresh packet on mismatch.",
|
|
297
297
|
"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.",
|
|
298
298
|
"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.",
|
|
299
299
|
"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.",
|
|
@@ -563,8 +563,13 @@ export async function executeRefillSendsCommand(input = {}) {
|
|
|
563
563
|
}
|
|
564
564
|
const targetPlanInput = targetPlanInputFor(scopedInput);
|
|
565
565
|
const targetPlanBeforePaidRefresh = await getRefillTargetPlan(targetPlanInput);
|
|
566
|
+
const durableResumeRequested = Boolean(yolo &&
|
|
567
|
+
workspaceId &&
|
|
568
|
+
scopedInput.runId &&
|
|
569
|
+
typeof scopedInput.fence === "number" &&
|
|
570
|
+
scopedInput.approvalFingerprint);
|
|
566
571
|
const approvedPacket = approvedPacketComparison(scopedInput, targetPlanBeforePaidRefresh);
|
|
567
|
-
if (approvedPacket.changed) {
|
|
572
|
+
if (!durableResumeRequested && approvedPacket.changed) {
|
|
568
573
|
return {
|
|
569
574
|
...command,
|
|
570
575
|
ok: false,
|
|
@@ -595,7 +600,8 @@ export async function executeRefillSendsCommand(input = {}) {
|
|
|
595
600
|
}
|
|
596
601
|
if (yolo &&
|
|
597
602
|
workspaceId &&
|
|
598
|
-
|
|
603
|
+
(durableResumeRequested ||
|
|
604
|
+
durableV2PlanEligible(targetPlanBeforePaidRefresh))) {
|
|
599
605
|
const base = durableYoloBaseResult({
|
|
600
606
|
command,
|
|
601
607
|
targetPlan: targetPlanBeforePaidRefresh,
|
package/package.json
CHANGED
|
@@ -101,6 +101,9 @@ Claude Code uses `/sellable:refill-sends`. Optional selectors are `--sender`,
|
|
|
101
101
|
6. Invoke only the public durable entrypoint:
|
|
102
102
|
`mcp__sellable__refill_sends({ workspaceId, targetDate, untilDate, horizonSendDays, senderIds, senderNames, actionTypes, yolo, executionMode, requireWorkspace:true })`.
|
|
103
103
|
Preserve the exact scope and approval echo returned by the tool on resume.
|
|
104
|
+
Pass `expectedActionKey` only when the rendered first global action contains
|
|
105
|
+
an actual non-empty `actionKey`; never infer it from `type`, `toolName`, or
|
|
106
|
+
`actionFingerprint`.
|
|
104
107
|
|
|
105
108
|
## Operator packet
|
|
106
109
|
|