@gravity-rail/sdk 0.15.0 → 0.17.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/dist/agui.cjs +13 -1
- package/dist/agui.cjs.map +1 -1
- package/dist/agui.d.cts +5 -1
- package/dist/agui.d.ts +5 -1
- package/dist/agui.js +12 -1
- package/dist/agui.js.map +1 -1
- package/dist/{chunk-QTJQTTIR.js → chunk-CEFOCGYO.js} +521 -69
- package/dist/chunk-CEFOCGYO.js.map +1 -0
- package/dist/{chunk-GP6ICMWF.js → chunk-PJFEGUHM.js} +8 -1
- package/dist/chunk-PJFEGUHM.js.map +1 -0
- package/dist/{client-BLuz00Qr.d.cts → client-8xakCsVs.d.cts} +2245 -232
- package/dist/{client-CefA5Mxe.d.ts → client-ASnNEN1U.d.ts} +2245 -232
- package/dist/helpers.cjs +7 -0
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +2 -2
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.js +1 -1
- package/dist/index.cjs +898 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +182 -9
- package/dist/index.d.ts +182 -9
- package/dist/index.js +397 -23
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +607 -68
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +328 -7
- package/dist/internal.d.ts +328 -7
- package/dist/internal.js +86 -4
- package/dist/internal.js.map +1 -1
- package/dist/{revision-state-axZ62kJk.d.cts → revision-state-BDsQo_xD.d.cts} +4 -2
- package/dist/{revision-state-DL1ViZ57.d.ts → revision-state-Cz3mcUHc.d.ts} +4 -2
- package/dist/schemas.cjs +179 -217
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +179 -210
- package/dist/schemas.js.map +1 -1
- package/dist/{tasks-Boi6AxGp.d.cts → tasks-Z4GcIqgO.d.cts} +200 -81
- package/dist/{tasks-Boi6AxGp.d.ts → tasks-Z4GcIqgO.d.ts} +200 -81
- package/package.json +3 -3
- package/dist/chunk-GP6ICMWF.js.map +0 -1
- package/dist/chunk-QTJQTTIR.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
MemberImportValidationError,
|
|
45
45
|
MemberType,
|
|
46
46
|
MenuItemResponseItemType,
|
|
47
|
+
MergeMemberRolesRequestAccessStrategy,
|
|
47
48
|
ModelProvider,
|
|
48
49
|
ModelType,
|
|
49
50
|
NO_ASSISTANT_PARTICIPANT_CHAT_TYPES,
|
|
@@ -65,6 +66,7 @@ import {
|
|
|
65
66
|
PIDS_SOURCES,
|
|
66
67
|
POSITIVE_RATING_THRESHOLD,
|
|
67
68
|
ProductType,
|
|
69
|
+
PromoCodeStatus,
|
|
68
70
|
QUERY_VALUELESS_OPS,
|
|
69
71
|
QueriesApi,
|
|
70
72
|
REALTIME_MODEL_CUSTOM,
|
|
@@ -100,9 +102,12 @@ import {
|
|
|
100
102
|
WIDGET_SLUGS,
|
|
101
103
|
WORKFLOW_IMPLICIT_USE_SCOPE,
|
|
102
104
|
WorkflowAccessWarningResourceType,
|
|
105
|
+
WorkflowRoutingMode,
|
|
103
106
|
WorkspaceFeature,
|
|
104
107
|
WorkspaceProvisionStatus,
|
|
108
|
+
archivedMemberConflictId,
|
|
105
109
|
areDependenciesSatisfied,
|
|
110
|
+
asIdentifiedMemberContactConflict,
|
|
106
111
|
canPreviewVoice,
|
|
107
112
|
clearRegistry,
|
|
108
113
|
createWorkspaceClient,
|
|
@@ -125,9 +130,12 @@ import {
|
|
|
125
130
|
getTTSProvider,
|
|
126
131
|
getVoiceModelId,
|
|
127
132
|
initializeFeatures,
|
|
133
|
+
isAccountContactConflict,
|
|
128
134
|
isAgentNoteOrigin,
|
|
129
135
|
isChatType,
|
|
130
136
|
isFeatureAvailable,
|
|
137
|
+
isIdentifiedMemberContactConflict,
|
|
138
|
+
isMemberContactConflict,
|
|
131
139
|
isPositiveRating,
|
|
132
140
|
isRegistryInitialized,
|
|
133
141
|
isSMSDeliveryFailureStatus,
|
|
@@ -137,7 +145,7 @@ import {
|
|
|
137
145
|
roleWorkflowAccess,
|
|
138
146
|
usesBuiltinAssistant,
|
|
139
147
|
validateFeatureDependencies
|
|
140
|
-
} from "./chunk-
|
|
148
|
+
} from "./chunk-CEFOCGYO.js";
|
|
141
149
|
import {
|
|
142
150
|
AttendeeResponseStatus,
|
|
143
151
|
AttendeeStatus,
|
|
@@ -158,7 +166,7 @@ import {
|
|
|
158
166
|
BaseApiClient,
|
|
159
167
|
getCookieValue,
|
|
160
168
|
parseOrgAuthChallenge
|
|
161
|
-
} from "./chunk-
|
|
169
|
+
} from "./chunk-PJFEGUHM.js";
|
|
162
170
|
|
|
163
171
|
// src/workspace-builder/resource.ts
|
|
164
172
|
function isRecord(value) {
|
|
@@ -195,6 +203,14 @@ var WorkspaceBuilderResource = class {
|
|
|
195
203
|
}
|
|
196
204
|
return this._uuid;
|
|
197
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* True for resources the server creates on our behalf (currently a
|
|
208
|
+
* journey's `Start` step). They are kept in the resource list so edges can
|
|
209
|
+
* reference them, but they are never created, printed, or counted.
|
|
210
|
+
*/
|
|
211
|
+
get isImplicit() {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
198
214
|
matchKey() {
|
|
199
215
|
return void 0;
|
|
200
216
|
}
|
|
@@ -217,6 +233,19 @@ function resolveRefValue(value) {
|
|
|
217
233
|
}
|
|
218
234
|
return out;
|
|
219
235
|
}
|
|
236
|
+
function resolveIdRefs(value) {
|
|
237
|
+
return resolveIdRefValue(value);
|
|
238
|
+
}
|
|
239
|
+
function resolveIdRefValue(value) {
|
|
240
|
+
if (value instanceof WorkspaceBuilderResource) return value.id;
|
|
241
|
+
if (Array.isArray(value)) return value.map(resolveIdRefValue);
|
|
242
|
+
if (!isPlainRecord(value)) return value;
|
|
243
|
+
const out = {};
|
|
244
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
245
|
+
out[key] = resolveIdRefValue(nestedValue);
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
220
249
|
|
|
221
250
|
// src/workspace-builder/automation.ts
|
|
222
251
|
var MemberFilterBuilder = class extends WorkspaceBuilderResource {
|
|
@@ -230,7 +259,12 @@ var MemberFilterBuilder = class extends WorkspaceBuilderResource {
|
|
|
230
259
|
description;
|
|
231
260
|
/** Query-engine predicate (JSON IR) authored on MemberFilter.query. */
|
|
232
261
|
query = null;
|
|
233
|
-
/**
|
|
262
|
+
/**
|
|
263
|
+
* Set the filter's query-engine predicate (JSON IR).
|
|
264
|
+
*
|
|
265
|
+
* Anywhere the IR wants a numeric id you may pass the builder that will own
|
|
266
|
+
* it (e.g. a label in a `member.labelId` value); refs resolve at apply time.
|
|
267
|
+
*/
|
|
234
268
|
setQuery(query) {
|
|
235
269
|
this.query = query;
|
|
236
270
|
return this;
|
|
@@ -239,10 +273,12 @@ var MemberFilterBuilder = class extends WorkspaceBuilderResource {
|
|
|
239
273
|
return `MemberFilter: ${this.name}${this.query ? " (query)" : ""}`;
|
|
240
274
|
}
|
|
241
275
|
async apply(client) {
|
|
276
|
+
if (this._existed) return;
|
|
242
277
|
const payload = {
|
|
243
278
|
name: this.name,
|
|
244
279
|
description: this.description,
|
|
245
|
-
|
|
280
|
+
// Builder refs inside the IR become ids here; see resolveIdRefs.
|
|
281
|
+
query: resolveIdRefs(this.query)
|
|
246
282
|
};
|
|
247
283
|
const result = await client.createMemberFilter(payload);
|
|
248
284
|
this._id = result.id;
|
|
@@ -537,35 +573,134 @@ var MemberLabelBuilder = class extends WorkspaceBuilderResource {
|
|
|
537
573
|
};
|
|
538
574
|
|
|
539
575
|
// src/workspace-builder/journeys.ts
|
|
576
|
+
var SECONDS_PER_DAY = 86400;
|
|
577
|
+
function edgeKey(parentId, childId) {
|
|
578
|
+
return `${parentId}:${childId}`;
|
|
579
|
+
}
|
|
580
|
+
function edgeCondition(options) {
|
|
581
|
+
const clauses = [];
|
|
582
|
+
if (options.afterDays !== void 0) {
|
|
583
|
+
clauses.push(`days_since(parent_step.completed_at) >= ${options.afterDays}`);
|
|
584
|
+
}
|
|
585
|
+
if (options.when) clauses.push(`(${options.when})`);
|
|
586
|
+
if (!clauses.length) return void 0;
|
|
587
|
+
return clauses.join(" && ");
|
|
588
|
+
}
|
|
540
589
|
var JourneyStepBuilder = class extends WorkspaceBuilderResource {
|
|
541
590
|
// journey-decoupling Phase 7: a Journey step no longer links to a Workflow.
|
|
542
591
|
// A step nudges via EventRule actions instead of running a Workflow, so the
|
|
543
592
|
// builder no longer accepts or emits a workflow reference.
|
|
544
|
-
constructor(name, journey, order = 0, description = "") {
|
|
593
|
+
constructor(name, journey, order = 0, description = "", options = {}) {
|
|
545
594
|
super();
|
|
546
595
|
this.name = name;
|
|
547
596
|
this.journey = journey;
|
|
548
597
|
this.order = order;
|
|
549
598
|
this.description = description;
|
|
599
|
+
this.assigneeId = options.assigneeId;
|
|
600
|
+
this.posX = options.posX;
|
|
601
|
+
this.posY = options.posY;
|
|
550
602
|
}
|
|
551
603
|
name;
|
|
552
604
|
journey;
|
|
553
605
|
order;
|
|
554
606
|
description;
|
|
555
607
|
connections = [];
|
|
556
|
-
|
|
557
|
-
|
|
608
|
+
rules = [];
|
|
609
|
+
assigneeId;
|
|
610
|
+
posX;
|
|
611
|
+
posY;
|
|
612
|
+
/** Wire an edge to `child` and return the CHILD, so chains read linearly. */
|
|
613
|
+
connectsTo(child, options = {}) {
|
|
614
|
+
if (child === this) {
|
|
615
|
+
throw new Error(`Journey step '${this.name}' cannot connect to itself`);
|
|
616
|
+
}
|
|
617
|
+
const abandonSeconds = options.abandonAfterDays !== void 0 ? options.abandonAfterDays * SECONDS_PER_DAY : options.timeoutSeconds;
|
|
618
|
+
this.connections.push({
|
|
619
|
+
parent: this,
|
|
620
|
+
child,
|
|
621
|
+
condition: edgeCondition(options),
|
|
622
|
+
required: options.required ?? false,
|
|
623
|
+
timeoutSeconds: abandonSeconds,
|
|
624
|
+
order: options.order,
|
|
625
|
+
filterId: options.filterId
|
|
626
|
+
});
|
|
627
|
+
return child;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Attach a `JOURNEY_STEP_EXECUTE` action — what this step actually DOES
|
|
631
|
+
* when a member reaches it. A step with no rules is inert: the member
|
|
632
|
+
* lands on it, nothing is sent, and the journey moves on.
|
|
633
|
+
*/
|
|
634
|
+
onExecute(actionType, params = {}, options = {}) {
|
|
635
|
+
this.rules.push({
|
|
636
|
+
uuid: crypto.randomUUID(),
|
|
637
|
+
actionType,
|
|
638
|
+
actionParams: params,
|
|
639
|
+
options
|
|
640
|
+
});
|
|
558
641
|
return this;
|
|
559
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* Text the member. `phoneNumberUUID` is a workspace PhoneNumber with SMS
|
|
645
|
+
* enabled. Pass `workflowUUID` to start an SMS workflow chat, or
|
|
646
|
+
* `messageTemplate` for a one-off send.
|
|
647
|
+
*/
|
|
648
|
+
sendsSms(params, options = {}) {
|
|
649
|
+
return this.onExecute("sms:send" /* SMS_SEND */, params, options);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Call the member. Both the workspace PhoneNumber uuid (voice enabled) and
|
|
653
|
+
* the Workflow uuid that drives the call are required.
|
|
654
|
+
*/
|
|
655
|
+
callsMember(params, options = {}) {
|
|
656
|
+
return this.onExecute("phone:call" /* PHONE_CALL */, params, options);
|
|
657
|
+
}
|
|
658
|
+
/** Email the member from a workspace inbox. */
|
|
659
|
+
sendsEmail(params, options = {}) {
|
|
660
|
+
return this.onExecute("email:send" /* EMAIL_SEND */, params, options);
|
|
661
|
+
}
|
|
662
|
+
/** Label the member on arrival — pass label builders, not ids. */
|
|
663
|
+
addsLabels(labels, options = {}) {
|
|
664
|
+
return this.onExecute(
|
|
665
|
+
"member:label" /* MEMBER_LABEL_ACTION */,
|
|
666
|
+
{ addLabelIds: labels },
|
|
667
|
+
options
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
/** True once this step has something to do when a member reaches it. */
|
|
671
|
+
get hasOutreach() {
|
|
672
|
+
return this.rules.length > 0;
|
|
673
|
+
}
|
|
674
|
+
eventRuleInputs() {
|
|
675
|
+
return this.rules.map((rule) => ({
|
|
676
|
+
uuid: rule.uuid,
|
|
677
|
+
eventType: "journey_step:execute" /* JOURNEY_STEP_EXECUTE */,
|
|
678
|
+
actionType: rule.actionType,
|
|
679
|
+
actionParams: resolveRefs(rule.actionParams),
|
|
680
|
+
...rule.options.name ? { name: rule.options.name } : {},
|
|
681
|
+
...rule.options.when ? { conditions: rule.options.when } : {},
|
|
682
|
+
...rule.options.delaySeconds ? { delay: rule.options.delaySeconds } : {},
|
|
683
|
+
active: rule.options.active ?? true
|
|
684
|
+
}));
|
|
685
|
+
}
|
|
560
686
|
planLine() {
|
|
561
|
-
|
|
687
|
+
const actions = this.rules.map((rule) => rule.actionType).join(" + ");
|
|
688
|
+
return `Step: ${this.name}${actions ? ` (${actions})` : ""}`;
|
|
562
689
|
}
|
|
563
690
|
async apply(client) {
|
|
691
|
+
if (this._existed) return;
|
|
564
692
|
if (this.journey._id === void 0) return;
|
|
693
|
+
const rules = this.eventRuleInputs();
|
|
565
694
|
const payload = {
|
|
566
695
|
name: this.name,
|
|
567
696
|
description: this.description,
|
|
568
|
-
order: this.order
|
|
697
|
+
order: this.order,
|
|
698
|
+
...this.assigneeId !== void 0 ? { assigneeId: this.assigneeId } : {},
|
|
699
|
+
...this.posX !== void 0 ? { posX: this.posX } : {},
|
|
700
|
+
...this.posY !== void 0 ? { posY: this.posY } : {},
|
|
701
|
+
// Saved atomically with the step, so a partial failure cannot leave
|
|
702
|
+
// an inert step behind.
|
|
703
|
+
...rules.length ? { eventRules: rules } : {}
|
|
569
704
|
};
|
|
570
705
|
const result = await client.createStep(this.journey._id, payload);
|
|
571
706
|
this._id = result.id;
|
|
@@ -573,26 +708,59 @@ var JourneyStepBuilder = class extends WorkspaceBuilderResource {
|
|
|
573
708
|
this._created = true;
|
|
574
709
|
}
|
|
575
710
|
};
|
|
711
|
+
var StartStepBuilder = class extends JourneyStepBuilder {
|
|
712
|
+
get isImplicit() {
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
planLine() {
|
|
716
|
+
return "Step: Start (auto-created)";
|
|
717
|
+
}
|
|
718
|
+
async apply() {
|
|
719
|
+
}
|
|
720
|
+
};
|
|
576
721
|
var JourneyBuilder = class extends WorkspaceBuilderResource {
|
|
577
722
|
constructor(name, options = {}) {
|
|
578
723
|
super();
|
|
579
724
|
this.name = name;
|
|
580
725
|
this.description = options.description ?? "";
|
|
726
|
+
this.start = new StartStepBuilder("Start", this, 0);
|
|
727
|
+
this.steps.push(this.start);
|
|
581
728
|
}
|
|
582
729
|
name;
|
|
583
730
|
description;
|
|
584
731
|
steps = [];
|
|
585
732
|
goals = [];
|
|
733
|
+
/** The auto-created `Start` step — connect the first real step to it. */
|
|
734
|
+
start;
|
|
735
|
+
/** `parentId:childId` for every edge the server already has (see `adopt`). */
|
|
736
|
+
existingEdges = /* @__PURE__ */ new Set();
|
|
586
737
|
step(name, options = {}) {
|
|
587
738
|
const step = new JourneyStepBuilder(
|
|
588
739
|
name,
|
|
589
740
|
this,
|
|
590
|
-
options.order ??
|
|
591
|
-
options.description ?? ""
|
|
741
|
+
options.order ?? this.steps.length,
|
|
742
|
+
options.description ?? "",
|
|
743
|
+
options
|
|
592
744
|
);
|
|
593
745
|
this.steps.push(step);
|
|
594
746
|
return step;
|
|
595
747
|
}
|
|
748
|
+
/**
|
|
749
|
+
* Declare a linear cadence: `Start → steps[0] → steps[1] → …`, each edge
|
|
750
|
+
* carrying the matching entry from `spacing`.
|
|
751
|
+
*
|
|
752
|
+
* Chaining is not optional polish. A step created with no incoming edge is
|
|
753
|
+
* a ROOT, and every root fires the moment a member enrols — a "three
|
|
754
|
+
* follow-ups over four days" journey built without edges sends all three
|
|
755
|
+
* at once.
|
|
756
|
+
*/
|
|
757
|
+
sequence(steps, spacing = []) {
|
|
758
|
+
let parent = this.start;
|
|
759
|
+
steps.forEach((step, index) => {
|
|
760
|
+
parent = parent.connectsTo(step, spacing[index] ?? {});
|
|
761
|
+
});
|
|
762
|
+
return this;
|
|
763
|
+
}
|
|
596
764
|
goal(name, options = {}) {
|
|
597
765
|
this.goals.push({
|
|
598
766
|
name,
|
|
@@ -600,17 +768,85 @@ var JourneyBuilder = class extends WorkspaceBuilderResource {
|
|
|
600
768
|
step: options.step,
|
|
601
769
|
completionCel: options.completionCel ?? options.completion_cel,
|
|
602
770
|
value: options.value,
|
|
603
|
-
order: options.order ??
|
|
771
|
+
order: options.order ?? this.goals.length
|
|
604
772
|
});
|
|
605
773
|
return this;
|
|
606
774
|
}
|
|
775
|
+
/**
|
|
776
|
+
* Authoring problems that make a journey silently misbehave at runtime.
|
|
777
|
+
* Reported at plan/preview time, before anything is written.
|
|
778
|
+
*/
|
|
779
|
+
validate() {
|
|
780
|
+
const problems = [];
|
|
781
|
+
const withParents = /* @__PURE__ */ new Set();
|
|
782
|
+
for (const step of this.steps) {
|
|
783
|
+
for (const connection of step.connections) withParents.add(connection.child);
|
|
784
|
+
}
|
|
785
|
+
for (const step of this.steps) {
|
|
786
|
+
if (step === this.start) continue;
|
|
787
|
+
if (!withParents.has(step)) {
|
|
788
|
+
problems.push(
|
|
789
|
+
`step '${step.name}' has no incoming edge \u2014 it is a root and fires for every member the moment they enrol. Connect it from journey.start or a prior step.`
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
if (!step.hasOutreach) {
|
|
793
|
+
problems.push(
|
|
794
|
+
`step '${step.name}' has no JOURNEY_STEP_EXECUTE action \u2014 a member reaching it gets nothing. Add one with .sendsSms / .callsMember / .sendsEmail / .onExecute.`
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (!this.goals.length) {
|
|
799
|
+
problems.push(`journey '${this.name}' has no goals \u2014 nothing measures whether it works.`);
|
|
800
|
+
}
|
|
801
|
+
return problems;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Adopt an existing journey, its steps, and its EDGES, matched by name.
|
|
805
|
+
*
|
|
806
|
+
* Re-running a builder script is normal — a program is usually built in
|
|
807
|
+
* stages as provisioning lands — and without adoption the second run would
|
|
808
|
+
* create a duplicate journey and a duplicate set of steps beside the live
|
|
809
|
+
* one, with members enrolled in neither.
|
|
810
|
+
*
|
|
811
|
+
* The edge set is recorded from the server's own `childConnections` rather
|
|
812
|
+
* than inferred from "both steps exist". A first run that created the steps
|
|
813
|
+
* and then failed before writing an edge leaves exactly that state, and
|
|
814
|
+
* inferring would skip the missing edge on every subsequent run — leaving a
|
|
815
|
+
* permanent root step that fires for every member at enrolment, silently.
|
|
816
|
+
* The journey detail read eager-loads connections, so the real answer is
|
|
817
|
+
* already in hand; there is no reason to guess.
|
|
818
|
+
*/
|
|
819
|
+
adopt(existing) {
|
|
820
|
+
super.adopt(existing);
|
|
821
|
+
const existingSteps = existing.steps ?? [];
|
|
822
|
+
const byName = new Map(existingSteps.map((step) => [step.name, step]));
|
|
823
|
+
const startStep = existingSteps.find((step) => step.id === existing.startStepId) ?? byName.get("Start");
|
|
824
|
+
if (startStep) this.start.adopt(startStep);
|
|
825
|
+
for (const step of this.steps) {
|
|
826
|
+
if (step === this.start) continue;
|
|
827
|
+
const match = byName.get(step.name);
|
|
828
|
+
if (match) step.adopt(match);
|
|
829
|
+
}
|
|
830
|
+
for (const step of existingSteps) {
|
|
831
|
+
for (const edge of step.childConnections ?? []) {
|
|
832
|
+
this.existingEdges.add(edgeKey(edge.parentStepId, edge.childStepId));
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
/** True when the server already has this exact edge. */
|
|
837
|
+
hasEdge(parent, child) {
|
|
838
|
+
if (parent._id === void 0 || child._id === void 0) return false;
|
|
839
|
+
return this.existingEdges.has(edgeKey(parent._id, child._id));
|
|
840
|
+
}
|
|
607
841
|
planLine() {
|
|
608
842
|
const parts = [];
|
|
609
|
-
|
|
843
|
+
const real = this.steps.filter((step) => step !== this.start);
|
|
844
|
+
if (real.length) parts.push(`${real.length} steps`);
|
|
610
845
|
if (this.goals.length) parts.push(`${this.goals.length} goals`);
|
|
611
846
|
return `Journey: ${this.name}${parts.length ? ` (${parts.join(", ")})` : ""}`;
|
|
612
847
|
}
|
|
613
848
|
async apply(client) {
|
|
849
|
+
if (this._existed) return;
|
|
614
850
|
const result = await client.createJourney({
|
|
615
851
|
name: this.name,
|
|
616
852
|
description: this.description
|
|
@@ -618,6 +854,15 @@ var JourneyBuilder = class extends WorkspaceBuilderResource {
|
|
|
618
854
|
this._id = result.id;
|
|
619
855
|
this._uuid = result.uuid;
|
|
620
856
|
this._created = true;
|
|
857
|
+
const startStep = (result.steps ?? []).find(
|
|
858
|
+
(step) => step.id === result.startStepId || step.name === "Start"
|
|
859
|
+
);
|
|
860
|
+
if (!startStep) {
|
|
861
|
+
throw new Error(
|
|
862
|
+
`Journey '${this.name}' was created without a Start step; edges from journey.start cannot be wired.`
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
this.start.adopt(startStep);
|
|
621
866
|
}
|
|
622
867
|
};
|
|
623
868
|
function buildJourneyConnectionPayload(connection) {
|
|
@@ -627,7 +872,9 @@ function buildJourneyConnectionPayload(connection) {
|
|
|
627
872
|
childStepUuid: connection.child._uuid,
|
|
628
873
|
...connection.condition ? { celCondition: connection.condition } : {},
|
|
629
874
|
...connection.required ? { required: true } : {},
|
|
630
|
-
...connection.timeoutSeconds ? { timeoutSeconds: connection.timeoutSeconds } : {}
|
|
875
|
+
...connection.timeoutSeconds ? { timeoutSeconds: connection.timeoutSeconds } : {},
|
|
876
|
+
...connection.order !== void 0 ? { order: connection.order } : {},
|
|
877
|
+
...connection.filterId !== void 0 ? { filterId: connection.filterId } : {}
|
|
631
878
|
};
|
|
632
879
|
}
|
|
633
880
|
function buildJourneyGoalPayload(goal) {
|
|
@@ -731,6 +978,16 @@ var TaskBuilder = class extends WorkspaceBuilderResource {
|
|
|
731
978
|
description;
|
|
732
979
|
transitions = [];
|
|
733
980
|
eventRules = [];
|
|
981
|
+
/** Child task ids the server already has, recorded when this task is adopted. */
|
|
982
|
+
existingChildIds = /* @__PURE__ */ new Set();
|
|
983
|
+
/** Record the edges an adopted task already carries, so a re-run adds only what is missing. */
|
|
984
|
+
adopt(existing) {
|
|
985
|
+
super.adopt(existing);
|
|
986
|
+
for (const child of existing.childTasks ?? []) {
|
|
987
|
+
const id = child.taskId ?? child.id;
|
|
988
|
+
if (typeof id === "number") this.existingChildIds.add(id);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
734
991
|
transitionsTo(child, when, autoNext = false, required = false, celCondition) {
|
|
735
992
|
if (child === this) {
|
|
736
993
|
throw new Error(`Task '${this.name}' cannot transition to itself`);
|
|
@@ -913,6 +1170,15 @@ var PHASES = /* @__PURE__ */ new Map([
|
|
|
913
1170
|
[JourneyBuilder, 7],
|
|
914
1171
|
[JourneyStepBuilder, 8]
|
|
915
1172
|
]);
|
|
1173
|
+
function phaseFor(resource) {
|
|
1174
|
+
let ctor = resource.constructor;
|
|
1175
|
+
while (ctor) {
|
|
1176
|
+
const phase = PHASES.get(ctor);
|
|
1177
|
+
if (phase !== void 0) return phase;
|
|
1178
|
+
ctor = Object.getPrototypeOf(ctor);
|
|
1179
|
+
}
|
|
1180
|
+
return 99;
|
|
1181
|
+
}
|
|
916
1182
|
var WorkspaceBuilder = class {
|
|
917
1183
|
constructor(client) {
|
|
918
1184
|
this.client = client;
|
|
@@ -975,7 +1241,8 @@ var WorkspaceBuilder = class {
|
|
|
975
1241
|
${"=".repeat(50)}`);
|
|
976
1242
|
writeLine("Plan");
|
|
977
1243
|
writeLine("=".repeat(50));
|
|
978
|
-
|
|
1244
|
+
const declared = resources.filter((resource) => !resource.isImplicit);
|
|
1245
|
+
for (const resource of declared) {
|
|
979
1246
|
writeLine(` + ${resource.planLine()}`);
|
|
980
1247
|
}
|
|
981
1248
|
const wiring = this.collectWiringSummary(resources);
|
|
@@ -983,8 +1250,9 @@ ${"=".repeat(50)}`);
|
|
|
983
1250
|
writeLine("\n Wiring:");
|
|
984
1251
|
for (const line of wiring) writeLine(` ${line}`);
|
|
985
1252
|
}
|
|
1253
|
+
this.reportJourneyWarnings();
|
|
986
1254
|
writeLine(`
|
|
987
|
-
Total: ${
|
|
1255
|
+
Total: ${declared.length} resources, ${wiring.length} connections`);
|
|
988
1256
|
writeLine(`${"=".repeat(50)}
|
|
989
1257
|
`);
|
|
990
1258
|
}
|
|
@@ -995,7 +1263,8 @@ ${"=".repeat(50)}`);
|
|
|
995
1263
|
${"=".repeat(50)}`);
|
|
996
1264
|
writeLine("Preview");
|
|
997
1265
|
writeLine("=".repeat(50));
|
|
998
|
-
|
|
1266
|
+
const declared = resources.filter((resource) => !resource.isImplicit);
|
|
1267
|
+
for (const resource of declared) {
|
|
999
1268
|
if (resource._existed) {
|
|
1000
1269
|
writeLine(` = ${resource.planLine()} (exists, id=${resource._id})`);
|
|
1001
1270
|
} else {
|
|
@@ -1007,8 +1276,9 @@ ${"=".repeat(50)}`);
|
|
|
1007
1276
|
writeLine("\n Wiring:");
|
|
1008
1277
|
for (const line of wiring) writeLine(` ${line}`);
|
|
1009
1278
|
}
|
|
1010
|
-
|
|
1011
|
-
const
|
|
1279
|
+
this.reportJourneyWarnings();
|
|
1280
|
+
const toCreate = declared.filter((resource) => !resource._existed);
|
|
1281
|
+
const existing = declared.filter((resource) => resource._existed);
|
|
1012
1282
|
writeLine(
|
|
1013
1283
|
`
|
|
1014
1284
|
Total: ${toCreate.length} to create, ${existing.length} existing, ${wiring.length} connections`
|
|
@@ -1016,13 +1286,72 @@ ${"=".repeat(50)}`);
|
|
|
1016
1286
|
writeLine(`${"=".repeat(50)}
|
|
1017
1287
|
`);
|
|
1018
1288
|
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Print journey authoring problems that only bite at runtime — a step with
|
|
1291
|
+
* no incoming edge fires for every member at enrolment, and a step with no
|
|
1292
|
+
* action does nothing at all. Both look fine in the editor, so they are
|
|
1293
|
+
* surfaced before the write rather than discovered from a member's phone.
|
|
1294
|
+
*/
|
|
1295
|
+
reportJourneyWarnings() {
|
|
1296
|
+
const problems = this.resources.filter((resource) => resource instanceof JourneyBuilder).flatMap((journey) => journey.validate());
|
|
1297
|
+
if (!problems.length) return;
|
|
1298
|
+
writeLine("\n Journey warnings:");
|
|
1299
|
+
for (const problem of problems) writeLine(` ! ${problem}`);
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Problems that make the plan unwritable, found before the first write.
|
|
1303
|
+
*
|
|
1304
|
+
* The API validates each CEL when you save it, and its diagnosis is right —
|
|
1305
|
+
* but it fires per-write, so a plan whose edges reference a non-collection
|
|
1306
|
+
* form gets four labels, two forms, a workflow and six tasks created before
|
|
1307
|
+
* the seventh call rejects it. The operator is left with a half-built
|
|
1308
|
+
* program to hand-repair. Checking here costs nothing and writes nothing.
|
|
1309
|
+
*
|
|
1310
|
+
* Deliberately narrow: only cross-resource facts the builder can see with
|
|
1311
|
+
* certainty. Per-field CEL schema checking stays server-side, where the
|
|
1312
|
+
* real validator lives — duplicating it here would drift.
|
|
1313
|
+
*/
|
|
1314
|
+
preflight() {
|
|
1315
|
+
const problems = [];
|
|
1316
|
+
const forms = this.resources.filter(
|
|
1317
|
+
(resource) => resource instanceof FormBuilder
|
|
1318
|
+
);
|
|
1319
|
+
const collectionBySlug = new Map(forms.map((form) => [form.slug, form.collection]));
|
|
1320
|
+
const reference = /assignment\.collections\.([A-Za-z0-9_]+)/g;
|
|
1321
|
+
for (const resource of this.sortedResources()) {
|
|
1322
|
+
if (!(resource instanceof TaskBuilder)) continue;
|
|
1323
|
+
for (const transition of resource.transitions) {
|
|
1324
|
+
const cel = transition.celCondition;
|
|
1325
|
+
if (!cel) continue;
|
|
1326
|
+
for (const [, slug] of cel.matchAll(reference)) {
|
|
1327
|
+
if (!collectionBySlug.has(slug)) continue;
|
|
1328
|
+
if (!collectionBySlug.get(slug)) {
|
|
1329
|
+
problems.push(
|
|
1330
|
+
`task '${transition.parent.name}' \u2192 '${transition.child.name}': CEL reads assignment.collections.${slug}, but this plan creates '${slug}' as a non-collection form. Declare it with { collection: true }.`
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
return [...new Set(problems)];
|
|
1337
|
+
}
|
|
1019
1338
|
async apply() {
|
|
1339
|
+
const problems = this.preflight();
|
|
1340
|
+
if (problems.length) {
|
|
1341
|
+
writeLine("\nPlan rejected \u2014 nothing was created:");
|
|
1342
|
+
for (const problem of problems) writeLine(` ! ${problem}`);
|
|
1343
|
+
throw new Error(
|
|
1344
|
+
`Plan has ${problems.length} problem(s) and was not applied:
|
|
1345
|
+
` + problems.map((problem) => ` - ${problem}`).join("\n")
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1020
1348
|
const resources = this.sortedResources();
|
|
1021
1349
|
await this.resolveExisting(resources);
|
|
1022
1350
|
const summary = {};
|
|
1023
1351
|
writeLine("Applying...");
|
|
1024
1352
|
for (const resource of resources) {
|
|
1025
1353
|
try {
|
|
1354
|
+
if (resource.isImplicit) continue;
|
|
1026
1355
|
if (resource._existed) {
|
|
1027
1356
|
const key = resourceKey(resource);
|
|
1028
1357
|
summary[key] ??= [];
|
|
@@ -1080,6 +1409,29 @@ Done! Created ${count} resources.`);
|
|
|
1080
1409
|
}
|
|
1081
1410
|
}
|
|
1082
1411
|
}
|
|
1412
|
+
const filterNames = new Map(
|
|
1413
|
+
resources.filter(
|
|
1414
|
+
(resource) => resource instanceof MemberFilterBuilder
|
|
1415
|
+
).map((resource) => [resource.name, resource])
|
|
1416
|
+
);
|
|
1417
|
+
if (filterNames.size) {
|
|
1418
|
+
for (const existing of await this.client.getMemberFilters()) {
|
|
1419
|
+
if (existing.name && filterNames.has(existing.name)) {
|
|
1420
|
+
filterNames.get(existing.name).adopt(existing);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
const journeyNames = new Map(
|
|
1425
|
+
resources.filter((resource) => resource instanceof JourneyBuilder).map((resource) => [resource.name, resource])
|
|
1426
|
+
);
|
|
1427
|
+
if (journeyNames.size) {
|
|
1428
|
+
const existingJourneys = await this.client.getJourneys();
|
|
1429
|
+
for (const existing of existingJourneys.items) {
|
|
1430
|
+
if (!existing.name || !journeyNames.has(existing.name)) continue;
|
|
1431
|
+
const detail = await this.client.getJourney(existing.id);
|
|
1432
|
+
journeyNames.get(existing.name).adopt(detail);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1083
1435
|
if (!workflowNames.size) return;
|
|
1084
1436
|
const existingWorkflows = await this.client.getWorkflows();
|
|
1085
1437
|
for (const { workflow: existing } of existingWorkflows.items) {
|
|
@@ -1130,9 +1482,7 @@ Done! Created ${count} resources.`);
|
|
|
1130
1482
|
all.push(...resource.steps);
|
|
1131
1483
|
}
|
|
1132
1484
|
}
|
|
1133
|
-
return all.sort(
|
|
1134
|
-
(a, b) => (PHASES.get(a.constructor) ?? 99) - (PHASES.get(b.constructor) ?? 99)
|
|
1135
|
-
);
|
|
1485
|
+
return all.sort((a, b) => phaseFor(a) - phaseFor(b));
|
|
1136
1486
|
}
|
|
1137
1487
|
collectWiringSummary(resources) {
|
|
1138
1488
|
const lines = [];
|
|
@@ -1153,7 +1503,11 @@ Done! Created ${count} resources.`);
|
|
|
1153
1503
|
lines.push(`${rule.actionType} rule on '${resource.name}'`);
|
|
1154
1504
|
} else if (resource instanceof JourneyStepBuilder) {
|
|
1155
1505
|
for (const connection of resource.connections) {
|
|
1156
|
-
|
|
1506
|
+
const guard = connection.condition ? ` [${connection.condition}]` : "";
|
|
1507
|
+
lines.push(`${connection.parent.name} -> ${connection.child.name}${guard}`);
|
|
1508
|
+
}
|
|
1509
|
+
for (const rule of resource.rules) {
|
|
1510
|
+
lines.push(`${rule.actionType} on step '${resource.name}'`);
|
|
1157
1511
|
}
|
|
1158
1512
|
} else if (resource instanceof JourneyBuilder) {
|
|
1159
1513
|
for (const goal of resource.goals) lines.push(`Goal: ${goal.name}`);
|
|
@@ -1177,6 +1531,7 @@ Done! Created ${count} resources.`);
|
|
|
1177
1531
|
const childTasks = transitions.flatMap((transition) => {
|
|
1178
1532
|
const taskId = transition.child._id;
|
|
1179
1533
|
if (taskId === void 0 || taskId === parent._id) return [];
|
|
1534
|
+
if (parent.existingChildIds.has(taskId)) return [];
|
|
1180
1535
|
return [
|
|
1181
1536
|
{
|
|
1182
1537
|
taskId,
|
|
@@ -1269,6 +1624,10 @@ Done! Created ${count} resources.`);
|
|
|
1269
1624
|
if (!(resource instanceof JourneyStepBuilder)) continue;
|
|
1270
1625
|
for (const connection of resource.connections) {
|
|
1271
1626
|
if (resource.journey._id === void 0) continue;
|
|
1627
|
+
if (resource.journey.hasEdge(connection.parent, connection.child)) {
|
|
1628
|
+
writeLine(` Exists: ${connection.parent.name} -> ${connection.child.name}`);
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1272
1631
|
const payload = buildJourneyConnectionPayload(connection);
|
|
1273
1632
|
if (!payload) continue;
|
|
1274
1633
|
await this.client.createConnection(resource.journey._id, payload);
|
|
@@ -1279,6 +1638,12 @@ Done! Created ${count} resources.`);
|
|
|
1279
1638
|
async applyJourneyGoals(resources) {
|
|
1280
1639
|
for (const resource of resources) {
|
|
1281
1640
|
if (!(resource instanceof JourneyBuilder) || resource._id === void 0) continue;
|
|
1641
|
+
if (resource._existed) {
|
|
1642
|
+
for (const goal of resource.goals) {
|
|
1643
|
+
writeLine(` Skipped (journey already exists): Goal: ${goal.name}`);
|
|
1644
|
+
}
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1282
1647
|
for (const goal of resource.goals) {
|
|
1283
1648
|
await this.client.createGoal(resource._id, buildJourneyGoalPayload(goal));
|
|
1284
1649
|
writeLine(` Created goal: ${goal.name}`);
|
|
@@ -1557,6 +1922,7 @@ export {
|
|
|
1557
1922
|
MemberType,
|
|
1558
1923
|
MenuItemResponseItemType,
|
|
1559
1924
|
MenuItemResponseItemType as MenuItemType,
|
|
1925
|
+
MergeMemberRolesRequestAccessStrategy,
|
|
1560
1926
|
ModelProvider,
|
|
1561
1927
|
ModelType,
|
|
1562
1928
|
NO_ASSISTANT_PARTICIPANT_CHAT_TYPES,
|
|
@@ -1579,6 +1945,7 @@ export {
|
|
|
1579
1945
|
PIDS_SOURCES,
|
|
1580
1946
|
POSITIVE_RATING_THRESHOLD,
|
|
1581
1947
|
ProductType,
|
|
1948
|
+
PromoCodeStatus,
|
|
1582
1949
|
QUERY_VALUELESS_OPS,
|
|
1583
1950
|
QueriesApi,
|
|
1584
1951
|
REALTIME_MODEL_CUSTOM,
|
|
@@ -1619,10 +1986,14 @@ export {
|
|
|
1619
1986
|
WorkflowAccessWarningResourceType,
|
|
1620
1987
|
WorkflowBuilder,
|
|
1621
1988
|
WorkflowAccessWarningResourceType as WorkflowResourceType,
|
|
1989
|
+
WorkflowRoutingMode,
|
|
1622
1990
|
WorkspaceBuilder,
|
|
1991
|
+
WorkspaceBuilderResource,
|
|
1623
1992
|
WorkspaceFeature,
|
|
1624
1993
|
WorkspaceProvisionStatus,
|
|
1994
|
+
archivedMemberConflictId,
|
|
1625
1995
|
areDependenciesSatisfied,
|
|
1996
|
+
asIdentifiedMemberContactConflict,
|
|
1626
1997
|
canPreviewVoice,
|
|
1627
1998
|
clearRegistry,
|
|
1628
1999
|
createWorkspaceClient,
|
|
@@ -1646,9 +2017,12 @@ export {
|
|
|
1646
2017
|
getTTSProvider,
|
|
1647
2018
|
getVoiceModelId,
|
|
1648
2019
|
initializeFeatures,
|
|
2020
|
+
isAccountContactConflict,
|
|
1649
2021
|
isAgentNoteOrigin,
|
|
1650
2022
|
isChatType,
|
|
1651
2023
|
isFeatureAvailable,
|
|
2024
|
+
isIdentifiedMemberContactConflict,
|
|
2025
|
+
isMemberContactConflict,
|
|
1652
2026
|
isPositiveRating,
|
|
1653
2027
|
isRegistryInitialized,
|
|
1654
2028
|
isSMSDeliveryFailureStatus,
|