@oxygen-agent/cli 1.844.6 → 1.845.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +1 -1
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +16 -0
- package/node_modules/@oxygen/shared/dist/sequences.js +80 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -12342,7 +12342,7 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
12342
12342
|
}
|
|
12343
12343
|
}))
|
|
12344
12344
|
.addCommand(new Command("update")
|
|
12345
|
-
.description("Update a sequence. Journey
|
|
12345
|
+
.description("Update a sequence. Journey structure/channels/email binding are draft-only so revised scope receives a fresh launch approval; a started sequence still accepts a definition that changes nothing but its wait steps (the delays between steps, including the one before step 1); sender pools can change while draft or paused; launch caps change through `sequences start` after first start. Name, throttles, tags, and open/click tracking toggles remain editable as allowed by the server. Pass only the fields you want to change.")
|
|
12346
12346
|
.argument("<sequence>", "Sequence id or slug.")
|
|
12347
12347
|
.option("--name <name>", "New human-readable sequence name.")
|
|
12348
12348
|
.option("--steps-file <path>", "Draft only: path to a JSON file: { \"steps\": [...] } replacing the journey. Copy supports {{column}} interpolation; native email steps also expose {{sender_name}} / {{sender_first_name}} / {{sender_email}} from the sending mailbox (a same-named row column wins). A `branch` can also route on the lead's data with a data leaf { has_column: \"email\" } (true when that row_values column is non-empty; present:false for \"missing\").")
|
|
@@ -815,6 +815,22 @@ export declare function sequenceWaitStepJitterMs(step: SequenceWaitStep, enrollm
|
|
|
815
815
|
* spreads a same-instant batch across the jitter window.
|
|
816
816
|
*/
|
|
817
817
|
export declare function sequenceWaitStepDelayWithJitterMs(step: SequenceWaitStep, enrollmentId: string): number;
|
|
818
|
+
/**
|
|
819
|
+
* Whether `after` changes nothing about `before` except its timing — the delays
|
|
820
|
+
* between steps, including the lead-in delay in front of the very first one.
|
|
821
|
+
*
|
|
822
|
+
* This is the one journey edit a LIVE sequence accepts. The journey lock exists
|
|
823
|
+
* so a revised journey gets a fresh launch preview and approval, and a wait
|
|
824
|
+
* provably cannot move that preview: estimateSequenceActions (the start route's
|
|
825
|
+
* estimator) skips every step with no channel, so waits contribute nothing to the
|
|
826
|
+
* planned actions, the channel mix, or the credit estimate the approval was
|
|
827
|
+
* granted against. What a delay does change is when the next step fires — exactly
|
|
828
|
+
* the knob a founder reaches for after watching day one of a live campaign.
|
|
829
|
+
*
|
|
830
|
+
* Inserting or removing a wait counts as timing too: a wait dispatches nothing, so
|
|
831
|
+
* adding one only postpones a send and removing one only brings it forward.
|
|
832
|
+
*/
|
|
833
|
+
export declare function sequenceDefinitionsDifferOnlyInTiming(before: SequenceDefinition, after: SequenceDefinition): boolean;
|
|
818
834
|
/**
|
|
819
835
|
* Render a sequence-copy template against a row's values. Delegates to the shared
|
|
820
836
|
* deterministic engine (sequence-template.ts): `{{column}}` substitution plus
|
|
@@ -1835,6 +1835,86 @@ export function sequenceWaitStepJitterMs(step, enrollmentId) {
|
|
|
1835
1835
|
export function sequenceWaitStepDelayWithJitterMs(step, enrollmentId) {
|
|
1836
1836
|
return sequenceWaitStepDelayMs(step) + sequenceWaitStepJitterMs(step, enrollmentId);
|
|
1837
1837
|
}
|
|
1838
|
+
/**
|
|
1839
|
+
* Branch fields that name another step. A signal branch spells its arms `then`/
|
|
1840
|
+
* `else`; a connection branch spells them `then_id`/`else_id`. Both are erased
|
|
1841
|
+
* from the skeleton and replaced by one normalized pair, so an arm that is absent
|
|
1842
|
+
* on one side and explicitly null on the other still compares equal.
|
|
1843
|
+
*/
|
|
1844
|
+
const BRANCH_TARGET_KEYS = ["then", "else", "then_id", "else_id"];
|
|
1845
|
+
/** Key-sorted JSON, so two structurally equal definitions compare equal whatever order their keys were built in. */
|
|
1846
|
+
function stableJson(value) {
|
|
1847
|
+
if (Array.isArray(value))
|
|
1848
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
1849
|
+
if (value && typeof value === "object") {
|
|
1850
|
+
const entries = Object.entries(value)
|
|
1851
|
+
.filter(([, entry]) => entry !== undefined)
|
|
1852
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
1853
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`).join(",")}}`;
|
|
1854
|
+
}
|
|
1855
|
+
return JSON.stringify(value) ?? "null";
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* The definition with every `wait` step erased: the actions, in order, with each
|
|
1859
|
+
* branch arm repointed to the step its delay leads INTO. Two definitions share a
|
|
1860
|
+
* skeleton exactly when they perform the same journey and differ only in when.
|
|
1861
|
+
*
|
|
1862
|
+
* Waits are folded rather than simply filtered out because a delay is modelled as
|
|
1863
|
+
* a step, so an arm whose delay was edited legitimately repoints from one wait id
|
|
1864
|
+
* to another (setBranchArmDelay in the web builder does exactly that) — a naive
|
|
1865
|
+
* filter would read that as a rerouted branch.
|
|
1866
|
+
*/
|
|
1867
|
+
function sequenceTimingSkeleton(definition) {
|
|
1868
|
+
const steps = Array.isArray(definition?.steps) ? definition.steps : [];
|
|
1869
|
+
const indexById = new Map(steps.map((step, index) => [step.id, index]));
|
|
1870
|
+
// The first non-wait step at or after `id`. A dangling target, an absent one,
|
|
1871
|
+
// and a trailing run of waits all end the path — all resolve to null.
|
|
1872
|
+
const throughWaits = (id) => {
|
|
1873
|
+
if (typeof id !== "string")
|
|
1874
|
+
return null;
|
|
1875
|
+
const from = indexById.get(id);
|
|
1876
|
+
if (from === undefined)
|
|
1877
|
+
return null;
|
|
1878
|
+
for (let k = from; k < steps.length; k++) {
|
|
1879
|
+
const step = steps[k];
|
|
1880
|
+
if (step.kind !== "wait")
|
|
1881
|
+
return step.id;
|
|
1882
|
+
}
|
|
1883
|
+
return null;
|
|
1884
|
+
};
|
|
1885
|
+
const actions = steps
|
|
1886
|
+
.filter((step) => step.kind !== "wait")
|
|
1887
|
+
.map((step) => {
|
|
1888
|
+
const fields = { ...step };
|
|
1889
|
+
const branch = step;
|
|
1890
|
+
for (const key of BRANCH_TARGET_KEYS)
|
|
1891
|
+
delete fields[key];
|
|
1892
|
+
return {
|
|
1893
|
+
...fields,
|
|
1894
|
+
arm_then: throughWaits(branch.then ?? branch.then_id),
|
|
1895
|
+
arm_else: throughWaits(branch.else ?? branch.else_id),
|
|
1896
|
+
};
|
|
1897
|
+
});
|
|
1898
|
+
return stableJson(actions);
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Whether `after` changes nothing about `before` except its timing — the delays
|
|
1902
|
+
* between steps, including the lead-in delay in front of the very first one.
|
|
1903
|
+
*
|
|
1904
|
+
* This is the one journey edit a LIVE sequence accepts. The journey lock exists
|
|
1905
|
+
* so a revised journey gets a fresh launch preview and approval, and a wait
|
|
1906
|
+
* provably cannot move that preview: estimateSequenceActions (the start route's
|
|
1907
|
+
* estimator) skips every step with no channel, so waits contribute nothing to the
|
|
1908
|
+
* planned actions, the channel mix, or the credit estimate the approval was
|
|
1909
|
+
* granted against. What a delay does change is when the next step fires — exactly
|
|
1910
|
+
* the knob a founder reaches for after watching day one of a live campaign.
|
|
1911
|
+
*
|
|
1912
|
+
* Inserting or removing a wait counts as timing too: a wait dispatches nothing, so
|
|
1913
|
+
* adding one only postpones a send and removing one only brings it forward.
|
|
1914
|
+
*/
|
|
1915
|
+
export function sequenceDefinitionsDifferOnlyInTiming(before, after) {
|
|
1916
|
+
return sequenceTimingSkeleton(before) === sequenceTimingSkeleton(after);
|
|
1917
|
+
}
|
|
1838
1918
|
/**
|
|
1839
1919
|
* Render a sequence-copy template against a row's values. Delegates to the shared
|
|
1840
1920
|
* deterministic engine (sequence-template.ts): `{{column}}` substitution plus
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const OXYGEN_VERSION = "1.
|
|
1
|
+
export declare const OXYGEN_VERSION = "1.845.0";
|
|
2
2
|
export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.181.0";
|
|
3
3
|
export declare const MANAGED_INBOX_MINIMUM_CLI_VERSION = "1.326.2";
|
|
4
4
|
export declare const SUPPORT_AGENT_REPLY_MINIMUM_CLI_VERSION = "1.747.0";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const OXYGEN_VERSION = "1.
|
|
1
|
+
export const OXYGEN_VERSION = "1.845.0";
|
|
2
2
|
// The GLOBAL CLI compatibility floor: the oldest CLI allowed to call any
|
|
3
3
|
// operational route. Raising it hard-rejects every older CLI from the entire
|
|
4
4
|
// product, so it obeys one law, enforced by scripts/ci/cli-min-version-gate.mjs:
|