@sellable/mcp 0.1.554 → 0.1.555

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 (40) hide show
  1. package/dist/index-dev.js +0 -0
  2. package/dist/index.js +0 -0
  3. package/dist/refill-contract.d.ts +157 -0
  4. package/dist/refill-contract.js +487 -0
  5. package/dist/refill-run-client.d.ts +5 -0
  6. package/dist/refill-run-client.js +15 -0
  7. package/dist/refill-run-loop.d.ts +12 -1
  8. package/dist/refill-run-loop.js +158 -13
  9. package/dist/tools/campaign-message-preparation.d.ts +62 -0
  10. package/dist/tools/campaign-message-preparation.js +41 -0
  11. package/dist/tools/evergreen-refill-plan.d.ts +3 -0
  12. package/dist/tools/evergreen-refill-plan.js +29 -7
  13. package/dist/tools/prompts.js +9 -0
  14. package/dist/tools/refill-executors.d.ts +38 -0
  15. package/dist/tools/refill-executors.js +222 -3
  16. package/dist/tools/refill-sends-v2.d.ts +112 -1
  17. package/dist/tools/refill-sends-v2.js +302 -2
  18. package/dist/tools/refill-sends.d.ts +678 -32
  19. package/dist/tools/refill-sends.js +274 -13
  20. package/dist/tools/refill-target-plan.js +486 -14
  21. package/dist/tools/registry.d.ts +96 -27
  22. package/dist/tools/registry.js +1 -3
  23. package/dist/tools/scheduler-fill-capacity.js +1 -1
  24. package/dist/tools/scheduler-run.d.ts +71 -0
  25. package/dist/tools/scheduler-run.js +203 -1
  26. package/dist/tools/workspaces.d.ts +4 -6
  27. package/dist/tools/workspaces.js +11 -13
  28. package/package.json +1 -1
  29. package/skills/refill-sends/SKILL.md +91 -353
  30. package/skills/refill-sends-v2/SKILL.md +6 -6
  31. package/skills/refill-sends-v2-workflow/SKILL.md +5 -5
  32. package/skills/refill-sends-v2-workflow/core/flow.v1.json +8 -8
  33. package/skills/refill-sends-workflow/SKILL.md +100 -743
  34. package/skills/refill-sends-workflow/core/contract.v2.json +543 -0
  35. package/skills/refill-sends-workflow/core/flow.v1.json +185 -1
  36. package/dist/refill-date-window.d.ts +0 -34
  37. package/dist/refill-date-window.js +0 -210
  38. package/dist/tools/refill-sends-evergreen.d.ts +0 -28
  39. package/dist/tools/refill-sends-evergreen.js +0 -47
  40. package/skills/research/config.json +0 -9
