@oxygen-agent/cli 1.246.0 → 1.263.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +1076 -32
- package/node_modules/@oxygen/shared/dist/directory.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/directory.js +34 -0
- package/node_modules/@oxygen/shared/dist/email-tracking-token.d.ts +82 -0
- package/node_modules/@oxygen/shared/dist/email-tracking-token.js +130 -0
- package/node_modules/@oxygen/shared/dist/index.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/index.js +3 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +55 -8
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +25 -15
- package/node_modules/@oxygen/shared/dist/sequence-template.d.ts +60 -0
- package/node_modules/@oxygen/shared/dist/sequence-template.js +0 -0
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +234 -12
- package/node_modules/@oxygen/shared/dist/sequences.js +423 -28
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type RenderTemplateOptions } from "./sequence-template.js";
|
|
1
2
|
/**
|
|
2
3
|
* Multichannel sequence DSL — the shared contract validated identically by CLI,
|
|
3
4
|
* MCP, API, and web. A sequence is an ordered list of steps applied to each
|
|
@@ -67,6 +68,54 @@ export declare const SEQUENCE_EMAIL_COLUMN_KEYS: readonly ["email", "email_addre
|
|
|
67
68
|
* Returns the trimmed raw value (callers normalize/lowercase as needed) or null.
|
|
68
69
|
*/
|
|
69
70
|
export declare function recipientEmailFromRow(rowValues: Record<string, unknown> | null | undefined): string | null;
|
|
71
|
+
/**
|
|
72
|
+
* ESP (email service provider) MATCHING mode for a sequence (settings.esp_matching).
|
|
73
|
+
* Deliverability lore: a send lands better when the SENDING mailbox and the
|
|
74
|
+
* RECIPIENT sit on the same provider (Google→Google, Microsoft→Microsoft), so at
|
|
75
|
+
* mailbox-pick the dispatcher can bias rotation toward a same-ESP mailbox.
|
|
76
|
+
* - "off" — no ESP bias (default); rotation is pure LRU / domain-spread.
|
|
77
|
+
* - "prefer" — pick a same-ESP mailbox when one is available, else fall back to
|
|
78
|
+
* any sendable mailbox (never blocks a send).
|
|
79
|
+
* - "strict" — require a same-ESP mailbox; when none is available the action is
|
|
80
|
+
* DEFERRED with a visible skipped_reason rather than sent cross-provider.
|
|
81
|
+
*/
|
|
82
|
+
export declare const ESP_MATCHING_MODES: readonly ["off", "prefer", "strict"];
|
|
83
|
+
export type EspMatchingMode = (typeof ESP_MATCHING_MODES)[number];
|
|
84
|
+
export declare const DEFAULT_ESP_MATCHING_MODE: EspMatchingMode;
|
|
85
|
+
export declare function isEspMatchingMode(value: unknown): value is EspMatchingMode;
|
|
86
|
+
/**
|
|
87
|
+
* Validate settings.esp_matching before persistence. Absent/null is valid (means
|
|
88
|
+
* DEFAULT_ESP_MATCHING_MODE "off"); any other value must be one of the three
|
|
89
|
+
* modes. Pure; throws OxygenError("invalid_sequence_settings") on a bad value so
|
|
90
|
+
* the CLI / MCP / API report an identical error. Called from the tenant-db
|
|
91
|
+
* settings validator alongside the budget/prioritization checks.
|
|
92
|
+
*/
|
|
93
|
+
export declare function validateEspMatchingSetting(value: unknown): void;
|
|
94
|
+
/**
|
|
95
|
+
* The email-provider FAMILY a recipient (or sending mailbox) routes through. Kept
|
|
96
|
+
* intentionally aligned with EMAIL_MAILBOX_PROVIDERS in tenant-db so a mailbox's
|
|
97
|
+
* `provider` and an inferred recipient ESP compare directly.
|
|
98
|
+
*/
|
|
99
|
+
export declare const RECIPIENT_ESPS: readonly ["google", "microsoft"];
|
|
100
|
+
export type RecipientEsp = (typeof RECIPIENT_ESPS)[number];
|
|
101
|
+
/**
|
|
102
|
+
* Infer a recipient's ESP from its email domain, DETERMINISTICALLY, for the
|
|
103
|
+
* well-known consumer domains (gmail/googlemail → google; outlook/hotmail/live/
|
|
104
|
+
* msn → microsoft). Returns null for any other domain — the caller then resolves
|
|
105
|
+
* the ESP via a cached MX lookup (custom domains fronted by Google Workspace /
|
|
106
|
+
* Microsoft 365 aren't decidable from the domain string alone). Pure and
|
|
107
|
+
* side-effect-free so it's the single source of truth for the deterministic map.
|
|
108
|
+
*/
|
|
109
|
+
export declare function inferRecipientEsp(domain: string | null | undefined): RecipientEsp | null;
|
|
110
|
+
/**
|
|
111
|
+
* Map a domain's resolved MX exchange hostnames to an ESP family, for the MX-cache
|
|
112
|
+
* path (custom domains on Google Workspace / Microsoft 365). Google MX hosts end
|
|
113
|
+
* in `google.com` / `googlemail.com` (e.g. aspmx.l.google.com); Microsoft 365 MX
|
|
114
|
+
* hosts end in `outlook.com` / `protection.outlook.com` (e.g.
|
|
115
|
+
* acme-com.mail.protection.outlook.com). Returns null when no host matches either
|
|
116
|
+
* family. Pure so both the resolver and its tests share one mapping.
|
|
117
|
+
*/
|
|
118
|
+
export declare function espFromMxHosts(hosts: readonly string[] | null | undefined): RecipientEsp | null;
|
|
70
119
|
/**
|
|
71
120
|
* row_values keys an enrollment's phone number may live under, in
|
|
72
121
|
* send-precedence order, for WhatsApp sends. The enroll path resolves the FIRST
|
|
@@ -118,7 +167,32 @@ export type SequenceSendWindow = {
|
|
|
118
167
|
};
|
|
119
168
|
/** Base content + up to this many alternates per A/B step (base counts as variant "a"). */
|
|
120
169
|
export declare const MAX_STEP_VARIANTS = 5;
|
|
121
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Metrics a step's opt-in auto-winner (A/B auto-optimize) can decide on. `reply`
|
|
172
|
+
* is the only metric today — open/click require tracking domains (a gated,
|
|
173
|
+
* separate capability), so the reversible auto-winner ships on reply first.
|
|
174
|
+
*/
|
|
175
|
+
export declare const SEQUENCE_AUTO_OPTIMIZE_METRICS: readonly ["reply"];
|
|
176
|
+
export type SequenceAutoOptimizeMetric = (typeof SEQUENCE_AUTO_OPTIMIZE_METRICS)[number];
|
|
177
|
+
/** Default send floor per variant before the auto-winner may decide. */
|
|
178
|
+
export declare const DEFAULT_AUTO_OPTIMIZE_MIN_SENDS = 100;
|
|
179
|
+
/** Default conversion (reply) floor per variant before the auto-winner may decide. */
|
|
180
|
+
export declare const DEFAULT_AUTO_OPTIMIZE_MIN_CONVERSIONS = 5;
|
|
181
|
+
/**
|
|
182
|
+
* Opt-in auto-winner config on a variant-bearing step. When present, once EVERY
|
|
183
|
+
* still-active variant of the step has cleared BOTH thresholds
|
|
184
|
+
* (min_sends_per_variant sends AND min_conversions conversions), the worker's
|
|
185
|
+
* auto-winner pass pauses the statistically-losing variant(s) on `metric`,
|
|
186
|
+
* reversibly, stamping evidence. Absent → the step's A/B test runs forever (no
|
|
187
|
+
* auto pause). The pause is written to ox_sequencer.sequence_variant_state and
|
|
188
|
+
* can be undone by a manual reset.
|
|
189
|
+
*/
|
|
190
|
+
export type SequenceAutoOptimizeConfig = {
|
|
191
|
+
metric: SequenceAutoOptimizeMetric;
|
|
192
|
+
min_sends_per_variant: number;
|
|
193
|
+
min_conversions: number;
|
|
194
|
+
};
|
|
195
|
+
export declare const SEQUENCE_STEP_KINDS: readonly ["visit_profile", "invite", "wait_for_connection", "message", "inmail", "follow", "like_post", "comment_post", "withdraw_invite", "email_send", "email_reply", "email_enroll", "email_move", "email_stop", "whatsapp_message", "wait", "wait_for_signal", "branch", "stop"];
|
|
122
196
|
export type SequenceStepKind = (typeof SEQUENCE_STEP_KINDS)[number];
|
|
123
197
|
export type SequenceLinkedInVisitProfileStep = {
|
|
124
198
|
id: string;
|
|
@@ -139,6 +213,11 @@ export type SequenceLinkedInWaitForConnectionStep = {
|
|
|
139
213
|
timeout_days: number;
|
|
140
214
|
on_timeout: "stop" | "continue";
|
|
141
215
|
};
|
|
216
|
+
/** A file to attach to a LinkedIn message (image / document), fetched at send time. */
|
|
217
|
+
export type SequenceLinkedInAttachment = {
|
|
218
|
+
url: string;
|
|
219
|
+
name?: string;
|
|
220
|
+
};
|
|
142
221
|
export type SequenceLinkedInMessageStep = {
|
|
143
222
|
id: string;
|
|
144
223
|
channel: "linkedin";
|
|
@@ -148,6 +227,10 @@ export type SequenceLinkedInMessageStep = {
|
|
|
148
227
|
variants?: {
|
|
149
228
|
template?: string;
|
|
150
229
|
}[];
|
|
230
|
+
/** Opt-in reversible auto-winner over the variants (reply metric). */
|
|
231
|
+
auto_optimize?: SequenceAutoOptimizeConfig;
|
|
232
|
+
/** Files attached to the message (≤ a few, fetched at dispatch). */
|
|
233
|
+
attachments?: SequenceLinkedInAttachment[];
|
|
151
234
|
};
|
|
152
235
|
export type SequenceLinkedInInMailStep = {
|
|
153
236
|
id: string;
|
|
@@ -160,6 +243,35 @@ export type SequenceLinkedInInMailStep = {
|
|
|
160
243
|
subject_template?: string;
|
|
161
244
|
template?: string;
|
|
162
245
|
}[];
|
|
246
|
+
/** Opt-in reversible auto-winner over the variants (reply metric). */
|
|
247
|
+
auto_optimize?: SequenceAutoOptimizeConfig;
|
|
248
|
+
};
|
|
249
|
+
export type SequenceLinkedInFollowStep = {
|
|
250
|
+
id: string;
|
|
251
|
+
channel: "linkedin";
|
|
252
|
+
kind: "follow";
|
|
253
|
+
};
|
|
254
|
+
export type SequenceLinkedInLikePostStep = {
|
|
255
|
+
id: string;
|
|
256
|
+
channel: "linkedin";
|
|
257
|
+
kind: "like_post";
|
|
258
|
+
reaction?: "like" | "celebrate" | "support" | "funny" | "love" | "insightful";
|
|
259
|
+
post_recency_days?: number;
|
|
260
|
+
on_no_post?: "stop";
|
|
261
|
+
};
|
|
262
|
+
export type SequenceLinkedInCommentPostStep = {
|
|
263
|
+
id: string;
|
|
264
|
+
channel: "linkedin";
|
|
265
|
+
kind: "comment_post";
|
|
266
|
+
text_template?: string;
|
|
267
|
+
ai_prompt?: string;
|
|
268
|
+
post_recency_days?: number;
|
|
269
|
+
on_no_post?: "stop";
|
|
270
|
+
};
|
|
271
|
+
export type SequenceLinkedInWithdrawInviteStep = {
|
|
272
|
+
id: string;
|
|
273
|
+
channel: "linkedin";
|
|
274
|
+
kind: "withdraw_invite";
|
|
163
275
|
};
|
|
164
276
|
export type SequenceEmailEnrollStep = {
|
|
165
277
|
id: string;
|
|
@@ -190,13 +302,21 @@ export type SequenceEmailSendStep = {
|
|
|
190
302
|
kind: "email_send";
|
|
191
303
|
/** Subject line. Supports {{column}} interpolation. */
|
|
192
304
|
subject_template: string;
|
|
193
|
-
/** Body. Supports {{column}} interpolation. */
|
|
305
|
+
/** Body (plain text). Supports {{column}} interpolation. */
|
|
194
306
|
body_template: string;
|
|
195
|
-
/**
|
|
307
|
+
/** Optional HTML body (B-W2): renders the send as multipart/alternative. Supports {{column}}. */
|
|
308
|
+
body_html_template?: string;
|
|
309
|
+
/** Static Cc / Bcc recipients (fixed addresses, not templated). */
|
|
310
|
+
cc?: string[];
|
|
311
|
+
bcc?: string[];
|
|
312
|
+
/** A/B alternates over subject/body/html; the base is variant "a". */
|
|
196
313
|
variants?: {
|
|
197
314
|
subject_template?: string;
|
|
198
315
|
body_template?: string;
|
|
316
|
+
body_html_template?: string;
|
|
199
317
|
}[];
|
|
318
|
+
/** Opt-in reversible auto-winner over the variants (reply metric). */
|
|
319
|
+
auto_optimize?: SequenceAutoOptimizeConfig;
|
|
200
320
|
/** Per-step send window (overrides the sequence-level email_send_window). */
|
|
201
321
|
send_window?: SequenceSendWindow;
|
|
202
322
|
};
|
|
@@ -205,12 +325,20 @@ export type SequenceEmailReplyStep = {
|
|
|
205
325
|
id: string;
|
|
206
326
|
channel: "email";
|
|
207
327
|
kind: "email_reply";
|
|
208
|
-
/** Reply body. Supports {{column}} interpolation. */
|
|
328
|
+
/** Reply body (plain text). Supports {{column}} interpolation. */
|
|
209
329
|
body_template: string;
|
|
210
|
-
/**
|
|
330
|
+
/** Optional HTML reply body (B-W2): renders the reply as multipart/alternative. Supports {{column}}. */
|
|
331
|
+
body_html_template?: string;
|
|
332
|
+
/** Static Cc / Bcc recipients (fixed addresses, not templated). */
|
|
333
|
+
cc?: string[];
|
|
334
|
+
bcc?: string[];
|
|
335
|
+
/** A/B alternates over the reply body/html; the base is variant "a". */
|
|
211
336
|
variants?: {
|
|
212
337
|
body_template?: string;
|
|
338
|
+
body_html_template?: string;
|
|
213
339
|
}[];
|
|
340
|
+
/** Opt-in reversible auto-winner over the variants (reply metric). */
|
|
341
|
+
auto_optimize?: SequenceAutoOptimizeConfig;
|
|
214
342
|
/** Per-step send window (overrides the sequence-level email_send_window). */
|
|
215
343
|
send_window?: SequenceSendWindow;
|
|
216
344
|
};
|
|
@@ -230,12 +358,23 @@ export type SequenceWhatsAppMessageStep = {
|
|
|
230
358
|
variants?: {
|
|
231
359
|
template?: string;
|
|
232
360
|
}[];
|
|
361
|
+
/** Opt-in reversible auto-winner over the variants (reply metric). */
|
|
362
|
+
auto_optimize?: SequenceAutoOptimizeConfig;
|
|
233
363
|
};
|
|
234
364
|
export type SequenceWaitStep = {
|
|
235
365
|
id: string;
|
|
236
366
|
kind: "wait";
|
|
237
367
|
days?: number;
|
|
238
368
|
hours?: number;
|
|
369
|
+
/**
|
|
370
|
+
* Optional deterministic jitter: spread the resume time of otherwise-identical
|
|
371
|
+
* enrollments across a [0, jitter_hours) window so a batch enrolled at the same
|
|
372
|
+
* instant doesn't fire its next step in a detectable synchronized burst
|
|
373
|
+
* (EmailBison/Instantly "randomize delay" parity). Seeded by the enrollment id
|
|
374
|
+
* (via the shared FNV hash), so it's replayable across retries/crash-replays and
|
|
375
|
+
* previewable — never Math.random. 0/absent → no jitter.
|
|
376
|
+
*/
|
|
377
|
+
jitter_hours?: number;
|
|
239
378
|
};
|
|
240
379
|
export type SequenceWaitForSignalStep = {
|
|
241
380
|
id: string;
|
|
@@ -254,7 +393,7 @@ export type SequenceWaitForSignalStep = {
|
|
|
254
393
|
* - already_connected: resolve the lead's current 1st-degree state at entry and
|
|
255
394
|
* route connected → then_id, not → else_id (no waiting).
|
|
256
395
|
*/
|
|
257
|
-
export declare const SEQUENCE_CONNECTION_BRANCH_CONDITIONS: readonly ["connection_accepted", "already_connected"];
|
|
396
|
+
export declare const SEQUENCE_CONNECTION_BRANCH_CONDITIONS: readonly ["connection_accepted", "already_connected", "open_profile"];
|
|
258
397
|
export type SequenceConnectionBranchCondition = (typeof SEQUENCE_CONNECTION_BRANCH_CONDITIONS)[number];
|
|
259
398
|
/**
|
|
260
399
|
* A branch has two flavors, discriminated by the runtime type of `condition`:
|
|
@@ -313,7 +452,7 @@ export type SequenceSignalCondition = {
|
|
|
313
452
|
} | {
|
|
314
453
|
not: SequenceSignalCondition;
|
|
315
454
|
};
|
|
316
|
-
export type SequenceStep = SequenceLinkedInVisitProfileStep | SequenceLinkedInInviteStep | SequenceLinkedInWaitForConnectionStep | SequenceLinkedInMessageStep | SequenceLinkedInInMailStep | SequenceEmailSendStep | SequenceEmailReplyStep | SequenceEmailEnrollStep | SequenceEmailMoveStep | SequenceEmailStopStep | SequenceWhatsAppMessageStep | SequenceWaitStep | SequenceWaitForSignalStep | SequenceBranchStep | SequenceStopStep;
|
|
455
|
+
export type SequenceStep = SequenceLinkedInVisitProfileStep | SequenceLinkedInInviteStep | SequenceLinkedInWaitForConnectionStep | SequenceLinkedInMessageStep | SequenceLinkedInInMailStep | SequenceLinkedInFollowStep | SequenceLinkedInLikePostStep | SequenceLinkedInCommentPostStep | SequenceLinkedInWithdrawInviteStep | SequenceEmailSendStep | SequenceEmailReplyStep | SequenceEmailEnrollStep | SequenceEmailMoveStep | SequenceEmailStopStep | SequenceWhatsAppMessageStep | SequenceWaitStep | SequenceWaitForSignalStep | SequenceBranchStep | SequenceStopStep;
|
|
317
456
|
export type SequenceDefinition = {
|
|
318
457
|
steps: SequenceStep[];
|
|
319
458
|
};
|
|
@@ -328,6 +467,16 @@ export type SequenceLintIssue = {
|
|
|
328
467
|
export type ValidateSequenceOptions = {
|
|
329
468
|
/** When provided, every channel-bearing step must use one of these channels. */
|
|
330
469
|
allowedChannels?: SequenceChannel[];
|
|
470
|
+
/**
|
|
471
|
+
* Whether this sequence has NATIVE open/click tracking enabled (a verified
|
|
472
|
+
* tracking domain + EMAIL_TRACKING_SECRET, so the dispatcher injects a pixel +
|
|
473
|
+
* rewrites links). Default false. When true, the native-email engagement-gate
|
|
474
|
+
* guard is relaxed: email_opened/email_clicked wait/branch gates are PERMITTED
|
|
475
|
+
* because a natively-tracked send DOES accumulate those signals. When false the
|
|
476
|
+
* guard still rejects them (a native send with no tracking never fires them, so
|
|
477
|
+
* the gate would silently dead-end).
|
|
478
|
+
*/
|
|
479
|
+
nativeTrackingEnabled?: boolean;
|
|
331
480
|
};
|
|
332
481
|
/**
|
|
333
482
|
* Validate + normalize a raw sequence definition. Assigns stable ids to steps
|
|
@@ -338,11 +487,36 @@ export type ValidateSequenceOptions = {
|
|
|
338
487
|
export declare function validateSequenceDefinition(input: unknown, options?: ValidateSequenceOptions): SequenceDefinition;
|
|
339
488
|
/** Non-throwing variant for lint surfaces. */
|
|
340
489
|
export declare function lintSequenceDefinition(input: unknown, options?: ValidateSequenceOptions): SequenceLintIssue[];
|
|
341
|
-
/** Total delay in milliseconds a wait step introduces. */
|
|
490
|
+
/** Total base delay in milliseconds a wait step introduces (no jitter). */
|
|
342
491
|
export declare function sequenceWaitStepDelayMs(step: SequenceWaitStep): number;
|
|
343
|
-
/**
|
|
344
|
-
|
|
345
|
-
|
|
492
|
+
/**
|
|
493
|
+
* Deterministic jitter (ms) a wait step adds for one enrollment, in
|
|
494
|
+
* [0, jitter_hours) hours. Seeded by the enrollment id + step id through the
|
|
495
|
+
* shared FNV hash (hashVariantKey) — the SAME (enrollment, step) always resolves
|
|
496
|
+
* to the same offset, so it's replayable across retries/crash-replays and never
|
|
497
|
+
* uses Math.random. 0/absent jitter_hours → 0. Resolution is minute-grained so a
|
|
498
|
+
* batch spreads smoothly across the window rather than landing on the hour.
|
|
499
|
+
*/
|
|
500
|
+
export declare function sequenceWaitStepJitterMs(step: SequenceWaitStep, enrollmentId: string): number;
|
|
501
|
+
/**
|
|
502
|
+
* Full delay (base + deterministic jitter) a wait step introduces for one
|
|
503
|
+
* enrollment. The dispatch planner uses this to compute a resume time that
|
|
504
|
+
* spreads a same-instant batch across the jitter window.
|
|
505
|
+
*/
|
|
506
|
+
export declare function sequenceWaitStepDelayWithJitterMs(step: SequenceWaitStep, enrollmentId: string): number;
|
|
507
|
+
/**
|
|
508
|
+
* Render a sequence-copy template against a row's values. Delegates to the shared
|
|
509
|
+
* deterministic engine (sequence-template.ts): `{{column}}` substitution plus
|
|
510
|
+
* `{{column|fallback}}`, `{{RANDOM|…}}` spintax, and `{% if … %}` conditionals.
|
|
511
|
+
* Pass `{ seed }` to make spintax choices replayable across retries/crash-replays.
|
|
512
|
+
*/
|
|
513
|
+
export declare function renderSequenceTemplate(template: string, values: Record<string, unknown>, options?: RenderTemplateOptions): string;
|
|
514
|
+
/**
|
|
515
|
+
* Column keys referenced across every step's copy — including keys nested inside
|
|
516
|
+
* spintax options, inline fallbacks, and conditional conditions/branches (via the
|
|
517
|
+
* shared engine's scanner), so the start preview's variable-resolution check
|
|
518
|
+
* catches them wherever they appear.
|
|
519
|
+
*/
|
|
346
520
|
export declare function sequenceTemplateVariables(definition: SequenceDefinition): string[];
|
|
347
521
|
/**
|
|
348
522
|
* Evaluate a condition against the set of signals an enrollment has fired.
|
|
@@ -366,12 +540,60 @@ export declare function sequenceStepVariantCount(step: SequenceStep): number;
|
|
|
366
540
|
* (key, step) always resolves to the same variant — replayable, evenly
|
|
367
541
|
* distributed, and previewable in a dry run before any send. `key` is the
|
|
368
542
|
* enrollment id.
|
|
543
|
+
*
|
|
544
|
+
* When `pausedVariantIds` is supplied (the auto-winner's paused set for this
|
|
545
|
+
* step), those variants are skipped and the deterministic split is RE-NORMALIZED
|
|
546
|
+
* over the remaining active variants — so pausing a losing variant shifts its
|
|
547
|
+
* share onto the survivors, still deterministically. If every variant would be
|
|
548
|
+
* paused (never expected — the winner is never paused), it fails safe by ignoring
|
|
549
|
+
* the paused set rather than stranding the step with nothing to send.
|
|
369
550
|
*/
|
|
370
|
-
export declare function selectStepVariant(step: SequenceStep, key: string
|
|
551
|
+
export declare function selectStepVariant(step: SequenceStep, key: string, options?: {
|
|
552
|
+
pausedVariantIds?: Iterable<string>;
|
|
553
|
+
}): {
|
|
371
554
|
variantId: string;
|
|
372
555
|
index: number;
|
|
373
556
|
content: StepVariantContent;
|
|
374
557
|
};
|
|
558
|
+
/** The step's opt-in auto-winner config, or null when it carries none. */
|
|
559
|
+
export declare function stepAutoOptimizeConfig(step: SequenceStep): SequenceAutoOptimizeConfig | null;
|
|
560
|
+
/** Per-variant conversion counts the auto-winner decides over (sends + conversions on the chosen metric). */
|
|
561
|
+
export type VariantConversionStat = {
|
|
562
|
+
variantId: string;
|
|
563
|
+
sends: number;
|
|
564
|
+
conversions: number;
|
|
565
|
+
};
|
|
566
|
+
/** Stamped justification for an auto-winner pause — written onto each paused variant's state. */
|
|
567
|
+
export type VariantAutoWinnerEvidence = {
|
|
568
|
+
metric: SequenceAutoOptimizeMetric;
|
|
569
|
+
variants: {
|
|
570
|
+
variant_id: string;
|
|
571
|
+
sends: number;
|
|
572
|
+
conversions: number;
|
|
573
|
+
rate: number;
|
|
574
|
+
}[];
|
|
575
|
+
winner_id: string;
|
|
576
|
+
decided_at: string;
|
|
577
|
+
};
|
|
578
|
+
export type VariantAutoWinnerDecision = {
|
|
579
|
+
/** Variant ids to pause (every still-active variant whose rate is strictly below the winner's). */
|
|
580
|
+
pause: string[];
|
|
581
|
+
/** The winning variant id (highest conversion rate). */
|
|
582
|
+
winnerId: string;
|
|
583
|
+
/** Evidence stamped onto each paused variant. */
|
|
584
|
+
evidence: VariantAutoWinnerEvidence;
|
|
585
|
+
};
|
|
586
|
+
/**
|
|
587
|
+
* Decide the A/B auto-winner for one step from per-variant conversion counts.
|
|
588
|
+
* Pure. Returns null (no decision yet) unless: at least two variants are still
|
|
589
|
+
* active (not already paused), EVERY active variant has cleared BOTH thresholds
|
|
590
|
+
* (min_sends_per_variant AND min_conversions — the min-conversion floor), and the
|
|
591
|
+
* best conversion rate is strictly ahead of at least one other variant (a tie
|
|
592
|
+
* pauses nothing). When it decides, it pauses every active variant below the best
|
|
593
|
+
* rate and stamps evidence. Reversible: a manual reset reactivates a paused
|
|
594
|
+
* variant, and this re-decides from the survivors on the next pass.
|
|
595
|
+
*/
|
|
596
|
+
export declare function decideVariantAutoWinner(stats: VariantConversionStat[], config: SequenceAutoOptimizeConfig, decidedAt: string, alreadyPaused?: Iterable<string>): VariantAutoWinnerDecision | null;
|
|
375
597
|
/**
|
|
376
598
|
* Resolve which IANA timezone a send window evaluates in for a given lead row.
|
|
377
599
|
* "recipient" mode reads the lead's tz from `recipient_timezone_column`, falling
|