@sellable/mcp 0.1.537 → 0.1.538

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 CHANGED
@@ -74,9 +74,12 @@ sending from:
74
74
 
75
75
  The refill-sends public command wrapper plans and executes approval-gated send
76
76
  refills. It supports `--yolo` and optional sender selectors such as
77
- `--sender "Christian Reyes"`, optional through-date selectors such as
77
+ `--sender "Christian Reyes"`, exact-date selectors such as
78
+ `--target-date 2026-06-30`, optional through-date selectors such as
78
79
  `--until 2026-06-30`, or typed MCP calls to `refill_sends({ yolo, senders,
79
- untilDate })` from:
80
+ horizonSendDays, targetDate, untilDate })`. Date selectors resolve by
81
+ `targetDate > untilDate > horizonSendDays`, where `targetDate` means one exact
82
+ sender-local date and `untilDate` means an inclusive through-date, from:
80
83
 
81
84
  - `mcp/sellable/skills/refill-sends/SKILL.md`
82
85
 
package/dist/index-dev.js CHANGED
File without changes
package/dist/index.js CHANGED
File without changes
@@ -0,0 +1,34 @@
1
+ export type RefillDateWindowSource = "default_scheduler_forward" | "horizon_send_days" | "until_date" | "target_date";
2
+ export type NormalizedRefillDateSelector = {
3
+ source: RefillDateWindowSource;
4
+ targetDate: string | null;
5
+ untilDate: string | null;
6
+ horizonSendDays: number | null;
7
+ };
8
+ export type RefillRunDateWindow = NormalizedRefillDateSelector & {
9
+ version: 1;
10
+ selectedDates: string[];
11
+ senderWindows?: Array<{
12
+ senderId: string | null;
13
+ timeZone: string | null;
14
+ selectedDates: string[];
15
+ }>;
16
+ };
17
+ type DateSelectorInput = {
18
+ targetDate?: unknown;
19
+ untilDate?: unknown;
20
+ horizonSendDays?: unknown;
21
+ };
22
+ export declare function normalizeRefillDateSelector(input: DateSelectorInput): NormalizedRefillDateSelector;
23
+ export declare function refillDateSelectorBody(selector: NormalizedRefillDateSelector): {
24
+ targetDate?: string;
25
+ untilDate?: string;
26
+ horizonSendDays?: number;
27
+ };
28
+ export declare function dateWindowFromSelector(selector: NormalizedRefillDateSelector): RefillRunDateWindow;
29
+ export declare function dateWindowFromPlan(plan: Record<string, unknown>, selector: NormalizedRefillDateSelector): RefillRunDateWindow;
30
+ export declare function dateWindowFromRunState(runState: unknown): RefillRunDateWindow | null;
31
+ export declare function selectorFromDateWindow(window: RefillRunDateWindow): NormalizedRefillDateSelector;
32
+ export declare function dateSelectorMatchesWindow(selector: NormalizedRefillDateSelector, window: RefillRunDateWindow): boolean;
33
+ export declare function schedulerSweepTargetDates(window: RefillRunDateWindow): string[];
34
+ export {};
@@ -0,0 +1,210 @@
1
+ function isRecord(value) {
2
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+ function stringValue(value) {
5
+ return typeof value === "string" && value.trim() ? value.trim() : null;
6
+ }
7
+ function numberValue(value) {
8
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
9
+ }
10
+ function arrayValue(value) {
11
+ return Array.isArray(value) ? value : [];
12
+ }
13
+ function isValidDateKey(value) {
14
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
15
+ return false;
16
+ const [year, month, day] = value.split("-").map(Number);
17
+ const date = new Date(Date.UTC(year, month - 1, day));
18
+ return (date.getUTCFullYear() === year &&
19
+ date.getUTCMonth() === month - 1 &&
20
+ date.getUTCDate() === day);
21
+ }
22
+ function normalizeDateKey(value, field) {
23
+ if (value === undefined || value === null || value === "")
24
+ return null;
25
+ if (typeof value !== "string") {
26
+ throw new Error(`${field} must be a string in YYYY-MM-DD format.`);
27
+ }
28
+ const trimmed = value.trim();
29
+ if (!isValidDateKey(trimmed)) {
30
+ throw new Error(`${field} must be a valid YYYY-MM-DD calendar date.`);
31
+ }
32
+ return trimmed;
33
+ }
34
+ function normalizeHorizonSendDays(value) {
35
+ if (value === undefined)
36
+ return null;
37
+ if (typeof value !== "number" ||
38
+ !Number.isInteger(value) ||
39
+ value < 1 ||
40
+ value > 7) {
41
+ throw new Error("horizonSendDays must be an integer between 1 and 7.");
42
+ }
43
+ return value;
44
+ }
45
+ export function normalizeRefillDateSelector(input) {
46
+ const targetDate = normalizeDateKey(input.targetDate, "targetDate");
47
+ const untilDate = normalizeDateKey(input.untilDate, "untilDate");
48
+ const horizonSendDays = normalizeHorizonSendDays(input.horizonSendDays);
49
+ if (targetDate) {
50
+ return {
51
+ source: "target_date",
52
+ targetDate,
53
+ untilDate: null,
54
+ horizonSendDays: null,
55
+ };
56
+ }
57
+ if (untilDate) {
58
+ return {
59
+ source: "until_date",
60
+ targetDate: null,
61
+ untilDate,
62
+ horizonSendDays: null,
63
+ };
64
+ }
65
+ if (horizonSendDays !== null) {
66
+ return {
67
+ source: "horizon_send_days",
68
+ targetDate: null,
69
+ untilDate: null,
70
+ horizonSendDays,
71
+ };
72
+ }
73
+ return {
74
+ source: "default_scheduler_forward",
75
+ targetDate: null,
76
+ untilDate: null,
77
+ horizonSendDays: null,
78
+ };
79
+ }
80
+ export function refillDateSelectorBody(selector) {
81
+ if (selector.source === "target_date" && selector.targetDate) {
82
+ return { targetDate: selector.targetDate };
83
+ }
84
+ if (selector.source === "until_date" && selector.untilDate) {
85
+ return { untilDate: selector.untilDate };
86
+ }
87
+ if (selector.source === "horizon_send_days" &&
88
+ selector.horizonSendDays !== null) {
89
+ return { horizonSendDays: selector.horizonSendDays };
90
+ }
91
+ return {};
92
+ }
93
+ export function dateWindowFromSelector(selector) {
94
+ return {
95
+ version: 1,
96
+ ...selector,
97
+ selectedDates: selector.targetDate ? [selector.targetDate] : [],
98
+ };
99
+ }
100
+ function selectedDatesFromPlan(plan) {
101
+ const packet = isRecord(plan.packet) ? plan.packet : {};
102
+ const target = isRecord(packet.target) ? packet.target : {};
103
+ const window = isRecord(target.window) ? target.window : {};
104
+ const senderWindows = arrayValue(window.senderWindows).filter(isRecord);
105
+ const fromWindows = senderWindows.flatMap((senderWindow) => arrayValue(senderWindow.selectedDates)
106
+ .map((entry) => stringValue(entry))
107
+ .filter((entry) => Boolean(entry)));
108
+ if (fromWindows.length > 0)
109
+ return Array.from(new Set(fromWindows)).sort();
110
+ const senderRefillPlans = arrayValue(target.senderRefillPlans).filter(isRecord);
111
+ return Array.from(new Set(senderRefillPlans.flatMap((senderPlan) => {
112
+ const horizon = isRecord(senderPlan.horizon) ? senderPlan.horizon : {};
113
+ return arrayValue(horizon.selectedDays)
114
+ .filter(isRecord)
115
+ .map((day) => stringValue(day.date))
116
+ .filter((entry) => Boolean(entry));
117
+ }))).sort();
118
+ }
119
+ function senderWindowsFromPlan(plan) {
120
+ const packet = isRecord(plan.packet) ? plan.packet : {};
121
+ const target = isRecord(packet.target) ? packet.target : {};
122
+ const window = isRecord(target.window) ? target.window : {};
123
+ return arrayValue(window.senderWindows)
124
+ .filter(isRecord)
125
+ .map((senderWindow) => ({
126
+ senderId: stringValue(senderWindow.senderId),
127
+ timeZone: stringValue(senderWindow.timeZone),
128
+ selectedDates: arrayValue(senderWindow.selectedDates)
129
+ .map((entry) => stringValue(entry))
130
+ .filter((entry) => Boolean(entry)),
131
+ }));
132
+ }
133
+ export function dateWindowFromPlan(plan, selector) {
134
+ const packet = isRecord(plan.packet) ? plan.packet : {};
135
+ const request = isRecord(packet.request) ? packet.request : {};
136
+ const targetDate = stringValue(request.targetDate) ?? selector.targetDate ?? null;
137
+ const untilDate = stringValue(request.untilDate) ?? selector.untilDate ?? null;
138
+ const horizonSendDays = numberValue(request.horizonSendDays) ?? selector.horizonSendDays ?? null;
139
+ const source = targetDate
140
+ ? "target_date"
141
+ : untilDate
142
+ ? "until_date"
143
+ : horizonSendDays !== null
144
+ ? "horizon_send_days"
145
+ : selector.source;
146
+ const selectedDates = selectedDatesFromPlan(plan);
147
+ return {
148
+ version: 1,
149
+ source,
150
+ targetDate: source === "target_date" ? targetDate : null,
151
+ untilDate: source === "until_date" ? untilDate : null,
152
+ horizonSendDays: source === "horizon_send_days" ? horizonSendDays : null,
153
+ selectedDates: selectedDates.length > 0
154
+ ? selectedDates
155
+ : targetDate
156
+ ? [targetDate]
157
+ : [],
158
+ senderWindows: senderWindowsFromPlan(plan),
159
+ };
160
+ }
161
+ export function dateWindowFromRunState(runState) {
162
+ const state = isRecord(runState) ? runState : {};
163
+ const raw = isRecord(state.dateWindow) ? state.dateWindow : null;
164
+ if (!raw || raw.version !== 1)
165
+ return null;
166
+ const source = raw.source === "target_date" ||
167
+ raw.source === "until_date" ||
168
+ raw.source === "horizon_send_days" ||
169
+ raw.source === "default_scheduler_forward"
170
+ ? raw.source
171
+ : null;
172
+ if (!source)
173
+ return null;
174
+ return {
175
+ version: 1,
176
+ source,
177
+ targetDate: source === "target_date" ? stringValue(raw.targetDate) : null,
178
+ untilDate: source === "until_date" ? stringValue(raw.untilDate) : null,
179
+ horizonSendDays: source === "horizon_send_days"
180
+ ? numberValue(raw.horizonSendDays)
181
+ : null,
182
+ selectedDates: arrayValue(raw.selectedDates)
183
+ .map((entry) => stringValue(entry))
184
+ .filter((entry) => Boolean(entry)),
185
+ };
186
+ }
187
+ export function selectorFromDateWindow(window) {
188
+ return {
189
+ source: window.source,
190
+ targetDate: window.targetDate,
191
+ untilDate: window.untilDate,
192
+ horizonSendDays: window.horizonSendDays,
193
+ };
194
+ }
195
+ export function dateSelectorMatchesWindow(selector, window) {
196
+ return (selector.source === window.source &&
197
+ selector.targetDate === window.targetDate &&
198
+ selector.untilDate === window.untilDate &&
199
+ selector.horizonSendDays === window.horizonSendDays);
200
+ }
201
+ export function schedulerSweepTargetDates(window) {
202
+ if (window.source === "target_date" && window.targetDate) {
203
+ return [window.targetDate];
204
+ }
205
+ if (window.source === "until_date" ||
206
+ window.source === "horizon_send_days") {
207
+ return window.selectedDates;
208
+ }
209
+ return [];
210
+ }
@@ -13,6 +13,9 @@ export interface RefillV2LoopInput {
13
13
  };
14
14
  approvalMode?: "approve" | "mark_ready";
15
15
  dryRun?: boolean;
16
+ targetDate?: string | null;
17
+ untilDate?: string | null;
18
+ horizonSendDays?: number | null;
16
19
  }
17
20
  export interface RefillV2LoopBudgets {
18
21
  maxGateCyclesPerInvocation: number;
@@ -60,6 +63,9 @@ export interface RefillV2LoopDeps {
60
63
  intent?: string;
61
64
  senderIds?: string[];
62
65
  runState?: Record<string, unknown>;
66
+ targetDate?: string;
67
+ untilDate?: string;
68
+ horizonSendDays?: number;
63
69
  journal?: boolean;
64
70
  }) => Promise<Record<string, unknown>>;
65
71
  executors: RefillV2Executors;
@@ -68,7 +74,9 @@ export interface RefillV2LoopDeps {
68
74
  budgets?: Partial<RefillV2LoopBudgets>;
69
75
  now?: () => Date;
70
76
  sleep?: (ms: number) => Promise<void>;
71
- requestSchedulerRun?: (workspaceId: string) => Promise<unknown>;
77
+ requestSchedulerRun?: (workspaceId: string, options?: {
78
+ targetDate?: string;
79
+ }) => Promise<unknown>;
72
80
  }
73
81
  export type RefillV2LoopResult = {
74
82
  status: "dry_run";
@@ -1,6 +1,7 @@
1
1
  import { SellableApiError } from "./api.js";
2
+ import { dateSelectorMatchesWindow, dateWindowFromPlan, dateWindowFromRunState, dateWindowFromSelector, normalizeRefillDateSelector, refillDateSelectorBody, schedulerSweepTargetDates, selectorFromDateWindow, } from "./refill-date-window.js";
2
3
  import { buildRunStateFromLocalHints } from "./tools/evergreen-refill-plan.js";
3
- import { classifyPaidInmailRefreshReceipt, prepareRowSelectorValue as defaultPrepareRowSelectorValue, refillPrepareRequestHash as defaultRefillPrepareRequestHash, } from "./tools/refill-executors.js";
4
+ import { prepareRowSelectorValue as defaultPrepareRowSelectorValue, refillPrepareRequestHash as defaultRefillPrepareRequestHash, } from "./tools/refill-executors.js";
4
5
  const DEFAULT_BUDGETS = {
5
6
  maxGateCyclesPerInvocation: 8,
6
7
  pollIntervalMs: 15_000,
@@ -379,9 +380,16 @@ function firstAction(plan) {
379
380
  }
380
381
  function doneReasonFromPlan(plan) {
381
382
  const action = firstAction(plan);
383
+ const packet = recordValue(plan.packet) ?? {};
384
+ const status = stringValue(plan.status) ?? stringValue(packet.status);
382
385
  return (stringValue(action?.doneReason) ??
383
- stringValue(recordValue(plan.packet)?.doneReason) ??
384
- stringValue(plan.doneReason));
386
+ stringValue(packet.doneReason) ??
387
+ stringValue(plan.doneReason) ??
388
+ (status === "complete" ? "complete" : null) ??
389
+ (status === "awaiting_scheduler_after_ready_buffer"
390
+ ? "loaded_awaiting_scheduler"
391
+ : null) ??
392
+ (status === "blocked" ? "blocked" : null));
385
393
  }
386
394
  function selectedLaneAuthority(plan, action) {
387
395
  const senderId = actionSenderId(action);
@@ -417,6 +425,41 @@ function mergeRunState(current, updates) {
417
425
  ...updates,
418
426
  };
419
427
  }
428
+ function dateSelectorFromInput(input) {
429
+ return normalizeRefillDateSelector({
430
+ targetDate: input.targetDate,
431
+ untilDate: input.untilDate,
432
+ horizonSendDays: input.horizonSendDays,
433
+ });
434
+ }
435
+ function runStateWithDateWindow(runState, dateWindow) {
436
+ return mergeRunState(runState, { dateWindow });
437
+ }
438
+ function readPlanRequest(input) {
439
+ return {
440
+ workspaceId: input.workspaceId,
441
+ intent: input.intent,
442
+ senderIds: input.senderIds,
443
+ runState: input.runState,
444
+ journal: input.journal,
445
+ ...refillDateSelectorBody(input.dateSelector),
446
+ };
447
+ }
448
+ async function readLoopPlan(input, deps, ctx, journal) {
449
+ return deps.readPlan(readPlanRequest({
450
+ workspaceId: input.workspaceId,
451
+ intent: input.intent,
452
+ senderIds: input.senderIds,
453
+ runState: ctx.runState,
454
+ journal,
455
+ dateSelector: ctx.dateSelector,
456
+ }));
457
+ }
458
+ function applyPlanDateWindow(ctx, plan) {
459
+ ctx.dateWindow = dateWindowFromPlan(plan, ctx.dateSelector);
460
+ ctx.dateSelector = selectorFromDateWindow(ctx.dateWindow);
461
+ ctx.runState = runStateWithDateWindow(ctx.runState, ctx.dateWindow);
462
+ }
420
463
  function normalizeDoneReason(value) {
421
464
  const raw = stringValue(value);
422
465
  if (!raw)
@@ -670,14 +713,63 @@ async function createJournal(input, deps, runId) {
670
713
  return null;
671
714
  }
672
715
  }
673
- async function startOrResume(input, deps) {
674
- const initialRunState = buildRunStateFromLocalHints(input.workspaceId);
716
+ async function preflightResumeDateWindow(input, deps, requestedSelector) {
717
+ const requestedWindow = dateWindowFromSelector(requestedSelector);
718
+ if (!input.resume) {
719
+ return { ok: true, selector: requestedSelector, window: requestedWindow };
720
+ }
721
+ const status = await deps.runClient.refillRunStatus({
722
+ workspaceId: input.workspaceId,
723
+ runId: input.resume.runId,
724
+ });
725
+ if (hasBlocker(status, "lease_lost")) {
726
+ return {
727
+ status: "blocked",
728
+ blocker: "lease_lost",
729
+ runId: input.resume.runId,
730
+ fence: input.resume.fence,
731
+ guidance: "Refill run lease was lost before resume date-window preflight.",
732
+ };
733
+ }
734
+ const storedWindow = dateWindowFromRunState(recordValue(status)?.runState);
735
+ const explicitSelector = requestedSelector.source !== "default_scheduler_forward";
736
+ if (!storedWindow) {
737
+ if (explicitSelector) {
738
+ return {
739
+ status: "blocked",
740
+ blocker: "date_window_conflict",
741
+ runId: input.resume.runId,
742
+ fence: input.resume.fence,
743
+ guidance: "This legacy refill_sends_v2 run has no stored date window. Resume it without a new date selector or start a new run.",
744
+ report: { requestedDateWindow: requestedWindow },
745
+ };
746
+ }
747
+ return { ok: true, selector: requestedSelector, window: requestedWindow };
748
+ }
749
+ if (explicitSelector && !dateSelectorMatchesWindow(requestedSelector, storedWindow)) {
750
+ return {
751
+ status: "blocked",
752
+ blocker: "date_window_conflict",
753
+ runId: input.resume.runId,
754
+ fence: input.resume.fence,
755
+ guidance: "Resume date selector conflicts with the stored refill run date window.",
756
+ report: { requestedDateWindow: requestedWindow, storedDateWindow: storedWindow },
757
+ };
758
+ }
759
+ const selector = explicitSelector
760
+ ? requestedSelector
761
+ : selectorFromDateWindow(storedWindow);
762
+ return { ok: true, selector, window: storedWindow };
763
+ }
764
+ async function startOrResume(input, deps, dateWindow) {
765
+ const initialRunState = runStateWithDateWindow(buildRunStateFromLocalHints(input.workspaceId), dateWindow);
675
766
  const started = await deps.runClient.startRefillRunRemote({
676
767
  workspaceId: input.workspaceId,
677
768
  config: {
678
769
  intent: input.intent ?? "auto",
679
770
  senderIds: input.senderIds,
680
771
  approvalMode: input.approvalMode ?? "approve",
772
+ dateWindow,
681
773
  },
682
774
  runState: initialRunState,
683
775
  resume: input.resume,
@@ -719,6 +811,8 @@ async function startOrResume(input, deps) {
719
811
  laneCooldowns: [],
720
812
  passRates: [],
721
813
  };
814
+ const storedDateWindow = dateWindowFromRunState(runState) ?? dateWindow;
815
+ const storedDateSelector = selectorFromDateWindow(storedDateWindow);
722
816
  const journalPath = await createJournal(input, deps, runId);
723
817
  const ctx = {
724
818
  workspaceId: input.workspaceId,
@@ -731,6 +825,8 @@ async function startOrResume(input, deps) {
731
825
  plan: null,
732
826
  headAction: null,
733
827
  lastFingerprint: null,
828
+ dateSelector: storedDateSelector,
829
+ dateWindow: storedDateWindow,
734
830
  recoveryPoint: recordValue(result.recoveryPoint),
735
831
  gateCycles: 0,
736
832
  sleptMs: 0,
@@ -749,45 +845,31 @@ async function startOrResume(input, deps) {
749
845
  return ctx;
750
846
  }
751
847
  async function gateBootstrap(input, deps, ctx) {
752
- const plan = await deps.readPlan({
753
- workspaceId: input.workspaceId,
754
- intent: input.intent,
755
- senderIds: input.senderIds,
756
- runState: ctx.runState,
757
- journal: false,
758
- });
848
+ const plan = await readLoopPlan(input, deps, ctx, false);
849
+ applyPlanDateWindow(ctx, plan);
759
850
  const paidCredit = arrayValue(recordValue(plan.bootstrap)?.paidCredit).filter(isRecord);
760
851
  const refreshedSenderIds = new Set(arrayValue(recordValue(ctx.runState.creditTrust)?.senderIds)
761
852
  .map((entry) => stringValue(entry))
762
853
  .filter((entry) => Boolean(entry)));
763
- const attemptedSenderIds = new Set(arrayValue(recordValue(ctx.runState.creditTrust)?.attemptedSenderIds)
764
- .map((entry) => stringValue(entry))
765
- .filter((entry) => Boolean(entry)));
766
854
  const receipts = [];
767
855
  for (const entry of paidCredit) {
768
856
  const senderId = stringValue(entry.senderId);
769
857
  if (!senderId ||
770
858
  !entry.plannedJitRefresh ||
771
- refreshedSenderIds.has(senderId) ||
772
- attemptedSenderIds.has(senderId)) {
859
+ refreshedSenderIds.has(senderId)) {
773
860
  continue;
774
861
  }
775
862
  const receipt = await deps.executors.refreshPaidInmailCreditsWithRetry(senderId, input.workspaceId);
776
- attemptedSenderIds.add(senderId);
777
- const classification = classifyPaidInmailRefreshReceipt(receipt.receipt);
778
- receipts.push({ senderId, receipt, classification });
779
- if (classification.usableCurrentFacts) {
780
- refreshedSenderIds.add(senderId);
781
- }
863
+ receipts.push({ senderId, receipt });
864
+ refreshedSenderIds.add(senderId);
782
865
  }
783
- const nextRunState = mergeRunState(ctx.runState, {
866
+ const nextRunState = runStateWithDateWindow(mergeRunState(ctx.runState, {
784
867
  creditTrust: {
785
868
  refreshedAt: (deps.now?.() ?? new Date()).toISOString(),
786
869
  senderIds: [...refreshedSenderIds],
787
- attemptedSenderIds: [...attemptedSenderIds],
788
870
  receipts,
789
871
  },
790
- });
872
+ }), ctx.dateWindow);
791
873
  await safeJournalAppend(ctx, deps, deps.journal.renderBootstrapSection({
792
874
  summary: "Refill v2 execution bootstrap",
793
875
  senderSummary: safeJson(recordValue(plan.bootstrap)?.senders ?? []),
@@ -895,13 +977,8 @@ async function completeTerminal(input, deps, ctx, doneReasonInput, reportInput =
895
977
  };
896
978
  }
897
979
  async function gatePlan(input, deps, ctx) {
898
- const plan = await deps.readPlan({
899
- workspaceId: input.workspaceId,
900
- intent: input.intent,
901
- senderIds: input.senderIds,
902
- runState: ctx.runState,
903
- journal: false,
904
- });
980
+ const plan = await readLoopPlan(input, deps, ctx, false);
981
+ applyPlanDateWindow(ctx, plan);
905
982
  if (plan.blocker === "workspace_access") {
906
983
  return {
907
984
  status: "blocked",
@@ -1004,13 +1081,7 @@ async function verifyRecoveryPoint(input, deps, ctx) {
1004
1081
  stateRevision: stringValue(planned.stateRevision),
1005
1082
  targetShapeRevision: stringValue(planned.targetShapeRevision),
1006
1083
  };
1007
- const fresh = await deps.readPlan({
1008
- workspaceId: input.workspaceId,
1009
- intent: input.intent,
1010
- senderIds: input.senderIds,
1011
- runState: ctx.runState,
1012
- journal: false,
1013
- });
1084
+ const fresh = await readLoopPlan(input, deps, ctx, false);
1014
1085
  const changed = fingerprintChanged(expected, fingerprintFromPlan(fresh));
1015
1086
  if (changed.length > 0) {
1016
1087
  await deps.runClient.recordRunOutcome({
@@ -1272,13 +1343,8 @@ async function verifyPrepareAction(input, deps, ctx, action, budgets) {
1272
1343
  }
1273
1344
  async function verifyReadOnlyWait(input, deps, ctx, budgets, waitKind) {
1274
1345
  for (let poll = 0; poll < budgets.maxPollsPerWait; poll += 1) {
1275
- const fresh = await deps.readPlan({
1276
- workspaceId: input.workspaceId,
1277
- intent: input.intent,
1278
- senderIds: input.senderIds,
1279
- runState: ctx.runState,
1280
- journal: false,
1281
- });
1346
+ const fresh = await readLoopPlan(input, deps, ctx, false);
1347
+ applyPlanDateWindow(ctx, fresh);
1282
1348
  ctx.plan = fresh;
1283
1349
  const doneReason = doneReasonFromPlan(fresh);
1284
1350
  if (doneReason) {
@@ -1310,31 +1376,30 @@ async function verifySchedulerWait(input, deps, ctx, action, budgets) {
1310
1376
  if (!jitFired) {
1311
1377
  const senderId = actionSenderId(action);
1312
1378
  if (senderId) {
1313
- const refresh = await deps.executors.refreshPaidInmailCreditsWithRetry(senderId, input.workspaceId);
1314
- const classification = classifyPaidInmailRefreshReceipt(refresh.receipt);
1315
- if (!classification.usableCurrentFacts) {
1316
- return {
1317
- status: "blocked",
1318
- blocker: "paid_inmail_refresh_failed",
1319
- runId: ctx.runId,
1320
- fence: ctx.fence,
1321
- gate: ctx.gate,
1322
- guidance: "Paid InMail credit refresh did not return usable current facts, so scheduler JIT was not requested.",
1323
- report: {
1324
- senderId,
1325
- receiptStatus: classification.status,
1326
- error: classification.error,
1327
- receipt: refresh.receipt,
1328
- attempts: refresh.attempts,
1329
- errors: refresh.errors,
1330
- },
1331
- journalPath: ctx.journalPath,
1332
- };
1333
- }
1379
+ await deps.executors.refreshPaidInmailCreditsWithRetry(senderId, input.workspaceId);
1334
1380
  }
1335
1381
  if (deps.requestSchedulerRun) {
1336
1382
  try {
1337
- schedulerRunReceipt = sanitizeSchedulerRunReceipt(await deps.requestSchedulerRun(input.workspaceId));
1383
+ const targetDates = schedulerSweepTargetDates(ctx.dateWindow);
1384
+ const sweepReceipts = [];
1385
+ if (targetDates.length === 0) {
1386
+ schedulerRunReceipt = sanitizeSchedulerRunReceipt(await deps.requestSchedulerRun(input.workspaceId));
1387
+ }
1388
+ else {
1389
+ for (const targetDate of targetDates) {
1390
+ const receipt = sanitizeSchedulerRunReceipt(await deps.requestSchedulerRun(input.workspaceId, { targetDate }));
1391
+ if (receipt)
1392
+ sweepReceipts.push({ targetDate, ...receipt });
1393
+ }
1394
+ schedulerRunReceipt = sweepReceipts[0] ?? null;
1395
+ }
1396
+ if (sweepReceipts.length > 1) {
1397
+ ctx.runState = mergeRunState(ctx.runState, {
1398
+ progress: mergeProgress(ctx, {
1399
+ schedulerRunReceipts: sweepReceipts,
1400
+ }),
1401
+ });
1402
+ }
1338
1403
  }
1339
1404
  catch {
1340
1405
  schedulerRunReceipt = null;
@@ -1354,13 +1419,8 @@ async function verifySchedulerWait(input, deps, ctx, action, budgets) {
1354
1419
  // full wait budget when the fresh receipt says nothing was placeable now.
1355
1420
  const readbackBudget = zeroScheduledFresh ? 1 : budgets.maxSchedulerReadbacks;
1356
1421
  for (let poll = 0; poll < readbackBudget; poll += 1) {
1357
- const fresh = await deps.readPlan({
1358
- workspaceId: input.workspaceId,
1359
- intent: input.intent,
1360
- senderIds: input.senderIds,
1361
- runState: ctx.runState,
1362
- journal: false,
1363
- });
1422
+ const fresh = await readLoopPlan(input, deps, ctx, false);
1423
+ applyPlanDateWindow(ctx, fresh);
1364
1424
  ctx.plan = fresh;
1365
1425
  const doneReason = normalizeDoneReason(doneReasonFromPlan(fresh));
1366
1426
  if (doneReason === "complete" || doneReason === "capped_by_scheduler") {
@@ -1416,13 +1476,8 @@ async function gateVerify(input, deps, ctx, budgets) {
1416
1476
  actionType === "wait_for_source_import") {
1417
1477
  return verifyReadOnlyWait(input, deps, ctx, budgets, actionType);
1418
1478
  }
1419
- const fresh = await deps.readPlan({
1420
- workspaceId: input.workspaceId,
1421
- intent: input.intent,
1422
- senderIds: input.senderIds,
1423
- runState: ctx.runState,
1424
- journal: false,
1425
- });
1479
+ const fresh = await readLoopPlan(input, deps, ctx, false);
1480
+ applyPlanDateWindow(ctx, fresh);
1426
1481
  ctx.plan = fresh;
1427
1482
  const doneReason = doneReasonFromPlan(fresh);
1428
1483
  if (doneReason) {
@@ -1495,15 +1550,21 @@ export async function runRefillV2Loop(input, deps) {
1495
1550
  let ctx = null;
1496
1551
  try {
1497
1552
  if (input.dryRun) {
1498
- const plan = await deps.readPlan({
1553
+ const dateSelector = dateSelectorFromInput(input);
1554
+ const plan = await deps.readPlan(readPlanRequest({
1499
1555
  workspaceId: input.workspaceId,
1500
1556
  intent: input.intent,
1501
1557
  senderIds: input.senderIds,
1502
1558
  journal: true,
1503
- });
1559
+ dateSelector,
1560
+ }));
1504
1561
  return { status: "dry_run", plan };
1505
1562
  }
1506
- const started = await startOrResume(input, deps);
1563
+ const requestedSelector = dateSelectorFromInput(input);
1564
+ const preflight = await preflightResumeDateWindow(input, deps, requestedSelector);
1565
+ if ("status" in preflight)
1566
+ return preflight;
1567
+ const started = await startOrResume(input, deps, preflight.window);
1507
1568
  if ("status" in started)
1508
1569
  return started;
1509
1570
  ctx = started;