@sellable/mcp 0.1.557 → 0.1.558
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/README.md +2 -13
- package/agents/registry.json +2 -2
- package/dist/api.js +6 -3
- package/dist/auth.d.ts +6 -0
- package/dist/auth.js +44 -2
- package/dist/refill-contract.d.ts +157 -0
- package/dist/refill-contract.js +487 -0
- package/dist/refill-run-client.d.ts +10 -0
- package/dist/refill-run-client.js +22 -0
- package/dist/refill-run-loop.d.ts +14 -2
- package/dist/refill-run-loop.js +160 -15
- package/dist/server.js +0 -23
- package/dist/tools/auth.d.ts +5 -0
- package/dist/tools/auth.js +49 -12
- package/dist/tools/campaign-message-preparation.d.ts +62 -0
- package/dist/tools/campaign-message-preparation.js +41 -0
- package/dist/tools/campaigns.js +2 -2
- package/dist/tools/csv-dnc.js +2 -2
- package/dist/tools/evergreen-refill-plan.d.ts +3 -0
- package/dist/tools/evergreen-refill-plan.js +29 -7
- package/dist/tools/leads.d.ts +32 -317
- package/dist/tools/leads.js +10 -171
- package/dist/tools/model-quality.js +6 -4
- package/dist/tools/prompts.d.ts +3 -3
- package/dist/tools/prompts.js +15 -7
- package/dist/tools/provider-preflight.d.ts +2 -65
- package/dist/tools/provider-preflight.js +10 -97
- package/dist/tools/readiness.d.ts +5 -89
- package/dist/tools/readiness.js +0 -66
- 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 +312 -3
- 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 +115 -330
- package/dist/tools/registry.js +1 -7
- 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/setup-evergreen-campaigns.js +1 -1
- package/dist/tools/workspace-context.d.ts +1 -1
- package/dist/tools/workspace-context.js +8 -3
- package/dist/tools/workspace-export.js +2 -2
- package/dist/tools/workspaces.d.ts +48 -2
- package/dist/tools/workspaces.js +48 -5
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +3 -3
- package/skills/create-campaign-v2/SKILL.md +1 -1
- package/skills/create-evergreen-campaigns/SKILL.md +16 -16
- package/skills/find-leads/SKILL.md +630 -48
- 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/tools/find-leads-runs.d.ts +0 -151
- package/dist/tools/find-leads-runs.js +0 -98
- package/skills/find-leads-v2/SKILL.md +0 -70
- package/skills/find-leads-v2/core/flow.v1.json +0 -31
|
@@ -1,6 +1,10 @@
|
|
|
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";
|
|
6
|
+
const REFILL_SEND_ACTION_TYPES = ["send_invite", "send_inmail_closed"];
|
|
7
|
+
const REFILL_SEND_ACTION_TYPE_SET = new Set(REFILL_SEND_ACTION_TYPES);
|
|
4
8
|
const DEFAULT_SCHEDULER_FORWARD_HOURS = 48;
|
|
5
9
|
const MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS = 4 * 60 * 60;
|
|
6
10
|
function normalizeHorizonSendDays(value) {
|
|
@@ -48,10 +52,60 @@ function normalizeTargetDate(value) {
|
|
|
48
52
|
}
|
|
49
53
|
return trimmed;
|
|
50
54
|
}
|
|
55
|
+
function normalizeRefillActionTypes(value) {
|
|
56
|
+
if (value === undefined || value === null)
|
|
57
|
+
return undefined;
|
|
58
|
+
if (!Array.isArray(value)) {
|
|
59
|
+
throw new Error("actionTypes must be an array of supported refill lanes.");
|
|
60
|
+
}
|
|
61
|
+
if (value.some((actionType) => typeof actionType !== "string")) {
|
|
62
|
+
throw new Error(`Unsupported refill actionTypes. Supported values are ${REFILL_SEND_ACTION_TYPES.join(", ")}.`);
|
|
63
|
+
}
|
|
64
|
+
const normalized = normalizeStrings(value);
|
|
65
|
+
const unsupported = normalized.filter((actionType) => !REFILL_SEND_ACTION_TYPE_SET.has(actionType));
|
|
66
|
+
if (unsupported.length > 0) {
|
|
67
|
+
throw new Error(`Unsupported refill actionTypes: ${unsupported.join(", ")}. Supported values are ${REFILL_SEND_ACTION_TYPES.join(", ")}.`);
|
|
68
|
+
}
|
|
69
|
+
return normalized.length > 0
|
|
70
|
+
? normalized
|
|
71
|
+
: undefined;
|
|
72
|
+
}
|
|
73
|
+
function normalizeExpectedPacketValue(value, field) {
|
|
74
|
+
if (value === undefined || value === null)
|
|
75
|
+
return undefined;
|
|
76
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
77
|
+
throw new Error(`${field} must be a non-empty string when provided.`);
|
|
78
|
+
}
|
|
79
|
+
return value.trim();
|
|
80
|
+
}
|
|
81
|
+
function normalizeRefillSendsInput(input) {
|
|
82
|
+
const actionTypes = normalizeRefillActionTypes(input.actionTypes);
|
|
83
|
+
const expectedTargetShapeRevision = normalizeExpectedPacketValue(input.expectedTargetShapeRevision, "expectedTargetShapeRevision");
|
|
84
|
+
const expectedActionKey = normalizeExpectedPacketValue(input.expectedActionKey, "expectedActionKey");
|
|
85
|
+
const normalized = { ...input };
|
|
86
|
+
delete normalized.actionTypes;
|
|
87
|
+
delete normalized.expectedTargetShapeRevision;
|
|
88
|
+
delete normalized.expectedActionKey;
|
|
89
|
+
return {
|
|
90
|
+
...normalized,
|
|
91
|
+
...(actionTypes ? { actionTypes } : {}),
|
|
92
|
+
...(expectedTargetShapeRevision ? { expectedTargetShapeRevision } : {}),
|
|
93
|
+
...(expectedActionKey ? { expectedActionKey } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
51
96
|
export const refillSendsToolDefinitions = [
|
|
52
97
|
{
|
|
53
98
|
name: "refill_sends",
|
|
54
|
-
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(" "),
|
|
55
109
|
inputSchema: {
|
|
56
110
|
type: "object",
|
|
57
111
|
properties: {
|
|
@@ -87,6 +141,14 @@ export const refillSendsToolDefinitions = [
|
|
|
87
141
|
items: { type: "string" },
|
|
88
142
|
description: "Optional sender display names to resolve through list_senders before campaign selection.",
|
|
89
143
|
},
|
|
144
|
+
actionTypes: {
|
|
145
|
+
type: "array",
|
|
146
|
+
items: {
|
|
147
|
+
type: "string",
|
|
148
|
+
enum: ["send_invite", "send_inmail_closed"],
|
|
149
|
+
},
|
|
150
|
+
description: "Optional explicit refill lane. Omit to preserve planner inference; supported values are connection invites and paid InMail / unified Sales Nav cascades.",
|
|
151
|
+
},
|
|
90
152
|
horizonSendDays: {
|
|
91
153
|
type: "number",
|
|
92
154
|
minimum: 1,
|
|
@@ -121,6 +183,22 @@ export const refillSendsToolDefinitions = [
|
|
|
121
183
|
enum: ["approve", "mark_ready"],
|
|
122
184
|
description: 'Preparation mode for the final packet. Use "approve" only when the user asked to fill/schedule sender sends; otherwise default to "mark_ready".',
|
|
123
185
|
},
|
|
186
|
+
expectedTargetShapeRevision: {
|
|
187
|
+
type: "string",
|
|
188
|
+
description: "Optional targetShapeRevision from an already rendered and approved packet. A mismatch returns approval_scope_changed before any refresh or mutation.",
|
|
189
|
+
},
|
|
190
|
+
expectedActionKey: {
|
|
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.",
|
|
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" },
|
|
124
202
|
},
|
|
125
203
|
required: [],
|
|
126
204
|
additionalProperties: false,
|
|
@@ -128,6 +206,9 @@ export const refillSendsToolDefinitions = [
|
|
|
128
206
|
},
|
|
129
207
|
];
|
|
130
208
|
export function refillSendsCommand(input = {}) {
|
|
209
|
+
return buildRefillSendsCommand(normalizeRefillSendsInput(input));
|
|
210
|
+
}
|
|
211
|
+
function buildRefillSendsCommand(input) {
|
|
131
212
|
const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
|
|
132
213
|
const senders = normalizeStrings(input.senders);
|
|
133
214
|
const senderIds = normalizeStrings(input.senderIds);
|
|
@@ -142,6 +223,7 @@ export function refillSendsCommand(input = {}) {
|
|
|
142
223
|
: normalizeHorizonSendDays(input.horizonSendDays);
|
|
143
224
|
const intent = input.intent ?? "plain";
|
|
144
225
|
const approvalMode = input.approvalMode ?? (yolo ? "approve" : "mark_ready");
|
|
226
|
+
const actionTypes = input.actionTypes;
|
|
145
227
|
const senderScope = hasSenderSelectors
|
|
146
228
|
? "selected_senders"
|
|
147
229
|
: yolo
|
|
@@ -161,6 +243,7 @@ export function refillSendsCommand(input = {}) {
|
|
|
161
243
|
workspaceId,
|
|
162
244
|
workspaceResolution: workspaceId ? "explicit" : "active_config",
|
|
163
245
|
intent,
|
|
246
|
+
...(actionTypes ? { actionTypes } : {}),
|
|
164
247
|
targetDate,
|
|
165
248
|
untilDate,
|
|
166
249
|
horizonSendDays,
|
|
@@ -201,7 +284,7 @@ export function refillSendsCommand(input = {}) {
|
|
|
201
284
|
"A skill cannot create or invoke /goal by itself. If this refill is already running inside an active Codex goal, keep that goal open until every selected sender lane is horizon-filled by projected coverage (sent + scheduled), Christian explicitly stops/statuses the run, or a concrete non-scheduler blocker appears.",
|
|
202
285
|
`Call get_refill_target_plan({ intent: "${intent}"${workspaceId ? `, workspaceId: "${workspaceId}"` : ""}${input.campaignId ? `, campaignId: "${input.campaignId}"` : ""}${input.tableId ? `, tableId: "${input.tableId}"` : ""}${senderIds.length > 0 ? `, senderIds: ${JSON.stringify(senderIds)}` : ""}${senderNames.length > 0
|
|
203
286
|
? `, senderNames: ${JSON.stringify(senderNames)}`
|
|
204
|
-
: ""}${senders.length > 0 ? `, senders: ${JSON.stringify(senders)}` : ""}${targetDate
|
|
287
|
+
: ""}${senders.length > 0 ? `, senders: ${JSON.stringify(senders)}` : ""}${actionTypes ? `, actionTypes: ${JSON.stringify(actionTypes)}` : ""}${targetDate
|
|
205
288
|
? `, targetDate: "${targetDate}"`
|
|
206
289
|
: untilDate
|
|
207
290
|
? `, untilDate: "${untilDate}"`
|
|
@@ -210,10 +293,11 @@ export function refillSendsCommand(input = {}) {
|
|
|
210
293
|
: `, horizonSendDays: ${horizonSendDays}`}, approvalMode: "${approvalMode}" }) before any import, prep, approval, start, or schedule-affecting action.`,
|
|
211
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.",
|
|
212
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.",
|
|
213
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.",
|
|
214
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.",
|
|
215
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.",
|
|
216
|
-
"If remainingReadyOrProjectedGap is 0 but remainingProjectedGap is positive, run only a persistent read-only scheduler wait/reread loop; do not ask for prep/import/approval and do not close out while scheduler pickup is the only remaining state.",
|
|
300
|
+
"If the exact-date global action is run_scheduler_sweep, execute it once and fully reread when remainingReadyOrProjectedGap is 0 but remainingProjectedGap is positive. Otherwise, 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.",
|
|
217
301
|
`Resolve route with resolve_campaign_fill_route({ intent: "${intent}"${workspaceId ? `, workspaceId: "${workspaceId}"` : ""}${input.campaignId ? `, campaignId: "${input.campaignId}"` : ""}${input.tableId ? `, tableId: "${input.tableId}"` : ""} }).`,
|
|
218
302
|
`If the plain route shows stale managed waterfall evidence, archived/completed shared slots, or targets that do not cover the selected sender set, immediately call resolve_campaign_fill_route({ intent: "active"${workspaceId ? `, workspaceId: "${workspaceId}"` : ""} }) and inspect current dashboard-active ACTIVE/PAUSED campaign-backed sequence campaigns before declaring a sender blocked.`,
|
|
219
303
|
workspaceId
|
|
@@ -231,7 +315,7 @@ export function refillSendsCommand(input = {}) {
|
|
|
231
315
|
"Fresh reread get_campaign_refill_state immediately before any import, prep, approval, or horizon-fill mutation.",
|
|
232
316
|
"Maintain a target-window saturation ledger per selected sender from get_refill_target_plan: selected days, gross capacity, actual sent counts, future scheduler-owned scheduled counts, projected counts, ready-to-schedule buffer, remaining projected gap, paid InMail threshold feasibility, targetShapeRevision, stateRevision, and next MCP primitive.",
|
|
233
317
|
"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.",
|
|
234
|
-
"In manual/non-yolo, scheduled, and --yolo modes, this tool may automatically refresh stale or missing paid-InMail credit facts once per selected sender when the first target plan returns refresh_paid_inmail_credits candidates. This is the only newly allowed automatic write in manual/non-yolo mode; it reruns get_refill_target_plan and returns autoPaidInmailRefresh with attemptedSenderIds, refreshedPaidInmailSenderIds, failedPaidInmailRefreshes, and the post-refresh targetPlan before any prep/source-copy/bounded-approval/read-only
|
|
318
|
+
"In manual/non-yolo, scheduled, and --yolo modes, this tool may automatically refresh stale or missing paid-InMail credit facts once per selected sender when the first target plan returns refresh_paid_inmail_credits candidates. This is the only newly allowed automatic write in manual/non-yolo mode; it reruns get_refill_target_plan and returns autoPaidInmailRefresh with attemptedSenderIds, refreshedPaidInmailSenderIds, failedPaidInmailRefreshes, and the post-refresh targetPlan before any prep/source-copy/bounded-approval/exact-date-sweep/read-only-wait action is chosen.",
|
|
235
319
|
"Freshness gate precedes scheduler wait: if any selected target.senderRefillPlans[].paidInmail.status is missing_credit_facts or stale_credit_facts, or the target plan contains refresh_paid_inmail_credits, do not enter wait_for_scheduler even when remainingReadyOrProjectedGap is 0; refresh exact sender credit facts once, reread get_refill_target_plan, then choose scheduler wait only if freshness is clean.",
|
|
236
320
|
"Scheduler-run receipt interpretation: cellsConsidered is allocation-attempt count, readyCellsFound is ready inventory before prefilters, and campaignScopeSummary must include the selected campaign/table before inferring it was ready-but-blocked. Read prefiltered, skipped, and deferred separately. For ready closed-InMail cells with stale paid-credit facts, route to refresh_paid_inmail_credits_then_rerun once, then rerun/status. wait_for_capacity_or_window means report loaded/capped/waiting and do not source or prep more rows. no_ready_cells_continue_refill_prep means return to the refill/prep ladder.",
|
|
237
321
|
"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.",
|
|
@@ -241,9 +325,9 @@ export function refillSendsCommand(input = {}) {
|
|
|
241
325
|
"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.",
|
|
242
326
|
],
|
|
243
327
|
approvalContract: yolo
|
|
244
|
-
? "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
|
|
328
|
+
? "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/exact-date-sweep/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 and the exact-date run_scheduler_sweep is target.globalActionQueue[0], execute it once and fully reread. Only non-exact or no-sweep scheduler waits are read-only; use a 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 workspace, sender set, campaign/table/stable source identity, configured caps, exact date/window, lane, or the approved action/side-effect envelope drifts. Treat coverage, blockers, receipts, timestamps, and current action progress as stateRevision changes inside that envelope. --yolo does not authorize lowering paid InMail thresholds or creating connection campaigns."
|
|
245
329
|
: hasSenderSelectors
|
|
246
|
-
? "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,
|
|
330
|
+
? "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 and the approved exact-date run_scheduler_sweep is target.globalActionQueue[0], execute it once and fully reread. Only non-exact or no-sweep scheduler waits are read-only; keep the run open until projected coverage fills or Christian stops/statuses it. Threshold changes and campaign creation need separate explicit approval."
|
|
247
331
|
: "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.",
|
|
248
332
|
forbiddenActions: [
|
|
249
333
|
"start_campaign outside an exact selected PAUSED campaign-backed refill packet",
|
|
@@ -276,6 +360,7 @@ export function refillSendsCommand(input = {}) {
|
|
|
276
360
|
senders,
|
|
277
361
|
senderIds,
|
|
278
362
|
senderNames,
|
|
363
|
+
...(actionTypes ? { actionTypes } : {}),
|
|
279
364
|
targetDate,
|
|
280
365
|
untilDate,
|
|
281
366
|
horizonSendDays,
|
|
@@ -284,6 +369,14 @@ export function refillSendsCommand(input = {}) {
|
|
|
284
369
|
intent,
|
|
285
370
|
approvalMode,
|
|
286
371
|
workspaceId,
|
|
372
|
+
...(input.expectedTargetShapeRevision
|
|
373
|
+
? {
|
|
374
|
+
expectedTargetShapeRevision: input.expectedTargetShapeRevision,
|
|
375
|
+
}
|
|
376
|
+
: {}),
|
|
377
|
+
...(input.expectedActionKey
|
|
378
|
+
? { expectedActionKey: input.expectedActionKey }
|
|
379
|
+
: {}),
|
|
287
380
|
},
|
|
288
381
|
},
|
|
289
382
|
},
|
|
@@ -307,6 +400,7 @@ function targetPlanInputFor(input = {}) {
|
|
|
307
400
|
...(senderIds.length > 0 ? { senderIds } : {}),
|
|
308
401
|
...(senderNames.length > 0 ? { senderNames } : {}),
|
|
309
402
|
...(senders.length > 0 ? { senders } : {}),
|
|
403
|
+
...(input.actionTypes ? { actionTypes: input.actionTypes } : {}),
|
|
310
404
|
...(targetDate
|
|
311
405
|
? { targetDate }
|
|
312
406
|
: untilDate
|
|
@@ -327,6 +421,24 @@ function targetPlanFingerprint(plan) {
|
|
|
327
421
|
generatedAt: stringValue(root?.generatedAt) ?? null,
|
|
328
422
|
};
|
|
329
423
|
}
|
|
424
|
+
function approvedPacketComparison(input, targetPlan) {
|
|
425
|
+
const root = recordValue(targetPlan);
|
|
426
|
+
const action = recordValue(firstGlobalAction(targetPlan));
|
|
427
|
+
const actualTargetShapeRevision = stringValue(root?.targetShapeRevision) ?? null;
|
|
428
|
+
const actualActionKey = stringValue(action?.actionKey) ?? null;
|
|
429
|
+
const shapeChanged = Boolean(input.expectedTargetShapeRevision &&
|
|
430
|
+
input.expectedTargetShapeRevision !== actualTargetShapeRevision);
|
|
431
|
+
const actionChanged = Boolean(input.expectedActionKey && input.expectedActionKey !== actualActionKey);
|
|
432
|
+
return {
|
|
433
|
+
changed: shapeChanged || actionChanged,
|
|
434
|
+
expectedTargetShapeRevision: input.expectedTargetShapeRevision ?? null,
|
|
435
|
+
actualTargetShapeRevision,
|
|
436
|
+
expectedActionKey: input.expectedActionKey ?? null,
|
|
437
|
+
actualActionKey,
|
|
438
|
+
shapeChanged,
|
|
439
|
+
actionChanged,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
330
442
|
function paidRefreshApprovalPacket(params) {
|
|
331
443
|
const action = params.action.action ?? {};
|
|
332
444
|
const ids = actionIds(action);
|
|
@@ -357,13 +469,61 @@ function paidRefreshOptionsFromApprovalPacket(packet) {
|
|
|
357
469
|
maxStalenessSeconds: packet.maxStalenessSeconds,
|
|
358
470
|
};
|
|
359
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
|
+
}
|
|
360
517
|
export async function executeRefillSendsCommand(input = {}) {
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
const
|
|
518
|
+
const normalizedInput = normalizeRefillSendsInput(input);
|
|
519
|
+
const yolo = normalizedInput.yolo === true;
|
|
520
|
+
const executionMode = normalizedInput.executionMode ?? (yolo ? "yolo" : "manual");
|
|
521
|
+
const mustHaveWorkspace = yolo ||
|
|
522
|
+
normalizedInput.requireWorkspace === true ||
|
|
523
|
+
executionMode === "scheduled";
|
|
364
524
|
const workspaceContext = mustHaveWorkspace
|
|
365
525
|
? createWorkspaceContext({
|
|
366
|
-
workspaceId:
|
|
526
|
+
workspaceId: normalizedInput.workspaceId,
|
|
367
527
|
executionMode,
|
|
368
528
|
toolName: "refill_sends",
|
|
369
529
|
})
|
|
@@ -372,9 +532,12 @@ export async function executeRefillSendsCommand(input = {}) {
|
|
|
372
532
|
return workspaceContext;
|
|
373
533
|
}
|
|
374
534
|
const scopedInput = workspaceContext && workspaceContext.ok
|
|
375
|
-
? {
|
|
376
|
-
|
|
377
|
-
|
|
535
|
+
? {
|
|
536
|
+
...normalizedInput,
|
|
537
|
+
workspaceId: workspaceContext.context.workspaceId,
|
|
538
|
+
}
|
|
539
|
+
: normalizedInput;
|
|
540
|
+
const command = buildRefillSendsCommand(scopedInput);
|
|
378
541
|
const workspaceId = workspaceContext && workspaceContext.ok
|
|
379
542
|
? workspaceContext.context.workspaceId
|
|
380
543
|
: normalizeExplicitWorkspaceId(scopedInput.workspaceId);
|
|
@@ -387,6 +550,7 @@ export async function executeRefillSendsCommand(input = {}) {
|
|
|
387
550
|
status: "not_run_without_yolo",
|
|
388
551
|
refreshedPaidInmailSenderIds: [],
|
|
389
552
|
failedPaidInmailRefreshes: [],
|
|
553
|
+
refreshReceipts: [],
|
|
390
554
|
note: "Automatic paid InMail credit refresh requires --yolo, scheduled mode, or an explicit workspaceId so the request can refresh exact sender facts safely.",
|
|
391
555
|
},
|
|
392
556
|
yoloExecution: {
|
|
@@ -399,6 +563,103 @@ export async function executeRefillSendsCommand(input = {}) {
|
|
|
399
563
|
}
|
|
400
564
|
const targetPlanInput = targetPlanInputFor(scopedInput);
|
|
401
565
|
const targetPlanBeforePaidRefresh = await getRefillTargetPlan(targetPlanInput);
|
|
566
|
+
const approvedPacket = approvedPacketComparison(scopedInput, targetPlanBeforePaidRefresh);
|
|
567
|
+
if (approvedPacket.changed) {
|
|
568
|
+
return {
|
|
569
|
+
...command,
|
|
570
|
+
ok: false,
|
|
571
|
+
code: "approval_scope_changed",
|
|
572
|
+
targetPlan: targetPlanBeforePaidRefresh,
|
|
573
|
+
targetPlanBeforePaidRefresh: null,
|
|
574
|
+
targetPlanBeforeYoloPrimitive: null,
|
|
575
|
+
...approvedPacket,
|
|
576
|
+
autoPaidInmailRefresh: {
|
|
577
|
+
enabled: false,
|
|
578
|
+
status: "skipped_approval_scope_changed",
|
|
579
|
+
refreshedPaidInmailSenderIds: [],
|
|
580
|
+
failedPaidInmailRefreshes: [],
|
|
581
|
+
refreshReceipts: [],
|
|
582
|
+
attemptedSenderIds: [],
|
|
583
|
+
targetPlanReread: false,
|
|
584
|
+
workspaceId,
|
|
585
|
+
workspaceResolution: workspaceId ? "explicit" : "active_config",
|
|
586
|
+
note: "Approved refill scope changed before paid-credit refresh or primitive execution.",
|
|
587
|
+
},
|
|
588
|
+
yoloExecution: {
|
|
589
|
+
enabled: yolo,
|
|
590
|
+
status: "approval_scope_changed",
|
|
591
|
+
selectedAction: firstGlobalAction(targetPlanBeforePaidRefresh),
|
|
592
|
+
targetPlanReread: false,
|
|
593
|
+
},
|
|
594
|
+
};
|
|
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
|
+
}
|
|
402
663
|
const refreshActions = collectPaidInmailRefreshActions(targetPlanBeforePaidRefresh);
|
|
403
664
|
const refreshedPaidInmailSenderIds = [];
|
|
404
665
|
const failedPaidInmailRefreshes = [];
|