@@ -662,6 +662,18 @@ export declare const allTools: ({
662
662
  enum: string[];
663
663
  description: string;
664
664
  };
665
+ targetDate: {
666
+ type: string;
667
+ description: string;
668
+ };
669
+ refillLaneKey: {
670
+ type: string;
671
+ description: string;
672
+ };
673
+ sourceEpoch: {
674
+ type: string;
675
+ description: string;
676
+ };
665
677
  disableLowPassRateStop: {
666
678
  type: string;
667
679
  description: string;
@@ -671,6 +683,7 @@ export declare const allTools: ({
671
683
  description: string;
672
684
  };
673
685
  jobId?: undefined;
686
+ expectedUpdatedAt?: undefined;
674
687
  };
675
688
  required: string[];
676
689
  additionalProperties: boolean;
@@ -705,11 +718,52 @@ export declare const allTools: ({
705
718
  autoContinue?: undefined;
706
719
  senderId?: undefined;
707
720
  actionType?: undefined;
721
+ targetDate?: undefined;
722
+ refillLaneKey?: undefined;
723
+ sourceEpoch?: undefined;
708
724
  disableLowPassRateStop?: undefined;
725
+ expectedUpdatedAt?: undefined;
709
726
  };
710
727
  additionalProperties: boolean;
711
728
  required?: undefined;
712
729
  };
730
+ } | {
731
+ name: string;
732
+ description: string;
733
+ inputSchema: {
734
+ type: string;
735
+ properties: {
736
+ jobId: {
737
+ type: string;
738
+ };
739
+ expectedUpdatedAt: {
740
+ type: string;
741
+ description: string;
742
+ };
743
+ workspaceId: {
744
+ type: string;
745
+ description?: undefined;
746
+ };
747
+ campaignId?: undefined;
748
+ tableId?: undefined;
749
+ targetPreparedMessages?: undefined;
750
+ maxRowsToCheck?: undefined;
751
+ batchSize?: undefined;
752
+ maxBatchRows?: undefined;
753
+ approvalMode?: undefined;
754
+ rowSelector?: undefined;
755
+ excludeRowIds?: undefined;
756
+ autoContinue?: undefined;
757
+ senderId?: undefined;
758
+ actionType?: undefined;
759
+ targetDate?: undefined;
760
+ refillLaneKey?: undefined;
761
+ sourceEpoch?: undefined;
762
+ disableLowPassRateStop?: undefined;
763
+ };
764
+ required: string[];
765
+ additionalProperties: boolean;
766
+ };
713
767
  } | {
714
768
  name: string;
715
769
  description: string;
@@ -7546,47 +7600,62 @@ export declare const allTools: ({
7546
7600
  type: string;
7547
7601
  description: string;
7548
7602
  };
7549
- };
7550
- required: string[];
7551
- additionalProperties: boolean;
7552
- };
7553
- } | {
7554
- name: string;
7555
- description: string;
7556
- inputSchema: {
7557
- type: string;
7558
- properties: {
7559
- workspaceId: {
7603
+ requestKey: {
7560
7604
  type: string;
7561
7605
  description: string;
7562
7606
  };
7563
- runId: {
7607
+ targetShapeRevision: {
7564
7608
  type: string;
7565
7609
  description: string;
7566
7610
  };
7567
- fence: {
7611
+ expectedTargets: {
7568
7612
  type: string;
7569
- description: string;
7613
+ maxItems: 25;
7614
+ items: {
7615
+ type: string;
7616
+ properties: {
7617
+ campaignId: {
7618
+ type: string;
7619
+ };
7620
+ tableId: {
7621
+ type: string;
7622
+ };
7623
+ refillLaneKey: {
7624
+ type: string;
7625
+ };
7626
+ branchActionTypes: {
7627
+ type: string;
7628
+ minItems: number;
7629
+ uniqueItems: boolean;
7630
+ items: {
7631
+ type: string;
7632
+ enum: string[];
7633
+ };
7634
+ };
7635
+ senderIds: {
7636
+ type: string;
7637
+ minItems: number;
7638
+ maxItems: 100;
7639
+ uniqueItems: boolean;
7640
+ items: {
7641
+ type: string;
7642
+ };
7643
+ };
7644
+ };
7645
+ required: string[];
7646
+ additionalProperties: boolean;
7647
+ };
7570
7648
  };
7571
- dryRun: {
7649
+ expectedTargetsHash: {
7572
7650
  type: string;
7573
7651
  description: string;
7574
7652
  };
7575
- approvalMode: {
7653
+ maxPlacements: {
7576
7654
  type: string;
7577
- enum: string[];
7655
+ minimum: number;
7656
+ maximum: 150;
7578
7657
  description: string;
7579
7658
  };
7580
- senderIds: {
7581
- type: string;
7582
- items: {
7583
- type: string;
7584
- };
7585
- };
7586
- intent: {
7587
- type: string;
7588
- enum: string[];
7589
- };
7590
7659
  };
7591
7660
  required: string[];
7592
7661
  additionalProperties: boolean;
@@ -34,10 +34,9 @@ import { onDemandToolDefinitions } from "./one-off.js";
34
34
  import { processingToolDefinitions } from "./processing.js";
35
35
  import { promptToolDefinitions } from "./prompts.js";
36
36
  import { readinessToolDefinitions } from "./readiness.js";
37
- import { refreshSenderEngagementToolDefinitions } from "./refresh-sender-engagement.js";
38
- import { refillSendsV2ToolDefinitions } from "./refill-sends-v2.js";
39
37
  import { refillSendsToolDefinitions } from "./refill-sends.js";
40
38
  import { refillTargetPlanToolDefinitions } from "./refill-target-plan.js";
39
+ import { refreshSenderEngagementToolDefinitions } from "./refresh-sender-engagement.js";
41
40
  import { rowToolDefinitions } from "./rows.js";
42
41
  import { rubricToolDefinitions } from "./rubrics.js";
43
42
  import { schedulerFillCapacityToolDefinitions } from "./scheduler-fill-capacity.js";
@@ -61,7 +60,6 @@ export const allTools = [
61
60
  ...schedulerFillCapacityToolDefinitions,
62
61
  ...schedulerRunToolDefinitions,
63
62
  ...refillSendsToolDefinitions,
64
- ...refillSendsV2ToolDefinitions,
65
63
  ...setupEvergreenCampaignsToolDefinitions,
66
64
  ...campaignHorizonFillToolDefinitions,
67
65
  ...campaignMessagePreparationToolDefinitions,
@@ -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. It returns scheduler-fillable slots, occupied scheduled/processing slots, rolling-weekly candidate timing that shows when capacity frees later in the send window, 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: {
@@ -1,8 +1,23 @@
1
1
  type SchedulerRunAction = "run" | "status";
2
+ export declare const REFILL_SCHEDULER_EXPECTED_TARGET_LIMIT: 25;
3
+ export declare const REFILL_SCHEDULER_EXPECTED_TARGET_SENDER_LIMIT: 100;
4
+ export declare const REFILL_SCHEDULER_MAX_PLACEMENTS: 150;
5
+ export type SchedulerExpectedTargetInput = {
6
+ campaignId: string;
7
+ tableId: string;
8
+ refillLaneKey: string;
9
+ branchActionTypes: Array<"send_invite" | "send_inmail_open" | "send_inmail_closed">;
10
+ senderIds: string[];
11
+ };
2
12
  type RunSchedulerSweepInput = {
3
13
  workspaceId: string;
4
14
  action?: SchedulerRunAction;
5
15
  targetDate?: string;
16
+ requestKey?: string;
17
+ targetShapeRevision?: string;
18
+ expectedTargets?: SchedulerExpectedTargetInput[];
19
+ expectedTargetsHash?: string;
20
+ maxPlacements?: number;
6
21
  };
7
22
  export declare const schedulerRunToolDefinitions: {
8
23
  name: string;
@@ -23,6 +38,62 @@ export declare const schedulerRunToolDefinitions: {
23
38
  type: string;
24
39
  description: string;
25
40
  };
41
+ requestKey: {
42
+ type: string;
43
+ description: string;
44
+ };
45
+ targetShapeRevision: {
46
+ type: string;
47
+ description: string;
48
+ };
49
+ expectedTargets: {
50
+ type: string;
51
+ maxItems: 25;
52
+ items: {
53
+ type: string;
54
+ properties: {
55
+ campaignId: {
56
+ type: string;
57
+ };
58
+ tableId: {
59
+ type: string;
60
+ };
61
+ refillLaneKey: {
62
+ type: string;
63
+ };
64
+ branchActionTypes: {
65
+ type: string;
66
+ minItems: number;
67
+ uniqueItems: boolean;
68
+ items: {
69
+ type: string;
70
+ enum: string[];
71
+ };
72
+ };
73
+ senderIds: {
74
+ type: string;
75
+ minItems: number;
76
+ maxItems: 100;
77
+ uniqueItems: boolean;
78
+ items: {
79
+ type: string;
80
+ };
81
+ };
82
+ };
83
+ required: string[];
84
+ additionalProperties: boolean;
85
+ };
86
+ };
87
+ expectedTargetsHash: {
88
+ type: string;
89
+ description: string;
90
+ };
91
+ maxPlacements: {
92
+ type: string;
93
+ minimum: number;
94
+ maximum: 150;
95
+ description: string;
96
+ };
26
97
  };
27
98
  required: string[];
28
99
  additionalProperties: boolean;
@@ -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);
@@ -21,10 +25,97 @@ function normalizeTargetDate(value) {
21
25
  }
22
26
  return targetDate;
23
27
  }
28
+ function normalizeOptionalScopeValue(value, field) {
29
+ if (value === undefined || value === null)
30
+ return null;
31
+ if (typeof value !== "string" || !value.trim()) {
32
+ throw new Error(`${field} must be a non-empty string when provided.`);
33
+ }
34
+ return value.trim();
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
+ }
24
115
  export const schedulerRunToolDefinitions = [
25
116
  {
26
117
  name: "run_scheduler_sweep",
27
- 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 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 verbatim. Status is read-only and scoped to the last on-demand run only; cron sweeps are not recorded here.',
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.',
28
119
  inputSchema: {
29
120
  type: "object",
30
121
  properties: {
@@ -41,6 +132,64 @@ export const schedulerRunToolDefinitions = [
41
132
  type: "string",
42
133
  description: "Optional one exact sender-local date (YYYY-MM-DD) to scope scheduler placement/status. The run remains workspace-wide inside that date.",
43
134
  },
135
+ requestKey: {
136
+ type: "string",
137
+ description: "Optional deterministic planner action key used to attach to or replay the same exact scheduler attempt.",
138
+ },
139
+ targetShapeRevision: {
140
+ type: "string",
141
+ description: "Optional stable approved refill target-shape revision associated with requestKey.",
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
+ },
44
193
  },
45
194
  required: ["workspaceId"],
46
195
  additionalProperties: false,
@@ -54,12 +203,65 @@ export async function runSchedulerSweep(input) {
54
203
  }
55
204
  const action = input.action ?? "run";
56
205
  const targetDate = normalizeTargetDate(input.targetDate);
206
+ const requestKey = normalizeOptionalScopeValue(input.requestKey, "requestKey");
207
+ const targetShapeRevision = normalizeOptionalScopeValue(input.targetShapeRevision, "targetShapeRevision");
57
208
  if (action !== "run" && action !== "status") {
58
209
  throw new Error('action must be "run" or "status" for run_scheduler_sweep.');
59
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
+ }
60
258
  return postSchedulerRun({
61
259
  workspaceId,
62
260
  action,
63
261
  ...(targetDate ? { targetDate } : {}),
262
+ ...(requestKey ? { requestKey } : {}),
263
+ ...(targetShapeRevision ? { targetShapeRevision } : {}),
264
+ ...(expectedTargetRequest ?? {}),
265
+ ...(expectedStatusScope ?? {}),
64
266
  }, workspaceId);
65
267
  }
@@ -142,17 +142,15 @@ export declare function listWorkspaces(): Promise<{
142
142
  workspaces: WorkspaceSummary[];
143
143
  workspaceLock: {
144
144
  enabled: boolean;
145
- workspaceId: string;
146
- hiddenWorkspaceCount: number;
147
- siblingInventoryFetched: boolean;
145
+ workspaceId?: undefined;
146
+ hiddenWorkspaceCount?: undefined;
148
147
  };
149
148
  } | {
150
149
  workspaces: WorkspaceSummary[];
151
150
  workspaceLock: {
152
151
  enabled: boolean;
153
- workspaceId?: undefined;
154
- hiddenWorkspaceCount?: undefined;
155
- siblingInventoryFetched?: undefined;
152
+ workspaceId: string;
153
+ hiddenWorkspaceCount: number;
156
154
  };
157
155
  }>;
158
156
  export declare function getActiveWorkspace(): {
@@ -80,21 +80,19 @@ export const workspaceToolDefinitions = [
80
80
  ];
81
81
  export async function listWorkspaces() {
82
82
  const api = getApi();
83
+ const { workspaces } = await api.get("/api/v3/workspaces");
83
84
  const lockedWorkspaceId = getLockedWorkspaceId();
84
- if (lockedWorkspaceId) {
85
- const { workspace } = await api.get(`/api/v3/workspaces/${encodeURIComponent(lockedWorkspaceId)}`);
86
- return {
87
- workspaces: [workspace],
88
- workspaceLock: {
89
- enabled: true,
90
- workspaceId: lockedWorkspaceId,
91
- hiddenWorkspaceCount: 0,
92
- siblingInventoryFetched: false,
93
- },
94
- };
85
+ if (!lockedWorkspaceId) {
86
+ return { workspaces, workspaceLock: { enabled: false } };
95
87
  }
96
- const { workspaces } = await api.get("/api/v3/workspaces");
97
- return { workspaces, workspaceLock: { enabled: false } };
88
+ return {
89
+ workspaces: workspaces.filter((ws) => ws.id === lockedWorkspaceId),
90
+ workspaceLock: {
91
+ enabled: true,
92
+ workspaceId: lockedWorkspaceId,
93
+ hiddenWorkspaceCount: workspaces.filter((ws) => ws.id !== lockedWorkspaceId).length,
94
+ },
95
+ };
98
96
  }
99
97
  export function getActiveWorkspace() {
100
98
  const config = getConfig();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.554",
3
+ "version": "0.1.555",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",