@sellable/mcp 0.1.535 → 0.1.536

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.
Files changed (35) hide show
  1. package/README.md +5 -2
  2. package/dist/index-dev.js +0 -0
  3. package/dist/index.js +0 -0
  4. package/dist/refill-date-window.d.ts +34 -0
  5. package/dist/refill-date-window.js +210 -0
  6. package/dist/refill-run-loop.d.ts +9 -1
  7. package/dist/refill-run-loop.js +435 -85
  8. package/dist/server.js +4 -0
  9. package/dist/tools/evergreen-refill-plan.d.ts +19 -0
  10. package/dist/tools/evergreen-refill-plan.js +19 -0
  11. package/dist/tools/prompts.d.ts +2 -0
  12. package/dist/tools/prompts.js +1 -0
  13. package/dist/tools/refill-executors.d.ts +0 -7
  14. package/dist/tools/refill-executors.js +0 -22
  15. package/dist/tools/refill-sends-evergreen.d.ts +28 -0
  16. package/dist/tools/refill-sends-evergreen.js +47 -0
  17. package/dist/tools/refill-sends-v2.d.ts +19 -0
  18. package/dist/tools/refill-sends-v2.js +32 -1
  19. package/dist/tools/refill-sends.d.ts +0 -12
  20. package/dist/tools/refill-sends.js +37 -58
  21. package/dist/tools/refill-target-plan.js +19 -6
  22. package/dist/tools/registry.d.ts +55 -0
  23. package/dist/tools/registry.js +2 -0
  24. package/dist/tools/scheduler-fill-capacity.js +1 -1
  25. package/dist/tools/scheduler-run.d.ts +32 -0
  26. package/dist/tools/scheduler-run.js +68 -0
  27. package/dist/tools/senders.d.ts +8 -14
  28. package/dist/tools/senders.js +10 -21
  29. package/package.json +1 -1
  30. package/skills/refill-sends/SKILL.md +31 -4
  31. package/skills/refill-sends-v2/SKILL.md +29 -5
  32. package/skills/refill-sends-v2-workflow/SKILL.md +27 -2
  33. package/skills/refill-sends-v2-workflow/core/flow.v1.json +19 -3
  34. package/skills/refill-sends-workflow/SKILL.md +27 -5
  35. package/skills/refill-sends-workflow/core/flow.v1.json +6 -1
@@ -1026,6 +1026,16 @@ function sanitizeRefillTargetPlanResult(result) {
1026
1026
  mcpSanitizedRefillLanes: true,
1027
1027
  };
1028
1028
  }
