@sellable/install 0.1.326 → 0.1.327

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.
@@ -58,97 +58,359 @@ allowed-tools:
58
58
 
59
59
  # Refill Sends
60
60
 
61
- This is the concise public adapter for the typed refill contract. The backend
62
- planner, durable run, flow asset, and contract asset own allocation, retries,
63
- circuit behavior, scheduler placement, and completion rules.
64
-
65
- Compatibility triggers include `fill`, `refill sends`, `max out sends`,
66
- `load everyone up`, and `fill horizon sends`. Route every one through this same
67
- typed workflow; none authorizes a broader or legacy execution path.
68
-
69
- Measure completion from canonical projected coverage (`sent + scheduled`). The
70
- workflow does not lower paid-InMail thresholds; a threshold change remains a
71
- separate operator gate.
72
-
73
- Exact zero-shot form:
74
-
75
- `$sellable:refill-sends in sellable.dev workspace --target-date YYYY-MM-DD --yolo`
76
-
77
- Claude Code uses `/sellable:refill-sends`. Optional selectors are `--sender`,
78
- `--target-date`, `--until`, and `--yolo`.
79
-
80
- ## Bootstrap and execute
81
-
82
- 1. Call `mcp__sellable__get_auth_status({})`. If authentication is incomplete,
83
- use the returned login path and stop until verified.
84
- 2. First resolve and echo the explicit workspace and date scope. In scheduled or
85
- `--yolo` mode, a missing `workspaceId` is `WORKSPACE_REQUIRED`; never change
86
- shared active-workspace state to guess. Echo `workspaceId`, workspace name,
87
- `targetDate` or other date selector, sender selectors, action selectors, and
88
- approval mode before continuing.
89
- 3. Load
90
- `mcp__sellable__get_subskill_prompt({ subskillName: "refill-sends-workflow" })`
91
- until `hasMore:false`.
92
- 4. Load
93
- `mcp__sellable__get_subskill_asset({ subskillName: "refill-sends-workflow", assetPath: "core/flow.v1.json" })`
94
- until `hasMore:false`.
95
- 5. Load
96
- `mcp__sellable__get_subskill_asset({ subskillName: "refill-sends-workflow", assetPath: "core/contract.v2.json" })`
97
- until `hasMore:false`. Validate `contractVersion`, `cacheVersion`, and
98
- `contractHash` against the flow and generated metadata. Stop with
99
- `unsupported_refill_contract_major` or `refill_contract_hash_mismatch` on
100
- incompatibility; do not emulate missing assets from repo files or memory.
101
- 6. Invoke only the public durable entrypoint:
102
- `mcp__sellable__refill_sends({ workspaceId, targetDate, untilDate, horizonSendDays, senderIds, senderNames, actionTypes, yolo, executionMode, requireWorkspace:true })`.
103
- Preserve the exact scope and approval echo returned by the tool on resume.
104
-
105
- ## Operator packet
106
-
107
- Always render the normal bounded approval packet, even under `--yolo`. Yolo is
108
- bounded auto-accept of that packet; it is not broader authority. Use campaign
109
- and sender names first, with IDs secondary. Preserve these six labels exactly:
110
-
111
- - Need to prepare
112
- - Goal
113
- - Already sent
114
- - Scheduled
115
- - Ready and waiting to be scheduled
116
- - Still need
117
-
118
- Show `targetShapeRevision`, `stateRevision`, action key, exact target date,
119
- side-effect class, expected targets, placement cap, and approval expiry. The
120
- public tool runs one packet-named primitive, records its receipt, rereads
121
- authoritative state, and replans. Keep any existing Codex goal open through
122
- settling; a skill cannot create `/goal` by itself.
123
-
124
- ## Safety and terminal truth
125
-
126
- The generated contract is authoritative. Never direct-send, raw-write scheduler
127
- fields, create/archive/delete campaigns, lower thresholds, switch an unselected
128
- source family, mutate sender configuration, or start an unapproved campaign.
129
- Paused campaign start and workspace-wide scheduler placement remain separately
130
- named, product-gated side effects. Sellable MCP receipts are the proof surface;
131
- never use Prisma, SQL, direct database access, or production scripts as proof.
132
-
133
- Render typed terminal truth name-first. Success is `projected_full`. Otherwise
134
- report the concrete blocker or `deadline_reached`, `iteration_limit`, or
135
- `operator_stopped` with target-specific evidence. A ready buffer or
136
- `awaiting_scheduler_after_ready_buffer` is
137
- `ready_buffer_exists_is_not_complete`, never completion.
138
-
139
- <!-- REFILL_CONTRACT_GENERATED:START -->
140
- ## Generated refill contract metadata
141
-
142
- contractVersion: 2.0.0
143
- cacheVersion: refill-contract-v1
144
- contractHash: e54ba0f80bc534fd70fd8c10c1caab109fe9a91c205cfc423f5f6735cd53fdc1
61
+ Use this public command wrapper for plain operator requests such as "fill",
62
+ "refill sends", "max out sends", "load everyone up", or "fill horizon sends".
63
+
64
+ Host command names:
65
+
66
+ - Claude Code: `/sellable:refill-sends`
67
+ - Codex: `$sellable:refill-sends`
68
+
69
+ Accepted invocation flags in the same user request:
70
+
71
+ - `--yolo`: auto-accept the rendered bounded refill packet after the required
72
+ fresh state reread.
73
+ - `workspaceId: <id>`: required for scheduled automation and `--yolo`; pass it
74
+ through on every refill MCP tool call instead of relying on shared config
75
+ state.
76
+ - `--sender <name-or-id>`: scope to a specific sender; repeat for multiple
77
+ senders.
78
+ - `--until <YYYY-MM-DD>`: fill through that sender-local date, inclusive,
79
+ instead of the default scheduler-forward 48-hour target window.
80
+ - `--target-date <YYYY-MM-DD>`: fill only that sender-local date. This means
81
+ prepare rows for scheduler-fillable slots on that date, not every remaining
82
+ daily-limit slot and not an inclusive through-date.
83
+ - `senderIds: <id>, <id>` or `senderNames: <name>, <name>`: explicit selector
84
+ alternatives when the host preserves natural-language arguments better than
85
+ shell-style flags.
86
+ - `untilDate: YYYY-MM-DD`: explicit date selector alternative when the host
87
+ preserves natural-language arguments better than shell-style flags.
88
+ - `targetDate: YYYY-MM-DD`: exact-date selector alternative when the host
89
+ preserves natural-language arguments better than shell-style flags.
90
+
91
+ When the host can call typed MCP tools, start with:
92
+
93
+ ```text
94
+ refill_sends({ yolo?: boolean, executionMode?: "manual" | "scheduled" | "yolo", requireWorkspace?: boolean, workspaceId?: string, senders?: string[], senderIds?: string[], senderNames?: string[], horizonSendDays?: number, untilDate?: "YYYY-MM-DD", targetDate?: "YYYY-MM-DD" })
95
+ ```
96
+
97
+ That command helper normalizes arguments and returns the execution contract. In
98
+ non-yolo mode it does not mutate. In `--yolo`, it may execute exactly one safe
99
+ bounded primitive from the fresh `target.globalActionQueue[0]`, then reread and
100
+ return the new target plan; currently safe primitives are paid-credit refresh,
101
+ existing-row message preparation, generated-message approval, receipt-proven
102
+ same-source row copy, and read-only wait rereads. Same-source copy/source
103
+ fallback is safe only after receipt-proven exhaustion:
104
+ `hasMoreFrontierRows:false`, zero `approvalCandidates`, no `stuckActiveCells`,
105
+ and no non-terminal `approvedNotDispatched` work. It does not run unbounded approval, lower
106
+ paid-InMail thresholds, switch source families, create campaigns, launch, send,
107
+ or write scheduler rows. Continue with the workflow below for route selection,
108
+ state rereads, approval gating, source import, preparation, and bounded
109
+ approval.
110
+
111
+ ## Workspace Contract
112
+
113
+ First resolve the target workspace id from the user's request, automation config,
114
+ or install-time workspace mapping. Pass `workspaceId` on every scheduled or
115
+ `--yolo` refill tool call, including setup/read calls such as
116
+ `refill_sends`, `get_refill_target_plan`, `list_senders`,
117
+ `get_sender_routing`, `resolve_campaign_fill_route`,
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.
123
+
124
+ Do not solve scheduled or `--yolo` workspace uncertainty by changing the shared
125
+ active workspace. Manual interactive workspace switching remains a separate
126
+ diagnostic/setup flow, outside automation.
127
+
128
+ Examples:
129
+
130
+ ```text
131
+ refill_sends({ yolo:true, executionMode:"yolo", requireWorkspace:true, workspaceId:"cmlq1v8ms0000jx04ang4hi7e" })
132
+ get_refill_target_plan({ intent:"plain", approvalMode:"approve", workspaceId:"cmlq1v8ms0000jx04ang4hi7e" })
133
+ list_senders({ workspaceId:"cmlq1v8ms0000jx04ang4hi7e" })
134
+ ```
135
+
136
+ Clover scheduled automation must name the Clover workspace explicitly:
145
137
 
