@oxygen-agent/cli 1.948.1 → 1.987.20
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/admin-primary-providers-render.js +9 -1
- package/dist/cli-values.d.ts +14 -0
- package/dist/cli-values.js +26 -0
- package/dist/command-manifest.js +6 -0
- package/dist/functions-commands.js +33 -9
- package/dist/help.d.ts +21 -0
- package/dist/help.js +94 -0
- package/dist/index.js +1493 -297
- package/dist/knowledge-repository-commands.d.ts +6 -0
- package/dist/knowledge-repository-commands.js +198 -0
- package/dist/skills.js +20 -0
- package/dist/ugc-commands.d.ts +3 -6
- package/dist/ugc-commands.js +2 -1086
- package/node_modules/@oxygen/cli-ugc/dist/commands.d.ts +3 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +1178 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +7 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +25 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.d.ts +14 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.js +5 -0
- package/node_modules/@oxygen/cli-ugc/package.json +15 -0
- package/node_modules/@oxygen/formula/dist/expression.js +14 -1
- package/node_modules/@oxygen/formula/dist/formula-functions.js +71 -1
- package/node_modules/@oxygen/formula/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/formula/dist/index.js +1 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +69 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.js +374 -0
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +27 -27
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +127 -18
- package/node_modules/@oxygen/shared/dist/column-output-fields.js +12 -4
- package/node_modules/@oxygen/shared/dist/copilot-errors.js +3 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.d.ts +19 -1
- package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.d.ts +19 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.js +26 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.js +8 -41
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.js +43 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +31 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +41 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.js +6 -0
- package/node_modules/@oxygen/shared/dist/inbox-avatar-url.d.ts +28 -0
- package/node_modules/@oxygen/shared/dist/inbox-avatar-url.js +57 -0
- package/node_modules/@oxygen/shared/dist/index.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/index.js +5 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bases.d.ts +74 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bases.js +456 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +32 -38
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +38 -41
- package/node_modules/@oxygen/shared/dist/knowledge-repository.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/knowledge-repository.js +121 -0
- package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.d.ts +20 -0
- package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.js +155 -0
- package/node_modules/@oxygen/shared/dist/langfuse.d.ts +12 -1
- package/node_modules/@oxygen/shared/dist/langfuse.js +48 -8
- package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/mailbox-import.js +53 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.js +8 -0
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +24 -0
- package/node_modules/@oxygen/shared/dist/recipes.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/recipes.js +23 -0
- package/node_modules/@oxygen/shared/dist/research-output-contract.d.ts +33 -1
- package/node_modules/@oxygen/shared/dist/research-output-contract.js +64 -2
- package/node_modules/@oxygen/shared/dist/sequence-crm-events.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequence-hubspot-sync.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +152 -2
- package/node_modules/@oxygen/shared/dist/sequences.js +304 -4
- package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.js +24 -0
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +8 -1
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +3 -1
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +6 -2
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +15 -4
- package/node_modules/@oxygen/shared/package.json +25 -0
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +15 -15
- package/package.json +6 -2
|
@@ -49,6 +49,27 @@ const MAILBOX_TENANT_HEADERS = new Set([
|
|
|
49
49
|
"microsofttenantid",
|
|
50
50
|
"tenantid",
|
|
51
51
|
]);
|
|
52
|
+
const MAILBOX_FIRST_NAME_HEADERS = new Set([
|
|
53
|
+
"first",
|
|
54
|
+
"firstname",
|
|
55
|
+
"givenname",
|
|
56
|
+
]);
|
|
57
|
+
const MAILBOX_LAST_NAME_HEADERS = new Set([
|
|
58
|
+
"familyname",
|
|
59
|
+
"last",
|
|
60
|
+
"lastname",
|
|
61
|
+
"surname",
|
|
62
|
+
]);
|
|
63
|
+
const MAILBOX_DISPLAY_NAME_HEADERS = new Set([
|
|
64
|
+
"displayname",
|
|
65
|
+
"fromname",
|
|
66
|
+
"name",
|
|
67
|
+
"sendername",
|
|
68
|
+
]);
|
|
69
|
+
const MAILBOX_SENDER_PROFILE_HEADERS = new Set([
|
|
70
|
+
"senderprofile",
|
|
71
|
+
"senderprofileid",
|
|
72
|
+
]);
|
|
52
73
|
const MAILBOX_NON_TRANSFERABLE_SECRET_HEADERS = new Set([
|
|
53
74
|
"accesstoken",
|
|
54
75
|
"applicationsecret",
|
|
@@ -328,6 +349,17 @@ function normalizeMailboxExportRow(row, index, mode) {
|
|
|
328
349
|
? "microsoft_azure"
|
|
329
350
|
: null);
|
|
330
351
|
const infrastructurePlatform = normalizeMailboxInfrastructurePlatform(infrastructurePlatformRaw, provider, index);
|
|
352
|
+
// WHO the inbox sends as. Carried through because an inbox always belongs to a
|
|
353
|
+
// sender (v1.954.0): a file that already names the person is the difference
|
|
354
|
+
// between a real sender profile and one OXYGEN guesses from the address.
|
|
355
|
+
const firstName = readMailboxIdentityName(byHeader, MAILBOX_FIRST_NAME_HEADERS, index, "first_name");
|
|
356
|
+
const lastName = readMailboxIdentityName(byHeader, MAILBOX_LAST_NAME_HEADERS, index, "last_name");
|
|
357
|
+
const displayName = readMailboxIdentityName(byHeader, MAILBOX_DISPLAY_NAME_HEADERS, index, "display_name");
|
|
358
|
+
const senderProfileId = readUniqueMailboxExportString(byHeader, MAILBOX_SENDER_PROFILE_HEADERS, index, "sender_profile_id", (value) => value.trim().toLowerCase());
|
|
359
|
+
if (senderProfileId &&
|
|
360
|
+
!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(senderProfileId)) {
|
|
361
|
+
throw new OxygenError("invalid_request", `mailboxes[${index}].sender_profile_id must be a sender profile id (UUID). List them with \`oxygen senders profiles list\`, or drop the column and let the row's first/last name name the sender.`, { exitCode: 1 });
|
|
362
|
+
}
|
|
331
363
|
return {
|
|
332
364
|
email_address: email.trim().toLowerCase(),
|
|
333
365
|
provider,
|
|
@@ -338,6 +370,10 @@ function normalizeMailboxExportRow(row, index, mode) {
|
|
|
338
370
|
? { infrastructure_platform: infrastructurePlatform }
|
|
339
371
|
: {}),
|
|
340
372
|
...(tenantId ? { tenant_id: tenantId.trim().toLowerCase() } : {}),
|
|
373
|
+
...(firstName ? { first_name: firstName } : {}),
|
|
374
|
+
...(lastName ? { last_name: lastName } : {}),
|
|
375
|
+
...(displayName ? { display_name: displayName } : {}),
|
|
376
|
+
...(senderProfileId ? { sender_profile_id: senderProfileId } : {}),
|
|
341
377
|
...(mode === "credential" &&
|
|
342
378
|
provider === "google" &&
|
|
343
379
|
distinctPasswords[0] !== undefined
|
|
@@ -345,6 +381,23 @@ function normalizeMailboxExportRow(row, index, mode) {
|
|
|
345
381
|
: {}),
|
|
346
382
|
};
|
|
347
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* One identity half off an export row. Header-bound (the From name rides an SMTP
|
|
386
|
+
* header), so a CR/LF is refused by field name rather than silently stripped — the
|
|
387
|
+
* caller is usually holding hundreds of rows and needs to know which one.
|
|
388
|
+
*/
|
|
389
|
+
function readMailboxIdentityName(byHeader, headers, index, field) {
|
|
390
|
+
const value = readUniqueMailboxExportString(byHeader, headers, index, field, (raw) => raw.trim());
|
|
391
|
+
if (!value)
|
|
392
|
+
return null;
|
|
393
|
+
if (value.length > 200) {
|
|
394
|
+
throw new OxygenError("invalid_request", `mailboxes[${index}].${field} must be at most 200 characters.`, { exitCode: 1 });
|
|
395
|
+
}
|
|
396
|
+
if (/[\r\n]/.test(value)) {
|
|
397
|
+
throw new OxygenError("invalid_request", `mailboxes[${index}].${field} must be a single line.`, { exitCode: 1 });
|
|
398
|
+
}
|
|
399
|
+
return value;
|
|
400
|
+
}
|
|
348
401
|
function normalizeMailboxExportHeader(value) {
|
|
349
402
|
return value
|
|
350
403
|
.replace(/^\uFEFF/, "")
|
|
@@ -78,6 +78,14 @@ export type PlanLimits = {
|
|
|
78
78
|
*/
|
|
79
79
|
export declare const TABLE_IMPORT_ROW_LIMIT: 3000000;
|
|
80
80
|
export { VERCEL_REQUEST_BODY_LIMIT_BYTES } from "./import-limits.js";
|
|
81
|
+
/**
|
|
82
|
+
* OXYGEN's own JSON-body ceiling for every `/api/cli/*` route (enforced by
|
|
83
|
+
* `assertCliJsonBodyWithinLimit` on the content-length header). Shared so the CLI
|
|
84
|
+
* pre-splits a row batch by measured bytes instead of learning the number from a
|
|
85
|
+
* 413. Must stay below VERCEL_REQUEST_BODY_LIMIT_BYTES, and must never be raised
|
|
86
|
+
* in the CLI alone — an older server would still 413 at the old number.
|
|
87
|
+
*/
|
|
88
|
+
export declare const MAX_CLI_JSON_BODY_BYTES = 2000000;
|
|
81
89
|
/**
|
|
82
90
|
* The per-rung limit matrix. `ai_live` deliberately equals `tool_live` at every
|
|
83
91
|
* rung — one "live actions" mental model; the per-call cost asymmetry between
|
|
@@ -21,6 +21,14 @@ export const TABLE_IMPORT_ROW_LIMIT = WORKSPACE_TABLE_CAPACITY.tableRowLimit;
|
|
|
21
21
|
// ceilings below ("how big can an import be"), but it must stay importable from a
|
|
22
22
|
// browser bundle, so it is defined in ./import-limits and re-exported here.
|
|
23
23
|
export { VERCEL_REQUEST_BODY_LIMIT_BYTES } from "./import-limits.js";
|
|
24
|
+
/**
|
|
25
|
+
* OXYGEN's own JSON-body ceiling for every `/api/cli/*` route (enforced by
|
|
26
|
+
* `assertCliJsonBodyWithinLimit` on the content-length header). Shared so the CLI
|
|
27
|
+
* pre-splits a row batch by measured bytes instead of learning the number from a
|
|
28
|
+
* 413. Must stay below VERCEL_REQUEST_BODY_LIMIT_BYTES, and must never be raised
|
|
29
|
+
* in the CLI alone — an older server would still 413 at the old number.
|
|
30
|
+
*/
|
|
31
|
+
export const MAX_CLI_JSON_BODY_BYTES = 2_000_000;
|
|
24
32
|
/**
|
|
25
33
|
* The per-rung limit matrix. `ai_live` deliberately equals `tool_live` at every
|
|
26
34
|
* rung — one "live actions" mental model; the per-call cost asymmetry between
|
|
@@ -176,7 +176,7 @@ export declare const ENRICHMENT_CREDITS: {
|
|
|
176
176
|
readonly company_enrich_typical: 99;
|
|
177
177
|
readonly web_search: 5;
|
|
178
178
|
readonly page_scrape: 40;
|
|
179
|
-
readonly linkedin_profile_scrape:
|
|
179
|
+
readonly linkedin_profile_scrape: 10;
|
|
180
180
|
};
|
|
181
181
|
/** Editable calculator default: blended credits per fully-enriched row. */
|
|
182
182
|
export declare const ENRICHMENT_CREDITS_PER_ROW_TYPICAL = 100;
|
|
@@ -163,7 +163,7 @@ export const ENRICHMENT_CREDITS = {
|
|
|
163
163
|
company_enrich_typical: 99,
|
|
164
164
|
web_search: 5, // Serper
|
|
165
165
|
page_scrape: 40, // Firecrawl (wedge price)
|
|
166
|
-
linkedin_profile_scrape:
|
|
166
|
+
linkedin_profile_scrape: 10, // Managed profile credit at $0.002 COGS, exactly 5x
|
|
167
167
|
};
|
|
168
168
|
/** Editable calculator default: blended credits per fully-enriched row. */
|
|
169
169
|
export const ENRICHMENT_CREDITS_PER_ROW_TYPICAL = SNAPSHOT_ENRICHMENT_CREDITS_PER_ROW_TYPICAL;
|
|
@@ -77,6 +77,30 @@ export declare const PRODUCT_EVENTS: {
|
|
|
77
77
|
readonly TRIAL_CANCEL_SCHEDULED: "trial_cancel_scheduled";
|
|
78
78
|
readonly TRIAL_CANCEL_RESUMED: "trial_cancel_resumed";
|
|
79
79
|
readonly TRIAL_ENDED_UNCONVERTED: "trial_ended_unconverted";
|
|
80
|
+
/**
|
|
81
|
+
* Marketing site. The public hero lookup on oxygen-agent.com: an ANONYMOUS
|
|
82
|
+
* visitor types their website and OXYGEN spends its own credits profiling it.
|
|
83
|
+
*
|
|
84
|
+
* Deliberately absent from the web's KPI_TYPE_TO_EVENT. These describe a
|
|
85
|
+
* pre-signup funnel step, not a lifecycle moment the company reports, and
|
|
86
|
+
* mapping one onto a `company_kpi_events` type would redefine a headline
|
|
87
|
+
* number without anyone editing that number's definition.
|
|
88
|
+
*
|
|
89
|
+
* Properties are enums, numbers and the domain HASH only — never the raw
|
|
90
|
+
* domain. The visitor has no account and never consented to their employer's
|
|
91
|
+
* name landing in an analytics warehouse (ADR 0021: PostHog stays sanitized).
|
|
92
|
+
* `hero_lookup_cached` is what says whether the lookup cache is paying for
|
|
93
|
+
* itself, which is the whole economics of the surface.
|
|
94
|
+
*/
|
|
95
|
+
readonly HERO_LOOKUP_STARTED: "hero_lookup_started";
|
|
96
|
+
readonly HERO_PROFILE_COMPLETED: "hero_profile_completed";
|
|
97
|
+
readonly HERO_LEADS_COMPLETED: "hero_leads_completed";
|
|
98
|
+
readonly HERO_LOOKUP_CACHED: "hero_lookup_cached";
|
|
99
|
+
readonly HERO_LOOKUP_DEGRADED: "hero_lookup_degraded";
|
|
100
|
+
/** Browser-side hero events; props are enums, booleans and counts only. */
|
|
101
|
+
readonly HERO_LOOKUP_SUBMITTED: "hero_lookup_submitted";
|
|
102
|
+
readonly HERO_LOOKUP_RENDERED: "hero_lookup_rendered";
|
|
103
|
+
readonly HERO_SIGNUP_CLICKED: "hero_signup_clicked";
|
|
80
104
|
/** Pipeline heartbeat: proves ingestion is alive when the product is quiet. */
|
|
81
105
|
readonly POSTHOG_CANARY: "posthog_canary";
|
|
82
106
|
};
|
|
@@ -77,6 +77,30 @@ export const PRODUCT_EVENTS = {
|
|
|
77
77
|
TRIAL_CANCEL_SCHEDULED: "trial_cancel_scheduled",
|
|
78
78
|
TRIAL_CANCEL_RESUMED: "trial_cancel_resumed",
|
|
79
79
|
TRIAL_ENDED_UNCONVERTED: "trial_ended_unconverted",
|
|
80
|
+
/**
|
|
81
|
+
* Marketing site. The public hero lookup on oxygen-agent.com: an ANONYMOUS
|
|
82
|
+
* visitor types their website and OXYGEN spends its own credits profiling it.
|
|
83
|
+
*
|
|
84
|
+
* Deliberately absent from the web's KPI_TYPE_TO_EVENT. These describe a
|
|
85
|
+
* pre-signup funnel step, not a lifecycle moment the company reports, and
|
|
86
|
+
* mapping one onto a `company_kpi_events` type would redefine a headline
|
|
87
|
+
* number without anyone editing that number's definition.
|
|
88
|
+
*
|
|
89
|
+
* Properties are enums, numbers and the domain HASH only — never the raw
|
|
90
|
+
* domain. The visitor has no account and never consented to their employer's
|
|
91
|
+
* name landing in an analytics warehouse (ADR 0021: PostHog stays sanitized).
|
|
92
|
+
* `hero_lookup_cached` is what says whether the lookup cache is paying for
|
|
93
|
+
* itself, which is the whole economics of the surface.
|
|
94
|
+
*/
|
|
95
|
+
HERO_LOOKUP_STARTED: "hero_lookup_started",
|
|
96
|
+
HERO_PROFILE_COMPLETED: "hero_profile_completed",
|
|
97
|
+
HERO_LEADS_COMPLETED: "hero_leads_completed",
|
|
98
|
+
HERO_LOOKUP_CACHED: "hero_lookup_cached",
|
|
99
|
+
HERO_LOOKUP_DEGRADED: "hero_lookup_degraded",
|
|
100
|
+
/** Browser-side hero events; props are enums, booleans and counts only. */
|
|
101
|
+
HERO_LOOKUP_SUBMITTED: "hero_lookup_submitted",
|
|
102
|
+
HERO_LOOKUP_RENDERED: "hero_lookup_rendered",
|
|
103
|
+
HERO_SIGNUP_CLICKED: "hero_signup_clicked",
|
|
80
104
|
/** Pipeline heartbeat: proves ingestion is alive when the product is quiet. */
|
|
81
105
|
POSTHOG_CANARY: "posthog_canary",
|
|
82
106
|
};
|
|
@@ -16,4 +16,10 @@ export type RecipePrerequisiteKind = typeof RECIPE_PREREQUISITE_KINDS[number];
|
|
|
16
16
|
export declare const RECIPE_BODY_SECTIONS: readonly ["The Play", "What You Get", "When To Run (And When Not To)", "Before You Start", "Steps", "Ready Assets", "Calibrate", "Troubleshooting", "Operator Notes", "Related"];
|
|
17
17
|
export declare const RECIPE_SLUG_PATTERN: RegExp;
|
|
18
18
|
export declare function recipeWikiSlug(recipeSlug: string): string;
|
|
19
|
+
export declare const RECIPE_KIT_MAX_STAGES = 6;
|
|
20
|
+
export declare const RECIPE_KIT_TABLE_REF_PATTERN: RegExp;
|
|
21
|
+
export declare const RECIPE_KIT_STAGE_STATUSES: readonly ["planned", "installed", "reused", "skipped_active", "blocked", "failed"];
|
|
22
|
+
export type RecipeKitStageStatus = typeof RECIPE_KIT_STAGE_STATUSES[number];
|
|
23
|
+
export type RecipeKitStatus = "not_applied" | "partial" | "applied";
|
|
24
|
+
export declare function recipeKitSourceRef(recipeSlug: string, version: number): string;
|
|
19
25
|
export declare const RECIPE_TRIAL_SAFE_PILOT_CREDIT_CAP = 100;
|
|
@@ -87,6 +87,29 @@ export const RECIPE_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,119}$/;
|
|
|
87
87
|
export function recipeWikiSlug(recipeSlug) {
|
|
88
88
|
return `playbook-${recipeSlug}`;
|
|
89
89
|
}
|
|
90
|
+
// ── Kits (ADR 0025) ─────────────────────────────────────────────────────────
|
|
91
|
+
// A recipe carries an ORDERED kit of Blueprint stages. `oxygen recipes apply`
|
|
92
|
+
// preflights every stage into one forecast and installs them all under one
|
|
93
|
+
// approval — 0 credits, no provider call, every installed Workflow disabled.
|
|
94
|
+
// A stage may bind a table created by an earlier stage through a
|
|
95
|
+
// `$stages.<index>.tables.<ref>` reference so later stages graft onto it
|
|
96
|
+
// instead of creating a second table.
|
|
97
|
+
export const RECIPE_KIT_MAX_STAGES = 6;
|
|
98
|
+
export const RECIPE_KIT_TABLE_REF_PATTERN = /^\$stages\.(\d)\.tables\.([a-z][a-z0-9_]*)$/;
|
|
99
|
+
export const RECIPE_KIT_STAGE_STATUSES = [
|
|
100
|
+
"planned",
|
|
101
|
+
"installed",
|
|
102
|
+
"reused",
|
|
103
|
+
"skipped_active",
|
|
104
|
+
"blocked",
|
|
105
|
+
"failed",
|
|
106
|
+
];
|
|
107
|
+
// page_sources ref an apply writes on the recipe's playbook page — one per
|
|
108
|
+
// recipe version, ON CONFLICT DO NOTHING, so first-apply provenance survives
|
|
109
|
+
// reapplies.
|
|
110
|
+
export function recipeKitSourceRef(recipeSlug, version) {
|
|
111
|
+
return `https://oxygen-agent.com/recipes/${recipeSlug}@v${version}#kit`;
|
|
112
|
+
}
|
|
90
113
|
// trial_safe recipes must keep their pilot path within 10% of the 7-day
|
|
91
114
|
// card-required trial's 1,000 managed credits.
|
|
92
115
|
export const RECIPE_TRIAL_SAFE_PILOT_CREDIT_CAP = 100;
|
|
@@ -14,6 +14,34 @@ export declare function deriveResearchOutputContract(prompt: string, options: {
|
|
|
14
14
|
evidenceRequired: boolean;
|
|
15
15
|
}): ResearchOutputContract | null;
|
|
16
16
|
export declare function buildResearchOutputSchema(contract: ResearchOutputContract | null): Record<string, unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Whether a found answer MUST cite evidence. Shared by the write-path normalizer
|
|
19
|
+
* (which bakes it into the derived research contract), the runner (which
|
|
20
|
+
* validates the model output against it) and the output-field reader, so none
|
|
21
|
+
* of them can disagree.
|
|
22
|
+
*
|
|
23
|
+
* Explicit "strict" always requires it. A FETCH column requires it by default —
|
|
24
|
+
* the evidence is the one page the customer pointed at, so an answer that cites
|
|
25
|
+
* nothing answered from memory — unless the author chose "estimate".
|
|
26
|
+
*/
|
|
27
|
+
export declare function researchEvidenceRequired(webSearch: unknown): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* True for a model-facing schema in the research envelope shape — the derived
|
|
30
|
+
* contract, the legacy broad schema, or a catalog template's typed answer
|
|
31
|
+
* envelope — as opposed to a hand-authored schema that replaces the envelope
|
|
32
|
+
* entirely. The output-field reader rewrites only the former into the stored
|
|
33
|
+
* cell shape (`sources` in, `citations` out); the latter is used verbatim.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isResearchEnvelopeSchema(schema: unknown): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The model-facing schema for a research column whose ANSWER is a typed object
|
|
38
|
+
* (a catalog template such as a pricing summary): the same
|
|
39
|
+
* `{answer, found, confidence, citations}` envelope the derived contract uses,
|
|
40
|
+
* with `answer` replaced by the template's own object schema. The runner still
|
|
41
|
+
* resolves `citations` to real sources on the way to the cell, so a typed
|
|
42
|
+
* extraction cannot cite a page that was never fetched either.
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildResearchOutputSchemaForAnswer(answerSchema: Record<string, unknown>): Record<string, unknown>;
|
|
17
45
|
/**
|
|
18
46
|
* The schema of the research cell as it is actually STORED — which is not the
|
|
19
47
|
* schema the model answers in.
|
|
@@ -27,7 +55,11 @@ export declare function buildResearchOutputSchema(contract: ResearchOutputContra
|
|
|
27
55
|
* `buildResearchOutputSchema` would offer `citations`, which no cell has, and
|
|
28
56
|
* hide `sources`, which every cell does.
|
|
29
57
|
*/
|
|
30
|
-
export declare function buildResearchCellSchema(contract: ResearchOutputContract | null
|
|
58
|
+
export declare function buildResearchCellSchema(contract: ResearchOutputContract | null,
|
|
59
|
+
/** A column's own model-facing schema (a catalog template's typed answer
|
|
60
|
+
* envelope). When given it replaces the derived one, so the cell shape of a
|
|
61
|
+
* typed extraction exposes `answer.plan_count`, not a string answer. */
|
|
62
|
+
modelSchemaOverride?: Record<string, unknown> | null): Record<string, unknown>;
|
|
31
63
|
/**
|
|
32
64
|
* Is this research column's output schema OWNED BY THE SERVER, i.e. re-derived
|
|
33
65
|
* from the prompt rather than authored by hand?
|
|
@@ -88,6 +88,64 @@ export function buildResearchOutputSchema(contract) {
|
|
|
88
88
|
required: ["answer", "found", "confidence", "citations"],
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Whether a found answer MUST cite evidence. Shared by the write-path normalizer
|
|
93
|
+
* (which bakes it into the derived research contract), the runner (which
|
|
94
|
+
* validates the model output against it) and the output-field reader, so none
|
|
95
|
+
* of them can disagree.
|
|
96
|
+
*
|
|
97
|
+
* Explicit "strict" always requires it. A FETCH column requires it by default —
|
|
98
|
+
* the evidence is the one page the customer pointed at, so an answer that cites
|
|
99
|
+
* nothing answered from memory — unless the author chose "estimate".
|
|
100
|
+
*/
|
|
101
|
+
export function researchEvidenceRequired(webSearch) {
|
|
102
|
+
if (!isObject(webSearch))
|
|
103
|
+
return false;
|
|
104
|
+
if (webSearch.evidenceMode === "strict")
|
|
105
|
+
return true;
|
|
106
|
+
if (webSearch.evidenceMode === "estimate")
|
|
107
|
+
return false;
|
|
108
|
+
return webSearch.source === "fetch";
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* True for a model-facing schema in the research envelope shape — the derived
|
|
112
|
+
* contract, the legacy broad schema, or a catalog template's typed answer
|
|
113
|
+
* envelope — as opposed to a hand-authored schema that replaces the envelope
|
|
114
|
+
* entirely. The output-field reader rewrites only the former into the stored
|
|
115
|
+
* cell shape (`sources` in, `citations` out); the latter is used verbatim.
|
|
116
|
+
*/
|
|
117
|
+
export function isResearchEnvelopeSchema(schema) {
|
|
118
|
+
if (!isObject(schema) || schema.type !== "object")
|
|
119
|
+
return false;
|
|
120
|
+
const properties = isObject(schema.properties) ? schema.properties : null;
|
|
121
|
+
if (!properties)
|
|
122
|
+
return false;
|
|
123
|
+
return ["answer", "found", "confidence", "citations"].every((key) => isObject(properties[key]));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The model-facing schema for a research column whose ANSWER is a typed object
|
|
127
|
+
* (a catalog template such as a pricing summary): the same
|
|
128
|
+
* `{answer, found, confidence, citations}` envelope the derived contract uses,
|
|
129
|
+
* with `answer` replaced by the template's own object schema. The runner still
|
|
130
|
+
* resolves `citations` to real sources on the way to the cell, so a typed
|
|
131
|
+
* extraction cannot cite a page that was never fetched either.
|
|
132
|
+
*/
|
|
133
|
+
export function buildResearchOutputSchemaForAnswer(answerSchema) {
|
|
134
|
+
const envelope = buildResearchOutputSchema(null);
|
|
135
|
+
const properties = isObject(envelope.properties) ? { ...envelope.properties } : {};
|
|
136
|
+
return {
|
|
137
|
+
...envelope,
|
|
138
|
+
properties: {
|
|
139
|
+
...properties,
|
|
140
|
+
answer: {
|
|
141
|
+
...answerSchema,
|
|
142
|
+
description: typeof answerSchema.description === "string"
|
|
143
|
+
? answerSchema.description
|
|
144
|
+
: "The typed answer read from the evidence; unsupported fields are null or empty.",
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
91
149
|
/**
|
|
92
150
|
* The schema of the research cell as it is actually STORED — which is not the
|
|
93
151
|
* schema the model answers in.
|
|
@@ -101,8 +159,12 @@ export function buildResearchOutputSchema(contract) {
|
|
|
101
159
|
* `buildResearchOutputSchema` would offer `citations`, which no cell has, and
|
|
102
160
|
* hide `sources`, which every cell does.
|
|
103
161
|
*/
|
|
104
|
-
export function buildResearchCellSchema(contract
|
|
105
|
-
|
|
162
|
+
export function buildResearchCellSchema(contract,
|
|
163
|
+
/** A column's own model-facing schema (a catalog template's typed answer
|
|
164
|
+
* envelope). When given it replaces the derived one, so the cell shape of a
|
|
165
|
+
* typed extraction exposes `answer.plan_count`, not a string answer. */
|
|
166
|
+
modelSchemaOverride) {
|
|
167
|
+
const modelSchema = modelSchemaOverride ?? buildResearchOutputSchema(contract);
|
|
106
168
|
const properties = isObject(modelSchema.properties) ? { ...modelSchema.properties } : {};
|
|
107
169
|
delete properties.citations;
|
|
108
170
|
return {
|
|
@@ -287,5 +287,5 @@ export declare const SEQUENCE_HUBSPOT_EVENT_SYNC_TEMPLATE_ID = "sequencer-hubspo
|
|
|
287
287
|
export declare const SEQUENCE_WORKFLOW_EVENT_SOURCE = "sequencer";
|
|
288
288
|
export declare const SEQUENCE_CONTACT_ACTIVITY_EVENT = "contact_activity";
|
|
289
289
|
export declare const SEQUENCE_HUBSPOT_LINKEDIN_IDENTITY_PROPERTY = "oxygen_linkedin_url";
|
|
290
|
-
export declare const DEFAULT_SEQUENCE_HUBSPOT_EVENT_MAPPINGS: Readonly<Record<"
|
|
290
|
+
export declare const DEFAULT_SEQUENCE_HUBSPOT_EVENT_MAPPINGS: Readonly<Record<"call_connected" | "call_no_answer" | "call_task_created" | "call_voicemail" | "crm_task_created" | "email_bounced" | "email_campaign_enrolled" | "email_campaign_moved" | "email_campaign_stopped" | "email_clicked" | "email_opened" | "email_reply_received" | "email_sent" | "email_spam_complaint" | "email_unsubscribed" | "linkedin_connection_accepted" | "linkedin_connection_sent" | "linkedin_connection_withdrawn" | "linkedin_followed" | "linkedin_inmail_sent" | "linkedin_message_sent" | "linkedin_post_commented" | "linkedin_post_liked" | "linkedin_profile_visited" | "linkedin_reply_received" | "meeting_booked" | "whatsapp_message_sent" | "whatsapp_reply_received", string>>;
|
|
291
291
|
export declare function isSequenceCrmEventKey(value: unknown): value is SequenceCrmEventKey;
|
|
@@ -17,7 +17,7 @@ export declare const HUBSPOT_DEFAULT_LEAD_STATUS_PROPERTY = "hs_lead_status";
|
|
|
17
17
|
export declare const DEFAULT_HUBSPOT_SEQUENCE_TIMELINE_EVENTS: readonly ["email_sent", "email_reply_received", "email_bounced", "linkedin_message_sent", "linkedin_inmail_sent", "linkedin_reply_received"];
|
|
18
18
|
/** Events for which the HubSpot adapter has a first-class activity object. */
|
|
19
19
|
export declare const HUBSPOT_SEQUENCE_TIMELINE_EVENT_KEYS: readonly ["email_sent", "email_reply_received", "email_bounced", "linkedin_message_sent", "linkedin_inmail_sent", "linkedin_reply_received"];
|
|
20
|
-
export declare const HUBSPOT_CLASSIFIED_REPLY_TIMELINE_EVENTS: Set<"
|
|
20
|
+
export declare const HUBSPOT_CLASSIFIED_REPLY_TIMELINE_EVENTS: Set<"call_connected" | "call_no_answer" | "call_task_created" | "call_voicemail" | "crm_task_created" | "email_bounced" | "email_campaign_enrolled" | "email_campaign_moved" | "email_campaign_stopped" | "email_clicked" | "email_opened" | "email_reply_received" | "email_sent" | "email_spam_complaint" | "email_unsubscribed" | "linkedin_connection_accepted" | "linkedin_connection_sent" | "linkedin_connection_withdrawn" | "linkedin_followed" | "linkedin_inmail_sent" | "linkedin_message_sent" | "linkedin_post_commented" | "linkedin_post_liked" | "linkedin_profile_visited" | "linkedin_reply_received" | "meeting_booked" | "whatsapp_message_sent" | "whatsapp_reply_received">;
|
|
21
21
|
export type HubSpotSequenceSyncConfig = {
|
|
22
22
|
/** Optional latest-occurrence snapshots on HubSpot contact properties. */
|
|
23
23
|
mappings: Record<SequenceCrmEventKey, string>;
|
|
@@ -108,8 +108,11 @@ export type EspMatchingMode = (typeof ESP_MATCHING_MODES)[number];
|
|
|
108
108
|
export declare const DEFAULT_ESP_MATCHING_MODE: EspMatchingMode;
|
|
109
109
|
export declare function isEspMatchingMode(value: unknown): value is EspMatchingMode;
|
|
110
110
|
/**
|
|
111
|
-
* Validate settings.esp_matching before persistence. Absent/null is valid
|
|
112
|
-
* DEFAULT_ESP_MATCHING_MODE "
|
|
111
|
+
* Validate settings.esp_matching before persistence. Absent/null is valid and
|
|
112
|
+
* means DEFAULT_ESP_MATCHING_MODE, which is "prefer" — an unset sequence still
|
|
113
|
+
* biases rotation toward a same-provider sender, so a workspace whose senders on
|
|
114
|
+
* one provider are in bad standing concentrates that provider's recipients on
|
|
115
|
+
* them until an operator sets "off". Any other value must be one of the three
|
|
113
116
|
* modes. Pure; throws OxygenError("invalid_sequence_settings") on a bad value so
|
|
114
117
|
* the CLI / MCP / API report an identical error. Called from the tenant-db
|
|
115
118
|
* settings validator alongside the budget/prioritization checks.
|
|
@@ -128,6 +131,127 @@ export declare function validateEspMatchingSetting(value: unknown): void;
|
|
|
128
131
|
*/
|
|
129
132
|
export declare const RECIPIENT_ESPS: readonly ["google", "microsoft"];
|
|
130
133
|
export type RecipientEsp = (typeof RECIPIENT_ESPS)[number];
|
|
134
|
+
/**
|
|
135
|
+
* WEIGHTED SENDER ROUTING — the object form of settings.esp_matching.
|
|
136
|
+
*
|
|
137
|
+
* The three scalar modes above answer one question ("bias toward the recipient's
|
|
138
|
+
* own provider, yes or no"), and they bake in the assumption that same-provider
|
|
139
|
+
* is always better. That assumption fails whenever one provider's senders are in
|
|
140
|
+
* bad standing: a workspace whose Google-hosted domains are refused by Gmail
|
|
141
|
+
* wants its Google-hosted RECIPIENTS served from Microsoft senders, which no
|
|
142
|
+
* scalar mode can express. "off" does not express it either, because free
|
|
143
|
+
* rotation still hands Google recipients a Google sender in proportion to the
|
|
144
|
+
* pool.
|
|
145
|
+
*
|
|
146
|
+
* So the object form states the routing directly, as SEND SHARES per recipient
|
|
147
|
+
* provider:
|
|
148
|
+
*
|
|
149
|
+
* { mode: "prefer",
|
|
150
|
+
* routes: { google: { microsoft: 100 }, microsoft: { microsoft: 100 } },
|
|
151
|
+
* exclude: [{ domain: "burned.example", from_recipients: ["google"] }] }
|
|
152
|
+
*
|
|
153
|
+
* `routes` is keyed by the RECIPIENT's resolved provider; the inner map is the
|
|
154
|
+
* SENDER provider. Weights are relative shares, so { google: 3, microsoft: 1 }
|
|
155
|
+
* and { google: 75, microsoft: 25 } are the same policy and nothing has to sum
|
|
156
|
+
* to 100. An omitted weight is zero. A bucket whose entry is explicitly present
|
|
157
|
+
* but carries no positive weight means "no opinion, rotate freely for this
|
|
158
|
+
* recipient class"; a bucket the operator never mentioned keeps the historical
|
|
159
|
+
* same-provider default, so editing Google routing cannot silently change how
|
|
160
|
+
* Microsoft recipients are served.
|
|
161
|
+
*
|
|
162
|
+
* `exclude` is a hard never, not a preference: a sender domain listed here is
|
|
163
|
+
* removed from the candidate pool for the named recipient buckets (all buckets
|
|
164
|
+
* when `from_recipients` is absent) in EVERY mode, including "off", and it
|
|
165
|
+
* survives the prefer-fallback. A deliverability guard that a matching toggle
|
|
166
|
+
* could disarm would not be a guard.
|
|
167
|
+
*
|
|
168
|
+
* The scalar modes remain valid values and are read as policies, so there is one
|
|
169
|
+
* routing code path rather than two: "prefer" becomes { mode: "prefer" } with no
|
|
170
|
+
* routes, which resolves to the same-provider default and behaves exactly as it
|
|
171
|
+
* did before.
|
|
172
|
+
*/
|
|
173
|
+
export declare const ESP_ROUTE_BUCKETS: readonly ["google", "microsoft", "unknown"];
|
|
174
|
+
/** The RECIPIENT side of a route: a matchable provider, or "unknown" when resolution failed. */
|
|
175
|
+
export type EspRouteBucket = (typeof ESP_ROUTE_BUCKETS)[number];
|
|
176
|
+
/** The SENDER side of a route: relative send shares per mailbox provider. */
|
|
177
|
+
export type EspRouteWeights = Partial<Record<RecipientEsp, number>>;
|
|
178
|
+
export type EspSenderExclusion = {
|
|
179
|
+
domain: string;
|
|
180
|
+
from_recipients?: EspRouteBucket[];
|
|
181
|
+
};
|
|
182
|
+
export type EspRoutingPolicy = {
|
|
183
|
+
mode?: EspMatchingMode;
|
|
184
|
+
routes?: Partial<Record<EspRouteBucket, EspRouteWeights>>;
|
|
185
|
+
exclude?: EspSenderExclusion[];
|
|
186
|
+
};
|
|
187
|
+
/** settings.esp_matching accepts either the legacy scalar or the routing policy. */
|
|
188
|
+
export type EspMatchingSetting = EspMatchingMode | EspRoutingPolicy;
|
|
189
|
+
/**
|
|
190
|
+
* A policy with every field present, which is what the dispatcher and the launch
|
|
191
|
+
* preview consume. Produced only by espPolicyFromSetting so both surfaces answer
|
|
192
|
+
* identically from the same stored value.
|
|
193
|
+
*/
|
|
194
|
+
export type ResolvedEspPolicy = {
|
|
195
|
+
mode: EspMatchingMode;
|
|
196
|
+
routes: Partial<Record<EspRouteBucket, EspRouteWeights>>;
|
|
197
|
+
exclude: Array<{
|
|
198
|
+
domain: string;
|
|
199
|
+
buckets: readonly EspRouteBucket[];
|
|
200
|
+
}>;
|
|
201
|
+
};
|
|
202
|
+
/** Cap on settings.esp_matching.exclude — a routing policy, not a suppression list. */
|
|
203
|
+
export declare const MAX_ESP_SENDER_EXCLUSIONS = 50;
|
|
204
|
+
/** Upper bound on a single route weight; relative shares never need more. */
|
|
205
|
+
export declare const MAX_ESP_ROUTE_WEIGHT = 1000000;
|
|
206
|
+
export declare function isEspRouteBucket(value: unknown): value is EspRouteBucket;
|
|
207
|
+
/**
|
|
208
|
+
* A sending domain as the mailbox table stores it: lowercase, no leading "@".
|
|
209
|
+
* Mirrors lower(split_part(email_address, '@', 2)) in pickEmailMailbox so an
|
|
210
|
+
* exclusion the operator typed as "@Burned.Example " still matches.
|
|
211
|
+
*/
|
|
212
|
+
export declare function normalizeEspSenderDomain(value: string): string;
|
|
213
|
+
/**
|
|
214
|
+
* LENIENT read of a persisted settings.esp_matching (never throws — a read must
|
|
215
|
+
* not break a send). Absent, null, or malformed reads as the default policy, and
|
|
216
|
+
* a malformed FIELD bails the WHOLE object rather than half-applying a routing
|
|
217
|
+
* policy, which is the readMailboxRampConfig convention in tenant-db. The strict
|
|
218
|
+
* counterpart that guards the write path is validateEspMatchingSetting.
|
|
219
|
+
*/
|
|
220
|
+
export declare function espPolicyFromSetting(value: unknown): ResolvedEspPolicy;
|
|
221
|
+
/**
|
|
222
|
+
* The effective send shares for one recipient bucket. An explicitly-present
|
|
223
|
+
* entry wins even when it is empty or all-zero (that is the operator saying
|
|
224
|
+
* "rotate freely here"); a bucket that was never mentioned keeps the historical
|
|
225
|
+
* same-provider default, and "unknown" has no same-provider to default to.
|
|
226
|
+
*/
|
|
227
|
+
export declare function espRouteWeightsFor(policy: ResolvedEspPolicy, bucket: EspRouteBucket): EspRouteWeights;
|
|
228
|
+
/**
|
|
229
|
+
* The sender providers to try, best first, for one recipient bucket — a
|
|
230
|
+
* deterministic weighted shuffle WITHOUT replacement.
|
|
231
|
+
*
|
|
232
|
+
* Returning an ORDER rather than a single draw is what makes "prefer" correct:
|
|
233
|
+
* a 90/10 Google policy over a pool holding no Google mailbox must send 100%
|
|
234
|
+
* from Microsoft, not defer and not fall through to provider-blind rotation.
|
|
235
|
+
* An empty result means "no opinion" and the caller rotates freely.
|
|
236
|
+
*
|
|
237
|
+
* Deterministic by construction: the seed carries the enrollment and step, never
|
|
238
|
+
* a clock or Math.random, so a dry-run preview and the later live send choose
|
|
239
|
+
* the same provider, and a replan after a defer re-derives the same route.
|
|
240
|
+
* Candidates are enumerated in RECIPIENT_ESPS order rather than object-key
|
|
241
|
+
* order, because jsonb does not preserve key order and a policy that round-trips
|
|
242
|
+
* through the database must not reroute the fleet.
|
|
243
|
+
*/
|
|
244
|
+
export declare function espRouteCandidates(input: {
|
|
245
|
+
policy: ResolvedEspPolicy;
|
|
246
|
+
recipient: EspRouteBucket;
|
|
247
|
+
seed: string;
|
|
248
|
+
}): RecipientEsp[];
|
|
249
|
+
/**
|
|
250
|
+
* Sending domains that must not serve this recipient bucket, normalized and
|
|
251
|
+
* de-duplicated for pickEmailMailbox. Applies in every mode and survives the
|
|
252
|
+
* prefer-fallback — see the exclude contract above.
|
|
253
|
+
*/
|
|
254
|
+
export declare function espExcludedSenderDomains(policy: ResolvedEspPolicy, bucket: EspRouteBucket): string[];
|
|
131
255
|
/**
|
|
132
256
|
* The DISPLAY taxonomy: the mail-infrastructure family a recipient domain sits
|
|
133
257
|
* on. Wider than RecipientEsp because "everything that isn't Google or Microsoft"
|
|
@@ -795,9 +919,28 @@ export type SequenceLintIssue = {
|
|
|
795
919
|
path: string;
|
|
796
920
|
message: string;
|
|
797
921
|
};
|
|
922
|
+
/**
|
|
923
|
+
* A non-blocking fact about how a definition was normalized that its author must
|
|
924
|
+
* be told, as opposed to an issue that rejects it. Today exactly one:
|
|
925
|
+
* `email_reply_rewritten`. The retired kind is accepted and rewritten into a
|
|
926
|
+
* subject-less `email_send`, and a caller that only reads the stored definition
|
|
927
|
+
* back sees a different `kind` than it wrote with nothing in the response to
|
|
928
|
+
* explain it — reported as a silent coercion by a customer (Plain T-163).
|
|
929
|
+
*/
|
|
930
|
+
export type SequenceDefinitionNotice = {
|
|
931
|
+
path: string;
|
|
932
|
+
code: "email_reply_rewritten";
|
|
933
|
+
message: string;
|
|
934
|
+
};
|
|
798
935
|
export type ValidateSequenceOptions = {
|
|
799
936
|
/** When provided, every channel-bearing step must use one of these channels. */
|
|
800
937
|
allowedChannels?: SequenceChannel[];
|
|
938
|
+
/**
|
|
939
|
+
* Sink for non-blocking normalization notices (see SequenceDefinitionNotice).
|
|
940
|
+
* Validation never throws for these; a caller that wants to surface them to
|
|
941
|
+
* the author passes an array and reads it back after the call.
|
|
942
|
+
*/
|
|
943
|
+
notices?: SequenceDefinitionNotice[];
|
|
801
944
|
/**
|
|
802
945
|
* Whether this sequence has NATIVE open/click tracking enabled (a verified
|
|
803
946
|
* tracking domain + EMAIL_TRACKING_SECRET, so the dispatcher injects a pixel +
|
|
@@ -856,6 +999,13 @@ export declare function sequenceStepsMissingCopy(steps: readonly SequenceStep[])
|
|
|
856
999
|
*/
|
|
857
1000
|
export declare function validateSequenceDefinition(input: unknown, options?: ValidateSequenceOptions): SequenceDefinition;
|
|
858
1001
|
/** Non-throwing variant for lint surfaces. */
|
|
1002
|
+
/**
|
|
1003
|
+
* The non-blocking notices normalizing this definition would raise, for a
|
|
1004
|
+
* response `warnings` array. Never throws: an invalid definition yields whatever
|
|
1005
|
+
* notices were collected before validation gave up (the caller's own validate
|
|
1006
|
+
* call reports the failure), so this is safe to run beside a persist.
|
|
1007
|
+
*/
|
|
1008
|
+
export declare function sequenceDefinitionNotices(input: unknown, options?: Omit<ValidateSequenceOptions, "notices">): SequenceDefinitionNotice[];
|
|
859
1009
|
export declare function lintSequenceDefinition(input: unknown, options?: ValidateSequenceOptions): SequenceLintIssue[];
|
|
860
1010
|
/** Total base delay in milliseconds a wait step introduces (no jitter). */
|
|
861
1011
|
export declare function sequenceWaitStepDelayMs(step: SequenceWaitStep): number;
|