@oxygen-agent/cli 1.982.3 → 1.1003.12
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.d.ts +0 -2
- package/dist/admin-primary-providers-render.js +1 -1
- package/dist/browser-login.js +1 -4
- package/dist/command-manifest.d.ts +3 -2
- package/dist/command-manifest.js +10 -0
- package/dist/credentials.d.ts +1 -1
- package/dist/functions-commands.js +27 -7
- package/dist/help.d.ts +29 -0
- package/dist/help.js +139 -0
- package/dist/index.js +1875 -164
- package/dist/knowledge-mirror.d.ts +2 -2
- package/dist/runtime.d.ts +0 -15
- package/dist/runtime.js +1 -1
- package/dist/session.d.ts +4 -3
- package/dist/skills.d.ts +8 -7
- package/dist/skills.js +24 -10
- package/dist/transcript.d.ts +2 -1
- package/dist/ugc-commands.d.ts +3 -6
- package/dist/ugc-commands.js +2 -1200
- package/dist/util.d.ts +1 -1
- package/dist/util.js +1 -3
- 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/coerce.d.ts +10 -0
- package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
- package/node_modules/@oxygen/formula/dist/expression.js +14 -1
- package/node_modules/@oxygen/formula/dist/formula-functions.js +136 -1
- package/node_modules/@oxygen/formula/dist/hash.d.ts +19 -0
- package/node_modules/@oxygen/formula/dist/hash.js +199 -0
- 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 +74 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.js +358 -0
- package/node_modules/@oxygen/shared/dist/array-utils.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/array-utils.js +11 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +103 -47
- package/node_modules/@oxygen/shared/dist/billing.js +150 -40
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +114 -16
- package/node_modules/@oxygen/shared/dist/column-autofill.d.ts +52 -0
- package/node_modules/@oxygen/shared/dist/column-autofill.js +80 -0
- package/node_modules/@oxygen/shared/dist/column-output-fields.js +14 -10
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +108 -0
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +545 -0
- 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/deploy-env.d.ts +74 -0
- package/node_modules/@oxygen/shared/dist/deploy-env.js +82 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.d.ts +130 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.js +221 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +103 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.js +819 -0
- package/node_modules/@oxygen/shared/dist/error-message.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/error-message.js +3 -0
- package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -3
- package/node_modules/@oxygen/shared/dist/external-write-policy.d.ts +33 -0
- package/node_modules/@oxygen/shared/dist/external-write-policy.js +68 -0
- package/node_modules/@oxygen/shared/dist/format-percent.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/format-percent.js +13 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +81 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.js +157 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1 -1
- package/node_modules/@oxygen/shared/dist/index.d.ts +14 -0
- package/node_modules/@oxygen/shared/dist/index.js +14 -0
- package/node_modules/@oxygen/shared/dist/json-path.js +1 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bases.js +1 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +17 -2
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +28 -6
- package/node_modules/@oxygen/shared/dist/langfuse.d.ts +12 -1
- package/node_modules/@oxygen/shared/dist/langfuse.js +57 -8
- package/node_modules/@oxygen/shared/dist/linkedin-countries.d.ts +32 -0
- package/node_modules/@oxygen/shared/dist/linkedin-countries.js +359 -0
- package/node_modules/@oxygen/shared/dist/log-sink-selector.d.ts +39 -0
- package/node_modules/@oxygen/shared/dist/log-sink-selector.js +56 -0
- package/node_modules/@oxygen/shared/dist/log.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/log.js +6 -1
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +21 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +54 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +213 -0
- package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +23 -22
- package/node_modules/@oxygen/shared/dist/plan-limits.js +45 -18
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +48 -41
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +36 -25
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +22 -22
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +40 -34
- package/node_modules/@oxygen/shared/dist/product-analytics-environment.js +9 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +15 -0
- package/node_modules/@oxygen/shared/dist/rate-window.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/rate-window.js +8 -0
- package/node_modules/@oxygen/shared/dist/research-output-contract.d.ts +33 -1
- package/node_modules/@oxygen/shared/dist/research-output-contract.js +65 -5
- package/node_modules/@oxygen/shared/dist/search-vocab.js +4 -5
- package/node_modules/@oxygen/shared/dist/select-options.js +6 -1
- package/node_modules/@oxygen/shared/dist/sequence-crm-events.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequence-failures.js +1 -5
- package/node_modules/@oxygen/shared/dist/sequence-hubspot-sync.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +49 -0
- package/node_modules/@oxygen/shared/dist/sequences.js +134 -4
- package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +22 -10
- package/node_modules/@oxygen/shared/dist/spend-safety.js +15 -21
- package/node_modules/@oxygen/shared/dist/sql-rows.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/sql-rows.js +3 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +9 -1
- package/node_modules/@oxygen/shared/dist/type-guards.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/type-guards.js +35 -0
- package/node_modules/@oxygen/shared/dist/value-readers.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/value-readers.js +59 -0
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/package.json +60 -0
- package/node_modules/@oxygen/workflows/dist/graph/expression.js +2 -5
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +15 -15
- package/node_modules/@oxygen/workflows/dist/graph/params.js +1 -1
- package/package.json +6 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord as isObject } from "./type-guards.js";
|
|
1
2
|
const MAX_RESEARCH_SECTIONS = 20;
|
|
2
3
|
const OUTPUT_SECTIONS_MARKER = /\b(?:return|provide|include|produce|output)\b.{0,80}\b(?:labeled|named|required|requested)?\s*(?:output\s+)?sections?\s*:/i;
|
|
3
4
|
const LABELED_SECTION = /^(?:[-*+]\s+|\d+[.)]\s+)?([A-Za-z][A-Za-z0-9_. /&()'’+\-]{1,79})\s*:\s*.*$/;
|
|
@@ -88,6 +89,64 @@ export function buildResearchOutputSchema(contract) {
|
|
|
88
89
|
required: ["answer", "found", "confidence", "citations"],
|
|
89
90
|
};
|
|
90
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Whether a found answer MUST cite evidence. Shared by the write-path normalizer
|
|
94
|
+
* (which bakes it into the derived research contract), the runner (which
|
|
95
|
+
* validates the model output against it) and the output-field reader, so none
|
|
96
|
+
* of them can disagree.
|
|
97
|
+
*
|
|
98
|
+
* Explicit "strict" always requires it. A FETCH column requires it by default —
|
|
99
|
+
* the evidence is the one page the customer pointed at, so an answer that cites
|
|
100
|
+
* nothing answered from memory — unless the author chose "estimate".
|
|
101
|
+
*/
|
|
102
|
+
export function researchEvidenceRequired(webSearch) {
|
|
103
|
+
if (!isObject(webSearch))
|
|
104
|
+
return false;
|
|
105
|
+
if (webSearch.evidenceMode === "strict")
|
|
106
|
+
return true;
|
|
107
|
+
if (webSearch.evidenceMode === "estimate")
|
|
108
|
+
return false;
|
|
109
|
+
return webSearch.source === "fetch";
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* True for a model-facing schema in the research envelope shape — the derived
|
|
113
|
+
* contract, the legacy broad schema, or a catalog template's typed answer
|
|
114
|
+
* envelope — as opposed to a hand-authored schema that replaces the envelope
|
|
115
|
+
* entirely. The output-field reader rewrites only the former into the stored
|
|
116
|
+
* cell shape (`sources` in, `citations` out); the latter is used verbatim.
|
|
117
|
+
*/
|
|
118
|
+
export function isResearchEnvelopeSchema(schema) {
|
|
119
|
+
if (!isObject(schema) || schema.type !== "object")
|
|
120
|
+
return false;
|
|
121
|
+
const properties = isObject(schema.properties) ? schema.properties : null;
|
|
122
|
+
if (!properties)
|
|
123
|
+
return false;
|
|
124
|
+
return ["answer", "found", "confidence", "citations"].every((key) => isObject(properties[key]));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The model-facing schema for a research column whose ANSWER is a typed object
|
|
128
|
+
* (a catalog template such as a pricing summary): the same
|
|
129
|
+
* `{answer, found, confidence, citations}` envelope the derived contract uses,
|
|
130
|
+
* with `answer` replaced by the template's own object schema. The runner still
|
|
131
|
+
* resolves `citations` to real sources on the way to the cell, so a typed
|
|
132
|
+
* extraction cannot cite a page that was never fetched either.
|
|
133
|
+
*/
|
|
134
|
+
export function buildResearchOutputSchemaForAnswer(answerSchema) {
|
|
135
|
+
const envelope = buildResearchOutputSchema(null);
|
|
136
|
+
const properties = isObject(envelope.properties) ? { ...envelope.properties } : {};
|
|
137
|
+
return {
|
|
138
|
+
...envelope,
|
|
139
|
+
properties: {
|
|
140
|
+
...properties,
|
|
141
|
+
answer: {
|
|
142
|
+
...answerSchema,
|
|
143
|
+
description: typeof answerSchema.description === "string"
|
|
144
|
+
? answerSchema.description
|
|
145
|
+
: "The typed answer read from the evidence; unsupported fields are null or empty.",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
91
150
|
/**
|
|
92
151
|
* The schema of the research cell as it is actually STORED — which is not the
|
|
93
152
|
* schema the model answers in.
|
|
@@ -101,8 +160,12 @@ export function buildResearchOutputSchema(contract) {
|
|
|
101
160
|
* `buildResearchOutputSchema` would offer `citations`, which no cell has, and
|
|
102
161
|
* hide `sources`, which every cell does.
|
|
103
162
|
*/
|
|
104
|
-
export function buildResearchCellSchema(contract
|
|
105
|
-
|
|
163
|
+
export function buildResearchCellSchema(contract,
|
|
164
|
+
/** A column's own model-facing schema (a catalog template's typed answer
|
|
165
|
+
* envelope). When given it replaces the derived one, so the cell shape of a
|
|
166
|
+
* typed extraction exposes `answer.plan_count`, not a string answer. */
|
|
167
|
+
modelSchemaOverride) {
|
|
168
|
+
const modelSchema = modelSchemaOverride ?? buildResearchOutputSchema(contract);
|
|
106
169
|
const properties = isObject(modelSchema.properties) ? { ...modelSchema.properties } : {};
|
|
107
170
|
delete properties.citations;
|
|
108
171
|
return {
|
|
@@ -191,6 +254,3 @@ function hasExactMembers(value, expected) {
|
|
|
191
254
|
&& value.length === expected.length
|
|
192
255
|
&& expected.every((member) => value.includes(member));
|
|
193
256
|
}
|
|
194
|
-
function isObject(value) {
|
|
195
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
196
|
-
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
// Shared search-planner vocabulary + stateless text helpers.
|
|
2
2
|
//
|
|
3
3
|
// Single source of truth for the constraint vocabulary and pure text helpers
|
|
4
|
-
// consumed by
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// here is what stops the two surfaces from silently drifting (OXY-414).
|
|
4
|
+
// consumed by the web search planners (apps/web/src/lib/company-search-plan.ts,
|
|
5
|
+
// people-search-plan.ts). Any other search classifier or compiler layers its
|
|
6
|
+
// tuning deltas on top of these bases rather than re-listing vocabulary, so the
|
|
7
|
+
// surfaces cannot silently drift (OXY-414).
|
|
9
8
|
//
|
|
10
9
|
// Everything here is a pure function or a static table: no Date.now, no
|
|
11
10
|
// randomness, no I/O — safe to import from any layer.
|
|
@@ -75,7 +75,12 @@ export const IMAGE_COLUMN_SEMANTIC = "image";
|
|
|
75
75
|
export function isImageColumnSemantic(semanticType) {
|
|
76
76
|
return semanticType === "image" || semanticType === "avatar" || semanticType === "photo";
|
|
77
77
|
}
|
|
78
|
-
|
|
78
|
+
// A sanity ceiling, not a correctness one: it stops a malformed write from
|
|
79
|
+
// turning one column into an unbounded list. Raised from 200 to 300 for the
|
|
80
|
+
// CRM's country column, whose vocabulary is the 250-entry LinkedIn country set
|
|
81
|
+
// (see @oxygen/shared/linkedin-countries) — at 200 the tail of the alphabet was
|
|
82
|
+
// silently truncated away, which reads as "Oxygen has never heard of Vietnam".
|
|
83
|
+
const MAX_OPTIONS = 300;
|
|
79
84
|
// Normalize a raw options array (from a column `definition.options`, CLI/MCP, or
|
|
80
85
|
// the editor) into canonical SelectOptions: dedupe by value, default colors,
|
|
81
86
|
// canonical 0..n ordering. Accepts strings or {value,label,color,order} objects.
|
|
@@ -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;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { redactCustomerFacingDetails, redactCustomerFacingError, } from "./error-redaction.js";
|
|
2
2
|
import { isProviderFundingErrorCode, isProviderRateLimitErrorCode, } from "./provider-funding-errors.js";
|
|
3
|
+
import { asRecordOrNull as asRecord } from "./type-guards.js";
|
|
3
4
|
export const SEQUENCE_FAILURE_SCHEMA_VERSION = 1;
|
|
4
5
|
export const SEQUENCE_FAILURE_RETRY_AFTER_MAX_MS = 24 * 60 * 60 * 1000;
|
|
5
6
|
export const SEQUENCE_FAILURE_CLASSES = [
|
|
@@ -318,8 +319,3 @@ function firstBoolean(records, keys) {
|
|
|
318
319
|
}
|
|
319
320
|
return null;
|
|
320
321
|
}
|
|
321
|
-
function asRecord(value) {
|
|
322
|
-
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
323
|
-
? value
|
|
324
|
-
: null;
|
|
325
|
-
}
|
|
@@ -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>;
|
|
@@ -919,9 +919,28 @@ export type SequenceLintIssue = {
|
|
|
919
919
|
path: string;
|
|
920
920
|
message: string;
|
|
921
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
|
+
};
|
|
922
935
|
export type ValidateSequenceOptions = {
|
|
923
936
|
/** When provided, every channel-bearing step must use one of these channels. */
|
|
924
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[];
|
|
925
944
|
/**
|
|
926
945
|
* Whether this sequence has NATIVE open/click tracking enabled (a verified
|
|
927
946
|
* tracking domain + EMAIL_TRACKING_SECRET, so the dispatcher injects a pixel +
|
|
@@ -980,6 +999,13 @@ export declare function sequenceStepsMissingCopy(steps: readonly SequenceStep[])
|
|
|
980
999
|
*/
|
|
981
1000
|
export declare function validateSequenceDefinition(input: unknown, options?: ValidateSequenceOptions): SequenceDefinition;
|
|
982
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[];
|
|
983
1009
|
export declare function lintSequenceDefinition(input: unknown, options?: ValidateSequenceOptions): SequenceLintIssue[];
|
|
984
1010
|
/** Total base delay in milliseconds a wait step introduces (no jitter). */
|
|
985
1011
|
export declare function sequenceWaitStepDelayMs(step: SequenceWaitStep): number;
|
|
@@ -1150,4 +1176,27 @@ export declare function secondsUntilSendWindowEnd(window: SequenceSendWindow, no
|
|
|
1150
1176
|
* describes this window's sends.
|
|
1151
1177
|
*/
|
|
1152
1178
|
export declare function secondsSinceSendWindowStart(window: SequenceSendWindow, now: Date, timezone?: string): number | null;
|
|
1179
|
+
/**
|
|
1180
|
+
* The next instant at which `isWithinSendWindow` becomes true, or null when the
|
|
1181
|
+
* window is already open, never opens, or cannot be evaluated.
|
|
1182
|
+
*
|
|
1183
|
+
* This is what a deferral should wait for. Before it existed, the two
|
|
1184
|
+
* native-email send-window gates deferred a flat +30 minutes, so every pending
|
|
1185
|
+
* action was re-claimed, re-planned and re-written twice an hour for as long as
|
|
1186
|
+
* the window stayed shut — ~128 times each across a closed weekend, which is
|
|
1187
|
+
* where 24,261 of one fortnight's deferral log lines came from.
|
|
1188
|
+
*
|
|
1189
|
+
* Null on exactly the cases its three siblings decline: an unresolvable
|
|
1190
|
+
* timezone, a malformed `start`/`end`, and a zero-width window. The caller keeps
|
|
1191
|
+
* its own fallback delay for those, so a bad window can never park an action
|
|
1192
|
+
* forever — the same fail-open posture isWithinSendWindow takes.
|
|
1193
|
+
*
|
|
1194
|
+
* Computed by stepping to the next allowed local `start` and then correcting
|
|
1195
|
+
* against the real clock, because minutes-of-day arithmetic alone is wrong
|
|
1196
|
+
* across a DST boundary (the offset moves between `now` and the candidate). The
|
|
1197
|
+
* correction re-reads the candidate's local time and closes the gap; the result
|
|
1198
|
+
* is only returned once isWithinSendWindow itself agrees, so this function can
|
|
1199
|
+
* never hand back an instant the gate would reject.
|
|
1200
|
+
*/
|
|
1201
|
+
export declare function nextSendWindowOpenAt(window: SequenceSendWindow, now: Date, timezone?: string): Date | null;
|
|
1153
1202
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { OxygenError } from "./cli-result.js";
|
|
2
2
|
import { hashVariantKey, renderTemplate, spintaxSyntaxIssues, templateColumnKeys, } from "./sequence-template.js";
|
|
3
|
+
import { isRecord } from "./type-guards.js";
|
|
3
4
|
/**
|
|
4
5
|
* Multichannel sequence DSL — the shared contract validated identically by CLI,
|
|
5
6
|
* MCP, API, and web. A sequence is an ordered list of steps applied to each
|
|
@@ -1176,6 +1177,22 @@ export function validateSequenceDefinition(input, options = {}) {
|
|
|
1176
1177
|
return { steps: normalized };
|
|
1177
1178
|
}
|
|
1178
1179
|
/** Non-throwing variant for lint surfaces. */
|
|
1180
|
+
/**
|
|
1181
|
+
* The non-blocking notices normalizing this definition would raise, for a
|
|
1182
|
+
* response `warnings` array. Never throws: an invalid definition yields whatever
|
|
1183
|
+
* notices were collected before validation gave up (the caller's own validate
|
|
1184
|
+
* call reports the failure), so this is safe to run beside a persist.
|
|
1185
|
+
*/
|
|
1186
|
+
export function sequenceDefinitionNotices(input, options = {}) {
|
|
1187
|
+
const notices = [];
|
|
1188
|
+
try {
|
|
1189
|
+
validateSequenceDefinition(input, { ...options, notices });
|
|
1190
|
+
}
|
|
1191
|
+
catch {
|
|
1192
|
+
// Reported by the caller's own validation; the notices gathered so far stand.
|
|
1193
|
+
}
|
|
1194
|
+
return notices;
|
|
1195
|
+
}
|
|
1179
1196
|
export function lintSequenceDefinition(input, options = {}) {
|
|
1180
1197
|
try {
|
|
1181
1198
|
validateSequenceDefinition(input, options);
|
|
@@ -1378,6 +1395,14 @@ raw, index, options, issues) {
|
|
|
1378
1395
|
const cc = optionalEmailList(raw.cc, `${path}.cc`, issues);
|
|
1379
1396
|
const bcc = optionalEmailList(raw.bcc, `${path}.bcc`, issues);
|
|
1380
1397
|
const sendWindow = normalizeSendWindow(raw.send_window, `${path}.send_window`, issues);
|
|
1398
|
+
// Say so. The rewrite is deliberate compatibility, but a definition read back
|
|
1399
|
+
// with a different `kind` than was written, and nothing in the response to
|
|
1400
|
+
// explain it, reads as data loss to the author.
|
|
1401
|
+
options.notices?.push({
|
|
1402
|
+
path: `${path}.kind`,
|
|
1403
|
+
code: "email_reply_rewritten",
|
|
1404
|
+
message: `step '${id}': kind 'email_reply' is retired and was stored as 'email_send' with no subject_template — the threaded follow-up. It goes out as "Re: <the previous email's subject>" inside the lead's previous thread; give it a subject_template of its own to start a new email instead.`,
|
|
1405
|
+
});
|
|
1381
1406
|
return {
|
|
1382
1407
|
id, channel: "email", kind: "email_send", body_template: body ?? "",
|
|
1383
1408
|
...(bodyHtml !== undefined ? { body_html_template: bodyHtml } : {}),
|
|
@@ -2539,6 +2564,36 @@ function hhmmToMinutes(value) {
|
|
|
2539
2564
|
return hours * 60 + minutes;
|
|
2540
2565
|
}
|
|
2541
2566
|
const ISO_WEEKDAY = { Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6, Sun: 7 };
|
|
2567
|
+
/**
|
|
2568
|
+
* Does this window admit `isoWeekday`? Shared by every gate so they cannot
|
|
2569
|
+
* disagree about what a day list means.
|
|
2570
|
+
*
|
|
2571
|
+
* `days` is contractually ISO numbers (normalizeWeekdays rejects anything else),
|
|
2572
|
+
* but `settings.email_send_window` is stored without passing through it, and
|
|
2573
|
+
* production holds windows whose days are `["mon","tue",...]`. A raw
|
|
2574
|
+
* `days.includes(1)` is false for every day of the week, so such a window is
|
|
2575
|
+
* never open and its Sequence can never send — a silent, permanent stall that
|
|
2576
|
+
* also contradicts this module's fail-open posture for a malformed window.
|
|
2577
|
+
* Names are therefore accepted, and a list with no recognisable entry at all is
|
|
2578
|
+
* treated as absent (every day), which is exactly what normalizeWeekdays
|
|
2579
|
+
* produces for the same input.
|
|
2580
|
+
*/
|
|
2581
|
+
function sendWindowAllowsWeekday(days, isoWeekday) {
|
|
2582
|
+
let recognised = 0;
|
|
2583
|
+
for (const entry of days) {
|
|
2584
|
+
const day = typeof entry === "number"
|
|
2585
|
+
? entry
|
|
2586
|
+
: typeof entry === "string"
|
|
2587
|
+
? ISO_WEEKDAY[`${entry.trim().slice(0, 1).toUpperCase()}${entry.trim().slice(1, 3).toLowerCase()}`] ?? Number(entry)
|
|
2588
|
+
: Number.NaN;
|
|
2589
|
+
if (!Number.isInteger(day) || day < 1 || day > 7)
|
|
2590
|
+
continue;
|
|
2591
|
+
recognised += 1;
|
|
2592
|
+
if (day === isoWeekday)
|
|
2593
|
+
return true;
|
|
2594
|
+
}
|
|
2595
|
+
return recognised === 0;
|
|
2596
|
+
}
|
|
2542
2597
|
/**
|
|
2543
2598
|
* Resolve which IANA timezone a send window evaluates in for a given lead row.
|
|
2544
2599
|
* "recipient" mode reads the lead's tz from `recipient_timezone_column`, falling
|
|
@@ -2569,7 +2624,7 @@ export function isWithinSendWindow(window, now, timezone) {
|
|
|
2569
2624
|
const endM = hhmmToMinutes(window.end);
|
|
2570
2625
|
if (startM === null || endM === null)
|
|
2571
2626
|
return true; // malformed window → fail open
|
|
2572
|
-
if (window.days && window.days.length > 0 && !window.days
|
|
2627
|
+
if (window.days && window.days.length > 0 && !sendWindowAllowsWeekday(window.days, parts.isoWeekday))
|
|
2573
2628
|
return false;
|
|
2574
2629
|
if (startM === endM)
|
|
2575
2630
|
return false; // empty window
|
|
@@ -2630,6 +2685,84 @@ export function secondsSinceSendWindowStart(window, now, timezone) {
|
|
|
2630
2685
|
: minutes + 24 * 60 - startM; // wrapped past midnight: opened yesterday evening
|
|
2631
2686
|
return elapsedMinutes >= 0 ? elapsedMinutes * 60 : null;
|
|
2632
2687
|
}
|
|
2688
|
+
/**
|
|
2689
|
+
* How far ahead nextSendWindowOpenAt will look for an opening. A window with a
|
|
2690
|
+
* `days` list opens at least weekly, so 8 days covers every configurable gap
|
|
2691
|
+
* with a day of slack; anything that finds nothing inside it is a window that
|
|
2692
|
+
* never opens (zero-width, or a malformed tz that fails the parts read).
|
|
2693
|
+
*/
|
|
2694
|
+
const SEND_WINDOW_LOOKAHEAD_DAYS = 8;
|
|
2695
|
+
/**
|
|
2696
|
+
* The next instant at which `isWithinSendWindow` becomes true, or null when the
|
|
2697
|
+
* window is already open, never opens, or cannot be evaluated.
|
|
2698
|
+
*
|
|
2699
|
+
* This is what a deferral should wait for. Before it existed, the two
|
|
2700
|
+
* native-email send-window gates deferred a flat +30 minutes, so every pending
|
|
2701
|
+
* action was re-claimed, re-planned and re-written twice an hour for as long as
|
|
2702
|
+
* the window stayed shut — ~128 times each across a closed weekend, which is
|
|
2703
|
+
* where 24,261 of one fortnight's deferral log lines came from.
|
|
2704
|
+
*
|
|
2705
|
+
* Null on exactly the cases its three siblings decline: an unresolvable
|
|
2706
|
+
* timezone, a malformed `start`/`end`, and a zero-width window. The caller keeps
|
|
2707
|
+
* its own fallback delay for those, so a bad window can never park an action
|
|
2708
|
+
* forever — the same fail-open posture isWithinSendWindow takes.
|
|
2709
|
+
*
|
|
2710
|
+
* Computed by stepping to the next allowed local `start` and then correcting
|
|
2711
|
+
* against the real clock, because minutes-of-day arithmetic alone is wrong
|
|
2712
|
+
* across a DST boundary (the offset moves between `now` and the candidate). The
|
|
2713
|
+
* correction re-reads the candidate's local time and closes the gap; the result
|
|
2714
|
+
* is only returned once isWithinSendWindow itself agrees, so this function can
|
|
2715
|
+
* never hand back an instant the gate would reject.
|
|
2716
|
+
*/
|
|
2717
|
+
export function nextSendWindowOpenAt(window, now, timezone) {
|
|
2718
|
+
const tz = timezone?.trim() ? timezone.trim() : window.timezone;
|
|
2719
|
+
const parts = tzParts(now, tz) ?? tzParts(now, window.timezone) ?? tzParts(now, "UTC");
|
|
2720
|
+
if (!parts)
|
|
2721
|
+
return null;
|
|
2722
|
+
const startM = hhmmToMinutes(window.start);
|
|
2723
|
+
const endM = hhmmToMinutes(window.end);
|
|
2724
|
+
if (startM === null || endM === null || startM === endM)
|
|
2725
|
+
return null;
|
|
2726
|
+
if (isWithinSendWindow(window, now, timezone))
|
|
2727
|
+
return null;
|
|
2728
|
+
const dayAllowed = (isoWeekday) => !window.days || window.days.length === 0 || sendWindowAllowsWeekday(window.days, isoWeekday);
|
|
2729
|
+
const minutes = parts.hour * 60 + parts.minute;
|
|
2730
|
+
// Days to skip before the window's local `start` is both in the future and on
|
|
2731
|
+
// an allowed weekday. 0 means "later today".
|
|
2732
|
+
let dayOffset = minutes < startM && dayAllowed(parts.isoWeekday) ? 0 : 1;
|
|
2733
|
+
while (dayOffset <= SEND_WINDOW_LOOKAHEAD_DAYS && !dayAllowed(((parts.isoWeekday - 1 + dayOffset) % 7) + 1)) {
|
|
2734
|
+
dayOffset += 1;
|
|
2735
|
+
}
|
|
2736
|
+
if (dayOffset > SEND_WINDOW_LOOKAHEAD_DAYS)
|
|
2737
|
+
return null;
|
|
2738
|
+
let candidate = new Date(now.getTime() + ((dayOffset * 24 * 60) + startM - minutes) * 60_000);
|
|
2739
|
+
// DST correction: the offset that applied at `now` may not apply at the
|
|
2740
|
+
// candidate, so the naive arithmetic can land an hour early or late. Re-read
|
|
2741
|
+
// the candidate's own local time and close the gap. Two rounds are enough for
|
|
2742
|
+
// any real transition (the second only runs if the first lands on the shifted
|
|
2743
|
+
// side of the boundary); a zone with no transition converges on the first.
|
|
2744
|
+
for (let round = 0; round < 2; round += 1) {
|
|
2745
|
+
const at = tzParts(candidate, tz);
|
|
2746
|
+
if (!at)
|
|
2747
|
+
break;
|
|
2748
|
+
const drift = (at.hour * 60 + at.minute) - startM;
|
|
2749
|
+
if (drift === 0)
|
|
2750
|
+
break;
|
|
2751
|
+
// Choose the nearer direction so a ±23h reading across midnight (the window
|
|
2752
|
+
// opens just after a backward transition) corrects by minutes, not a day.
|
|
2753
|
+
const shift = drift > 12 * 60 ? drift - 24 * 60 : drift < -12 * 60 ? drift + 24 * 60 : drift;
|
|
2754
|
+
candidate = new Date(candidate.getTime() - shift * 60_000);
|
|
2755
|
+
}
|
|
2756
|
+
// A local `start` can be skipped entirely by a forward DST jump, and a
|
|
2757
|
+
// corrected candidate can land a minute on the wrong side. Walk forward to the
|
|
2758
|
+
// first minute the gate actually accepts rather than trusting the arithmetic.
|
|
2759
|
+
for (let nudge = 0; nudge <= 120; nudge += 1) {
|
|
2760
|
+
const at = new Date(candidate.getTime() + nudge * 60_000);
|
|
2761
|
+
if (at.getTime() > now.getTime() && isWithinSendWindow(window, at, timezone))
|
|
2762
|
+
return at;
|
|
2763
|
+
}
|
|
2764
|
+
return null;
|
|
2765
|
+
}
|
|
2633
2766
|
function tzParts(now, tz) {
|
|
2634
2767
|
try {
|
|
2635
2768
|
const formatted = new Intl.DateTimeFormat("en-US", {
|
|
@@ -2828,6 +2961,3 @@ function normalizeWeekdays(value, path, issues) {
|
|
|
2828
2961
|
}
|
|
2829
2962
|
return out.length > 0 ? out.sort((a, b) => a - b) : undefined;
|
|
2830
2963
|
}
|
|
2831
|
-
function isRecord(value) {
|
|
2832
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2833
|
-
}
|
|
@@ -20,19 +20,19 @@ export declare const AUTONOMOUS_WORKFLOW_TRIGGER_TYPES: ReadonlySet<string>;
|
|
|
20
20
|
* Per-run managed-credit ceiling for a LIVE workflow run fired by an autonomous
|
|
21
21
|
* trigger when neither run metadata nor the manifest declares `max_credits`.
|
|
22
22
|
*/
|
|
23
|
-
export declare const DEFAULT_TRIGGER_RUN_CREDIT_CEILING: Record<
|
|
23
|
+
export declare const DEFAULT_TRIGGER_RUN_CREDIT_CEILING: Record<SpendSafetyPlanTier, number | null>;
|
|
24
24
|
/**
|
|
25
25
|
* Per-delivery credit ceiling stamped onto a standing/webhook table auto-run
|
|
26
26
|
* batch when the armed configuration carries no explicit `max_credits`.
|
|
27
27
|
*/
|
|
28
|
-
export declare const DEFAULT_AUTO_RUN_BATCH_CREDIT_CEILING: Record<
|
|
28
|
+
export declare const DEFAULT_AUTO_RUN_BATCH_CREDIT_CEILING: Record<SpendSafetyPlanTier, number | null>;
|
|
29
29
|
/**
|
|
30
30
|
* Row ceiling for a BYOK AI-column run whose caller gave no explicit row bound
|
|
31
31
|
* (no limit, no row_ids, selection "all"). BYOK bills the customer's own
|
|
32
32
|
* provider account, so a credit ceiling is meaningless — row count is the
|
|
33
33
|
* enforceable unit. An explicit limit (up to the 500k platform row cap) wins.
|
|
34
34
|
*/
|
|
35
|
-
export declare const DEFAULT_BYOK_COLUMN_RUN_MAX_ROWS: Record<
|
|
35
|
+
export declare const DEFAULT_BYOK_COLUMN_RUN_MAX_ROWS: Record<SpendSafetyPlanTier, number | null>;
|
|
36
36
|
/**
|
|
37
37
|
* Default per-provider DAILY call cap for BYOK provider traffic (the customer's
|
|
38
38
|
* own key). Short-window pacing defaults already exist in provider-fetch; this
|
|
@@ -40,7 +40,7 @@ export declare const DEFAULT_BYOK_COLUMN_RUN_MAX_ROWS: Record<PlanTier, number |
|
|
|
40
40
|
* with the self-serve override surface live. A workspace
|
|
41
41
|
* provider_rate_limit_policies row with windowSeconds=86400 overrides it.
|
|
42
42
|
*/
|
|
43
|
-
export declare const DEFAULT_BYOK_PROVIDER_DAILY_CALL_CAP: Record<
|
|
43
|
+
export declare const DEFAULT_BYOK_PROVIDER_DAILY_CALL_CAP: Record<SpendSafetyPlanTier, number | null>;
|
|
44
44
|
export declare const BYOK_PROVIDER_DAILY_WINDOW_SECONDS: number;
|
|
45
45
|
export declare const BYOK_PROVIDER_DAILY_CAP_ENFORCEMENT_ENV = "OXYGEN_BYOK_DAILY_CAPS";
|
|
46
46
|
export type ByokProviderDailyCapEnforcementMode = "observe_only" | "enforced";
|
|
@@ -105,10 +105,14 @@ export type OrgDailySpendGuard = {
|
|
|
105
105
|
* decision is made, so enforcement and the read surfaces cannot drift apart.
|
|
106
106
|
*/
|
|
107
107
|
export declare function resolveOrgDailySpendGuard(monthlyCredits: number | null | undefined): OrgDailySpendGuard | null;
|
|
108
|
-
|
|
109
|
-
export declare function
|
|
110
|
-
|
|
111
|
-
export declare function
|
|
108
|
+
/** Credits carry 3 decimals everywhere (control-DB numeric(18,3)). */
|
|
109
|
+
export declare function roundCredits(value: number): number;
|
|
110
|
+
/** Credit estimates that present to 2 decimals (cent-grained customer copy). */
|
|
111
|
+
export declare function roundCreditsToCents(value: number): number;
|
|
112
|
+
export declare function resolveDefaultTriggerRunCreditCeiling(tier: SpendSafetyPlanTier): number | null;
|
|
113
|
+
export declare function resolveDefaultAutoRunBatchCreditCeiling(tier: SpendSafetyPlanTier): number | null;
|
|
114
|
+
export declare function resolveDefaultByokColumnRunMaxRows(tier: SpendSafetyPlanTier): number | null;
|
|
115
|
+
export declare function resolveDefaultByokProviderDailyCallCap(tier: SpendSafetyPlanTier): number | null;
|
|
112
116
|
/**
|
|
113
117
|
* The plan tiers the spend-safety default tables above are keyed by. `limitsTier`
|
|
114
118
|
* collapses "enterprise" onto the "scale" enforcement rung, but these tables carry
|
|
@@ -119,5 +123,13 @@ export declare function resolveDefaultByokProviderDailyCallCap(tier: PlanTier):
|
|
|
119
123
|
* `oxygen limits` report, the org-facing provider-limits API, and the BYOK
|
|
120
124
|
* daily-cap enforcement resolver — they must agree on which column an org reads.
|
|
121
125
|
*/
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
/**
|
|
127
|
+
* The tiers these tables are keyed by. `oxygen` is deliberately ABSENT: its six
|
|
128
|
+
* rungs span four enforcement rungs, so an Oxygen plan must fall through to its
|
|
129
|
+
* limitsTier (the price band) rather than read one shared column. Adding an
|
|
130
|
+
* "oxygen" column here would silently give a $1,999 workspace the same ceiling
|
|
131
|
+
* as a $49 one.
|
|
132
|
+
*/
|
|
133
|
+
export type SpendSafetyPlanTier = Exclude<PlanTier, "oxygen">;
|
|
134
|
+
export declare const SPEND_SAFETY_PLAN_TIERS: readonly SpendSafetyPlanTier[];
|
|
135
|
+
export declare function resolveSpendSafetyPlanTier(planTier: string | null, limitsTier: LimitsTier): SpendSafetyPlanTier;
|
|
@@ -9,11 +9,11 @@ export const AUTONOMOUS_WORKFLOW_TRIGGER_TYPES = new Set([
|
|
|
9
9
|
* trigger when neither run metadata nor the manifest declares `max_credits`.
|
|
10
10
|
*/
|
|
11
11
|
export const DEFAULT_TRIGGER_RUN_CREDIT_CEILING = {
|
|
12
|
-
free:
|
|
13
|
-
starter:
|
|
14
|
-
pro:
|
|
15
|
-
team:
|
|
16
|
-
scale:
|
|
12
|
+
free: 50,
|
|
13
|
+
starter: 500,
|
|
14
|
+
pro: 1_000,
|
|
15
|
+
team: 2_500,
|
|
16
|
+
scale: 25_000,
|
|
17
17
|
enterprise: null,
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
@@ -21,11 +21,11 @@ export const DEFAULT_TRIGGER_RUN_CREDIT_CEILING = {
|
|
|
21
21
|
* batch when the armed configuration carries no explicit `max_credits`.
|
|
22
22
|
*/
|
|
23
23
|
export const DEFAULT_AUTO_RUN_BATCH_CREDIT_CEILING = {
|
|
24
|
-
free:
|
|
25
|
-
starter:
|
|
26
|
-
pro:
|
|
27
|
-
team:
|
|
28
|
-
scale:
|
|
24
|
+
free: 50,
|
|
25
|
+
starter: 500,
|
|
26
|
+
pro: 1_000,
|
|
27
|
+
team: 2_500,
|
|
28
|
+
scale: 25_000,
|
|
29
29
|
enterprise: null,
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
@@ -129,9 +129,13 @@ export function resolveOrgDailySpendGuard(monthlyCredits) {
|
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
/** Credits carry 3 decimals everywhere (control-DB numeric(18,3)). */
|
|
132
|
-
function roundCredits(value) {
|
|
132
|
+
export function roundCredits(value) {
|
|
133
133
|
return Math.round(value * 1000) / 1000;
|
|
134
134
|
}
|
|
135
|
+
/** Credit estimates that present to 2 decimals (cent-grained customer copy). */
|
|
136
|
+
export function roundCreditsToCents(value) {
|
|
137
|
+
return Math.round((value + Number.EPSILON) * 100) / 100;
|
|
138
|
+
}
|
|
135
139
|
export function resolveDefaultTriggerRunCreditCeiling(tier) {
|
|
136
140
|
return DEFAULT_TRIGGER_RUN_CREDIT_CEILING[tier];
|
|
137
141
|
}
|
|
@@ -144,16 +148,6 @@ export function resolveDefaultByokColumnRunMaxRows(tier) {
|
|
|
144
148
|
export function resolveDefaultByokProviderDailyCallCap(tier) {
|
|
145
149
|
return DEFAULT_BYOK_PROVIDER_DAILY_CALL_CAP[tier];
|
|
146
150
|
}
|
|
147
|
-
/**
|
|
148
|
-
* The plan tiers the spend-safety default tables above are keyed by. `limitsTier`
|
|
149
|
-
* collapses "enterprise" onto the "scale" enforcement rung, but these tables carry
|
|
150
|
-
* a distinct "enterprise" column (null = runs on custom limits) — so prefer the
|
|
151
|
-
* raw plan_tier whenever it is itself a table key, and fall back to the limits rung
|
|
152
|
-
* (always a valid PlanTier key) when plan_tier is absent or an unknown/legacy
|
|
153
|
-
* string (e.g. a Stripe price nickname). Single source of truth for the
|
|
154
|
-
* `oxygen limits` report, the org-facing provider-limits API, and the BYOK
|
|
155
|
-
* daily-cap enforcement resolver — they must agree on which column an org reads.
|
|
156
|
-
*/
|
|
157
151
|
export const SPEND_SAFETY_PLAN_TIERS = [
|
|
158
152
|
"free",
|
|
159
153
|
"starter",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function executeRows<T>(value: unknown): T[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SpanStatusCode, metrics, trace, } from "@opentelemetry/api";
|
|
2
|
+
import { deployEnvTelemetryEnvironment, deployPlatformAttribute } from "./deploy-env.js";
|
|
2
3
|
import { errorId } from "./log.js";
|
|
3
4
|
import { normalizeTelemetryAttributes } from "./redaction.js";
|
|
4
5
|
import { redactSqlParameters, sqlErrorTelemetryAttributes } from "./sql-error.js";
|
|
@@ -73,7 +74,14 @@ export function commonTelemetryAttributes(attributes) {
|
|
|
73
74
|
const pick = (workerKeys, webKeys) => pickEnvBySurface(workerProcess, workerKeys, webKeys);
|
|
74
75
|
return {
|
|
75
76
|
"oxygen.version": OXYGEN_VERSION,
|
|
76
|
-
|
|
77
|
+
// Which infrastructure this process runs on, so a self-hosted span is
|
|
78
|
+
// never mistaken for a managed one while both targets are live.
|
|
79
|
+
"oxygen.platform": deployPlatformAttribute(),
|
|
80
|
+
// OXYGEN_DEPLOY_ENV is consulted first and is the only signal a self-hosted
|
|
81
|
+
// box has: it carries neither FLY_ENVIRONMENT nor VERCEL_ENV, so without
|
|
82
|
+
// this the whole fleet would report its NODE_ENV and collapse prod, shadow
|
|
83
|
+
// and dev into one bucket. Unset leaves the managed chain untouched.
|
|
84
|
+
"deployment.environment": deployEnvTelemetryEnvironment() ?? pick(["FLY_ENVIRONMENT", "NODE_ENV"], ["VERCEL_ENV", "NODE_ENV"]),
|
|
77
85
|
// deployment.sha stays the deploy artifact id: the Fly registry image ref on
|
|
78
86
|
// the worker (release cross-reference), VERCEL_GIT_COMMIT_SHA on the web. The
|
|
79
87
|
// worker's image ref is NOT a git commit, so deployment.git_sha carries the
|
|
@@ -13,3 +13,25 @@
|
|
|
13
13
|
* `Record<string, unknown>`. Rejects `null`, arrays, and every primitive.
|
|
14
14
|
*/
|
|
15
15
|
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* `value` as a `Record<string, unknown>` when it is a non-null, non-array
|
|
18
|
+
* object, else null.
|
|
19
|
+
*/
|
|
20
|
+
export declare function asRecordOrNull(value: unknown): Record<string, unknown> | null;
|
|
21
|
+
/** Alias of {@link asRecordOrNull} for call sites that spell it `asRecord`. */
|
|
22
|
+
export declare const asRecord: typeof asRecordOrNull;
|
|
23
|
+
/**
|
|
24
|
+
* `value` as a `Record<string, unknown>`, falling back to an empty object.
|
|
25
|
+
* Deliberately masks "absent" and "present but wrong shape" the same way its
|
|
26
|
+
* call sites always have — use {@link asRecordOrNull} when that matters.
|
|
27
|
+
*/
|
|
28
|
+
export declare function asRecordOrEmpty(value: unknown): Record<string, unknown>;
|
|
29
|
+
/** Finite number, else null. Does not parse numeric strings. */
|
|
30
|
+
export declare function readFiniteNumber(value: unknown): number | null;
|
|
31
|
+
/** Trimmed non-empty string, else null. */
|
|
32
|
+
export declare function readTrimmedString(value: unknown): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Trimmed non-empty string, else null — additionally coercing a number to its
|
|
35
|
+
* string form. Distinct from {@link readTrimmedString}, which rejects numbers.
|
|
36
|
+
*/
|
|
37
|
+
export declare function readCoercedString(value: unknown): string | null;
|