146
138
  ```text
147
- scope: workspace_id, target_date, sender_selectors, campaign_selectors, approval_mode, contract_identity, installed_identity
148
- gates: approval_packet, one_primitive, fresh_reread, normalized_accounting, prep_circuit
149
- release identities (independent of semantic hash): mcpPackageVersion, installPackageVersion, codexPluginVersion
150
- forbidden: direct_send, raw_scheduler_write, campaign_create, campaign_archive_delete, threshold_lowering, unselected_source_mutation, unapproved_campaign_start, sender_config_write
151
- terminal: projected_full, concrete_blocker, deadline_reached, iteration_limit, operator_stopped, capped_by_scheduler, loaded_awaiting_scheduler, lanes_exhausted, not_an_evergreen_workspace, no_refillable_campaigns
152
- ready_buffer_exists_is_not_complete
139
+ refill_sends({ yolo:false, executionMode:"scheduled", requireWorkspace:true, workspaceId:"cmlq1v8ms0000jx04ang4hi7e" })
140
+ get_refill_target_plan({ intent:"plain", approvalMode:"mark_ready", workspaceId:"cmlq1v8ms0000jx04ang4hi7e" })
153
141
  ```
154
- <!-- REFILL_CONTRACT_GENERATED:END -->
142
+
143
+ First call `get_auth_status({})`. If auth is not OK, follow the returned login
144
+ guidance before route resolution. Do not run refill research against an implicit
145
+ or guessed workspace.
146
+
147
+ Treat "refill senders", "fill senders", "load everyone up", and "max out
148
+ senders" as sender-scoped requests. The target set is senders enrolled in active
149
+ campaign-backed sequence campaigns, not the first active campaign returned by a
150
+ resolver.
151
+
152
+ Goal-mode continuation: a skill cannot create or invoke `/goal` by itself. When
153
+ this command is already running inside an active Codex goal, keep that goal open
154
+ until every selected sender lane is filled by projected coverage
155
+ (`sent + scheduled`) for the scheduler-forward target window, Christian
156
+ explicitly stops/statuses the run, or a concrete non-scheduler blocker appears.
157
+ Do not call the goal complete or blocked only because the current state is
158
+ `awaiting_scheduler_after_ready_buffer`; treat it as loaded, awaiting scheduler.
159
+
160
+ Load the internal workflow prompt and deterministic flow asset before taking
161
+ any operational step:
162
+
163
+ ```text
164
+ get_subskill_prompt({ subskillName: "refill-sends-workflow" })
165
+ get_subskill_asset({ subskillName: "refill-sends-workflow", assetPath: "core/flow.v1.json" })
166
+ ```
167
+
168
+ Continue both loads until `hasMore:false`; parse the flow JSON and verify
169
+ `workflow:"refill-sends-workflow"` with a `v1` version. Then follow that
170
+ workflow exactly. The default path is read-only research: compute the refill
171
+ target plan first, resolve the route, identify the sender-relevant campaign
172
+ that most recently had scheduler-owned sends, read refill state for that target,
173
+ report the next safe step using campaign names first, and stop before mutation
174
+ unless the user has explicitly approved the exact workspace,
175
+ campaign/table/source ids, caps/dates, approval mode, expected side effects,
176
+ and stop/rollback condition.
177
+
178
+ Immediately after loading the internal workflow, call
179
+ `get_refill_target_plan`. The target plan is the canonical first fact receipt:
180
+ eligible senders, selected sender-local days, gross target, actual sent
181
+ coverage, scheduler-owned scheduled coverage across active enrolled campaigns,
182
+ projected coverage (`sent + scheduled`), inferred per-sender send lane/action
183
+ selections, ready buffer, remaining projected gap, paid-InMail credit/threshold
184
+ feasibility, `targetShapeRevision`, and `stateRevision`. Refill target lanes are
185
+ connection invites (`send_invite`), standalone paid InMails
186
+ (`send_inmail_closed`), or unified Sales Nav cascades represented publicly as
187
+ `send_inmail_closed` with `campaignClassification:"sales_nav_cascade"`. For a
188
+ Sales Nav cascade, refill the selected campaign first; its sequence can route
189
+ prospects to Open InMail, paid InMail while fresh credits are >= 5, or
190
+ same-campaign connection fallback without asking for separate open/paid/
191
+ connection campaigns. DMs (`send_dm`) are follow-up actions, not refill horizon
192
+ target capacity, and must not be counted as refill sent, scheduled, or ready
193
+ coverage. When `actionTypes` are omitted, trust the target plan's inferred lane
194
+ rather than asking which campaign class to fill. If a stale target plan selects
195
+ `send_dm` or `send_inmail_open` as the lane, stop and re-plan with the current
196
+ planner before mutation.
197
+ Short form: trust the target plan's inferred lane when it is a connection invite,
198
+ paid-InMail refill lane, or unified Sales Nav cascade.
199
+ Connection-only evergreen campaigns with exact `["send_invite"]` sequence proof
200
+ are plain invite capacity: treat them as `selectedLane:"send_invite"` only. Do
201
+ not infer DM, InMail, Sales Nav cascade, paid-credit refresh, message
202
+ generation, follow-up, sequence mutation, launch/start, scheduling override, or
203
+ direct-send work from a connection-only lane.
204
+
205
+ Structured planner packet:
206
+
207
+ - `target.eligibleSenderLedger` is the public sender eligibility ledger.
208
+ - `target.senderRefillPlans[]` is the canonical sender-level packet; read and
209
+ display it before mutation.
210
+ - Each sender packet includes `campaignRanking.options`, `sourcePlan`,
211
+ `refillReceipt`, `nextActions`, and `manualAlternates`.
212
+ - `refillReceipt` is the public ladder receipt. It carries the selected
213
+ campaign/sender/lane summary, skipped rungs, existing-row frontier proof, and
214
+ any absolute `wait.deadlineAt`.
215
+ - Preserve these coverage labels exactly: `Need to prepare`, `Goal`,
216
+ `Already sent`, `Scheduled`, `Ready and waiting to be scheduled`, and
217
+ `Still need`.
218
+ - `target.globalActionQueue` is the only cross-sender yolo execution queue.
219
+ Execute exactly one globally ranked primitive from
220
+ `target.globalActionQueue[0]`, then rerun `get_refill_target_plan` with the
221
+ same `workspaceId` before
222
+ choosing another action.
223
+ - `manualAlternates` are not yolo actions. Threshold lowering and campaign
224
+ creation are manual continuations only.
225
+
226
+ Refill action ladder: approve generated rows only when an explicit bounded
227
+ approval gate exists, process all existing same-campaign unenriched/unprepared
228
+ rows in bounded batches before any source work, then copy bounded net-new rows
229
+ from the selected source (`selectedLeadListId`, provider, and source
230
+ fingerprint preserved), then use provider-aligned source-more. A new source or
231
+ provider switch changes the reply-rate baseline and is a manual alternate, not a
232
+ `--yolo` side effect.
233
+ Source/copy/fallback requires receipt-proven exhaustion of earlier rungs:
234
+ `existingRowFrontier.hasMoreFrontierRows:false`, zero `approvalCandidates`, no
235
+ fresh active prep, no `stuckActiveCells`, and no non-terminal
236
+ `approvedNotDispatched` rows. Treat anomalies, `stuckActiveCells`, and
237
+ non-terminal `approvedNotDispatched` as diagnose-and-report gates, not
238
+ exhaustion. Terminal `approvedNotDispatched` blockers may be reported, then the
239
+ ladder can proceed.
240
+
241
+ Run-local paid-credit guard: in `--yolo`, the `refill_sends` MCP command
242
+ automatically maintains a `refreshedPaidInmailSenderIds` set for the current
243
+ command call. If its first target plan has stale/missing paid-InMail credit
244
+ facts, it refreshes each selected sender at most once, reruns
245
+ `get_refill_target_plan`, and returns the post-refresh `targetPlan` before
246
+ choosing the next prep/source-copy/bounded-approval/read-only wait action. If fresh facts are still below
247
+ threshold, below-threshold paid-InMail facts fall back to an existing connection
248
+ lane, the same Sales Nav cascade campaign's connection branch, or a manual
249
+ continuation.
250
+
251
+ Freshness gate precedes scheduler wait: if any selected
252
+ `target.senderRefillPlans[].paidInmail.status` is `missing_credit_facts` or
253
+ `stale_credit_facts`, or the target plan contains a
254
+ `refresh_paid_inmail_credits` candidate, do not enter `wait_for_scheduler` even
255
+ when `remainingReadyOrProjectedGap:0`. Refresh the exact selected sender credit
256
+ facts once, rerun `get_refill_target_plan`, and only then decide whether
257
+ scheduler wait is the next safe action. If facts remain missing/stale after the
258
+ single refresh attempt, stop with a paid-InMail freshness blocker instead of
259
+ waiting on scheduler pickup.
260
+ The standalone MCP refresh surface is the scoped route
261
+ `/api/v3/mcp/senders/:senderId/refresh-inmail-credits` with explicit
262
+ `workspaceId`; do not use active-workspace mutation as the automation control
263
+ path.
264
+
265
+ Compact refill lessons: sender-level target plan is final truth; trust
266
+ `schedulerGate.sendable` and scheduler gate blockers, not raw
267
+ `unipileAccountStatus` labels alone; use compact prep status checks for
268
+ long-running jobs; reread target plans after prep or cancel; treat ready rows as
269
+ intermediate; avoid huge parallel target-plan reads when output is large. If a
270
+ campaign produces prepared/ready rows but sender-level projected coverage does
271
+ not move after one bounded settle loop, pivot to compact prep status or a
272
+ scheduler-proven lane. Do not keep waiting on campaign-level ready counts.
273
+
274
+ For exact-date requests, pass `targetDate` to `get_refill_target_plan`. If you
275
+ need raw proof, call the read-only `get_scheduler_fill_capacity` query for the
276
+ same sender/action/date; it tells the MCP how many cells the product scheduler
277
+ will try to place and does not import, approve, schedule, refresh credits, or
278
+ mutate.
279
+ When the refill loop has ready rows and needs scheduler pickup now, use
280
+ `run_scheduler_sweep` with the same explicit `workspaceId`; it can place cells
281
+ within existing scheduler gates and returns the receipt, but it never sends or
282
+ bypasses limits.
283
+ Scheduler-run receipt interpretation: `cellsConsidered is allocation-attempt
284
+ count`, not total ready supply, while `readyCellsFound` is ready inventory found
285
+ before prefilters. Inspect `campaignScopeSummary` before assuming the selected
286
+ refill campaign/table was included; if absent, do not infer that target was
287
+ ready-but-blocked. Interpret `prefiltered` as ready cells removed before
288
+ allocation, `skipped` as considered cells blocked by scheduler gates, and
289
+ `deferred` as considered cells waiting on windows/capacity/cooldown. For ready
290
+ closed-InMail cells with stale paid-credit prefilter/defer reasons, refresh
291
+ paid-InMail credits once through existing tools, then rerun `run_scheduler_sweep`
292
+ or read `action:"status"`; `refresh_paid_inmail_credits_then_rerun` is that
293
+ path. `wait_for_capacity_or_window` means report loaded/capped/waiting and do
294
+ not source or prep more rows; `no_ready_cells_continue_refill_prep` means return
295
+ to the refill/prep ladder. Do not treat `cellsScheduled:0` alone as failure.
296
+ If the target plan is complete by projected coverage, report that the selected
297
+ target is already filled and no-op without asking for approval. If the ready
298
+ buffer covers the projected gap, paid InMail credit facts are fresh for every
299
+ selected paid-InMail lane, but scheduled coverage is still short, keep the run
300
+ open in a persistent read-only scheduler wait loop. Poll
301
+ `get_refill_target_plan` every 60-120 seconds, or on the host's next continuation
302
+ interval, until projected coverage fills the target, a concrete non-scheduler
303
+ blocker appears, or Christian explicitly asks to stop or only receive a status
304
+ report. Treat `awaiting_scheduler_after_ready_buffer` as an in-progress wait
305
+ state, not a close-out condition.
306
+ Wait actions are gates, not competing goals. When `wait_for_active_work` or
307
+ `wait_for_scheduler` includes receipt `wait.deadlineAt`, honor that absolute
308
+ deadline; if it is expired on this call, escalate to diagnostics with the
309
+ receipt evidence instead of issuing another blind wait.
310
+ If paid InMail credit facts are stale or missing and the first target plan
311
+ contains `refresh_paid_inmail_credits`, do not present that as the operator's
312
+ next action in `--yolo`. Do not present paid-credit refresh as the next operator
313
+ action after `refill_sends` returns `autoPaidInmailRefresh` and the
314
+ post-refresh `targetPlan`. Trust `refill_sends.autoPaidInmailRefresh`: it should
315
+ show the exact sender ids refreshed once, sender-credit-cache write receipts,
316
+ and a returned post-refresh `targetPlan`. Continue from that post-refresh packet.
317
+ If paid InMail is below threshold after the fresh credit read, report the exact
318
+ campaign/table/column threshold action or same-campaign connection fallback;
319
+ `--yolo` does not lower paid-InMail thresholds or create campaigns.
320
+
321
+ If the plain route's managed waterfall targets are stale, for example skipped
322
+ targets show archived/completed shared slots or the returned targets do not cover
323
+ the named sender, immediately run the active route refetch before declaring a
324
+ sender blocked. Current dashboard-active `PAUSED` campaign-backed sequence
325
+ campaigns are start-eligible refill candidates; read refill state before deciding
326
+ whether to prep, approve, start, or skip. Do not treat them as archived inventory.
327
+
328
+ For interactive Codex or Claude Code sessions, that approval must use the
329
+ host-native structured question gate, not plain chat:
330
+
331
+ - Codex: use `request_user_input`.
332
+ - Claude Code: use `AskUserQuestion`.
333
+
334
+ The approval question must present the final refill step with exactly two
335
+ choices: `Accept` and `Decline`. Render the full operator output packet in
336
+ normal chat immediately before opening the structured approval question so it
337
+ can be displayed as Markdown. The chat packet must include workspace, sender
338
+ scope, a campaign-by-campaign table with campaign name, sender names, action,
339
+ target count/cap, source/list, and blocker/skip reason, then exact ids,
340
+ expected side effects, forbidden actions, and the stop condition. The structured
341
+ question body must be compact and refer back to the posted packet instead of
342
+ duplicating it. Treat `Accept` as permission for only the rendered target, caps,
343
+ mode, and side effects. Treat `Decline` as a hard stop with no mutation.
344
+
345
+ If Christian includes `--yolo` in the same refill request, treat that flag as
346
+ auto-accept for the rendered bounded refill packet after the required fresh state
347
+ reread. For sender-scoped language with no named senders, `--yolo` means all
348
+ eligible healthy senders enrolled in active campaign-backed sequence campaigns in
349
+ the requested `workspaceId`. Without `--yolo`, if Christian did not name senders, ask
350
+ which eligible enrolled senders to refill before choosing campaigns or mutating.
351
+
352
+ `--yolo` only covers the exact sender set, per-sender target campaigns, caps,
353
+ approval mode, and side effects in the packet. It may authorize `start_campaign`
354
+ only for selected `PAUSED`, dashboard-active, campaign-backed sequence refill
355
+ targets when the rendered packet names the exact campaign ids and start side
356
+ effect. Starting a paused campaign can let the product scheduler schedule/send
357
+ approved eligible sequence actions, so include that expected side effect in the
358
+ packet. It does not authorize starting unrelated, archived, completed, draft, or
359
+ direct campaigns, separate launch/send actions, archive or delete cleanup,
360
+ direct scheduler writes, sender reassignment, paid-InMail threshold changes,
361
+ connection campaign creation, paid-InMail credit refreshes outside the exact
362
+ rendered sender packet, or campaigns outside those
363
+ selected for the eligible sender set. Stop and re-plan if the route, sender set,
364
+ ids, caps, blockers, paid-InMail feasibility, action class, or side-effect class
365
+ drift before mutation. Sent/scheduled counts increasing toward the approved
366
+ target are expected progress:
367
+ they change `stateRevision`, not `targetShapeRevision`, and do not require a
368
+ second approval.
369
+
370
+ In `--yolo`, continue as far as the rendered packet safely allows. After each
371
+ apply/prep/source-copy/bounded-approval/read-only wait result, reread state, settle processing when needed, and move to
372
+ the next selected sender or start-eligible same-packet campaign instead of
373
+ stopping after the first partial result. If no in-packet safe action remains,
374
+ return concrete continuation options with campaign names, exact ids, which option
375
+ is still covered by the current packet, and which option needs a new approval
376
+ packet.
377
+
378
+ For `--yolo` fill/schedule requests, completion means projected saturation
379
+ (`sent + scheduled`) for the scheduler-forward target window, not merely
380
+ prepared/approved/ready rows. Maintain a target-window saturation ledger per
381
+ selected sender: selected send days, gross capacity, actual sent cells, future
382
+ scheduler-owned scheduled cells with non-null `scheduledFor`, projected count,
383
+ ready-to-schedule buffer, remaining projected gap, paid-InMail feasibility,
384
+ `targetShapeRevision`, `stateRevision`, and the next MCP primitive that can
385
+ reduce the gap. After every apply/prep/source-copy/bounded-approval/read-only wait result, wait for processing, reread
386
+ the target plan/refill state, recompute the ledger, then keep applying safe
387
+ bounded actions until projected coverage fills the target window or a concrete
388
+ non-scheduler blocker is proven.
389
+ If ready rows cover the projected gap but the scheduler has not picked them up
390
+ yet, report loaded, awaiting scheduler and run the persistent read-only
391
+ scheduler wait loop. Do not finish the refill goal, mark it complete, or mark it
392
+ blocked only because it is still `awaiting_scheduler_after_ready_buffer`; keep
393
+ waiting unless Christian stops the run or the host cannot continue.
394
+
395
+ In `--yolo`, the default fill target is the scheduler-forward 48-hour window
396
+ unless `--target-date`/`targetDate`, `--until`/`untilDate`, or an explicit
397
+ compatibility `horizonSendDays` is provided. If a target date is provided,
398
+ compute bounded gaps for only that sender-local date using scheduler-fillable
399
+ slots. If an until date is provided, compute bounded gaps through that
400
+ sender-local date inclusive, skipping no-send days and never extending beyond
401
+ that date without a new packet. For each target sender, compute the bounded gap
402
+ from that sender's healthy daily capacity, existing future scheduler-owned
403
+ scheduled sends, and rows already ready to schedule across active campaigns
404
+ enrolled with that sender. Then pick the best same-sender campaign to fill the
405
+ gap: prefer recent/future scheduler-owned sends for that sender, then strongest
406
+ recent result evidence, then source health. Do not stop
407
+ after filling only one sender when the request was sender-scoped. If a
408
+ same-source copy hits the campaign-table row cap, split the current source into
409
+ a bounded LinkedIn profile source list, confirm only that smaller list into the
410
+ same campaign, and operate on the copied review batch. Do not fall back to
411
+ on-demand campaigns or unrelated active-campaign fills.
412
+
413
+ Public concepts are regular campaign and evergreen campaign. Internal direct
414
+ campaign types are unsupported refill targets. `fill_campaign_horizon` is only a
415
+ legacy evergreen-only lower-level primitive after route and refill-state
416
+ evidence proves an evergreen target.
@@ -1,169 +0,0 @@
1
- # Hermes Slack Profile Scoping
2
-
3
- Use this when creating or updating customer Hermes profiles that need Slack
4
- access.
5
-
6
- ## Default Rule
7
-
8
- For Sellable-managed customer channels, use one dedicated Slack app/token pair
9
- per active Hermes customer profile. Run native `hermes -p <profile> gateway run`
10
- for that profile only. Do not run multiple active Hermes profile gateways
11
- against the same `SLACK_APP_TOKEN`.
12
-
13
- The customer's Slack app is bound to the exact Slack channel id and the
14
- customer's profile-local Sellable MCP config. Customer profiles must not store
15
- or read Sellable Admin MCP credentials, admin Slack app tokens, or sibling
16
- profile config.
17
-
18
- The shared dispatcher remains a fallback/research path only. Use it only when
19
- Sellable Admin explicitly owns one shared listener and route registry.
20
-
21
- The 2026-07-06 Hostinger POC proved the native Hermes listener for the
22
- `sellable-admin` profile with the existing `Sellable Reply Bot` app in
23
- `#team-chat`: a human mention produced a new Hermes session and a threaded bot
24
- reply. Treat that as proof that the gateway/listener path works for one owning
25
- profile, not as approval for multiple active customer profiles sharing one app
26
- token.
27
-
28
- The same day, the `acme` profile reused the same redacted Slack token
29
- fingerprints for an outbound-only smoke to `#sellable-acme` via:
30
-
31
- ```bash
32
- hermes --profile acme send --to slack:C0BFERDV3N0 --subject '[Hermes acme smoke]' ...
33
- ```
34
-
35
- That succeeded and is useful as an internal smoke test. It does not change the
36
- inbound listener rule: each active customer profile needs its own native Slack
37
- app token pair unless Sellable Admin deliberately chooses the dispatcher
38
- fallback.
39
-
40
- ## Which Slack Values Are Enough
41
-
42
- For outbound sends and ordinary Slack Web API calls, the important value is the
43
- profile-local `SLACK_BOT_TOKEN`.
44
-
45
- For native Hermes live listening through Slack Socket Mode, the profile also
46
- needs `SLACK_APP_TOKEN`, an app-level `xapp-...` token with `connections:write`.
47
- `SLACK_APP_ID`, `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`,
48
- `SLACK_SIGNING_SECRET`, `SLACK_VERIFICATION_TOKEN`, `SLACK_TEAM_ID`, and
49
- `SLACK_OPERATOR_EMAIL` do not replace `SLACK_APP_TOKEN`.
50
-
51
- ## Required Slack App Shape
52
-
53
- Outbound Slack sends can work with fewer permissions. Native Hermes inbound
54
- listening needs the Slack app itself to be configured for Socket Mode and event
55
- delivery:
56
-
57
- - Enable Socket Mode.
58
- - Create an app-level token with `connections:write`.
59
- - Install the bot with `app_mentions:read`, `channels:history`,
60
- `channels:read`, `chat:write`, `groups:history`, `groups:read`,
61
- `im:history`, `im:read`, `mpim:history`, `mpim:read`, and `users:read`.
62
- - Subscribe bot events for `app_mention`, `message.channels`,
63
- `message.groups`, `message.im`, and `message.mpim`.
64
- - Reinstall the app after changing scopes or event subscriptions.
65
-
66
- If the Slack app has `incoming-webhook`, Slack will ask for a webhook channel
67
- during reinstall. Pick the profile home channel. That webhook selection does
68
- not replace `SLACK_ALLOWED_CHANNELS`; keep the profile-local allowlist explicit.
69
-
70
- ## Profile Bootstrap Command
71
-
72
- Use the Sellable installer profile bootstrap:
73
-
74
- ```bash
75
- sellable hermes profile bootstrap \
76
- --profile acme \
77
- --profiles-root /srv/hermes/profiles \
78
- --workspace-id ws_acme \
79
- --workspace-name Acme \
80
- --token-file /run/secrets/sellable-acme-token \
81
- --slack-bot-token "$SLACK_BOT_TOKEN" \
82
- --slack-app-token "$SLACK_APP_TOKEN" \
83
- --slack-home-channel C0ACME12345 \
84
- --slack-home-channel-name sellable-acme \
85
- --slack-allowed-users U0OPERATOR1 \
86
- --slack-require-mention true \
87
- --json
88
- ```
89
-
90
- If `--slack-home-channel` is supplied and `--slack-allowed-channels` is omitted,
91
- the installer writes `SLACK_ALLOWED_CHANNELS=<home-channel-id>` automatically.
92
- That makes the profile fail closed to the customer channel by default.
93
-
94
- Use `--slack-allowed-channels C...,G...` only when a profile is intentionally
95
- allowed to listen in more than one channel. Pass Slack IDs, not `#channel-name`
96
- strings or wildcards.
97
-
98
- ## Resulting Profile Env
99
-
100
- For the dedicated customer Slack app model, the profile-local `.env` should
101
- contain only scoped Slack keys for that profile:
102
-
103
- ```env
104
- SLACK_BOT_TOKEN=...
105
- SLACK_APP_TOKEN=...
106
- SLACK_HOME_CHANNEL=C0ACME12345
107
- SLACK_HOME_CHANNEL_NAME=sellable-acme
108
- SLACK_ALLOWED_CHANNELS=C0ACME12345
109
- SLACK_ALLOWED_USERS=U0OPERATOR1
110
- SLACK_REQUIRE_MENTION=true
111
- ```
112
-
113
- Keep the file mode at `0600`. Do not copy raw tokens into planning docs, chat,
114
- or committed artifacts. Record token fingerprints only.
115
-
116
- The profile's Hermes MCP env must also include the profile-local Sellable
117
- config, customer workspace lock, and fail-closed guard:
118
-
119
- ```yaml
120
- SELLABLE_CONFIG_PATH: /srv/hermes/profiles/acme/sellable/config.json
121
- SELLABLE_CONFIGS_DIR: /srv/hermes/profiles/acme/sellable/configs
122
- SELLABLE_LOCK_WORKSPACE_ID: ws_acme
123
- SELLABLE_REQUIRE_WORKSPACE_LOCK: "1"
124
- ```
125
-
126
- `SELLABLE_REQUIRE_WORKSPACE_LOCK=1` means an unbound customer profile fails
127
- closed before making Sellable API calls instead of falling back to shared or
128
- admin config state. Only the `sellable-admin` profile should provision,
129
- inspect, or repair other profiles.
130
-
131
- For the shared dispatcher model, do not copy the shared `SLACK_BOT_TOKEN` or
132
- `SLACK_APP_TOKEN` into customer profile `.env` files. The dispatcher owns those
133
- tokens and route metadata decides the customer profile.
134
-
135
- ## Shared Dispatcher Ownership
136
-
137
- The shared Slack dispatcher and its route registry are fallback Sellable Admin
138
- surfaces. This customer-facing installer must not create shared route bundles or
139
- write shared listener tokens. Sellable Admin owns Slack app creation and profile
140
- provisioning, then calls this installer for profile-local Sellable runtime
141
- bootstrap inside the already-provisioned Hermes profile.
142
-
143
- ## Verification
144
-
145
- Run the installer and dispatcher UAT before publishing installer changes:
146
-
147
- ```bash
148
- npm run test:unit -- tests/install-package/agent-preferences.test.ts
149
- scripts/run-phase92-hermes-profile-bootstrap-uat.sh --mode local-temp
150
- scripts/run-phase92-hermes-profile-bootstrap-uat.sh --mode linux-shaped
151
- scripts/run-phase031-hermes-slack-dispatcher-uat.sh --mode local-fixture
152
- ```
153
-
154
- Expected proof:
155
-
156
- - profile-local Sellable config paths remain authoritative
157
- - generated profile `.env` has `SLACK_HOME_CHANNEL`
158
- - generated profile `.env` has `SLACK_ALLOWED_CHANNELS`
159
- - `acme` allowlist points at the `sellable-acme` fixture channel id
160
- - redaction scan passes
161
-
162
- ## Current Phase 03 Status
163
-
164
- Phase 03 proved the profile-scoped installer path, outbound Hermes send path,
165
- and live Slack listener for the `sellable-admin` profile. Phase 03.1 now targets
166
- dedicated native Slack apps per customer profile, profile-local Sellable MCP
167
- locks, and fail-closed customer profile behavior so managed customer channels
168
- can be onboarded through the VPS without local-only browser state, manual SSH
169
- edits, or untracked processes.