@sellable/mcp 0.1.509 → 0.1.511
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/server.js +1 -4
- package/dist/tools/campaigns.d.ts +1 -54
- package/dist/tools/campaigns.js +3 -18
- package/dist/tools/leads.d.ts +0 -2
- package/dist/tools/leads.js +33 -116
- package/dist/tools/prompts.js +1 -1
- package/dist/tools/refill-sends.d.ts +40 -38
- package/dist/tools/refill-sends.js +130 -261
- package/dist/tools/refill-target-plan.js +22 -3
- package/dist/tools/registry.d.ts +0 -49
- package/dist/tools/senders.d.ts +2 -1
- package/dist/tools/senders.js +9 -7
- package/package.json +1 -1
- package/skills/refill-sends/SKILL.md +30 -10
- package/skills/refill-sends-workflow/SKILL.md +27 -2
- package/skills/refill-sends-workflow/core/flow.v1.json +281 -0
|
@@ -183,7 +183,9 @@ function sanitizeActionCandidate(candidate) {
|
|
|
183
183
|
: undefined,
|
|
184
184
|
activeJobId: stringValue(candidate.activeJobId) ?? null,
|
|
185
185
|
activeImportStatus: stringValue(candidate.activeImportStatus) ?? null,
|
|
186
|
-
targetRows: typeof candidate.targetRows === "number"
|
|
186
|
+
targetRows: typeof candidate.targetRows === "number"
|
|
187
|
+
? candidate.targetRows
|
|
188
|
+
: undefined,
|
|
187
189
|
columnRole: stringValue(candidate.columnRole),
|
|
188
190
|
rowSelector: sanitizeRowSelector(candidate.rowSelector),
|
|
189
191
|
actionKey: stringValue(candidate.actionKey),
|
|
@@ -209,7 +211,9 @@ function sanitizeRowSelector(value) {
|
|
|
209
211
|
return {
|
|
210
212
|
type: value.type,
|
|
211
213
|
...(typeof value.limit === "number" ? { limit: value.limit } : {}),
|
|
212
|
-
...(Array.isArray(value.rowIds)
|
|
214
|
+
...(Array.isArray(value.rowIds)
|
|
215
|
+
? { rowIds: stringArray(value.rowIds) }
|
|
216
|
+
: {}),
|
|
213
217
|
};
|
|
214
218
|
}
|
|
215
219
|
function sanitizeToolInput(value) {
|
|
@@ -451,11 +455,15 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
|
|
|
451
455
|
selectedDays: Array.isArray(plan.horizon.selectedDays)
|
|
452
456
|
? plan.horizon.selectedDays
|
|
453
457
|
: [],
|
|
458
|
+
requestedTarget: numberValue(plan.horizon.requestedTarget),
|
|
454
459
|
grossTarget: numberValue(plan.horizon.grossTarget),
|
|
455
460
|
schedulerFillableSlots: typeof plan.horizon.schedulerFillableSlots === "number"
|
|
456
461
|
? plan.horizon.schedulerFillableSlots
|
|
457
462
|
: null,
|
|
458
463
|
schedulerFillableSlotsKnown: plan.horizon.schedulerFillableSlotsKnown === true,
|
|
464
|
+
schedulerCapacityTarget: typeof plan.horizon.schedulerCapacityTarget === "number"
|
|
465
|
+
? plan.horizon.schedulerCapacityTarget
|
|
466
|
+
: null,
|
|
459
467
|
capacityConfidence: stringValue(plan.horizon.capacityConfidence) ?? "unknown",
|
|
460
468
|
schedulerCapacityMode: stringValue(plan.horizon.schedulerCapacityMode),
|
|
461
469
|
}
|
|
@@ -517,7 +525,9 @@ function emptyUnsupportedResult(result) {
|
|
|
517
525
|
eligibleSenderLedger: [],
|
|
518
526
|
senderRefillPlans: [],
|
|
519
527
|
globalActionQueue: [],
|
|
528
|
+
requestedTarget: 0,
|
|
520
529
|
grossTarget: 0,
|
|
530
|
+
effectiveTarget: 0,
|
|
521
531
|
sent: 0,
|
|
522
532
|
scheduled: 0,
|
|
523
533
|
projected: 0,
|
|
@@ -564,7 +574,7 @@ function sanitizeBlockers(blockers, selectedKeys) {
|
|
|
564
574
|
if (!actionType)
|
|
565
575
|
return true;
|
|
566
576
|
const senderId = parts[0];
|
|
567
|
-
return selectedKeys.size === 0 || selectedKeys.has(`${senderId}:${actionType}`);
|
|
577
|
+
return (selectedKeys.size === 0 || selectedKeys.has(`${senderId}:${actionType}`));
|
|
568
578
|
});
|
|
569
579
|
}
|
|
570
580
|
function sanitizeRefillTargetPlanResult(result) {
|
|
@@ -626,6 +636,13 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
626
636
|
};
|
|
627
637
|
});
|
|
628
638
|
const grossTarget = senderPlans.reduce((sum, plan) => sum + numberValue(plan.grossTarget), 0);
|
|
639
|
+
const requestedTarget = senderPlans.reduce((sum, plan) => sum + numberValue(plan.requestedTarget), 0);
|
|
640
|
+
const effectiveTarget = senderPlans.reduce((sum, plan) => {
|
|
641
|
+
const target = typeof plan.schedulerCapacityTarget === "number"
|
|
642
|
+
? plan.schedulerCapacityTarget
|
|
643
|
+
: numberValue(plan.grossTarget);
|
|
644
|
+
return sum + target;
|
|
645
|
+
}, 0);
|
|
629
646
|
const sent = senderPlans.reduce((sum, plan) => sum + numberValue(plan.sent), 0);
|
|
630
647
|
const scheduled = senderPlans.reduce((sum, plan) => sum + numberValue(plan.scheduled), 0);
|
|
631
648
|
const projected = sent + scheduled;
|
|
@@ -662,7 +679,9 @@ function sanitizeRefillTargetPlanResult(result) {
|
|
|
662
679
|
eligibleSenderLedger,
|
|
663
680
|
senderRefillPlans,
|
|
664
681
|
globalActionQueue,
|
|
682
|
+
requestedTarget,
|
|
665
683
|
grossTarget,
|
|
684
|
+
effectiveTarget,
|
|
666
685
|
sent,
|
|
667
686
|
scheduled,
|
|
668
687
|
projected,
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -1079,7 +1079,6 @@ export declare const allTools: ({
|
|
|
1079
1079
|
useMessagingTemplate?: undefined;
|
|
1080
1080
|
rubric?: undefined;
|
|
1081
1081
|
flowVersion?: undefined;
|
|
1082
|
-
workspaceId?: undefined;
|
|
1083
1082
|
};
|
|
1084
1083
|
required: string[];
|
|
1085
1084
|
additionalProperties: boolean;
|
|
@@ -1118,7 +1117,6 @@ export declare const allTools: ({
|
|
|
1118
1117
|
useMessagingTemplate?: undefined;
|
|
1119
1118
|
rubric?: undefined;
|
|
1120
1119
|
flowVersion?: undefined;
|
|
1121
|
-
workspaceId?: undefined;
|
|
1122
1120
|
};
|
|
1123
1121
|
required: never[];
|
|
1124
1122
|
additionalProperties?: undefined;
|
|
@@ -1157,7 +1155,6 @@ export declare const allTools: ({
|
|
|
1157
1155
|
useMessagingTemplate?: undefined;
|
|
1158
1156
|
rubric?: undefined;
|
|
1159
1157
|
flowVersion?: undefined;
|
|
1160
|
-
workspaceId?: undefined;
|
|
1161
1158
|
};
|
|
1162
1159
|
required: string[];
|
|
1163
1160
|
additionalProperties?: undefined;
|
|
@@ -1239,7 +1236,6 @@ export declare const allTools: ({
|
|
|
1239
1236
|
useMessagingTemplate?: undefined;
|
|
1240
1237
|
rubric?: undefined;
|
|
1241
1238
|
flowVersion?: undefined;
|
|
1242
|
-
workspaceId?: undefined;
|
|
1243
1239
|
};
|
|
1244
1240
|
required: string[];
|
|
1245
1241
|
additionalProperties: boolean;
|
|
@@ -1434,7 +1430,6 @@ export declare const allTools: ({
|
|
|
1434
1430
|
useMessagingTemplate?: undefined;
|
|
1435
1431
|
rubric?: undefined;
|
|
1436
1432
|
flowVersion?: undefined;
|
|
1437
|
-
workspaceId?: undefined;
|
|
1438
1433
|
};
|
|
1439
1434
|
required: never[];
|
|
1440
1435
|
additionalProperties?: undefined;
|
|
@@ -1640,49 +1635,6 @@ export declare const allTools: ({
|
|
|
1640
1635
|
clientProspectId?: undefined;
|
|
1641
1636
|
senderLinkedinUrl?: undefined;
|
|
1642
1637
|
messageGenerationMode?: undefined;
|
|
1643
|
-
workspaceId?: undefined;
|
|
1644
|
-
};
|
|
1645
|
-
required: string[];
|
|
1646
|
-
additionalProperties?: undefined;
|
|
1647
|
-
};
|
|
1648
|
-
} | {
|
|
1649
|
-
name: string;
|
|
1650
|
-
description: string;
|
|
1651
|
-
inputSchema: {
|
|
1652
|
-
type: string;
|
|
1653
|
-
properties: {
|
|
1654
|
-
campaignId: {
|
|
1655
|
-
type: string;
|
|
1656
|
-
description: string;
|
|
1657
|
-
};
|
|
1658
|
-
workspaceId: {
|
|
1659
|
-
type: string;
|
|
1660
|
-
description: string;
|
|
1661
|
-
};
|
|
1662
|
-
limit?: undefined;
|
|
1663
|
-
tableId?: undefined;
|
|
1664
|
-
leadLimit?: undefined;
|
|
1665
|
-
page?: undefined;
|
|
1666
|
-
filters?: undefined;
|
|
1667
|
-
name?: undefined;
|
|
1668
|
-
clientProspectId?: undefined;
|
|
1669
|
-
senderLinkedinUrl?: undefined;
|
|
1670
|
-
offerPositioning?: undefined;
|
|
1671
|
-
campaignBrief?: undefined;
|
|
1672
|
-
messageGenerationMode?: undefined;
|
|
1673
|
-
currentStep?: undefined;
|
|
1674
|
-
watchNarration?: undefined;
|
|
1675
|
-
leadSourceType?: undefined;
|
|
1676
|
-
leadSourceProvider?: undefined;
|
|
1677
|
-
selectedLeadListId?: undefined;
|
|
1678
|
-
senderIds?: undefined;
|
|
1679
|
-
currentStepTransition?: undefined;
|
|
1680
|
-
clearCurrentStepIfMatches?: undefined;
|
|
1681
|
-
interactionMode?: undefined;
|
|
1682
|
-
enableICPFilters?: undefined;
|
|
1683
|
-
useMessagingTemplate?: undefined;
|
|
1684
|
-
rubric?: undefined;
|
|
1685
|
-
flowVersion?: undefined;
|
|
1686
1638
|
};
|
|
1687
1639
|
required: string[];
|
|
1688
1640
|
additionalProperties?: undefined;
|
|
@@ -1724,7 +1676,6 @@ export declare const allTools: ({
|
|
|
1724
1676
|
useMessagingTemplate?: undefined;
|
|
1725
1677
|
rubric?: undefined;
|
|
1726
1678
|
flowVersion?: undefined;
|
|
1727
|
-
workspaceId?: undefined;
|
|
1728
1679
|
};
|
|
1729
1680
|
required: string[];
|
|
1730
1681
|
additionalProperties?: undefined;
|
package/dist/tools/senders.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export type SenderDetailResponse = {
|
|
|
34
34
|
};
|
|
35
35
|
export type RefreshPaidInmailCreditsInput = {
|
|
36
36
|
senderId: string;
|
|
37
|
-
workspaceId
|
|
37
|
+
workspaceId: string;
|
|
38
38
|
};
|
|
39
39
|
export type PaidInmailCreditStatus = {
|
|
40
40
|
available: number;
|
|
@@ -47,6 +47,7 @@ export type RefreshPaidInmailCreditsResponse = {
|
|
|
47
47
|
senderId: string;
|
|
48
48
|
refreshed: true;
|
|
49
49
|
credits: PaidInmailCreditStatus;
|
|
50
|
+
receipt?: unknown;
|
|
50
51
|
sideEffects: {
|
|
51
52
|
refreshedLinkedInDerivedCreditFacts: true;
|
|
52
53
|
updatedSenderCreditCache: true;
|
package/dist/tools/senders.js
CHANGED
|
@@ -136,20 +136,20 @@ export const senderToolDefinitions = [
|
|
|
136
136
|
},
|
|
137
137
|
{
|
|
138
138
|
name: "refresh_paid_inmail_credits",
|
|
139
|
-
description: "Refresh the cached paid InMail credit facts for one exact sender in the
|
|
139
|
+
description: "Refresh the cached paid InMail credit facts for one exact sender in the explicit request-scoped workspace by calling Sellable's scoped MCP credit refresh path. This updates only the sender credit cache/attempt metadata (balance, sentSince, checkedAt, last attempted/error/lease) and returns the sanitized coordinator receipt. It does not mutate campaigns, lower thresholds, approve messages, schedule sends, start campaigns, send messages, spend InMail credits, or write scheduler fields. refill_sends --yolo normally handles get_refill_target_plan refresh_paid_inmail_credits candidates internally; use this standalone tool for manual diagnostics or explicit one-off refreshes, then rerun get_refill_target_plan before any refill mutation.",
|
|
140
140
|
inputSchema: {
|
|
141
141
|
type: "object",
|
|
142
142
|
properties: {
|
|
143
143
|
senderId: {
|
|
144
144
|
type: "string",
|
|
145
|
-
description: "Exact Sender.id to refresh. The API verifies the sender belongs to the
|
|
145
|
+
description: "Exact Sender.id to refresh. The API verifies the sender belongs to the explicit workspace.",
|
|
146
146
|
},
|
|
147
147
|
workspaceId: {
|
|
148
148
|
type: "string",
|
|
149
149
|
description: "Explicit request-scoped workspace id for scheduled/yolo refill automation. Pass this instead of switching the shared active workspace.",
|
|
150
150
|
},
|
|
151
151
|
},
|
|
152
|
-
required: ["senderId"],
|
|
152
|
+
required: ["senderId", "workspaceId"],
|
|
153
153
|
additionalProperties: false,
|
|
154
154
|
},
|
|
155
155
|
},
|
|
@@ -209,15 +209,17 @@ export async function refreshPaidInmailCredits(input) {
|
|
|
209
209
|
}
|
|
210
210
|
const api = getApi();
|
|
211
211
|
const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
|
|
212
|
+
if (!workspaceId) {
|
|
213
|
+
throw new Error("workspaceId is required.");
|
|
214
|
+
}
|
|
212
215
|
const requestOptions = workspaceRequestOptions(workspaceId);
|
|
213
|
-
const body =
|
|
214
|
-
const result = requestOptions
|
|
215
|
-
? await api.post(`/api/v3/senders/${encodeURIComponent(senderId)}/refresh-inmail-credits`, body, requestOptions)
|
|
216
|
-
: await api.post(`/api/v3/senders/${encodeURIComponent(senderId)}/refresh-inmail-credits`, body);
|
|
216
|
+
const body = { workspaceId };
|
|
217
|
+
const result = await api.post(`/api/v3/mcp/senders/${encodeURIComponent(senderId)}/refresh-inmail-credits`, body, requestOptions);
|
|
217
218
|
return {
|
|
218
219
|
senderId,
|
|
219
220
|
refreshed: true,
|
|
220
221
|
credits: normalizeCreditStatus(result?.credits),
|
|
222
|
+
receipt: result?.receipt,
|
|
221
223
|
sideEffects: {
|
|
222
224
|
refreshedLinkedInDerivedCreditFacts: true,
|
|
223
225
|
updatedSenderCreditCache: true,
|
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ allowed-tools:
|
|
|
7
7
|
- mcp__sellable__get_refill_target_plan
|
|
8
8
|
- mcp__sellable__get_scheduler_fill_capacity
|
|
9
9
|
- mcp__sellable__refresh_paid_inmail_credits
|
|
10
|
+
- mcp__sellable__get_subskill_asset
|
|
10
11
|
- mcp__sellable__get_auth_status
|
|
11
12
|
- mcp__sellable__start_cli_login
|
|
12
13
|
- mcp__sellable__wait_for_cli_login
|
|
@@ -151,19 +152,23 @@ explicitly stops/statuses the run, or a concrete non-scheduler blocker appears.
|
|
|
151
152
|
Do not call the goal complete or blocked only because the current state is
|
|
152
153
|
`awaiting_scheduler_after_ready_buffer`; treat it as loaded, awaiting scheduler.
|
|
153
154
|
|
|
154
|
-
Load the internal workflow prompt
|
|
155
|
+
Load the internal workflow prompt and deterministic flow asset before taking
|
|
156
|
+
any operational step:
|
|
155
157
|
|
|
156
158
|
```text
|
|
157
159
|
get_subskill_prompt({ subskillName: "refill-sends-workflow" })
|
|
160
|
+
get_subskill_asset({ subskillName: "refill-sends-workflow", assetPath: "core/flow.v1.json" })
|
|
158
161
|
```
|
|
159
162
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
Continue both loads until `hasMore:false`; parse the flow JSON and verify
|
|
164
|
+
`workflow:"refill-sends-workflow"` with a `v1` version. Then follow that
|
|
165
|
+
workflow exactly. The default path is read-only research: compute the refill
|
|
166
|
+
target plan first, resolve the route, identify the sender-relevant campaign
|
|
167
|
+
that most recently had scheduler-owned sends, read refill state for that target,
|
|
168
|
+
report the next safe step using campaign names first, and stop before mutation
|
|
169
|
+
unless the user has explicitly approved the exact workspace,
|
|
170
|
+
campaign/table/source ids, caps/dates, approval mode, expected side effects,
|
|
171
|
+
and stop/rollback condition.
|
|
167
172
|
|
|
168
173
|
Immediately after loading the internal workflow, call
|
|
169
174
|
`get_refill_target_plan`. The target plan is the canonical first fact receipt:
|
|
@@ -223,6 +228,20 @@ threshold, below-threshold paid-InMail facts fall back to an existing connection
|
|
|
223
228
|
lane, the same Sales Nav cascade campaign's connection branch, or a manual
|
|
224
229
|
continuation.
|
|
225
230
|
|
|
231
|
+
Freshness gate precedes scheduler wait: if any selected
|
|
232
|
+
`target.senderRefillPlans[].paidInmail.status` is `missing_credit_facts` or
|
|
233
|
+
`stale_credit_facts`, or the target plan contains a
|
|
234
|
+
`refresh_paid_inmail_credits` candidate, do not enter `wait_for_scheduler` even
|
|
235
|
+
when `remainingReadyOrProjectedGap:0`. Refresh the exact selected sender credit
|
|
236
|
+
facts once, rerun `get_refill_target_plan`, and only then decide whether
|
|
237
|
+
scheduler wait is the next safe action. If facts remain missing/stale after the
|
|
238
|
+
single refresh attempt, stop with a paid-InMail freshness blocker instead of
|
|
239
|
+
waiting on scheduler pickup.
|
|
240
|
+
The standalone MCP refresh surface is the scoped route
|
|
241
|
+
`/api/v3/mcp/senders/:senderId/refresh-inmail-credits` with explicit
|
|
242
|
+
`workspaceId`; do not use active-workspace mutation as the automation control
|
|
243
|
+
path.
|
|
244
|
+
|
|
226
245
|
Compact refill lessons: sender-level target plan is final truth; trust
|
|
227
246
|
`schedulerGate.sendable` and scheduler gate blockers, not raw
|
|
228
247
|
`unipileAccountStatus` labels alone; use compact prep status checks for
|
|
@@ -239,8 +258,9 @@ will try to place and does not import, approve, schedule, refresh credits, or
|
|
|
239
258
|
mutate.
|
|
240
259
|
If the target plan is complete by projected coverage, report that the selected
|
|
241
260
|
target is already filled and no-op without asking for approval. If the ready
|
|
242
|
-
buffer covers the projected gap
|
|
243
|
-
|
|
261
|
+
buffer covers the projected gap, paid InMail credit facts are fresh for every
|
|
262
|
+
selected paid-InMail lane, but scheduled coverage is still short, keep the run
|
|
263
|
+
open in a persistent read-only scheduler wait loop. Poll
|
|
244
264
|
`get_refill_target_plan` every 60-120 seconds, or on the host's next continuation
|
|
245
265
|
interval, until projected coverage fills the target, a concrete non-scheduler
|
|
246
266
|
blocker appears, or Christian explicitly asks to stop or only receive a status
|
|
@@ -3,6 +3,7 @@ name: refill-sends-workflow
|
|
|
3
3
|
description: Internal skill-led refill sends workflow for regular campaigns and evergreen campaigns.
|
|
4
4
|
visibility: internal
|
|
5
5
|
allowed-tools:
|
|
6
|
+
- mcp__sellable__get_subskill_asset
|
|
6
7
|
- mcp__sellable__get_refill_target_plan
|
|
7
8
|
- mcp__sellable__get_scheduler_fill_capacity
|
|
8
9
|
- mcp__sellable__refresh_paid_inmail_credits
|
|
@@ -79,6 +80,16 @@ open until every selected sender lane is horizon-filled by projected coverage
|
|
|
79
80
|
non-scheduler blocker appears. `awaiting_scheduler_after_ready_buffer` is an
|
|
80
81
|
in-progress wait state, not a reason to mark the goal complete or blocked.
|
|
81
82
|
|
|
83
|
+
Runtime flow asset: after loading this prompt, load
|
|
84
|
+
`get_subskill_asset({ subskillName: "refill-sends-workflow", assetPath: "core/flow.v1.json" })`
|
|
85
|
+
through MCP, continue chunks until `hasMore:false`, parse the JSON, and verify
|
|
86
|
+
`workflow:"refill-sends-workflow"` with a `v1` version before any operational
|
|
87
|
+
step. The flow asset is the deterministic gate order: target-plan read, paid
|
|
88
|
+
InMail credit freshness gate, action selection, bounded table action, scheduler
|
|
89
|
+
wait readback, then completion/blocker. If the asset cannot load or parse, stop
|
|
90
|
+
with `blocked:refill_workflow_asset_unavailable`; do not emulate it from local
|
|
91
|
+
files or memory.
|
|
92
|
+
|
|
82
93
|
1. Call `get_refill_target_plan` first. This read-only target plan is the
|
|
83
94
|
canonical opening receipt: eligible senders, selected sender-local days,
|
|
84
95
|
gross target, inferred per-sender send lane/action selections, actual sent
|
|
@@ -136,6 +147,19 @@ in-progress wait state, not a reason to mark the goal complete or blocked.
|
|
|
136
147
|
threshold, below-threshold paid-InMail facts fall back to an existing
|
|
137
148
|
connection lane, the same Sales Nav cascade campaign's connection branch, or
|
|
138
149
|
a manual continuation.
|
|
150
|
+
Freshness gate precedes scheduler wait: if any selected
|
|
151
|
+
`target.senderRefillPlans[].paidInmail.status` is `missing_credit_facts` or
|
|
152
|
+
`stale_credit_facts`, or the target plan contains a
|
|
153
|
+
`refresh_paid_inmail_credits` candidate, do not enter
|
|
154
|
+
`wait_for_scheduler` even when `remainingReadyOrProjectedGap:0`. Refresh the
|
|
155
|
+
exact selected sender credit facts once, rerun `get_refill_target_plan`, and
|
|
156
|
+
only then decide whether scheduler wait is the next safe action. If facts
|
|
157
|
+
remain missing/stale after the single refresh attempt, stop with a
|
|
158
|
+
paid-InMail freshness blocker instead of waiting on scheduler pickup.
|
|
159
|
+
The standalone MCP refresh surface is the scoped route
|
|
160
|
+
`/api/v3/mcp/senders/:senderId/refresh-inmail-credits` with explicit
|
|
161
|
+
`workspaceId`; do not use active-workspace mutation as the automation
|
|
162
|
+
control path.
|
|
139
163
|
Compact refill lessons: sender-level target plan is final truth; trust
|
|
140
164
|
`schedulerGate.sendable` and scheduler gate blockers, not raw
|
|
141
165
|
`unipileAccountStatus` labels alone; use compact prep status checks for
|
|
@@ -154,8 +178,9 @@ in-progress wait state, not a reason to mark the goal complete or blocked.
|
|
|
154
178
|
If `status:"complete"`, report the target, selected dates, sent count,
|
|
155
179
|
scheduled count, projected count, campaign ids, and no-op proof without
|
|
156
180
|
asking for approval or mutating.
|
|
157
|
-
If `remainingReadyOrProjectedGap:0` but `remainingProjectedGap>0`,
|
|
158
|
-
|
|
181
|
+
If `remainingReadyOrProjectedGap:0` but `remainingProjectedGap>0`, and paid
|
|
182
|
+
InMail credit freshness is clean for every selected paid-InMail lane, run
|
|
183
|
+
only a persistent read-only scheduler wait/reread loop; do not ask for
|
|
159
184
|
prep/import/approval. Poll `get_refill_target_plan` every 60-120 seconds, or
|
|
160
185
|
on the host's next continuation interval, until projected coverage fills,
|
|
161
186
|
a concrete non-scheduler blocker appears, or Christian explicitly asks to
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "v1.0",
|
|
3
|
+
"workflow": "refill-sends-workflow",
|
|
4
|
+
"principle": "Use the target planner and scheduler readback as canonical facts. Refresh stale paid-InMail credit facts before scheduler-wait decisions; do not change scheduler allocation logic.",
|
|
5
|
+
"requiredBootstrap": [
|
|
6
|
+
{
|
|
7
|
+
"tool": "get_subskill_prompt",
|
|
8
|
+
"requiredValues": {
|
|
9
|
+
"subskillName": "refill-sends-workflow"
|
|
10
|
+
},
|
|
11
|
+
"mustReadUntil": {
|
|
12
|
+
"hasMore": false
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"tool": "get_subskill_asset",
|
|
17
|
+
"requiredValues": {
|
|
18
|
+
"subskillName": "refill-sends-workflow",
|
|
19
|
+
"assetPath": "core/flow.v1.json"
|
|
20
|
+
},
|
|
21
|
+
"mustReadUntil": {
|
|
22
|
+
"hasMore": false
|
|
23
|
+
},
|
|
24
|
+
"validation": {
|
|
25
|
+
"workflow": "refill-sends-workflow",
|
|
26
|
+
"versionPrefix": "v1",
|
|
27
|
+
"requiredTopLevelKeys": [
|
|
28
|
+
"states",
|
|
29
|
+
"gateOrder",
|
|
30
|
+
"yoloMode"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"gateOrder": [
|
|
36
|
+
"target_plan_read",
|
|
37
|
+
"paid_inmail_credit_freshness_gate",
|
|
38
|
+
"target_action_selection",
|
|
39
|
+
"bounded_table_action",
|
|
40
|
+
"scheduler_wait_readback",
|
|
41
|
+
"completion_or_blocker"
|
|
42
|
+
],
|
|
43
|
+
"sharedFacts": [
|
|
44
|
+
"workspaceId",
|
|
45
|
+
"senderId",
|
|
46
|
+
"campaignId",
|
|
47
|
+
"tableId",
|
|
48
|
+
"columnId",
|
|
49
|
+
"actionType",
|
|
50
|
+
"targetDate",
|
|
51
|
+
"untilDate",
|
|
52
|
+
"targetShapeRevision",
|
|
53
|
+
"stateRevision",
|
|
54
|
+
"paidInmail.status",
|
|
55
|
+
"paidInmail.available",
|
|
56
|
+
"paidInmail.threshold",
|
|
57
|
+
"paidInmail.maxStalenessSeconds",
|
|
58
|
+
"remainingProjectedGap",
|
|
59
|
+
"remainingReadyOrProjectedGap",
|
|
60
|
+
"readyToSchedule",
|
|
61
|
+
"scheduled",
|
|
62
|
+
"projected"
|
|
63
|
+
],
|
|
64
|
+
"states": [
|
|
65
|
+
{
|
|
66
|
+
"id": "target_plan_read",
|
|
67
|
+
"description": "Read the canonical target plan before any mutation or wait loop.",
|
|
68
|
+
"allowedTools": [
|
|
69
|
+
"get_refill_target_plan"
|
|
70
|
+
],
|
|
71
|
+
"requiredOutput": [
|
|
72
|
+
"target.eligibleSenderLedger",
|
|
73
|
+
"target.senderRefillPlans",
|
|
74
|
+
"target.globalActionQueue",
|
|
75
|
+
"targetShapeRevision",
|
|
76
|
+
"stateRevision"
|
|
77
|
+
],
|
|
78
|
+
"transitions": [
|
|
79
|
+
{
|
|
80
|
+
"when": "status == complete",
|
|
81
|
+
"to": "completion_or_blocker"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"when": "any selected senderRefillPlans[].paidInmail.status is missing_credit_facts or stale_credit_facts",
|
|
85
|
+
"to": "paid_inmail_credit_freshness_gate"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"when": "globalActionQueue or actionCandidates contains refresh_paid_inmail_credits",
|
|
89
|
+
"to": "paid_inmail_credit_freshness_gate"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"otherwise": "target_action_selection"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "paid_inmail_credit_freshness_gate",
|
|
98
|
+
"description": "Make paid-InMail credit freshness deterministic before trusting scheduler wait decisions.",
|
|
99
|
+
"allowedTools": [
|
|
100
|
+
"refresh_paid_inmail_credits",
|
|
101
|
+
"get_refill_target_plan"
|
|
102
|
+
],
|
|
103
|
+
"rules": [
|
|
104
|
+
"This gate precedes scheduler_wait_readback, even when remainingReadyOrProjectedGap is 0.",
|
|
105
|
+
"Refresh only exact sender ids present in the rendered target packet.",
|
|
106
|
+
"Refresh each selected sender at most once per refill_sends command call.",
|
|
107
|
+
"The refresh receipt must include approvalSource, workspaceId, senderId, actionKey, campaignId, tableId, columnId, threshold, maxStalenessSeconds, and targetPlanFingerprint.",
|
|
108
|
+
"After refresh, rerun get_refill_target_plan with the same workspaceId and selectors before choosing a prep, approval, source-copy, or scheduler-wait action.",
|
|
109
|
+
"If facts remain missing or stale after the single refresh attempt, stop with paid_inmail_credit_freshness_blocked rather than entering scheduler wait.",
|
|
110
|
+
"If fresh facts are below threshold, report the exact threshold blocker or same-campaign connection fallback; do not lower thresholds in yolo mode."
|
|
111
|
+
],
|
|
112
|
+
"disallowedBeforeReread": [
|
|
113
|
+
"wait_for_scheduler",
|
|
114
|
+
"start_campaign_message_preparation",
|
|
115
|
+
"confirm_lead_list",
|
|
116
|
+
"select_campaign_cells",
|
|
117
|
+
"queue_campaign_cells",
|
|
118
|
+
"start_campaign"
|
|
119
|
+
],
|
|
120
|
+
"transitions": [
|
|
121
|
+
{
|
|
122
|
+
"when": "post_refresh_target_plan has no missing/stale paid-InMail facts for selected lanes",
|
|
123
|
+
"to": "target_action_selection"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"when": "post_refresh_target_plan still has missing/stale paid-InMail facts",
|
|
127
|
+
"to": "completion_or_blocker"
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"id": "target_action_selection",
|
|
133
|
+
"description": "Choose only the globally ranked planner primitive after freshness gates pass.",
|
|
134
|
+
"allowedTools": [
|
|
135
|
+
"get_refill_target_plan",
|
|
136
|
+
"get_campaign_refill_state",
|
|
137
|
+
"get_scheduler_fill_capacity"
|
|
138
|
+
],
|
|
139
|
+
"rules": [
|
|
140
|
+
"Use target.globalActionQueue[0] as the only cross-sender yolo action.",
|
|
141
|
+
"manualAlternates are not yolo actions.",
|
|
142
|
+
"If projected coverage is complete, no-op.",
|
|
143
|
+
"If remainingReadyOrProjectedGap is 0 and remainingProjectedGap is positive, scheduler wait is allowed only after paid_inmail_credit_freshness_gate is clean.",
|
|
144
|
+
"If ready buffer is short, choose bounded existing-row prep, bounded approval, same-source copy, or provider-aligned source-more according to the planner."
|
|
145
|
+
],
|
|
146
|
+
"transitions": [
|
|
147
|
+
{
|
|
148
|
+
"when": "next action is wait_for_scheduler and paid freshness gate is clean",
|
|
149
|
+
"to": "scheduler_wait_readback"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"when": "next action is a bounded table/source/prep/approval primitive",
|
|
153
|
+
"to": "bounded_table_action"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"when": "no safe action remains or non-scheduler blocker exists",
|
|
157
|
+
"to": "completion_or_blocker"
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"id": "bounded_table_action",
|
|
163
|
+
"description": "Run one safe primitive, then reread the target plan.",
|
|
164
|
+
"allowedTools": [
|
|
165
|
+
"start_campaign_message_preparation",
|
|
166
|
+
"get_campaign_message_preparation_status",
|
|
167
|
+
"import_leads",
|
|
168
|
+
"wait_for_lead_list_ready",
|
|
169
|
+
"confirm_lead_list",
|
|
170
|
+
"select_campaign_cells",
|
|
171
|
+
"queue_campaign_cells",
|
|
172
|
+
"wait_for_campaign_processing",
|
|
173
|
+
"get_refill_target_plan"
|
|
174
|
+
],
|
|
175
|
+
"rules": [
|
|
176
|
+
"One primitive per loop.",
|
|
177
|
+
"No broad approval.",
|
|
178
|
+
"No scheduler writes.",
|
|
179
|
+
"Reread get_refill_target_plan before any second primitive.",
|
|
180
|
+
"Continue while targetShapeRevision is stable and projected coverage progresses."
|
|
181
|
+
],
|
|
182
|
+
"transitions": [
|
|
183
|
+
{
|
|
184
|
+
"after": "terminal primitive result and target reread",
|
|
185
|
+
"to": "target_plan_read"
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"id": "scheduler_wait_readback",
|
|
191
|
+
"description": "Read-only scheduler polling after rows are ready and paid-credit facts are fresh.",
|
|
192
|
+
"allowedTools": [
|
|
193
|
+
"get_refill_target_plan",
|
|
194
|
+
"get_scheduler_fill_capacity"
|
|
195
|
+
],
|
|
196
|
+
"entryConditions": [
|
|
197
|
+
"remainingReadyOrProjectedGap == 0",
|
|
198
|
+
"remainingProjectedGap > 0",
|
|
199
|
+
"no selected paid-InMail lane has missing_credit_facts or stale_credit_facts",
|
|
200
|
+
"no refresh_paid_inmail_credits action remains in target.globalActionQueue or actionCandidates"
|
|
201
|
+
],
|
|
202
|
+
"rules": [
|
|
203
|
+
"Poll every 60-120 seconds or on the host continuation interval.",
|
|
204
|
+
"Stop waiting when projected coverage fills the target.",
|
|
205
|
+
"Leave wait if a concrete non-scheduler blocker appears.",
|
|
206
|
+
"Do not mark complete or blocked only because awaiting_scheduler_after_ready_buffer persists."
|
|
207
|
+
],
|
|
208
|
+
"transitions": [
|
|
209
|
+
{
|
|
210
|
+
"when": "projected coverage fills target",
|
|
211
|
+
"to": "completion_or_blocker"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"when": "paid facts become missing/stale again",
|
|
215
|
+
"to": "paid_inmail_credit_freshness_gate"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"when": "ready buffer no longer covers gap",
|
|
219
|
+
"to": "target_action_selection"
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"id": "completion_or_blocker",
|
|
225
|
+
"description": "Report exact no-op, completion proof, or blocker with ids and revisions.",
|
|
226
|
+
"allowedTools": [
|
|
227
|
+
"get_refill_target_plan",
|
|
228
|
+
"get_campaign_refill_state",
|
|
229
|
+
"get_scheduler_fill_capacity"
|
|
230
|
+
],
|
|
231
|
+
"terminal": true
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
"yoloMode": {
|
|
235
|
+
"approvalSource": "explicit_yolo_flag",
|
|
236
|
+
"autoExecutableActions": [
|
|
237
|
+
"refresh_paid_inmail_credits",
|
|
238
|
+
"start_campaign_message_preparation",
|
|
239
|
+
"same_source_copy",
|
|
240
|
+
"bounded_generated_message_approval",
|
|
241
|
+
"wait_for_scheduler"
|
|
242
|
+
],
|
|
243
|
+
"neverAutoExecute": [
|
|
244
|
+
"lower_paid_inmail_threshold",
|
|
245
|
+
"create_campaign",
|
|
246
|
+
"create_connection_campaign",
|
|
247
|
+
"switch_source_family",
|
|
248
|
+
"direct_scheduler_write",
|
|
249
|
+
"start_unselected_campaign",
|
|
250
|
+
"archive_or_delete"
|
|
251
|
+
],
|
|
252
|
+
"driftStops": [
|
|
253
|
+
"workspaceId",
|
|
254
|
+
"sender set",
|
|
255
|
+
"campaignId",
|
|
256
|
+
"tableId",
|
|
257
|
+
"columnId",
|
|
258
|
+
"actionType",
|
|
259
|
+
"targetDate",
|
|
260
|
+
"untilDate",
|
|
261
|
+
"targetShapeRevision",
|
|
262
|
+
"paid-InMail threshold feasibility",
|
|
263
|
+
"side-effect class"
|
|
264
|
+
]
|
|
265
|
+
},
|
|
266
|
+
"verification": {
|
|
267
|
+
"requiredProof": [
|
|
268
|
+
"final get_refill_target_plan",
|
|
269
|
+
"projected coverage sent + scheduled",
|
|
270
|
+
"scheduler-owned cells with non-null scheduledFor when claiming scheduled",
|
|
271
|
+
"paid-InMail freshness status clean before scheduler wait",
|
|
272
|
+
"refresh receipt for each refreshed sender"
|
|
273
|
+
],
|
|
274
|
+
"notCompletionProof": [
|
|
275
|
+
"prepared rows alone",
|
|
276
|
+
"approved rows alone",
|
|
277
|
+
"ready-to-schedule rows alone",
|
|
278
|
+
"awaiting_scheduler_after_ready_buffer"
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
}
|