@sellable/mcp 0.1.532 → 0.1.533
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/refill-run-loop.js +125 -2
- package/dist/server.js +4 -0
- package/dist/tools/refill-sends-v2.js +2 -0
- package/dist/tools/registry.d.ts +19 -0
- package/dist/tools/registry.js +2 -0
- package/dist/tools/scheduler-run.d.ts +27 -0
- package/dist/tools/scheduler-run.js +45 -0
- package/package.json +1 -1
- package/skills/refill-sends/SKILL.md +10 -4
- package/skills/refill-sends-workflow/SKILL.md +11 -5
package/dist/refill-run-loop.js
CHANGED
|
@@ -19,6 +19,34 @@ const REFILL_DONE_REASONS = new Set([
|
|
|
19
19
|
"not_an_evergreen_workspace",
|
|
20
20
|
"no_refillable_campaigns",
|
|
21
21
|
]);
|
|
22
|
+
const SCHEDULER_RUN_ENVELOPE_STATUSES = new Set([
|
|
23
|
+
"ran",
|
|
24
|
+
"attached",
|
|
25
|
+
"backoff",
|
|
26
|
+
"window_closed_noop",
|
|
27
|
+
"failed",
|
|
28
|
+
]);
|
|
29
|
+
const SCHEDULER_RUN_RECEIPT_STATUSES = new Set([
|
|
30
|
+
"ran",
|
|
31
|
+
"window_closed_noop",
|
|
32
|
+
"failed",
|
|
33
|
+
]);
|
|
34
|
+
const SCHEDULER_RUN_SKIP_REASONS = [
|
|
35
|
+
"window_closed",
|
|
36
|
+
"daily_limit",
|
|
37
|
+
"cooldown",
|
|
38
|
+
"sender_gate",
|
|
39
|
+
"credit_threshold",
|
|
40
|
+
"billing_blocked",
|
|
41
|
+
"duplicate_lead",
|
|
42
|
+
"no_senders",
|
|
43
|
+
"other",
|
|
44
|
+
];
|
|
45
|
+
const SCHEDULER_RUN_HARD_BLOCK_REASONS = new Set([
|
|
46
|
+
"billing_blocked",
|
|
47
|
+
"credit_threshold",
|
|
48
|
+
"cooldown",
|
|
49
|
+
]);
|
|
22
50
|
function isRecord(value) {
|
|
23
51
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
24
52
|
}
|
|
@@ -40,6 +68,89 @@ function arrayValue(value) {
|
|
|
40
68
|
function hasBlocker(value, blocker) {
|
|
41
69
|
return isRecord(value) && value.blocker === blocker;
|
|
42
70
|
}
|
|
71
|
+
function schedulerRunSkipReasons(value) {
|
|
72
|
+
const raw = recordValue(value) ?? {};
|
|
73
|
+
const normalized = {};
|
|
74
|
+
for (const reason of SCHEDULER_RUN_SKIP_REASONS) {
|
|
75
|
+
normalized[reason] = numberValue(raw[reason]) ?? 0;
|
|
76
|
+
}
|
|
77
|
+
return normalized;
|
|
78
|
+
}
|
|
79
|
+
function dominantSchedulerRunSkipReason(skipReasons) {
|
|
80
|
+
let dominant = null;
|
|
81
|
+
let dominantCount = 0;
|
|
82
|
+
for (const reason of SCHEDULER_RUN_SKIP_REASONS) {
|
|
83
|
+
const count = skipReasons[reason] ?? 0;
|
|
84
|
+
if (count > dominantCount) {
|
|
85
|
+
dominant = reason;
|
|
86
|
+
dominantCount = count;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return dominantCount > 0 ? dominant : null;
|
|
90
|
+
}
|
|
91
|
+
function sanitizeSchedulerRunReceipt(raw) {
|
|
92
|
+
const envelope = recordValue(raw);
|
|
93
|
+
if (!envelope)
|
|
94
|
+
return null;
|
|
95
|
+
const status = stringValue(envelope.status);
|
|
96
|
+
if (!status || !SCHEDULER_RUN_ENVELOPE_STATUSES.has(status))
|
|
97
|
+
return null;
|
|
98
|
+
const retryAfterMs = envelope.retryAfterMs == null ? null : numberValue(envelope.retryAfterMs);
|
|
99
|
+
if (envelope.retryAfterMs != null && retryAfterMs == null)
|
|
100
|
+
return null;
|
|
101
|
+
const receiptInput = envelope.receipt;
|
|
102
|
+
let receipt = null;
|
|
103
|
+
let dominantSkipReason = null;
|
|
104
|
+
if (receiptInput != null) {
|
|
105
|
+
const rawReceipt = recordValue(receiptInput);
|
|
106
|
+
if (!rawReceipt)
|
|
107
|
+
return null;
|
|
108
|
+
const receiptStatus = stringValue(rawReceipt.status);
|
|
109
|
+
if (!receiptStatus || !SCHEDULER_RUN_RECEIPT_STATUSES.has(receiptStatus)) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const cellsConsidered = numberValue(rawReceipt.cellsConsidered);
|
|
113
|
+
const cellsScheduled = numberValue(rawReceipt.cellsScheduled);
|
|
114
|
+
const cellsSkipped = numberValue(rawReceipt.cellsSkipped);
|
|
115
|
+
const cellsDeferred = numberValue(rawReceipt.cellsDeferred);
|
|
116
|
+
const tablesFilteredForNoCapacity = numberValue(rawReceipt.tablesFilteredForNoCapacity);
|
|
117
|
+
if (cellsConsidered == null ||
|
|
118
|
+
cellsScheduled == null ||
|
|
119
|
+
cellsSkipped == null ||
|
|
120
|
+
cellsDeferred == null ||
|
|
121
|
+
tablesFilteredForNoCapacity == null) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const skipReasons = schedulerRunSkipReasons(rawReceipt.skipReasons);
|
|
125
|
+
dominantSkipReason = dominantSchedulerRunSkipReason(skipReasons);
|
|
126
|
+
receipt = {
|
|
127
|
+
status: receiptStatus,
|
|
128
|
+
cellsConsidered,
|
|
129
|
+
cellsScheduled,
|
|
130
|
+
cellsSkipped,
|
|
131
|
+
cellsDeferred,
|
|
132
|
+
tablesFilteredForNoCapacity,
|
|
133
|
+
skipReasons,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
status,
|
|
138
|
+
retryAfterMs,
|
|
139
|
+
receipt,
|
|
140
|
+
dominantSkipReason,
|
|
141
|
+
hardBlocked: dominantSkipReason != null &&
|
|
142
|
+
SCHEDULER_RUN_HARD_BLOCK_REASONS.has(dominantSkipReason),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function schedulerRunReceiptIsFreshZeroScheduled(schedulerRunReceipt) {
|
|
146
|
+
const status = stringValue(schedulerRunReceipt?.status);
|
|
147
|
+
const fresh = status === "ran" || status === "window_closed_noop";
|
|
148
|
+
if (!fresh)
|
|
149
|
+
return false;
|
|
150
|
+
const receipt = recordValue(schedulerRunReceipt?.receipt);
|
|
151
|
+
return (status === "window_closed_noop" ||
|
|
152
|
+
numberValue(receipt?.cellsScheduled) === 0);
|
|
153
|
+
}
|
|
43
154
|
function isLeaseLost(value) {
|
|
44
155
|
return hasBlocker(value, "lease_lost");
|
|
45
156
|
}
|
|
@@ -1013,22 +1124,34 @@ async function verifySchedulerWait(input, deps, ctx, action, budgets) {
|
|
|
1013
1124
|
const enteredAt = stringValue(progress.schedulerWaitEnteredAt) ??
|
|
1014
1125
|
(deps.now?.() ?? new Date()).toISOString();
|
|
1015
1126
|
const jitFired = booleanValue(progress.schedulerJitFired) ?? false;
|
|
1127
|
+
let schedulerRunReceipt = recordValue(progress.schedulerRunReceipt) ?? null;
|
|
1016
1128
|
if (!jitFired) {
|
|
1017
1129
|
const senderId = actionSenderId(action);
|
|
1018
1130
|
if (senderId) {
|
|
1019
1131
|
await deps.executors.refreshPaidInmailCreditsWithRetry(senderId, input.workspaceId);
|
|
1020
1132
|
}
|
|
1021
1133
|
if (deps.requestSchedulerRun) {
|
|
1022
|
-
|
|
1134
|
+
try {
|
|
1135
|
+
schedulerRunReceipt = sanitizeSchedulerRunReceipt(await deps.requestSchedulerRun(input.workspaceId));
|
|
1136
|
+
}
|
|
1137
|
+
catch {
|
|
1138
|
+
schedulerRunReceipt = null;
|
|
1139
|
+
}
|
|
1023
1140
|
}
|
|
1024
1141
|
ctx.runState = mergeRunState(ctx.runState, {
|
|
1025
1142
|
progress: mergeProgress(ctx, {
|
|
1026
1143
|
schedulerWaitEnteredAt: enteredAt,
|
|
1027
1144
|
schedulerJitFired: true,
|
|
1145
|
+
...(schedulerRunReceipt ? { schedulerRunReceipt } : {}),
|
|
1028
1146
|
}),
|
|
1029
1147
|
});
|
|
1030
1148
|
}
|
|
1031
|
-
|
|
1149
|
+
const zeroScheduledFresh = schedulerRunReceiptIsFreshZeroScheduled(schedulerRunReceipt);
|
|
1150
|
+
// EDGE-2: the on-demand run only executes placement; cron can still process
|
|
1151
|
+
// due/timed-out cells later. We still do one readback, then avoid burning the
|
|
1152
|
+
// full wait budget when the fresh receipt says nothing was placeable now.
|
|
1153
|
+
const readbackBudget = zeroScheduledFresh ? 1 : budgets.maxSchedulerReadbacks;
|
|
1154
|
+
for (let poll = 0; poll < readbackBudget; poll += 1) {
|
|
1032
1155
|
const fresh = await deps.readPlan({
|
|
1033
1156
|
workspaceId: input.workspaceId,
|
|
1034
1157
|
intent: input.intent,
|
package/dist/server.js
CHANGED
|
@@ -45,6 +45,7 @@ import { allTools } from "./tools/registry.js";
|
|
|
45
45
|
import { getRows, getTableRows, getTableRowsMinimal } from "./tools/rows.js";
|
|
46
46
|
import { addRubricItem, checkRubric, deleteRubricItem, draftRubrics, saveRubrics, selectNecessaryRubrics, updateRubricItem, waitForRubricResults, } from "./tools/rubrics.js";
|
|
47
47
|
import { getSchedulerFillCapacity } from "./tools/scheduler-fill-capacity.js";
|
|
48
|
+
import { runSchedulerSweep } from "./tools/scheduler-run.js";
|
|
48
49
|
import { getSenderRoutingTool, setSenderRoutingTool, } from "./tools/sender-routing.js";
|
|
49
50
|
import { getSender, listSenders, refreshPaidInmailCredits, } from "./tools/senders.js";
|
|
50
51
|
import { attachRecommendedSequence, attachSequence, createWorkflowTable, } from "./tools/sequencer.js";
|
|
@@ -235,6 +236,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
235
236
|
case "get_scheduler_fill_capacity":
|
|
236
237
|
result = await getSchedulerFillCapacity(args);
|
|
237
238
|
break;
|
|
239
|
+
case "run_scheduler_sweep":
|
|
240
|
+
result = await runSchedulerSweep(args);
|
|
241
|
+
break;
|
|
238
242
|
case "refill_sends":
|
|
239
243
|
result = await executeRefillSendsCommand(args);
|
|
240
244
|
break;
|
|
@@ -5,6 +5,7 @@ import { runRefillV2Loop } from "../refill-run-loop.js";
|
|
|
5
5
|
import { getPrepareCampaignMessagesStatus } from "./campaign-message-preparation.js";
|
|
6
6
|
import { getRefillPlanV2 } from "./evergreen-refill-plan.js";
|
|
7
7
|
import { executeOneYoloPrimitive, executeStartCampaignPrimitive, prepareRowSelectorValue, refillPrepareRequestHash, refreshPaidInmailCreditsWithRetry, } from "./refill-executors.js";
|
|
8
|
+
import { runSchedulerSweep } from "./scheduler-run.js";
|
|
8
9
|
const FORBIDDEN_ACTIONS = [
|
|
9
10
|
"Do not schedule sends.",
|
|
10
11
|
"Do not send messages.",
|
|
@@ -152,6 +153,7 @@ export async function refillSendsV2Command(input) {
|
|
|
152
153
|
localState: {
|
|
153
154
|
writeRefillWorkspaceState,
|
|
154
155
|
},
|
|
156
|
+
requestSchedulerRun: (workspaceId) => runSchedulerSweep({ workspaceId, action: "run" }),
|
|
155
157
|
});
|
|
156
158
|
return {
|
|
157
159
|
...(await maybeAddLostFenceGuidance(result, { ...input, workspaceId })),
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -7370,6 +7370,25 @@ export declare const allTools: ({
|
|
|
7370
7370
|
};
|
|
7371
7371
|
required: string[];
|
|
7372
7372
|
};
|
|
7373
|
+
} | {
|
|
7374
|
+
name: string;
|
|
7375
|
+
description: string;
|
|
7376
|
+
inputSchema: {
|
|
7377
|
+
type: string;
|
|
7378
|
+
properties: {
|
|
7379
|
+
workspaceId: {
|
|
7380
|
+
type: string;
|
|
7381
|
+
description: string;
|
|
7382
|
+
};
|
|
7383
|
+
action: {
|
|
7384
|
+
type: string;
|
|
7385
|
+
enum: string[];
|
|
7386
|
+
description: string;
|
|
7387
|
+
};
|
|
7388
|
+
};
|
|
7389
|
+
required: string[];
|
|
7390
|
+
additionalProperties: boolean;
|
|
7391
|
+
};
|
|
7373
7392
|
} | {
|
|
7374
7393
|
name: string;
|
|
7375
7394
|
description: string;
|
package/dist/tools/registry.js
CHANGED
|
@@ -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,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type SchedulerRunAction = "run" | "status";
|
|
2
|
+
type RunSchedulerSweepInput = {
|
|
3
|
+
workspaceId: string;
|
|
4
|
+
action?: SchedulerRunAction;
|
|
5
|
+
};
|
|
6
|
+
export declare const schedulerRunToolDefinitions: {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: string;
|
|
11
|
+
properties: {
|
|
12
|
+
workspaceId: {
|
|
13
|
+
type: string;
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
action: {
|
|
17
|
+
type: string;
|
|
18
|
+
enum: string[];
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
required: string[];
|
|
23
|
+
additionalProperties: boolean;
|
|
24
|
+
};
|
|
25
|
+
}[];
|
|
26
|
+
export declare function runSchedulerSweep(input: RunSchedulerSweepInput): Promise<unknown>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
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". A run returns a synchronous envelope with status ran, attached, backoff, window_closed_noop, or failed; retryAfterMs for backoff replays; and a receipt with cellsConsidered, cellsScheduled, cellsSkipped, cellsDeferred, deterministic skipReasons, and tablesFilteredForNoCapacity. 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.',
|
|
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
|
+
},
|
|
27
|
+
required: ["workspaceId"],
|
|
28
|
+
additionalProperties: false,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
export async function runSchedulerSweep(input) {
|
|
33
|
+
const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
|
|
34
|
+
if (!workspaceId) {
|
|
35
|
+
throw new Error("workspaceId is required for run_scheduler_sweep.");
|
|
36
|
+
}
|
|
37
|
+
const action = input.action ?? "run";
|
|
38
|
+
if (action !== "run" && action !== "status") {
|
|
39
|
+
throw new Error('action must be "run" or "status" for run_scheduler_sweep.');
|
|
40
|
+
}
|
|
41
|
+
return postSchedulerRun({
|
|
42
|
+
workspaceId,
|
|
43
|
+
action,
|
|
44
|
+
}, workspaceId);
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -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
|
|
@@ -114,10 +115,11 @@ or install-time workspace mapping. Pass `workspaceId` on every scheduled or
|
|
|
114
115
|
`--yolo` refill tool call, including setup/read calls such as
|
|
115
116
|
`refill_sends`, `get_refill_target_plan`, `list_senders`,
|
|
116
117
|
`get_sender_routing`, `resolve_campaign_fill_route`,
|
|
117
|
-
`get_campaign_refill_state`, `get_scheduler_fill_capacity`,
|
|
118
|
-
refill mutation covered by the packet.
|
|
119
|
-
`--yolo` mode is a blocker; stop with
|
|
120
|
-
against an implicit or guessed
|
|
118
|
+
`get_campaign_refill_state`, `get_scheduler_fill_capacity`,
|
|
119
|
+
`run_scheduler_sweep`, and any later refill mutation covered by the packet.
|
|
120
|
+
Missing `workspaceId` in scheduled or `--yolo` mode is a blocker; stop with
|
|
121
|
+
`WORKSPACE_REQUIRED` instead of running against an implicit or guessed
|
|
122
|
+
workspace.
|
|
121
123
|
|
|
122
124
|
Do not solve scheduled or `--yolo` workspace uncertainty by changing the shared
|
|
123
125
|
active workspace. Manual interactive workspace switching remains a separate
|
|
@@ -269,6 +271,10 @@ need raw proof, call the read-only `get_scheduler_fill_capacity` query for the
|
|
|
269
271
|
same sender/action/date; it tells the MCP how many cells the product scheduler
|
|
270
272
|
will try to place and does not import, approve, schedule, refresh credits, or
|
|
271
273
|
mutate.
|
|
274
|
+
When the refill loop has ready rows and needs scheduler pickup now, use
|
|
275
|
+
`run_scheduler_sweep` with the same explicit `workspaceId`; it can place cells
|
|
276
|
+
within existing scheduler gates and returns the receipt, but it never sends or
|
|
277
|
+
bypasses limits.
|
|
272
278
|
If the target plan is complete by projected coverage, report that the selected
|
|
273
279
|
target is already filled and no-op without asking for approval. If the ready
|
|
274
280
|
buffer covers the projected gap, paid InMail credit facts are fresh for every
|
|
@@ -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
|
|
71
|
-
approval calls, and campaign start calls. Missing
|
|
72
|
-
`--yolo` mode is a blocker; return or report
|
|
73
|
-
falling back to shared config state. Manual
|
|
74
|
-
diagnostic setup only and is not an
|
|
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,10 @@ 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` with the same explicit `workspaceId` to request the
|
|
193
|
+
product scheduler placement pass now and read its receipt. This may place
|
|
194
|
+
cells within existing gates, never sends messages, and never bypasses limits.
|
|
189
195
|
If `status:"complete"`, report the target, selected dates, sent count,
|
|
190
196
|
scheduled count, projected count, campaign ids, and no-op proof without
|
|
191
197
|
asking for approval or mutating.
|