@oxygen-agent/cli 1.146.1 → 1.160.18

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.
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Multichannel sequence DSL — the shared contract validated identically by CLI,
3
+ * MCP, API, and web. A sequence is an ordered list of steps applied to each
4
+ * enrolled lead, spanning LinkedIn (executed natively by the sequencer dispatch
5
+ * engine) and email (native send through a mailbox, or delegated to a bound
6
+ * Instantly campaign). Three control kinds make it cross-channel:
7
+ *
8
+ * - wait_for_signal — gate until a provider signal fires (LinkedIn connection
9
+ * accepted, email opened/replied, ...) or timeout_days elapse; generalizes
10
+ * the LinkedIn-only wait_for_connection gate.
11
+ * - branch — evaluate a condition over the signals an enrollment has
12
+ * accumulated and jump to a target step (then) or fall through (else).
13
+ * - stop — explicit terminal step, the only way to give a branch's two arms
14
+ * genuinely distinct endings.
15
+ *
16
+ * A LinkedIn-only sequence (channels = ['linkedin'], no email steps) is exactly
17
+ * the behavior of the original LinkedIn sequencer, so the same engine and tables
18
+ * back both. This module is the single home for the schema; the legacy
19
+ * `linkedin-sequences.ts` validator remains for back-compat reads of stored
20
+ * LinkedIn-only definitions.
21
+ *
22
+ * Email steps: email_send/email_reply send natively through one of the
23
+ * sequence's mailboxes (Gmail API / Microsoft Graph) — Oxygen owns the send.
24
+ * email_enroll/move/stop delegate to a bound Instantly campaign (legacy, removed
25
+ * once native send fully replaces them); Instantly owns send cadence, warmup,
26
+ * and inbox rotation while Oxygen owns the cross-channel timeline + reply
27
+ * suppression.
28
+ */
29
+ export declare const SEQUENCE_CHANNELS: readonly ["linkedin", "email"];
30
+ export type SequenceChannel = (typeof SEQUENCE_CHANNELS)[number];
31
+ /**
32
+ * Signals an enrollment accumulates from provider webhooks. wait_for_signal gates
33
+ * and signal-branch conditions read these. linkedin_connected also advances the
34
+ * legacy wait_for_connection gate. (The lead's current connection *degree* is NOT
35
+ * a signal — it's resolved on demand by the dispatcher via a connection branch,
36
+ * `condition: "already_connected"`.)
37
+ */
38
+ export declare const SEQUENCE_SIGNALS: readonly ["linkedin_connected", "linkedin_replied", "email_sent", "email_opened", "email_clicked", "email_replied", "email_bounced"];
39
+ export type SequenceSignal = (typeof SEQUENCE_SIGNALS)[number];
40
+ /**
41
+ * row_values keys an enrollment's email may live under, in send-precedence order.
42
+ * The dispatcher picks the FIRST present key as the recipient address; the
43
+ * enrollment-lookup queries OR across all of them (order-independent there).
44
+ * Single source of truth so the send path and the lookup path can't drift.
45
+ */
46
+ export declare const SEQUENCE_EMAIL_COLUMN_KEYS: readonly ["email", "email_address", "work_email", "primary_email", "Email"];
47
+ export declare const SEQUENCE_STEP_KINDS: readonly ["visit_profile", "invite", "wait_for_connection", "message", "inmail", "email_send", "email_reply", "email_enroll", "email_move", "email_stop", "wait", "wait_for_signal", "branch", "stop"];
48
+ export type SequenceStepKind = (typeof SEQUENCE_STEP_KINDS)[number];
49
+ export type SequenceLinkedInVisitProfileStep = {
50
+ id: string;
51
+ channel: "linkedin";
52
+ kind: "visit_profile";
53
+ };
54
+ export type SequenceLinkedInInviteStep = {
55
+ id: string;
56
+ channel: "linkedin";
57
+ kind: "invite";
58
+ /** Optional connection-request note. Supports {{column}} interpolation. ~300 char max on LinkedIn. */
59
+ note_template?: string;
60
+ };
61
+ export type SequenceLinkedInWaitForConnectionStep = {
62
+ id: string;
63
+ channel: "linkedin";
64
+ kind: "wait_for_connection";
65
+ timeout_days: number;
66
+ on_timeout: "stop" | "continue";
67
+ };
68
+ export type SequenceLinkedInMessageStep = {
69
+ id: string;
70
+ channel: "linkedin";
71
+ kind: "message";
72
+ template: string;
73
+ };
74
+ export type SequenceLinkedInInMailStep = {
75
+ id: string;
76
+ channel: "linkedin";
77
+ kind: "inmail";
78
+ subject_template: string;
79
+ template: string;
80
+ };
81
+ export type SequenceEmailEnrollStep = {
82
+ id: string;
83
+ channel: "email";
84
+ kind: "email_enroll";
85
+ /** Optional Instantly subsequence to enroll into (a specific angle); defaults to the main sequence. */
86
+ subsequence_id?: string;
87
+ };
88
+ export type SequenceEmailMoveStep = {
89
+ id: string;
90
+ channel: "email";
91
+ kind: "email_move";
92
+ /** Instantly subsequence to move the lead into (switch angle). Required. */
93
+ subsequence_id: string;
94
+ };
95
+ export type SequenceEmailStopStep = {
96
+ id: string;
97
+ channel: "email";
98
+ kind: "email_stop";
99
+ };
100
+ /**
101
+ * Native per-step email send through one of the sequence's mailboxes (Gmail API
102
+ * / Microsoft Graph). Oxygen owns the send; no Instantly campaign.
103
+ */
104
+ export type SequenceEmailSendStep = {
105
+ id: string;
106
+ channel: "email";
107
+ kind: "email_send";
108
+ /** Subject line. Supports {{column}} interpolation. */
109
+ subject_template: string;
110
+ /** Body. Supports {{column}} interpolation. */
111
+ body_template: string;
112
+ };
113
+ /** Threaded follow-up in the same email thread as the lead's prior email_send. */
114
+ export type SequenceEmailReplyStep = {
115
+ id: string;
116
+ channel: "email";
117
+ kind: "email_reply";
118
+ /** Reply body. Supports {{column}} interpolation. */
119
+ body_template: string;
120
+ };
121
+ export type SequenceWaitStep = {
122
+ id: string;
123
+ kind: "wait";
124
+ days?: number;
125
+ hours?: number;
126
+ };
127
+ export type SequenceWaitForSignalStep = {
128
+ id: string;
129
+ kind: "wait_for_signal";
130
+ signal: SequenceSignal;
131
+ /** Days to wait for the signal before acting on on_timeout. */
132
+ timeout_days: number;
133
+ /** "stop" ends the sequence on timeout; "continue" advances to the next step. Defaults to "stop". */
134
+ on_timeout: "stop" | "continue";
135
+ };
136
+ /**
137
+ * Conditions a connection branch routes on. Both need live LinkedIn access, so
138
+ * the dispatcher (not the planner) resolves them over time:
139
+ * - connection_accepted: wait until the invite is accepted (or timeout_days) then
140
+ * route accepted → then_id, timeout → else_id.
141
+ * - already_connected: resolve the lead's current 1st-degree state at entry and
142
+ * route connected → then_id, not → else_id (no waiting).
143
+ */
144
+ export declare const SEQUENCE_CONNECTION_BRANCH_CONDITIONS: readonly ["connection_accepted", "already_connected"];
145
+ export type SequenceConnectionBranchCondition = (typeof SEQUENCE_CONNECTION_BRANCH_CONDITIONS)[number];
146
+ /**
147
+ * A branch has two flavors, discriminated by the runtime type of `condition`:
148
+ * - SequenceConnectionBranchStep (string condition) — a LinkedIn connection
149
+ * branch resolved by the dispatcher over time, routed by then_id/else_id.
150
+ * - SequenceSignalBranchStep (object condition) — a synchronous expression over
151
+ * the signals the enrollment has accumulated, routed by then/else.
152
+ * Keeping the string form (rather than mapping it to a signal) is load-bearing:
153
+ * the dispatcher's degree-probe / accept-wait machinery only runs for the string
154
+ * form, and stored LinkedIn-only definitions round-trip through this validator
155
+ * unchanged.
156
+ */
157
+ export type SequenceConnectionBranchStep = {
158
+ id: string;
159
+ kind: "branch";
160
+ condition: SequenceConnectionBranchCondition;
161
+ /** connection_accepted only: days to wait for acceptance (default 14). */
162
+ timeout_days?: number;
163
+ /** Step id when TRUE (accepted / already connected). Omitted = end this path. */
164
+ then_id?: string;
165
+ /** Step id when FALSE / timeout. Omitted = stop this path. */
166
+ else_id?: string;
167
+ };
168
+ export type SequenceSignalBranchStep = {
169
+ id: string;
170
+ kind: "branch";
171
+ condition: SequenceSignalCondition;
172
+ /** Step id to jump to when the condition is true. null/omitted = fall through to the next step. */
173
+ then: string | null;
174
+ /** Step id to jump to when the condition is false. null/omitted = fall through to the next step. */
175
+ else: string | null;
176
+ };
177
+ export type SequenceBranchStep = SequenceConnectionBranchStep | SequenceSignalBranchStep;
178
+ /**
179
+ * Explicit terminal step. Ends the enrollment here regardless of any steps that
180
+ * follow in the array — the only way to give a branch's two arms genuinely
181
+ * distinct endings (e.g. a "warm" message that stops, vs an invite→wait→"cold"
182
+ * message that stops), instead of forcing both arms to fall through into the
183
+ * same trailing step.
184
+ */
185
+ export type SequenceStopStep = {
186
+ id: string;
187
+ kind: "stop";
188
+ };
189
+ /**
190
+ * A boolean expression over the signals an enrollment has accumulated. A leaf
191
+ * tests whether a signal is present (default) or absent (present:false).
192
+ */
193
+ export type SequenceSignalCondition = {
194
+ signal: SequenceSignal;
195
+ present?: boolean;
196
+ } | {
197
+ all: SequenceSignalCondition[];
198
+ } | {
199
+ any: SequenceSignalCondition[];
200
+ } | {
201
+ not: SequenceSignalCondition;
202
+ };
203
+ export type SequenceStep = SequenceLinkedInVisitProfileStep | SequenceLinkedInInviteStep | SequenceLinkedInWaitForConnectionStep | SequenceLinkedInMessageStep | SequenceLinkedInInMailStep | SequenceEmailSendStep | SequenceEmailReplyStep | SequenceEmailEnrollStep | SequenceEmailMoveStep | SequenceEmailStopStep | SequenceWaitStep | SequenceWaitForSignalStep | SequenceBranchStep | SequenceStopStep;
204
+ export type SequenceDefinition = {
205
+ steps: SequenceStep[];
206
+ };
207
+ /** Which channel a step executes on, or null for channel-agnostic control steps. */
208
+ export declare function sequenceStepChannel(step: SequenceStep): SequenceChannel | null;
209
+ /** Distinct channels a sequence touches (drives the sequence's channels[] tag). */
210
+ export declare function sequenceChannels(definition: SequenceDefinition): SequenceChannel[];
211
+ export type SequenceLintIssue = {
212
+ path: string;
213
+ message: string;
214
+ };
215
+ export type ValidateSequenceOptions = {
216
+ /** When provided, every channel-bearing step must use one of these channels. */
217
+ allowedChannels?: SequenceChannel[];
218
+ };
219
+ /**
220
+ * Validate + normalize a raw sequence definition. Assigns stable ids to steps
221
+ * that omit one (s{index}), verifies branch/gate targets resolve, and enforces
222
+ * per-step shapes + channel restrictions. Returns the normalized definition or
223
+ * throws OxygenError("invalid_sequence") with per-step issues. Pure.
224
+ */
225
+ export declare function validateSequenceDefinition(input: unknown, options?: ValidateSequenceOptions): SequenceDefinition;
226
+ /** Non-throwing variant for lint surfaces. */
227
+ export declare function lintSequenceDefinition(input: unknown, options?: ValidateSequenceOptions): SequenceLintIssue[];
228
+ /** Total delay in milliseconds a wait step introduces. */
229
+ export declare function sequenceWaitStepDelayMs(step: SequenceWaitStep): number;
230
+ /** Render a {{column}} template against a row's values (reuses the LinkedIn impl). */
231
+ export declare function renderSequenceTemplate(template: string, values: Record<string, unknown>): string;
232
+ /** Column keys referenced by {{...}} placeholders across every step's copy. */
233
+ export declare function sequenceTemplateVariables(definition: SequenceDefinition): string[];
234
+ /**
235
+ * Evaluate a condition against the set of signals an enrollment has fired.
236
+ * Used by the dispatch planner for branch steps. Pure.
237
+ */
238
+ export declare function evaluateSequenceCondition(condition: SequenceSignalCondition, firedSignals: Iterable<string>): boolean;