1029
+ function stripInternalTargetDateKeys(value) {
1030
+ if (Array.isArray(value)) {
1031
+ return value.map((item) => stripInternalTargetDateKeys(item));
1032
+ }
1033
+ if (!isRecord(value))
1034
+ return value;
1035
+ return Object.fromEntries(Object.entries(value)
1036
+ .filter(([key]) => key !== "targetDateKey")
1037
+ .map(([key, nested]) => [key, stripInternalTargetDateKeys(nested)]));
1038
+ }
1029
1039
  export const refillTargetPlanToolDefinitions = [
1030
1040
  {
1031
1041
  name: "get_refill_target_plan",
@@ -1042,7 +1052,7 @@ export const refillTargetPlanToolDefinitions = [
1042
1052
  type: "number",
1043
1053
  minimum: 1,
1044
1054
  maximum: 7,
1045
- description: "Compatibility override for explicit sender-local send-day horizon planning. Omit for the default scheduler-forward 48-hour target window; no-send days contribute zero target.",
1055
+ description: "Compatibility override for explicit next N sender-local send days. Omit for the default scheduler-forward 48-hour target window; no-send days are skipped while choosing the requested send days.",
1046
1056
  },
1047
1057
  untilDate: {
1048
1058
  type: "string",
@@ -1052,7 +1062,7 @@ export const refillTargetPlanToolDefinitions = [
1052
1062
  targetDate: {
1053
1063
  type: "string",
1054
1064
  pattern: "^\\d{4}-\\d{2}-\\d{2}$",
1055
- description: "Optional exact sender-local YYYY-MM-DD date to fill. Distinct from untilDate: targetDate asks how many scheduler-fillable slots exist on only this date.",
1065
+ description: "Optional one exact sender-local date (YYYY-MM-DD) to fill. Distinct from untilDate: targetDate asks how many scheduler-fillable slots exist on only this date.",
1056
1066
  },
1057
1067
  campaignId: {
1058
1068
  type: "string",
@@ -1102,11 +1112,14 @@ export const refillTargetPlanToolDefinitions = [
1102
1112
  ];
1103
1113
  export async function getRefillTargetPlan(input = {}) {
1104
1114
  const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
1115
+ const targetDate = input.targetDate;
1116
+ const untilDate = targetDate ? undefined : input.untilDate;
1117
+ const horizonSendDays = targetDate || untilDate ? undefined : input.horizonSendDays;
1105
1118
  const result = await postRefillTargetPlan({
1106
1119
  intent: input.intent,
1107
- horizonSendDays: input.horizonSendDays,
1108
- untilDate: input.untilDate,
1109
- targetDate: input.targetDate,
1120
+ horizonSendDays,
1121
+ untilDate,
1122
+ targetDate,
1110
1123
  campaignId: input.campaignId,
1111
1124
  tableId: input.tableId,
1112
1125
  senderIds: input.senderIds,
@@ -1117,7 +1130,7 @@ export async function getRefillTargetPlan(input = {}) {
1117
1130
  paidInmailCreditsMaxStalenessSeconds: MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS,
1118
1131
  ...(workspaceId ? { workspaceId } : {}),
1119
1132
  }, workspaceId ?? undefined);
1120
- const sanitized = sanitizeRefillTargetPlanResult(result);
1133
+ const sanitized = stripInternalTargetDateKeys(sanitizeRefillTargetPlanResult(result));
1121
1134
  if (!workspaceId || !isRecord(sanitized))
1122
1135
  return sanitized;
1123
1136
  return {
@@ -2758,6 +2758,22 @@ export declare const allTools: ({
2758
2758
  enum: string[];
2759
2759
  description: string;
2760
2760
  };
2761
+ targetDate: {
2762
+ type: string;
2763
+ pattern: string;
2764
+ description: string;
2765
+ };
2766
+ untilDate: {
2767
+ type: string;
2768
+ pattern: string;
2769
+ description: string;
2770
+ };
2771
+ horizonSendDays: {
2772
+ type: string;
2773
+ minimum: number;
2774
+ maximum: number;
2775
+ description: string;
2776
+ };
2761
2777
  journal: {
2762
2778
  type: string;
2763
2779
  description: string;
@@ -7370,6 +7386,29 @@ export declare const allTools: ({
7370
7386
  };
7371
7387
  required: string[];
7372
7388
  };
7389
+ } | {
7390
+ name: string;
7391
+ description: string;
7392
+ inputSchema: {
7393
+ type: string;
7394
+ properties: {
7395
+ workspaceId: {
7396
+ type: string;
7397
+ description: string;
7398
+ };
7399
+ action: {
7400
+ type: string;
7401
+ enum: string[];
7402
+ description: string;
7403
+ };
7404
+ targetDate: {
7405
+ type: string;
7406
+ description: string;
7407
+ };
7408
+ };
7409
+ required: string[];
7410
+ additionalProperties: boolean;
7411
+ };
7373
7412
  } | {
7374
7413
  name: string;
7375
7414
  description: string;
@@ -7407,6 +7446,22 @@ export declare const allTools: ({
7407
7446
  type: string;
7408
7447
  enum: string[];
7409
7448
  };
7449
+ targetDate: {
7450
+ type: string;
7451
+ pattern: string;
7452
+ description: string;
7453
+ };
7454
+ untilDate: {
7455
+ type: string;
7456
+ pattern: string;
7457
+ description: string;
7458
+ };
7459
+ horizonSendDays: {
7460
+ type: string;
7461
+ minimum: number;
7462
+ maximum: number;
7463
+ description: string;
7464
+ };
7410
7465
  };
7411
7466
  required: string[];
7412
7467
  additionalProperties: boolean;
@@ -40,6 +40,7 @@ import { refillTargetPlanToolDefinitions } from "./refill-target-plan.js";
40
40
  import { rowToolDefinitions } from "./rows.js";
41
41
  import { rubricToolDefinitions } from "./rubrics.js";
42
42
  import { schedulerFillCapacityToolDefinitions } from "./scheduler-fill-capacity.js";
43
+ import { schedulerRunToolDefinitions } from "./scheduler-run.js";
43
44
  import { senderRoutingToolDefinitions } from "./sender-routing.js";
44
45
  import { senderToolDefinitions } from "./senders.js";
45
46
  import { sequencerToolDefinitions } from "./sequencer.js";
@@ -57,6 +58,7 @@ export const allTools = [
57
58
  ...refillPlanV2ToolDefinitions,
58
59
  ...refillTargetPlanToolDefinitions,
59
60
  ...schedulerFillCapacityToolDefinitions,
61
+ ...schedulerRunToolDefinitions,
60
62
  ...refillSendsToolDefinitions,
61
63
  ...refillSendsV2ToolDefinitions,
62
64
  ...setupEvergreenCampaignsToolDefinitions,
@@ -10,7 +10,7 @@ async function postSchedulerFillCapacity(body, workspaceId) {
10
10
  export const schedulerFillCapacityToolDefinitions = [
11
11
  {
12
12
  name: "get_scheduler_fill_capacity",
13
- description: "read-only scheduler capacity query for refill-sends before any refill mutation. It asks the product scheduler model how many additional cells the scheduler will try to place for exact sender/action requests, using the default scheduler-forward horizon or one exact targetDate. It returns scheduler-fillable slots, occupied scheduled/processing slots, sender sendability gates, cooldowns, Sales Navigator status for paid InMail, cached paid-InMail credit feasibility, threshold source, blockers, warnings, and explicit sideEffects false. This tool does not create rows, import leads, prepare messages, approve messages, does not schedule sends, does not refresh paid InMail credits, mutate thresholds, start campaigns, launch, or send. Use exact senderId values only; never pass sender names.",
13
+ description: "read-only scheduler capacity query for refill-sends before any refill mutation. It asks the product scheduler model how many additional cells the scheduler will try to place for exact sender/action requests, using the default scheduler-forward horizon or one exact targetDate. Use get_refill_target_plan for multi-day untilDate or horizonSendDays planning. It returns scheduler-fillable slots, occupied scheduled/processing slots, sender sendability gates, cooldowns, Sales Navigator status for paid InMail, cached paid-InMail credit feasibility, threshold source, blockers, warnings, and explicit sideEffects false. This tool does not create rows, import leads, prepare messages, approve messages, does not schedule sends, does not refresh paid InMail credits, mutate thresholds, start campaigns, launch, or send. Use exact senderId values only; never pass sender names.",
14
14
  inputSchema: {
15
15
  type: "object",
16
16
  properties: {
@@ -0,0 +1,32 @@
1
+ type SchedulerRunAction = "run" | "status";
2
+ type RunSchedulerSweepInput = {
3
+ workspaceId: string;
4
+ action?: SchedulerRunAction;
5
+ targetDate?: string | null;
6
+ };
7
+ export declare const schedulerRunToolDefinitions: {
8
+ name: string;
9
+ description: string;
10
+ inputSchema: {
11
+ type: string;
12
+ properties: {
13
+ workspaceId: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ action: {
18
+ type: string;
19
+ enum: string[];
20
+ description: string;
21
+ };
22
+ targetDate: {
23
+ type: string;
24
+ description: string;
25
+ };
26
+ };
27
+ required: string[];
28
+ additionalProperties: boolean;
29
+ };
30
+ }[];
31
+ export declare function runSchedulerSweep(input: RunSchedulerSweepInput): Promise<unknown>;
32
+ export {};
@@ -0,0 +1,68 @@
1
+ import { getApi } from "../api.js";
2
+ import { normalizeExplicitWorkspaceId, workspaceRequestOptions, } from "./workspace-context.js";
3
+ async function postSchedulerRun(body, workspaceId) {
4
+ const api = getApi();
5
+ const requestOptions = workspaceRequestOptions(workspaceId);
6
+ return requestOptions
7
+ ? api.post("/api/v3/mcp/scheduler-run", body, requestOptions)
8
+ : api.post("/api/v3/mcp/scheduler-run", body);
9
+ }
10
+ export const schedulerRunToolDefinitions = [
11
+ {
12
+ name: "run_scheduler_sweep",
13
+ description: 'Trigger the product scheduler placement sweep for one explicit workspace now, or read the last on-demand scheduler run status with action "status". By default a run is workspace-wide, not a scoped run for one campaign/table. Pass targetDate to scope placement to one exact sender-local date while still remaining workspace-wide inside that date. Multi-day untilDate or horizonSendDays planning should come from get_refill_target_plan, then use exact per-date run_scheduler_sweep calls only for dates that still need scheduler placement. The synchronous envelope includes status ran, attached, backoff, window_closed_noop, or failed; retryAfterMs for matching-scope backoff replays; and a backward-compatible receipt with public scope metadata. In v2 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 lists bounded tables/campaigns the workspace-wide run inspected; absence there is not a scoped-run guarantee that the target was ready. 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 only when the recorded public scope matches this request. Status is read-only and scoped to the last on-demand run only; cron sweeps are not recorded here.',
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ workspaceId: {
18
+ type: "string",
19
+ description: "Explicit request-scoped workspace id for scheduled/yolo refill automation. Pass this instead of switching the shared active workspace.",
20
+ },
21
+ action: {
22
+ type: "string",
23
+ enum: ["run", "status"],
24
+ description: 'Use "run" to trigger a placement sweep now. Use "status" for a read-only view of the last on-demand run. Defaults to "run".',
25
+ },
26
+ targetDate: {
27
+ type: "string",
28
+ description: "Optional one exact sender-local date (YYYY-MM-DD) to scope scheduler placement/status. The run remains workspace-wide inside that date.",
29
+ },
30
+ },
31
+ required: ["workspaceId"],
32
+ additionalProperties: false,
33
+ },
34
+ },
35
+ ];
36
+ function isValidDateKey(value) {
37
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
38
+ return false;
39
+ const [year, month, day] = value.split("-").map(Number);
40
+ const date = new Date(Date.UTC(year, month - 1, day));
41
+ return (date.getUTCFullYear() === year &&
42
+ date.getUTCMonth() === month - 1 &&
43
+ date.getUTCDate() === day);
44
+ }
45
+ function normalizeTargetDate(value) {
46
+ if (value === undefined)
47
+ return undefined;
48
+ if (typeof value !== "string" || !isValidDateKey(value)) {
49
+ throw new Error("targetDate must be a valid YYYY-MM-DD calendar date.");
50
+ }
51
+ return value;
52
+ }
53
+ export async function runSchedulerSweep(input) {
54
+ const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
55
+ if (!workspaceId) {
56
+ throw new Error("workspaceId is required for run_scheduler_sweep.");
57
+ }
58
+ const action = input.action ?? "run";
59
+ if (action !== "run" && action !== "status") {
60
+ throw new Error('action must be "run" or "status" for run_scheduler_sweep.');
61
+ }
62
+ const targetDate = normalizeTargetDate(input.targetDate);
63
+ return postSchedulerRun({
64
+ workspaceId,
65
+ action,
66
+ targetDate,
67
+ }, workspaceId);
68
+ }
@@ -52,22 +52,16 @@ export type PaidInmailCreditStatus = {
52
52
  };
53
53
  export type RefreshPaidInmailCreditsResponse = {
54
54
  senderId: string;
55
- refreshed: boolean;
56
- error?: string | null;
55
+ refreshed: true;
57
56
  credits: PaidInmailCreditStatus;
58
- receipt?: {
59
- status?: string;
60
- refreshed?: boolean;
61
- error?: string;
62
- [key: string]: unknown;
63
- } | null;
57
+ receipt?: unknown;
64
58
  sideEffects: {
65
- refreshedLinkedInDerivedCreditFacts: boolean;
66
- updatedSenderCreditCache: boolean;
67
- campaignMutation: boolean;
68
- schedulerMutation: boolean;
69
- thresholdMutation: boolean;
70
- sendMutation: boolean;
59
+ refreshedLinkedInDerivedCreditFacts: true;
60
+ updatedSenderCreditCache: true;
61
+ campaignMutation: false;
62
+ schedulerMutation: false;
63
+ thresholdMutation: false;
64
+ sendMutation: false;
71
65
  };
72
66
  };
73
67
  export declare const senderToolDefinitions: ({
@@ -237,21 +237,6 @@ function normalizeCreditStatus(raw) {
237
237
  needsRefresh: raw?.needsRefresh === true,
238
238
  };
239
239
  }
240
- function normalizeRefreshReceipt(raw) {
241
- if (!raw || typeof raw !== "object")
242
- return null;
243
- return raw;
244
- }
245
- function normalizeCreditRefreshSideEffects(raw, receiptRefreshed) {
246
- return {
247
- refreshedLinkedInDerivedCreditFacts: raw?.refreshedLinkedInDerivedCreditFacts === true || receiptRefreshed,
248
- updatedSenderCreditCache: raw?.updatedSenderCreditCache === true || receiptRefreshed,
249
- campaignMutation: raw?.campaignMutation === true,
250
- schedulerMutation: raw?.schedulerMutation === true,
251
- thresholdMutation: raw?.thresholdMutation === true,
252
- sendMutation: raw?.sendMutation === true,
253
- };
254
- }
255
240
  export async function refreshPaidInmailCredits(input) {
256
241
  const senderId = input.senderId?.trim();
257
242
  if (!senderId) {
@@ -283,14 +268,18 @@ export async function refreshPaidInmailCredits(input) {
283
268
  if (threshold !== null)
284
269
  body.threshold = threshold;
285
270
  const result = await api.post(`/api/v3/mcp/senders/${encodeURIComponent(senderId)}/refresh-inmail-credits`, body, requestOptions);
286
- const receipt = normalizeRefreshReceipt(result?.receipt);
287
- const receiptRefreshed = result?.refreshed === true || receipt?.refreshed === true;
288
271
  return {
289
272
  senderId,
290
- refreshed: receiptRefreshed,
291
- error: pickString(result?.error, receipt?.error),
273
+ refreshed: true,
292
274
  credits: normalizeCreditStatus(result?.credits),
293
- receipt,
294
- sideEffects: normalizeCreditRefreshSideEffects(result?.sideEffects, receiptRefreshed),
275
+ receipt: result?.receipt,
276
+ sideEffects: {
277
+ refreshedLinkedInDerivedCreditFacts: true,
278
+ updatedSenderCreditCache: true,
279
+ campaignMutation: false,
280
+ schedulerMutation: false,
281
+ thresholdMutation: false,
282
+ sendMutation: false,
283
+ },
295
284
  };
296
285
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.535",
3
+ "version": "0.1.536",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",
@@ -6,6 +6,7 @@ allowed-tools:
6
6
  - mcp__sellable__refill_sends
7
7
  - mcp__sellable__get_refill_target_plan
8
8
  - mcp__sellable__get_scheduler_fill_capacity
9
+ - mcp__sellable__run_scheduler_sweep
9
10
  - mcp__sellable__refresh_paid_inmail_credits
10
11
  - mcp__sellable__get_subskill_asset
11
12
  - mcp__sellable__get_auth_status
@@ -86,6 +87,12 @@ Accepted invocation flags in the same user request:
86
87
  preserves natural-language arguments better than shell-style flags.
87
88
  - `targetDate: YYYY-MM-DD`: exact-date selector alternative when the host
88
89
  preserves natural-language arguments better than shell-style flags.
90
+ - `horizonSendDays: N`: compatibility selector for the next N sender-local send
91
+ days when no exact or through-date selector is present.
92
+
93
+ Date selector precedence is `targetDate > untilDate > horizonSendDays`. Use
94
+ `targetDate` for one exact sender-local date, `untilDate` for an inclusive
95
+ through-date, and `horizonSendDays` for the next N sender-local send days.
89
96
 
90
97
  When the host can call typed MCP tools, start with:
91
98
 
@@ -114,10 +121,11 @@ or install-time workspace mapping. Pass `workspaceId` on every scheduled or
114
121
  `--yolo` refill tool call, including setup/read calls such as
115
122
  `refill_sends`, `get_refill_target_plan`, `list_senders`,
116
123
  `get_sender_routing`, `resolve_campaign_fill_route`,
117
- `get_campaign_refill_state`, `get_scheduler_fill_capacity`, and any later
118
- refill mutation covered by the packet. Missing `workspaceId` in scheduled or
119
- `--yolo` mode is a blocker; stop with `WORKSPACE_REQUIRED` instead of running
120
- against an implicit or guessed workspace.
124
+ `get_campaign_refill_state`, `get_scheduler_fill_capacity`,
125
+ `run_scheduler_sweep`, and any later refill mutation covered by the packet.
126
+ Missing `workspaceId` in scheduled or `--yolo` mode is a blocker; stop with
127
+ `WORKSPACE_REQUIRED` instead of running against an implicit or guessed
128
+ workspace.
121
129
 
122
130
  Do not solve scheduled or `--yolo` workspace uncertainty by changing the shared
123
131
  active workspace. Manual interactive workspace switching remains a separate
@@ -269,6 +277,25 @@ need raw proof, call the read-only `get_scheduler_fill_capacity` query for the
269
277
  same sender/action/date; it tells the MCP how many cells the product scheduler
270
278
  will try to place and does not import, approve, schedule, refresh credits, or
271
279
  mutate.
280
+ When the refill loop has ready rows and needs scheduler pickup now, use
281
+ `run_scheduler_sweep({ workspaceId, targetDate })` for exact-date requests. For
282
+ `untilDate` or `horizonSendDays` requests, sweep each planner-selected
283
+ `targetDate` with the same explicit `workspaceId`. The sweep can place cells
284
+ within existing scheduler gates and returns the receipt, but it never sends or
285
+ bypasses limits.
286
+ Scheduler-run receipt interpretation: `cellsConsidered is allocation-attempt
287
+ count`, not total ready supply, while `readyCellsFound` is ready inventory found
288
+ before prefilters. Inspect `campaignScopeSummary` before assuming the selected
289
+ refill campaign/table was included; if absent, do not infer that target was
290
+ ready-but-blocked. Interpret `prefiltered` as ready cells removed before
291
+ allocation, `skipped` as considered cells blocked by scheduler gates, and
292
+ `deferred` as considered cells waiting on windows/capacity/cooldown. For ready
293
+ closed-InMail cells with stale paid-credit prefilter/defer reasons, refresh
294
+ paid-InMail credits once through existing tools, then rerun `run_scheduler_sweep`
295
+ or read `action:"status"`; `refresh_paid_inmail_credits_then_rerun` is that
296
+ path. `wait_for_capacity_or_window` means report loaded/capped/waiting and do
297
+ not source or prep more rows; `no_ready_cells_continue_refill_prep` means return
298
+ to the refill/prep ladder. Do not treat `cellsScheduled:0` alone as failure.
272
299
  If the target plan is complete by projected coverage, report that the selected
273
300
  target is already filled and no-op without asking for approval. If the ready
274
301
  buffer covers the projected gap, paid InMail credit facts are fresh for every
@@ -37,16 +37,21 @@ active workspace readback. Scheduled or autonomous usage must carry
37
37
  `workspaceId` on every tool call. If the workspace is missing or ambiguous, stop
38
38
  with `WORKSPACE_REQUIRED`; do not switch the shared active workspace.
39
39
 
40
+ Date selectors are sender-local and mutually exclusive by precedence:
41
+ `targetDate > untilDate > horizonSendDays`. Use `targetDate` for one exact sender-local date, `untilDate` for an inclusive through-date, and `horizonSendDays` for the next N sender-local send days. If none is provided, the planner uses the default scheduler-forward 48-hour behavior.
42
+
40
43
  For a real refill run:
41
44
 
42
45
  ```text
43
- refill_sends_v2({ workspaceId, intent:"auto", senderIds?, approvalMode? })
46
+ refill_sends_v2({ workspaceId, intent:"auto", senderIds?, approvalMode?, targetDate?, untilDate?, horizonSendDays? })
47
+ refill_sends_v2({ workspaceId, targetDate:"2026-07-07" })
48
+ refill_sends_v2({ workspaceId, untilDate:"2026-07-09" })
44
49
  ```
45
50
 
46
51
  For read-only inspection:
47
52
 
48
53
  ```text
49
- refill_sends_v2({ workspaceId, dryRun:true, intent:"auto", senderIds? })
54
+ refill_sends_v2({ workspaceId, dryRun:true, intent:"auto", senderIds?, targetDate?, untilDate?, horizonSendDays? })
50
55
  ```
51
56
 
52
57
  To resume an in-progress run, pass the handle back exactly:
@@ -55,6 +60,11 @@ To resume an in-progress run, pass the handle back exactly:
55
60
  refill_sends_v2({ workspaceId, runId, fence })
56
61
  ```
57
62
 
63
+ The original run's date window is stored in the run record. On resume, the
64
+ stored window is reused and conflicting resume input is blocked; do not add,
65
+ change, or remove `targetDate`, `untilDate`, or `horizonSendDays` while passing
66
+ an existing `{ runId, fence }`.
67
+
58
68
  If a stale handle loses the lease, the tool reports the holder status and the
59
69
  approximately 10 minute lockout window. Reinvoke with the current handle or wait
60
70
  for lease expiry; never guess a fence.
@@ -125,9 +135,23 @@ host guard, heartbeats the run lease, and may return:
125
135
  ```
126
136
 
127
137
  Reinvoke with that handle. The approximately five minute scheduler budget is at
128
- the run level, not one tool call. A window-closed or loaded-awaiting scheduler
129
- report must include remaining-ready count, exact expected pickup time, and the
130
- resume handle; never treat bare "awaiting scheduler" copy as a final answer.
138
+ the run level, not one tool call. Exact-date runs request pickup with
139
+ `run_scheduler_sweep({ workspaceId, targetDate })`; `untilDate` and
140
+ `horizonSendDays` runs sweep each planner-selected exact target date. A
141
+ window-closed or loaded-awaiting scheduler report must include remaining-ready
142
+ count, exact expected pickup time, and the resume handle; never treat bare
143
+ "awaiting scheduler" copy as a final answer.
144
+ Scheduler-run receipt interpretation still applies to terminal progress:
145
+ `cellsConsidered is allocation-attempt count`, not total ready supply, while
146
+ `readyCellsFound` is ready inventory found before prefilters. Inspect
147
+ `campaignScopeSummary` before assuming the selected refill campaign/table was
148
+ included; if absent, do not infer that target was ready-but-blocked. Interpret
149
+ `prefiltered`, `skipped`, and `deferred` separately. For ready closed-InMail
150
+ cells with stale paid-credit reasons, route to
151
+ `refresh_paid_inmail_credits_then_rerun` once, then rerun/status.
152
+ `wait_for_capacity_or_window` means report loaded/capped/waiting and do not
153
+ source or prep more rows; `no_ready_cells_continue_refill_prep` means return to
154
+ the refill/prep ladder. Do not treat `cellsScheduled:0` alone as failure.
131
155
 
132
156
  After the first prep batch, read the conversion-verdict journal line. It is
133
157
  rendered from packet facts: `supplyCensus`, `censusReason`, and
@@ -33,10 +33,16 @@ local files or memory.
33
33
  Resolve request-scoped `workspaceId` using read-only auth and workspace tools.
34
34
  Scheduled or autonomous runs must not rely on implicit workspace state.
35
35
 
36
+ Carry the request date selector through bootstrap, planning, execution, and
37
+ resume. Date selector precedence is `targetDate > untilDate > horizonSendDays`:
38
+ `targetDate` is one exact sender-local date, `untilDate` is an inclusive
39
+ through-date, and `horizonSendDays` is the next N sender-local send days. If no
40
+ selector is present, use the default scheduler-forward 48-hour behavior.
41
+
36
42
  Start or resume the execution loop:
37
43
 
38
44
  ```text
39
- refill_sends_v2({ workspaceId, intent:"auto", senderIds?, approvalMode?, runId?, fence? })
45
+ refill_sends_v2({ workspaceId, intent:"auto", senderIds?, approvalMode?, targetDate?, untilDate?, horizonSendDays?, runId?, fence? })
40
46
  ```
41
47
 
42
48
  Use `dryRun:true` only when the user asked for read-only inspection. A dry run
@@ -44,6 +50,11 @@ writes a journal only. A real run creates or resumes a run record, refreshes
44
50
  paid-credit trust only when packet facts require it, and returns a resume handle
45
51
  when the current invocation must pause.
46
52
 
53
+ The run record stores the selected date window. On resume, reuse that stored
54
+ window and block conflicting resume input; do not accept a changed
55
+ `targetDate`, `untilDate`, or `horizonSendDays` with an existing `{ runId,
56
+ fence }`.
57
+
47
58
  ## G1 Plan
48
59
 
49
60
  The loop reads `get_refill_plan_v2` with runState fed back from the run record.
@@ -116,7 +127,21 @@ outcome, then verifies before replanning.
116
127
  Scheduler wait is cross-invocation. If the tool returns `in_progress` with
117
128
  sweep guidance, re-invoke `refill_sends_v2` with `{runId, fence}`. The scheduler
118
129
  budget is cumulative at the run level. Zero pickup after a confirmed sweep is
119
- debugged from `pipelineDiagnosis`; it is not waited out forever.
130
+ debugged from `pipelineDiagnosis`; it is not waited out forever. Exact-date
131
+ runs request pickup with `run_scheduler_sweep({ workspaceId, targetDate })`;
132
+ `untilDate` and `horizonSendDays` runs sweep each planner-selected exact target
133
+ date.
134
+ Scheduler-run receipt interpretation still applies to terminal progress:
135
+ `cellsConsidered is allocation-attempt count`, not total ready supply, while
136
+ `readyCellsFound` is ready inventory found before prefilters. Inspect
137
+ `campaignScopeSummary` before assuming the selected refill campaign/table was
138
+ included; if absent, do not infer that target was ready-but-blocked. Interpret
139
+ `prefiltered`, `skipped`, and `deferred` separately. For ready closed-InMail
140
+ cells with stale paid-credit reasons, route to
141
+ `refresh_paid_inmail_credits_then_rerun` once, then rerun/status.
142
+ `wait_for_capacity_or_window` means report loaded/capped/waiting and do not
143
+ source or prep more rows; `no_ready_cells_continue_refill_prep` means return to
144
+ the refill/prep ladder. Do not treat `cellsScheduled:0` alone as failure.
120
145
 
121
146
  ## G4 Report
122
147
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "v1.0",
3
3
  "workflow": "refill-sends-v2-workflow",
4
- "principle": "Execute refill sends v2 through a fenced run record: bootstrap, plan, execute, verify, and report. Dry runs stay read-only; real runs execute only packet-named bounded work.",
4
+ "principle": "Execute refill sends v2 through a fenced run record: bootstrap, plan, execute, verify, and report. Dry runs stay read-only; real runs execute only packet-named bounded work. Date selectors use targetDate > untilDate > horizonSendDays precedence.",
5
5
  "laneSources": [
6
6
  "managed_waterfall",
7
7
  "dashboard_evergreen",
@@ -50,6 +50,9 @@
50
50
  "repair rungs come from the plan packet; the loop bounds and refuses, never self-emits repair actions",
51
51
  "capped_by_scheduler is complete, never poll",
52
52
  "awaiting scheduler is not an end state; window-closed reports name remaining-ready, expected pickup time, and resume handle",
53
+ "date selectors resolve by targetDate > untilDate > horizonSendDays; targetDate is one exact sender-local date, untilDate is an inclusive through-date, and horizonSendDays means next N sender-local send days",
54
+ "dateWindow is stored on the run record and conflicting resume input is blocked",
55
+ "exact-date scheduler pickup uses run_scheduler_sweep({ workspaceId, targetDate }); untilDate and horizonSendDays runs sweep each planner-selected exact targetDate",
53
56
  "honest-rubric-fail means a human updates the rubric or changes lead source",
54
57
  "source replenishment beyond same-source-automatic is a scoped create-campaign handoff, never refill-owned provider search",
55
58
  "no campaign creation, provider-family switch, threshold lowering, scheduler writes, sends, archives, deletes, or brief/filter/message/sequence/sender mutation without a separate approval packet"
@@ -143,7 +146,16 @@
143
146
  },
144
147
  {
145
148
  "tool": "refill_sends_v2",
146
- "requiredFields": ["workspaceId"]
149
+ "requiredFields": ["workspaceId"],
150
+ "optionalFields": [
151
+ "senderIds",
152
+ "approvalMode",
153
+ "targetDate",
154
+ "untilDate",
155
+ "horizonSendDays",
156
+ "runId",
157
+ "fence"
158
+ ]
147
159
  },
148
160
  {
149
161
  "tool": "get_subskill_prompt",
@@ -176,6 +188,9 @@
176
188
  "optionalFields": [
177
189
  "intent",
178
190
  "senderIds",
191
+ "targetDate",
192
+ "untilDate",
193
+ "horizonSendDays",
179
194
  "runState",
180
195
  "journal",
181
196
  "journalNote"
@@ -311,7 +326,7 @@
311
326
  },
312
327
  {
313
328
  "id": "verify",
314
- "description": "Verify whole-row outcomes before another plan read. Reattach to an own active prep job by requestSource/requestHash; never re-dispatch prep while a matching job is active. Foreign prep waits until clear or blocks foreign_prep_active. wait_for_active_work and wait_for_source_import reread the plan on bounded cadence. wait_for_scheduler is cross-invocation: each tool call polls briefly under the host guard, heartbeats the lease, and returns in_progress sweep guidance so the agent re-invokes refill_sends_v2 with {runId,fence}. The approximately five minute scheduler budget is cumulative at the run level.",
329
+ "description": "Verify whole-row outcomes before another plan read. Reattach to an own active prep job by requestSource/requestHash; never re-dispatch prep while a matching job is active. Foreign prep waits until clear or blocks foreign_prep_active. wait_for_active_work and wait_for_source_import reread the plan on bounded cadence. wait_for_scheduler is cross-invocation: each tool call polls briefly under the host guard, heartbeats the lease, and returns in_progress sweep guidance so the agent re-invokes refill_sends_v2 with {runId,fence}. Exact-date scheduler pickup uses run_scheduler_sweep({ workspaceId, targetDate }); untilDate and horizonSendDays runs sweep each planner-selected exact targetDate. The approximately five minute scheduler budget is cumulative at the run level.",
315
330
  "allowedTools": ["refill_sends_v2", "get_refill_plan_v2"],
316
331
  "onEnter": [
317
332
  {
@@ -322,6 +337,7 @@
322
337
  "hardRules": [
323
338
  "do not re-dispatch prep when an own active job exists; reattach and poll status",
324
339
  "capped_by_scheduler is complete",
340
+ "resume uses the stored dateWindow; conflicting resume input is blocked",
325
341
  "replan-driven repair: the planner names rerun_errored_cells, approve_messages, start_campaign, or source work; the loop bounds and refuses",
326
342
  "conversion verdict is journaled from packet facts after the first prep batch; the plan gate withholds a second unproven prep batch",
327
343
  "zero pickup after a confirmed sweep is debugged from pipelineDiagnosis, not waited out forever",
@@ -6,6 +6,7 @@ allowed-tools:
6
6
  - mcp__sellable__get_subskill_asset
7
7
  - mcp__sellable__get_refill_target_plan
8
8
  - mcp__sellable__get_scheduler_fill_capacity
9
+ - mcp__sellable__run_scheduler_sweep
9
10
  - mcp__sellable__refresh_paid_inmail_credits
10
11
  - mcp__sellable__list_senders
11
12
  - mcp__sellable__get_sender_routing
@@ -67,11 +68,12 @@ request-scoped `workspaceId`. Pass that same `workspaceId` on every refill tool
67
68
  call in this workflow: `get_refill_target_plan`, `list_senders`,
68
69
  `get_sender_routing`, `resolve_campaign_fill_route`,
69
70
  `get_campaign_refill_state`, `get_scheduler_fill_capacity`,
70
- `refresh_paid_inmail_credits`, source import/readiness calls, preparation calls,
71
- approval calls, and campaign start calls. Missing `workspaceId` in scheduled or
72
- `--yolo` mode is a blocker; return or report `WORKSPACE_REQUIRED` instead of
73
- falling back to shared config state. Manual interactive workspace switching is
74
- diagnostic setup only and is not an automation control path.
71
+ `run_scheduler_sweep`, `refresh_paid_inmail_credits`, source import/readiness
72
+ calls, preparation calls, approval calls, and campaign start calls. Missing
73
+ `workspaceId` in scheduled or `--yolo` mode is a blocker; return or report
74
+ `WORKSPACE_REQUIRED` instead of falling back to shared config state. Manual
75
+ interactive workspace switching is diagnostic setup only and is not an
76
+ automation control path.
75
77
 
76
78
  Goal-mode continuation: a skill cannot create or invoke `/goal` by itself. When
77
79
  this workflow is already running inside an active Codex goal, keep that goal
@@ -186,6 +188,26 @@ files or memory.
186
188
  how many cells the product scheduler will try to place for that sender; it
187
189
  does not create rows, import, approve, schedule, refresh paid-InMail credits,
188
190
  or mutate thresholds.
191
+ When ready rows exist and the wait is for scheduler pickup, call
192
+ `run_scheduler_sweep({ workspaceId, targetDate })` for exact-date requests
193
+ to request the product scheduler placement pass now and read its receipt. For
194
+ `untilDate` or `horizonSendDays` requests, sweep each planner-selected
195
+ `targetDate` with the same explicit `workspaceId`. This may place cells
196
+ within existing gates, never sends messages, and never bypasses limits.
197
+ Scheduler-run receipt interpretation: `cellsConsidered is
198
+ allocation-attempt count`, not total ready supply, while `readyCellsFound`
199
+ is ready inventory found before prefilters. Inspect `campaignScopeSummary`
200
+ before assuming the selected refill campaign/table was included; if absent,
201
+ do not infer that target was ready-but-blocked. Interpret `prefiltered` as
202
+ ready cells removed before allocation, `skipped` as considered cells blocked
203
+ by scheduler gates, and `deferred` as considered cells waiting on
204
+ windows/capacity/cooldown. For ready closed-InMail cells with stale
205
+ paid-credit prefilter/defer reasons, refresh paid-InMail credits once through
206
+ existing tools, then rerun `run_scheduler_sweep` or read `action:"status"`;
207
+ `refresh_paid_inmail_credits_then_rerun` is that path.
208
+ `wait_for_capacity_or_window` means report loaded/capped/waiting and do not
209
+ source or prep more rows; `no_ready_cells_continue_refill_prep` means return
210
+ to the refill/prep ladder. Do not treat `cellsScheduled:0` alone as failure.
189
211
  If `status:"complete"`, report the target, selected dates, sent count,
190
212
  scheduled count, projected count, campaign ids, and no-op proof without
191
213
  asking for approval or mutating.