@sellable/mcp 0.1.570 → 0.1.572
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-date-window.d.ts +34 -0
- package/dist/refill-date-window.js +210 -0
- package/dist/tools/prompts.d.ts +2 -0
- package/dist/tools/prompts.js +2 -1
- package/dist/tools/refill-executors.d.ts +20 -1
- package/dist/tools/refill-executors.js +168 -8
- package/dist/tools/refill-sends-evergreen.d.ts +28 -0
- package/dist/tools/refill-sends-evergreen.js +47 -0
- package/dist/tools/refill-sends.d.ts +4 -0
- package/dist/tools/refill-sends.js +14 -3
- package/dist/tools/refill-target-plan.js +122 -30
- package/dist/tools/registry.d.ts +48 -0
- package/dist/tools/scheduler-run.d.ts +61 -0
- package/dist/tools/scheduler-run.js +183 -1
- package/package.json +1 -1
- package/skills/refill-sends/SKILL.md +16 -8
- package/skills/refill-sends-workflow/SKILL.md +6 -2
- package/skills/research/config.json +9 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
1
2
|
import { getApi } from "../api.js";
|
|
2
3
|
import { normalizeExplicitWorkspaceId, workspaceRequestOptions, } from "./workspace-context.js";
|
|
4
|
+
export const REFILL_SCHEDULER_EXPECTED_TARGET_LIMIT = 25;
|
|
5
|
+
export const REFILL_SCHEDULER_EXPECTED_TARGET_SENDER_LIMIT = 100;
|
|
6
|
+
export const REFILL_SCHEDULER_MAX_PLACEMENTS = 150;
|
|
3
7
|
async function postSchedulerRun(body, workspaceId) {
|
|
4
8
|
const api = getApi();
|
|
5
9
|
const requestOptions = workspaceRequestOptions(workspaceId);
|
|
@@ -29,10 +33,89 @@ function normalizeOptionalScopeValue(value, field) {
|
|
|
29
33
|
}
|
|
30
34
|
return value.trim();
|
|
31
35
|
}
|
|
36
|
+
function canonicalizeExpectedTargets(value) {
|
|
37
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
38
|
+
throw new Error("expectedTargets must be a non-empty array.");
|
|
39
|
+
}
|
|
40
|
+
if (value.length > REFILL_SCHEDULER_EXPECTED_TARGET_LIMIT) {
|
|
41
|
+
throw new Error(`expected_target_overflow: expectedTargets exceeds ${REFILL_SCHEDULER_EXPECTED_TARGET_LIMIT}.`);
|
|
42
|
+
}
|
|
43
|
+
const supportedActions = new Set([
|
|
44
|
+
"send_invite",
|
|
45
|
+
"send_inmail_open",
|
|
46
|
+
"send_inmail_closed",
|
|
47
|
+
]);
|
|
48
|
+
const canonical = value.map((rawTarget, index) => {
|
|
49
|
+
if (!rawTarget ||
|
|
50
|
+
typeof rawTarget !== "object" ||
|
|
51
|
+
Array.isArray(rawTarget)) {
|
|
52
|
+
throw new Error(`expectedTargets[${index}] must be an object.`);
|
|
53
|
+
}
|
|
54
|
+
const target = rawTarget;
|
|
55
|
+
const allowedKeys = new Set([
|
|
56
|
+
"campaignId",
|
|
57
|
+
"tableId",
|
|
58
|
+
"refillLaneKey",
|
|
59
|
+
"branchActionTypes",
|
|
60
|
+
"senderIds",
|
|
61
|
+
]);
|
|
62
|
+
if (Object.keys(target).some((key) => !allowedKeys.has(key))) {
|
|
63
|
+
throw new Error(`expectedTargets[${index}] contains unsupported fields.`);
|
|
64
|
+
}
|
|
65
|
+
const campaignId = normalizeOptionalScopeValue(target.campaignId, `expectedTargets[${index}].campaignId`);
|
|
66
|
+
const tableId = normalizeOptionalScopeValue(target.tableId, `expectedTargets[${index}].tableId`);
|
|
67
|
+
const refillLaneKey = normalizeOptionalScopeValue(target.refillLaneKey, `expectedTargets[${index}].refillLaneKey`);
|
|
68
|
+
if (!campaignId ||
|
|
69
|
+
!tableId ||
|
|
70
|
+
!refillLaneKey ||
|
|
71
|
+
!Array.isArray(target.branchActionTypes) ||
|
|
72
|
+
target.branchActionTypes.length === 0 ||
|
|
73
|
+
!Array.isArray(target.senderIds) ||
|
|
74
|
+
target.senderIds.length === 0) {
|
|
75
|
+
throw new Error(`expectedTargets[${index}] is incomplete.`);
|
|
76
|
+
}
|
|
77
|
+
if (target.senderIds.length > REFILL_SCHEDULER_EXPECTED_TARGET_SENDER_LIMIT) {
|
|
78
|
+
throw new Error(`expected_target_overflow: expectedTargets[${index}].senderIds exceeds ${REFILL_SCHEDULER_EXPECTED_TARGET_SENDER_LIMIT}.`);
|
|
79
|
+
}
|
|
80
|
+
const branchActionTypes = target.branchActionTypes.map((actionType) => {
|
|
81
|
+
if (typeof actionType !== "string" || !supportedActions.has(actionType)) {
|
|
82
|
+
throw new Error(`expectedTargets[${index}] contains an unsupported branch action.`);
|
|
83
|
+
}
|
|
84
|
+
return actionType;
|
|
85
|
+
});
|
|
86
|
+
const senderIds = target.senderIds.map((senderId) => {
|
|
87
|
+
const normalized = normalizeOptionalScopeValue(senderId, `expectedTargets[${index}].senderIds`);
|
|
88
|
+
if (!normalized) {
|
|
89
|
+
throw new Error(`expectedTargets[${index}].senderIds must be non-empty.`);
|
|
90
|
+
}
|
|
91
|
+
return normalized;
|
|
92
|
+
});
|
|
93
|
+
if (new Set(branchActionTypes).size !== branchActionTypes.length ||
|
|
94
|
+
new Set(senderIds).size !== senderIds.length) {
|
|
95
|
+
throw new Error(`expectedTargets[${index}] branchActionTypes and senderIds must be unique.`);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
campaignId,
|
|
99
|
+
tableId,
|
|
100
|
+
refillLaneKey,
|
|
101
|
+
branchActionTypes: branchActionTypes.sort(),
|
|
102
|
+
senderIds: senderIds.sort(),
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
canonical.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
106
|
+
const targetKeys = canonical.map((target) => JSON.stringify(target));
|
|
107
|
+
if (new Set(targetKeys).size !== targetKeys.length) {
|
|
108
|
+
throw new Error("expectedTargets contains duplicate canonical targets.");
|
|
109
|
+
}
|
|
110
|
+
return canonical;
|
|
111
|
+
}
|
|
112
|
+
function computeExpectedTargetsHash(targets) {
|
|
113
|
+
return createHash("sha256").update(JSON.stringify(targets)).digest("hex");
|
|
114
|
+
}
|
|
32
115
|
export const schedulerRunToolDefinitions = [
|
|
33
116
|
{
|
|
34
117
|
name: "run_scheduler_sweep",
|
|
35
|
-
description: 'Trigger the product scheduler placement sweep for one explicit workspace now, or read the last on-demand scheduler run status with action "status". A run is workspace-wide, not a scoped run for one campaign/table, and returns a synchronous envelope with status ran, attached, backoff, window_closed_noop, or failed; retryAfterMs for backoff replays; and a backward-compatible receipt. In
|
|
118
|
+
description: 'Trigger the product scheduler placement sweep for one explicit workspace now, or read the last on-demand scheduler run status with action "status". A run is workspace-wide, not a scoped run for one campaign/table, and returns a synchronous envelope with status ran, attached, backoff, window_closed_noop, or failed; retryAfterMs for backoff replays; and a backward-compatible receipt. In v3 receipts, readyCellsFound is total scheduler-ready supply found before filters, cellsConsidered is the allocation-attempt count that survived prefilters, prefiltered means ready cells removed before allocation such as no capacity, stale paid InMail credit facts, or sender mismatch, skipped means considered cells rejected by hard scheduler gates, and deferred means considered cells waiting on windows/capacity/cooldown. campaignScopeSummary remains bounded display evidence, while expectedTargetSummary explains every hash-bound planner target exactly once without filtering placement. Expected targets require a frozen numeric total placement cap; unrelated eligible campaigns may consume that cap through normal workspace-wide gates, and changedCounts audits every actual placement by campaign, table, action, and sender. The receipt can include readyCellsByType, consideredCellsByType, prefilterReasons, skippedReasons, deferredReasons, summary, and nextAction. It places cells within existing scheduler gates only: it can move placement earlier, but it cannot bypass sending windows, daily limits, cooldowns, sender gates, billing, or credit thresholds, and it never sends messages directly. Repeated calls inside the backoff window replay the last receipt verbatim. Status is read-only and scoped to the last on-demand run only; cron sweeps are not recorded here.',
|
|
36
119
|
inputSchema: {
|
|
37
120
|
type: "object",
|
|
38
121
|
properties: {
|
|
@@ -57,6 +140,56 @@ export const schedulerRunToolDefinitions = [
|
|
|
57
140
|
type: "string",
|
|
58
141
|
description: "Optional stable approved refill target-shape revision associated with requestKey.",
|
|
59
142
|
},
|
|
143
|
+
expectedTargets: {
|
|
144
|
+
type: "array",
|
|
145
|
+
maxItems: REFILL_SCHEDULER_EXPECTED_TARGET_LIMIT,
|
|
146
|
+
items: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
campaignId: { type: "string" },
|
|
150
|
+
tableId: { type: "string" },
|
|
151
|
+
refillLaneKey: { type: "string" },
|
|
152
|
+
branchActionTypes: {
|
|
153
|
+
type: "array",
|
|
154
|
+
minItems: 1,
|
|
155
|
+
uniqueItems: true,
|
|
156
|
+
items: {
|
|
157
|
+
type: "string",
|
|
158
|
+
enum: [
|
|
159
|
+
"send_invite",
|
|
160
|
+
"send_inmail_open",
|
|
161
|
+
"send_inmail_closed",
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
senderIds: {
|
|
166
|
+
type: "array",
|
|
167
|
+
minItems: 1,
|
|
168
|
+
maxItems: REFILL_SCHEDULER_EXPECTED_TARGET_SENDER_LIMIT,
|
|
169
|
+
uniqueItems: true,
|
|
170
|
+
items: { type: "string" },
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
required: [
|
|
174
|
+
"campaignId",
|
|
175
|
+
"tableId",
|
|
176
|
+
"refillLaneKey",
|
|
177
|
+
"branchActionTypes",
|
|
178
|
+
"senderIds",
|
|
179
|
+
],
|
|
180
|
+
additionalProperties: false,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
expectedTargetsHash: {
|
|
184
|
+
type: "string",
|
|
185
|
+
description: "Required SHA-256 of canonical expectedTargets for a target-aware run.",
|
|
186
|
+
},
|
|
187
|
+
maxPlacements: {
|
|
188
|
+
type: "integer",
|
|
189
|
+
minimum: 1,
|
|
190
|
+
maximum: REFILL_SCHEDULER_MAX_PLACEMENTS,
|
|
191
|
+
description: "Required frozen numeric total placement cap across the workspace-wide sweep when expectedTargets are present.",
|
|
192
|
+
},
|
|
60
193
|
},
|
|
61
194
|
required: ["workspaceId"],
|
|
62
195
|
additionalProperties: false,
|
|
@@ -75,11 +208,60 @@ export async function runSchedulerSweep(input) {
|
|
|
75
208
|
if (action !== "run" && action !== "status") {
|
|
76
209
|
throw new Error('action must be "run" or "status" for run_scheduler_sweep.');
|
|
77
210
|
}
|
|
211
|
+
const hasExpectedTargets = input.expectedTargets !== undefined;
|
|
212
|
+
const hasExpectedTargetScope = input.expectedTargetsHash !== undefined ||
|
|
213
|
+
input.maxPlacements !== undefined;
|
|
214
|
+
if (action === "status" && hasExpectedTargets) {
|
|
215
|
+
throw new Error("expectedTargets are only valid for action run.");
|
|
216
|
+
}
|
|
217
|
+
let expectedTargetRequest;
|
|
218
|
+
if (action === "run" && (hasExpectedTargets || hasExpectedTargetScope)) {
|
|
219
|
+
const expectedTargets = canonicalizeExpectedTargets(input.expectedTargets);
|
|
220
|
+
const expectedTargetsHash = normalizeOptionalScopeValue(input.expectedTargetsHash, "expectedTargetsHash");
|
|
221
|
+
if (!expectedTargetsHash ||
|
|
222
|
+
expectedTargetsHash !== computeExpectedTargetsHash(expectedTargets)) {
|
|
223
|
+
throw new Error("expectedTargetsHash must match the canonical expectedTargets payload.");
|
|
224
|
+
}
|
|
225
|
+
if (typeof input.maxPlacements !== "number" ||
|
|
226
|
+
!Number.isInteger(input.maxPlacements) ||
|
|
227
|
+
input.maxPlacements <= 0 ||
|
|
228
|
+
input.maxPlacements > REFILL_SCHEDULER_MAX_PLACEMENTS) {
|
|
229
|
+
throw new Error(`maxPlacements must be an integer from 1 to ${REFILL_SCHEDULER_MAX_PLACEMENTS}.`);
|
|
230
|
+
}
|
|
231
|
+
if (!targetDate || !requestKey || !targetShapeRevision) {
|
|
232
|
+
throw new Error("expectedTargets require targetDate, requestKey, and targetShapeRevision.");
|
|
233
|
+
}
|
|
234
|
+
expectedTargetRequest = {
|
|
235
|
+
expectedTargets,
|
|
236
|
+
expectedTargetsHash,
|
|
237
|
+
maxPlacements: input.maxPlacements,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
let expectedStatusScope;
|
|
241
|
+
if (action === "status" && hasExpectedTargetScope) {
|
|
242
|
+
const expectedTargetsHash = normalizeOptionalScopeValue(input.expectedTargetsHash, "expectedTargetsHash");
|
|
243
|
+
if (!expectedTargetsHash ||
|
|
244
|
+
typeof input.maxPlacements !== "number" ||
|
|
245
|
+
!Number.isInteger(input.maxPlacements) ||
|
|
246
|
+
input.maxPlacements <= 0 ||
|
|
247
|
+
input.maxPlacements > REFILL_SCHEDULER_MAX_PLACEMENTS ||
|
|
248
|
+
!targetDate ||
|
|
249
|
+
!requestKey ||
|
|
250
|
+
!targetShapeRevision) {
|
|
251
|
+
throw new Error("hash-bound status requires expectedTargetsHash, maxPlacements, targetDate, requestKey, and targetShapeRevision.");
|
|
252
|
+
}
|
|
253
|
+
expectedStatusScope = {
|
|
254
|
+
expectedTargetsHash,
|
|
255
|
+
maxPlacements: input.maxPlacements,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
78
258
|
return postSchedulerRun({
|
|
79
259
|
workspaceId,
|
|
80
260
|
action,
|
|
81
261
|
...(targetDate ? { targetDate } : {}),
|
|
82
262
|
...(requestKey ? { requestKey } : {}),
|
|
83
263
|
...(targetShapeRevision ? { targetShapeRevision } : {}),
|
|
264
|
+
...(expectedTargetRequest ?? {}),
|
|
265
|
+
...(expectedStatusScope ?? {}),
|
|
84
266
|
}, workspaceId);
|
|
85
267
|
}
|
package/package.json
CHANGED
|
@@ -223,13 +223,17 @@ Structured planner packet:
|
|
|
223
223
|
campaign/sender/lane summary, skipped rungs, existing-row frontier proof, and
|
|
224
224
|
any absolute `wait.deadlineAt`.
|
|
225
225
|
- Preserve these coverage labels exactly: `Need to prepare`, `Goal`,
|
|
226
|
-
`Already sent`, `Scheduled`, `Ready and waiting to be scheduled`,
|
|
227
|
-
`Still need`.
|
|
226
|
+
`Already sent`, `Scheduled`, `Ready and waiting to be scheduled`,
|
|
227
|
+
`Still need to schedule`, and `Still need to prepare`. Never render an
|
|
228
|
+
unlabeled `Still need` value.
|
|
228
229
|
- `target.globalActionQueue` is the only cross-sender yolo execution queue.
|
|
229
230
|
Execute exactly one globally ranked primitive from
|
|
230
231
|
`target.globalActionQueue[0]`, then rerun `get_refill_target_plan` with the
|
|
231
232
|
same `workspaceId` before
|
|
232
233
|
choosing another action.
|
|
234
|
+
- When that primitive is `run_scheduler_sweep`, show `workspaceWide:true` and
|
|
235
|
+
its reason in the approval/yolo packet so the operator sees that the sweep
|
|
236
|
+
may schedule other eligible workspace cells on the approved date.
|
|
233
237
|
- `manualAlternates` are not yolo actions. Threshold lowering and campaign
|
|
234
238
|
creation are manual continuations only.
|
|
235
239
|
|
|
@@ -324,12 +328,16 @@ If the target plan is complete by projected coverage, report that the selected
|
|
|
324
328
|
target is already filled and no-op without asking for approval. If the ready
|
|
325
329
|
buffer covers the projected gap, paid InMail credit facts are fresh for every
|
|
326
330
|
selected paid-InMail lane, but scheduled coverage is still short, keep the run
|
|
327
|
-
open in a persistent read-only scheduler wait loop.
|
|
328
|
-
`get_refill_target_plan`
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
open in a persistent read-only scheduler wait loop. Honor the emitted wait
|
|
332
|
+
metadata: reread `get_refill_target_plan` after about 30 seconds by default (or
|
|
333
|
+
the action's explicit `rereadAfterMs`), never use a blind hard-coded sleep, and
|
|
334
|
+
stop at the named bound using `receipt_deadline > unchanged_poll_bound >
|
|
335
|
+
run_budget` precedence. A matching active/uncertain request key is never
|
|
336
|
+
redispatched. Continue until projected coverage fills the target, a concrete
|
|
337
|
+
non-scheduler blocker appears, a named bound stops the run, or Christian
|
|
338
|
+
explicitly asks to stop or only receive a status report. Treat
|
|
339
|
+
`awaiting_scheduler_after_ready_buffer` as an in-progress wait state, not a
|
|
340
|
+
close-out condition.
|
|
333
341
|
Wait actions are gates, not competing goals. When `wait_for_active_work` or
|
|
334
342
|
`wait_for_scheduler` includes receipt `wait.deadlineAt`, honor that absolute
|
|
335
343
|
deadline; if it is expired on this call, escalate to diagnostics with the
|
|
@@ -140,13 +140,17 @@ files or memory.
|
|
|
140
140
|
campaign/sender/lane summary, skipped rungs, existing-row frontier proof,
|
|
141
141
|
and any absolute `wait.deadlineAt`.
|
|
142
142
|
- Preserve these coverage labels exactly: `Need to prepare`, `Goal`,
|
|
143
|
-
`Already sent`, `Scheduled`, `Ready and waiting to be scheduled`,
|
|
144
|
-
`Still need`.
|
|
143
|
+
`Already sent`, `Scheduled`, `Ready and waiting to be scheduled`,
|
|
144
|
+
`Still need to schedule`, and `Still need to prepare`. Never render an
|
|
145
|
+
unlabeled `Still need` value.
|
|
145
146
|
- `target.globalActionQueue` is the only cross-sender yolo execution queue.
|
|
146
147
|
execute exactly one globally ranked primitive from
|
|
147
148
|
`target.globalActionQueue[0]`, then rerun `get_refill_target_plan` with
|
|
148
149
|
the same `workspaceId` before
|
|
149
150
|
choosing another action.
|
|
151
|
+
- When that primitive is `run_scheduler_sweep`, show `workspaceWide:true`
|
|
152
|
+
and its reason in the approval/yolo packet so the operator sees that the
|
|
153
|
+
sweep may schedule other eligible workspace cells on the approved date.
|
|
150
154
|
- `nextActions[0]` is the current sender's smallest safe primitive.
|
|
151
155
|
- `manualAlternates` are not yolo actions. Threshold lowering and campaign
|
|
152
156
|
creation are manual continuations only.
|