@gravity-rail/sdk 0.22.0 → 0.32.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 +57 -8
- package/dist/agui.d.cts +1 -1
- package/dist/agui.d.ts +1 -1
- package/dist/audio.d.cts +1 -1
- package/dist/audio.d.ts +1 -1
- package/dist/{client-C10qSd6b.d.ts → chat-interaction-BKdImgZ5.d.ts} +5604 -1531
- package/dist/{client-Ct0soeip.d.cts → chat-interaction-DLfC_h_P.d.cts} +5604 -1531
- package/dist/{chunk-3HAV5XUJ.js → chunk-HRUMZ46V.js} +1 -1
- package/dist/chunk-HRUMZ46V.js.map +1 -0
- package/dist/{chunk-WMA6YCAS.js → chunk-N446FGCT.js} +1230 -201
- package/dist/chunk-N446FGCT.js.map +1 -0
- package/dist/{chunk-7OLBLLV7.js → chunk-P2E6LU7I.js} +57 -7
- package/dist/chunk-P2E6LU7I.js.map +1 -0
- package/dist/helpers.cjs +54 -1
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +3 -2
- package/dist/helpers.d.ts +3 -2
- package/dist/helpers.js +11 -3
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +2291 -148
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +509 -6
- package/dist/index.d.ts +509 -6
- package/dist/index.js +1032 -23
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +1320 -220
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +33 -49
- package/dist/internal.d.ts +33 -49
- package/dist/internal.js +73 -33
- package/dist/internal.js.map +1 -1
- package/dist/realtime.cjs +279 -14
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +169 -17
- package/dist/realtime.d.ts +169 -17
- package/dist/realtime.js +264 -13
- package/dist/realtime.js.map +1 -1
- package/dist/{revision-state-CCPTiW-i.d.cts → revision-state-Bsq5dKwi.d.cts} +236 -5
- package/dist/{revision-state-C6aM-PHu.d.ts → revision-state-CQTYcIRG.d.ts} +236 -5
- package/dist/schemas.cjs +3 -1
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +31 -13
- package/dist/schemas.d.ts +31 -13
- package/dist/schemas.js +4 -2
- package/dist/schemas.js.map +1 -1
- package/dist/{tasks-WH_VfzMv.d.cts → tasks-B-31JPuq.d.cts} +298 -114
- package/dist/{tasks-WH_VfzMv.d.ts → tasks-B-31JPuq.d.ts} +298 -114
- package/dist/types-0c6_NGU4.d.cts +90 -0
- package/dist/types-0c6_NGU4.d.ts +90 -0
- package/package.json +3 -3
- package/dist/chunk-3HAV5XUJ.js.map +0 -1
- package/dist/chunk-7OLBLLV7.js.map +0 -1
- package/dist/chunk-WMA6YCAS.js.map +0 -1
- package/dist/types-DkYyrvoC.d.cts +0 -19
- package/dist/types-DkYyrvoC.d.ts +0 -19
package/dist/index.js
CHANGED
|
@@ -7,6 +7,12 @@ import {
|
|
|
7
7
|
AccessGrantStatus,
|
|
8
8
|
AccessGrantTrigger,
|
|
9
9
|
AccessLevel,
|
|
10
|
+
ActivityEventFamily,
|
|
11
|
+
ActivityGrain,
|
|
12
|
+
ActivityGroupBy,
|
|
13
|
+
ActivityOpenItemOrder,
|
|
14
|
+
ActivityOutcome,
|
|
15
|
+
ActivityWindowBasis,
|
|
10
16
|
AppsApi,
|
|
11
17
|
BUILTIN_ASSISTANT_CHAT_TYPES,
|
|
12
18
|
BuildersApi,
|
|
@@ -16,13 +22,20 @@ import {
|
|
|
16
22
|
CELApi,
|
|
17
23
|
CHAT_TYPES,
|
|
18
24
|
CapabilityClass,
|
|
25
|
+
ChannelMessageDirection,
|
|
26
|
+
ChannelMessageSource,
|
|
27
|
+
ChannelMessagesUnavailableReason,
|
|
19
28
|
ConsentMethod,
|
|
20
29
|
CriterionType,
|
|
30
|
+
DEFAULT_AGENT_CHAT_MODEL,
|
|
31
|
+
DEFAULT_AGENT_REALTIME_MODEL,
|
|
32
|
+
DEFAULT_AGENT_REALTIME_MODEL_CONFIG,
|
|
21
33
|
DEFAULT_CHAT_MODEL,
|
|
22
34
|
DEFAULT_CUSTOM_VOICE_MODEL,
|
|
23
35
|
DEFAULT_THINKING_SPEECH_PHRASES,
|
|
24
36
|
DEFAULT_VOICE_INFERENCE_MODEL,
|
|
25
37
|
DriverMode,
|
|
38
|
+
EhrWritebackOutcomeResponseStatus,
|
|
26
39
|
EmailDnsRecordType,
|
|
27
40
|
EventRuleActionType,
|
|
28
41
|
EventRuleEventType,
|
|
@@ -33,15 +46,22 @@ import {
|
|
|
33
46
|
FhirConnectionState,
|
|
34
47
|
FhirVendor,
|
|
35
48
|
GravityRailClient,
|
|
49
|
+
ImportSkippedRuleResourceGroupResourceType,
|
|
50
|
+
ImportSkippedRuleResponseNeededResourceTypesItem,
|
|
51
|
+
ImportSkippedRuleResponseParentKind,
|
|
52
|
+
ImportSkippedRuleResponseReasonCode,
|
|
53
|
+
ImportSkippedRuleResponseStatus,
|
|
36
54
|
InboundFaxStatus,
|
|
37
55
|
InvitationStatus,
|
|
38
56
|
JobPosition,
|
|
39
57
|
MASKED_SECRET_SENTINEL,
|
|
58
|
+
MEMBER_RELATIONSHIP_KINDS,
|
|
40
59
|
McpServerState,
|
|
41
60
|
MemberFeature,
|
|
42
61
|
MemberImportMatchInfoMatchType,
|
|
43
62
|
MemberImportPreviewRowAction,
|
|
44
63
|
MemberImportValidationError,
|
|
64
|
+
MemberRelationshipKind,
|
|
45
65
|
MemberType,
|
|
46
66
|
MenuItemResponseItemType,
|
|
47
67
|
MergeMemberRolesRequestAccessStrategy,
|
|
@@ -75,8 +95,11 @@ import {
|
|
|
75
95
|
REALTIME_MODEL_OPENAI,
|
|
76
96
|
REALTIME_MODEL_OPENAI_2,
|
|
77
97
|
REALTIME_MODEL_OPENAI_MINI,
|
|
98
|
+
RefResolutionStatus,
|
|
78
99
|
Region,
|
|
79
100
|
RegistrationIdentifier,
|
|
101
|
+
RelatedMemberRole,
|
|
102
|
+
RelationshipCardinality,
|
|
80
103
|
Resource,
|
|
81
104
|
SCOPES,
|
|
82
105
|
SCOPE_PRESETS,
|
|
@@ -98,7 +121,12 @@ import {
|
|
|
98
121
|
TTS_MODEL_ELEVENLABS_TURBO,
|
|
99
122
|
TTS_PREVIEW_PROVIDERS,
|
|
100
123
|
TemplatesApi,
|
|
124
|
+
TestChatRunLane,
|
|
125
|
+
TestChatRunOutcome,
|
|
101
126
|
ToolkitState,
|
|
127
|
+
UiPromptPresentedKind,
|
|
128
|
+
UiPromptResponseKind,
|
|
129
|
+
UiPromptResponseStatus,
|
|
102
130
|
WIDGET_SLUGS,
|
|
103
131
|
WORKFLOW_IMPLICIT_USE_SCOPE,
|
|
104
132
|
WorkflowAccessWarningResourceType,
|
|
@@ -142,7 +170,7 @@ import {
|
|
|
142
170
|
roleWorkflowAccess,
|
|
143
171
|
usesBuiltinAssistant,
|
|
144
172
|
validateFeatureDependencies
|
|
145
|
-
} from "./chunk-
|
|
173
|
+
} from "./chunk-N446FGCT.js";
|
|
146
174
|
import {
|
|
147
175
|
AttendeeResponseStatus,
|
|
148
176
|
AttendeeStatus,
|
|
@@ -157,13 +185,14 @@ import {
|
|
|
157
185
|
DataSensitivity,
|
|
158
186
|
DataTypeRecordScope,
|
|
159
187
|
IndexStatus
|
|
160
|
-
} from "./chunk-
|
|
188
|
+
} from "./chunk-HRUMZ46V.js";
|
|
161
189
|
import {
|
|
162
190
|
ApiError,
|
|
163
191
|
BaseApiClient,
|
|
192
|
+
ChatInteractionController,
|
|
164
193
|
getCookieValue,
|
|
165
194
|
parseOrgAuthChallenge
|
|
166
|
-
} from "./chunk-
|
|
195
|
+
} from "./chunk-P2E6LU7I.js";
|
|
167
196
|
|
|
168
197
|
// src/workspace-builder/resource.ts
|
|
169
198
|
function isRecord(value) {
|
|
@@ -208,6 +237,19 @@ var WorkspaceBuilderResource = class {
|
|
|
208
237
|
get isImplicit() {
|
|
209
238
|
return false;
|
|
210
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* True when `apply()` is safe — and required — to run against a resource
|
|
242
|
+
* that was ADOPTED rather than created.
|
|
243
|
+
*
|
|
244
|
+
* False by default because most builders create only: re-running their
|
|
245
|
+
* `apply()` on an existing resource would make a duplicate. Override it
|
|
246
|
+
* when `apply()` is an idempotent full write keyed on stable identities,
|
|
247
|
+
* which is the only way a re-run can express an EDIT (a removed goal, a
|
|
248
|
+
* reordered timeline) rather than just an addition.
|
|
249
|
+
*/
|
|
250
|
+
get appliesWhenExisting() {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
211
253
|
matchKey() {
|
|
212
254
|
return void 0;
|
|
213
255
|
}
|
|
@@ -886,6 +928,755 @@ function buildJourneyGoalPayload(goal) {
|
|
|
886
928
|
...goal.value !== void 0 ? { value: goal.value } : {}
|
|
887
929
|
};
|
|
888
930
|
}
|
|
931
|
+
var V2RouteBuilder = class {
|
|
932
|
+
constructor(name, options = {}) {
|
|
933
|
+
this.name = name;
|
|
934
|
+
this.options = options;
|
|
935
|
+
}
|
|
936
|
+
name;
|
|
937
|
+
options;
|
|
938
|
+
touches = [];
|
|
939
|
+
/**
|
|
940
|
+
* The DECISION RULE uuid. Adopted for the same reason an Ending's is:
|
|
941
|
+
* `MemberJourneyStep.entry_decision_rule_id` points at this row with
|
|
942
|
+
* `ON DELETE SET NULL`, so recreating it on a re-run would erase which arm
|
|
943
|
+
* every already-routed enrollment is on — invisibly, since both rules
|
|
944
|
+
* "exist" afterwards.
|
|
945
|
+
*/
|
|
946
|
+
_ruleUuid;
|
|
947
|
+
/** Append a touch to this arm. Order is call order. */
|
|
948
|
+
touch(name, options = {}) {
|
|
949
|
+
const touch = new V2TouchBuilder(name, options);
|
|
950
|
+
this.touches.push(touch);
|
|
951
|
+
return touch;
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
var V2BranchBuilder = class {
|
|
955
|
+
routes = [];
|
|
956
|
+
/** Append a route. Declaration order is evaluation order. */
|
|
957
|
+
route(name, options = {}) {
|
|
958
|
+
const route = new V2RouteBuilder(name, options);
|
|
959
|
+
this.routes.push(route);
|
|
960
|
+
return route;
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
var V2TouchBuilder = class {
|
|
964
|
+
/**
|
|
965
|
+
* The name is CANONICALIZED here, once, and everything downstream reads it:
|
|
966
|
+
* the `key:<name>` address, the payload, adoption's name lookup, and the
|
|
967
|
+
* uniqueness gate. The API trims it before storing
|
|
968
|
+
* (`JourneyV2TouchInput`/`JourneyV2EndingInput` name validators), so a
|
|
969
|
+
* builder holding `" Welcome "` would send an address for a name the server
|
|
970
|
+
* does not have, fail to find its own step on the way back, and treat it as
|
|
971
|
+
* new on the next run — archiving the live step and its history. One
|
|
972
|
+
* representation at the boundary is the only way that cannot happen; a
|
|
973
|
+
* second trim anywhere downstream would be a place for the two to drift.
|
|
974
|
+
*/
|
|
975
|
+
constructor(name, options = {}) {
|
|
976
|
+
this.options = options;
|
|
977
|
+
this.name = name.trim();
|
|
978
|
+
}
|
|
979
|
+
options;
|
|
980
|
+
rules = [];
|
|
981
|
+
/** Filled in from the server's response so a re-run keeps this touch. */
|
|
982
|
+
_uuid;
|
|
983
|
+
/**
|
|
984
|
+
* The exclusive branch that follows this touch, when it has one. A touch
|
|
985
|
+
* carrying a branch gets NO ordinary outgoing connection: its routes are
|
|
986
|
+
* the only way out, so touches declared after it are reached only by an arm
|
|
987
|
+
* naming them in `continueAt`.
|
|
988
|
+
*/
|
|
989
|
+
_branch;
|
|
990
|
+
name;
|
|
991
|
+
/**
|
|
992
|
+
* Attach an exclusive branch after this touch, or return the existing one.
|
|
993
|
+
*
|
|
994
|
+
* Idempotent so a script can add routes in stages without accidentally
|
|
995
|
+
* declaring two branches on one touch — which the server has no shape for,
|
|
996
|
+
* since a touch owns exactly one ordered route set.
|
|
997
|
+
*/
|
|
998
|
+
branch() {
|
|
999
|
+
this._branch ??= new V2BranchBuilder();
|
|
1000
|
+
return this._branch;
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* The request-local address this step answers to (plan §3.8 / phase 6).
|
|
1004
|
+
*
|
|
1005
|
+
* A saved step is addressed by its uuid; an unsaved one by a `key` derived
|
|
1006
|
+
* from its authored name. Deriving from the NAME rather than minting a
|
|
1007
|
+
* uuid4 keeps the payload stable across re-runs of the same script — a
|
|
1008
|
+
* fresh random key on every run would make the request differ from itself
|
|
1009
|
+
* for no reason, and the server's EventRule sync matches on the uuids the
|
|
1010
|
+
* payload carries. Names are already asserted unique within a journey,
|
|
1011
|
+
* which is what makes a name-derived key unique.
|
|
1012
|
+
*
|
|
1013
|
+
* {@link V2EndingBuilder} INHERITS this rather than deriving its own: an
|
|
1014
|
+
* Ending's anchor key and a touch's key land in one server-side namespace
|
|
1015
|
+
* (`stepKey` is one field), so two derivations that drifted would produce
|
|
1016
|
+
* two spellings of the same address and a goal would 404 on the one its
|
|
1017
|
+
* step was not sent under.
|
|
1018
|
+
*
|
|
1019
|
+
* Two steps of one name therefore answer to one address. {@link preflight}
|
|
1020
|
+
* is the first enforcement point and refuses it before any write, counting
|
|
1021
|
+
* touches and Endings together and comparing names trimmed, as the API
|
|
1022
|
+
* stores them. Without that gate the collision surfaces twice and both
|
|
1023
|
+
* times too late: the server rejects the definition PUT after `apply()` has
|
|
1024
|
+
* created the Journey, and {@link indexStoredByName} rejects the SECOND run
|
|
1025
|
+
* of a script whose two stored steps make `adopt()` a coin flip.
|
|
1026
|
+
*/
|
|
1027
|
+
get _address() {
|
|
1028
|
+
return this._uuid ?? `key:${this.name}`;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Re-attach the EventRule uuids the server already stores for this touch.
|
|
1032
|
+
*
|
|
1033
|
+
* Identity is the operator-set rule NAME. `.sendsSms(...)` does not require
|
|
1034
|
+
* one, so an unnamed rule falls back to its actionType — but ONLY when that
|
|
1035
|
+
* answer is 1:1: exactly one unnamed draft of that actionType here, and
|
|
1036
|
+
* exactly one stored rule of it left unclaimed there. Anything else throws.
|
|
1037
|
+
*
|
|
1038
|
+
* The ordinal-within-actionType fallback this replaces was not a weaker
|
|
1039
|
+
* identity, it was the wrong one. Inserting a new unnamed rule ABOVE an
|
|
1040
|
+
* existing same-type rule shifts every position by one, so the new rule
|
|
1041
|
+
* adopted the existing rule's uuid — taking over its delivery bookkeeping —
|
|
1042
|
+
* and the existing rule was written as brand new and archived. Nothing in
|
|
1043
|
+
* the payload showed it: both rules "exist" afterwards. When the script
|
|
1044
|
+
* cannot say which rule is which, the honest answer is to ask the author to
|
|
1045
|
+
* name them, not to guess by position.
|
|
1046
|
+
*/
|
|
1047
|
+
adoptRuleUuids(serverRules) {
|
|
1048
|
+
const namedDrafts = this.rules.filter((draft) => !!draft.options.name);
|
|
1049
|
+
const storedByName = indexStoredByName(
|
|
1050
|
+
serverRules,
|
|
1051
|
+
new Set(namedDrafts.map((draft) => draft.options.name)),
|
|
1052
|
+
`touch '${this.name}'`,
|
|
1053
|
+
"action"
|
|
1054
|
+
);
|
|
1055
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
1056
|
+
for (const draft of namedDrafts) {
|
|
1057
|
+
const match = storedByName.get(draft.options.name);
|
|
1058
|
+
if (!match) continue;
|
|
1059
|
+
draft.adoptedUuid = match.uuid;
|
|
1060
|
+
claimed.add(match);
|
|
1061
|
+
}
|
|
1062
|
+
const unnamedByActionType = /* @__PURE__ */ new Map();
|
|
1063
|
+
for (const draft of this.rules) {
|
|
1064
|
+
if (draft.options.name) continue;
|
|
1065
|
+
const actionType = String(draft.actionType);
|
|
1066
|
+
unnamedByActionType.set(actionType, [
|
|
1067
|
+
...unnamedByActionType.get(actionType) ?? [],
|
|
1068
|
+
draft
|
|
1069
|
+
]);
|
|
1070
|
+
}
|
|
1071
|
+
for (const [actionType, drafts] of unnamedByActionType) {
|
|
1072
|
+
const candidates = serverRules.filter(
|
|
1073
|
+
(rule) => String(rule.actionType) === actionType && !claimed.has(rule)
|
|
1074
|
+
);
|
|
1075
|
+
if (!candidates.length) continue;
|
|
1076
|
+
if (drafts.length === 1 && candidates.length === 1) {
|
|
1077
|
+
drafts[0].adoptedUuid = candidates[0].uuid;
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
throw new Error(
|
|
1081
|
+
`touch '${this.name}' has ${drafts.length} unnamed ${actionType} action(s) and the server has ${candidates.length} \u2014 so there is no way to tell which stored action each one is. Adopting by position swaps their identities the moment an action is inserted or removed, which moves one action's delivery history onto another. Give these actions names (\`{ name: ... }\`) so each one can be matched to the right rule.`
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
/** Attach a `JOURNEY_STEP_EXECUTE` action. Same contract as legacy. */
|
|
1086
|
+
onExecute(actionType, params = {}, options = {}) {
|
|
1087
|
+
this.rules.push({
|
|
1088
|
+
uuid: crypto.randomUUID(),
|
|
1089
|
+
actionType,
|
|
1090
|
+
actionParams: params,
|
|
1091
|
+
options
|
|
1092
|
+
});
|
|
1093
|
+
return this;
|
|
1094
|
+
}
|
|
1095
|
+
sendsSms(params, options = {}) {
|
|
1096
|
+
return this.onExecute("sms:send" /* SMS_SEND */, params, options);
|
|
1097
|
+
}
|
|
1098
|
+
callsMember(params, options = {}) {
|
|
1099
|
+
return this.onExecute("phone:call" /* PHONE_CALL */, params, options);
|
|
1100
|
+
}
|
|
1101
|
+
sendsEmail(params, options = {}) {
|
|
1102
|
+
return this.onExecute("email:send" /* EMAIL_SEND */, params, options);
|
|
1103
|
+
}
|
|
1104
|
+
get hasOutreach() {
|
|
1105
|
+
return this.rules.length > 0;
|
|
1106
|
+
}
|
|
1107
|
+
eventRuleInputs() {
|
|
1108
|
+
return this.rules.map((rule) => ({
|
|
1109
|
+
uuid: rule.adoptedUuid ?? rule.uuid,
|
|
1110
|
+
eventType: "journey_step:execute" /* JOURNEY_STEP_EXECUTE */,
|
|
1111
|
+
actionType: rule.actionType,
|
|
1112
|
+
actionParams: resolveRefs(rule.actionParams),
|
|
1113
|
+
...rule.options.name ? { name: rule.options.name } : {},
|
|
1114
|
+
...rule.options.when ? { conditions: rule.options.when } : {},
|
|
1115
|
+
...rule.options.delaySeconds ? { delay: rule.options.delaySeconds } : {},
|
|
1116
|
+
active: rule.options.active ?? true
|
|
1117
|
+
}));
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
var V2EndingBuilder = class extends V2TouchBuilder {
|
|
1121
|
+
constructor(name, when, filterId) {
|
|
1122
|
+
super(name);
|
|
1123
|
+
this.when = when;
|
|
1124
|
+
this.filterId = filterId;
|
|
1125
|
+
}
|
|
1126
|
+
when;
|
|
1127
|
+
filterId;
|
|
1128
|
+
/**
|
|
1129
|
+
* The DECISION RULE uuid, distinct from `_uuid` (the anchor step's).
|
|
1130
|
+
*
|
|
1131
|
+
* An Ending is two rows: a `JourneyDecisionRule` carrying the condition and
|
|
1132
|
+
* precedence, and a `JourneyStep` anchor carrying its actions, goals, and
|
|
1133
|
+
* status history. Adopting only the anchor left the rule to be recreated on
|
|
1134
|
+
* every re-run — and `MemberJourney.latched_ending_rule_id` /
|
|
1135
|
+
* `JourneyStatusLog.journey_decision_rule_id` point at the RULE with
|
|
1136
|
+
* `ON DELETE SET NULL`, so every completed enrollment silently forgot which
|
|
1137
|
+
* Ending it took.
|
|
1138
|
+
*/
|
|
1139
|
+
_ruleUuid;
|
|
1140
|
+
/**
|
|
1141
|
+
* An Ending cannot carry a branch, and saying so is the whole override.
|
|
1142
|
+
*
|
|
1143
|
+
* `V2EndingBuilder extends V2TouchBuilder` for the identity and addressing
|
|
1144
|
+
* an Ending's anchor shares with a touch, which also inherits every touch
|
|
1145
|
+
* method — `branch()` among them. Inherited unchanged it would set
|
|
1146
|
+
* `_branch` on an object nothing ever reads it from: the Ending serializer
|
|
1147
|
+
* writes `{uuid|key, name, celCondition, filterId, eventRules}` and no
|
|
1148
|
+
* `branch`, and `allTouches()` walks only the timeline, so the routes are
|
|
1149
|
+
* never numbered, validated, adopted or sent. The author gets no error and
|
|
1150
|
+
* a definition quietly missing what they asked for.
|
|
1151
|
+
*
|
|
1152
|
+
* An Ending is terminal by construction — it is the rule that ENDS an
|
|
1153
|
+
* enrollment — so there is no shape to serialize even if the payload had a
|
|
1154
|
+
* field for it. Refusing at the call is the only point where the author is
|
|
1155
|
+
* still holding the mistake.
|
|
1156
|
+
*
|
|
1157
|
+
* @see foreignAnchorProblem for why this names no Ending.
|
|
1158
|
+
*/
|
|
1159
|
+
branch() {
|
|
1160
|
+
throw new Error(
|
|
1161
|
+
"an Ending cannot carry a branch. An Ending terminates an enrollment, so there is nothing after it to route to, and the atomic definition has no field to send one under \u2014 the routes would be silently dropped. Put the branch on the touch that leads to this Ending instead, and give each arm its own Ending."
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
var JourneyV2Builder = class extends WorkspaceBuilderResource {
|
|
1166
|
+
constructor(name, options = {}) {
|
|
1167
|
+
super();
|
|
1168
|
+
this.name = name;
|
|
1169
|
+
this.description = options.description ?? "";
|
|
1170
|
+
this.activate = options.activate ?? false;
|
|
1171
|
+
}
|
|
1172
|
+
name;
|
|
1173
|
+
description;
|
|
1174
|
+
touches = [];
|
|
1175
|
+
endings = [];
|
|
1176
|
+
goals = [];
|
|
1177
|
+
/** Activate after the definition is written and validated. */
|
|
1178
|
+
activate;
|
|
1179
|
+
/** Optimistic-concurrency token, from create or adoption. */
|
|
1180
|
+
_expectedUpdatedAt;
|
|
1181
|
+
/** Server status at adoption — decides whether a re-run must pause first. */
|
|
1182
|
+
_status;
|
|
1183
|
+
/**
|
|
1184
|
+
* A v2 definition write is idempotent, so an ADOPTED journey is written
|
|
1185
|
+
* too. That is the whole point of adopting one: the PUT is a full,
|
|
1186
|
+
* uuid-keyed replacement, so re-running a script is how an author expresses
|
|
1187
|
+
* an edit. The legacy builder stays create-only — its `apply()` only knows
|
|
1188
|
+
* how to create, and re-running it would duplicate.
|
|
1189
|
+
*/
|
|
1190
|
+
get appliesWhenExisting() {
|
|
1191
|
+
return true;
|
|
1192
|
+
}
|
|
1193
|
+
/** Append a touch to the timeline. Order is call order. */
|
|
1194
|
+
touch(name, options = {}) {
|
|
1195
|
+
const touch = new V2TouchBuilder(name, options);
|
|
1196
|
+
this.touches.push(touch);
|
|
1197
|
+
return touch;
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Every touch in the timeline, arms included, depth first.
|
|
1201
|
+
*
|
|
1202
|
+
* Identity adoption, name-uniqueness and validation all have to see arm
|
|
1203
|
+
* touches — a walk over the top level alone would leave every arm touch
|
|
1204
|
+
* recreated on each re-run, silently archiving its member history.
|
|
1205
|
+
*/
|
|
1206
|
+
allTouches() {
|
|
1207
|
+
const out = [];
|
|
1208
|
+
const walk = (touches) => {
|
|
1209
|
+
for (const touch of touches) {
|
|
1210
|
+
out.push(touch);
|
|
1211
|
+
for (const route of touch._branch?.routes ?? []) walk(route.touches);
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
walk(this.touches);
|
|
1215
|
+
return out;
|
|
1216
|
+
}
|
|
1217
|
+
/** Every route in the timeline, at every nesting depth, depth first. */
|
|
1218
|
+
allRoutes() {
|
|
1219
|
+
const out = [];
|
|
1220
|
+
for (const touch of this.allTouches()) {
|
|
1221
|
+
for (const route of touch._branch?.routes ?? []) out.push(route);
|
|
1222
|
+
}
|
|
1223
|
+
return out;
|
|
1224
|
+
}
|
|
1225
|
+
/** Append an Ending. Call order is evaluation order. */
|
|
1226
|
+
ending(name, options) {
|
|
1227
|
+
const ending = new V2EndingBuilder(name, options.when, options.filterId);
|
|
1228
|
+
this.endings.push(ending);
|
|
1229
|
+
return ending;
|
|
1230
|
+
}
|
|
1231
|
+
goal(name, options = {}) {
|
|
1232
|
+
this.goals.push({
|
|
1233
|
+
name,
|
|
1234
|
+
description: options.description,
|
|
1235
|
+
step: options.step,
|
|
1236
|
+
completionCel: options.completionCel,
|
|
1237
|
+
value: options.value
|
|
1238
|
+
});
|
|
1239
|
+
return this;
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Authoring problems the server's publish validator would reject.
|
|
1243
|
+
*
|
|
1244
|
+
* A different rule set from the legacy builder's on purpose. "No incoming
|
|
1245
|
+
* edge" has no meaning in a timeline — position is the order — and the v2
|
|
1246
|
+
* failure modes are different ones: an action-less touch that is not an
|
|
1247
|
+
* explicit wait, and an Ending with no condition.
|
|
1248
|
+
*/
|
|
1249
|
+
validate() {
|
|
1250
|
+
const problems = [];
|
|
1251
|
+
if (!this.touches.length) {
|
|
1252
|
+
problems.push(
|
|
1253
|
+
`journey '${this.name}' has no touches \u2014 an empty timeline has nothing to execute.`
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
const armHeads = new Set(
|
|
1257
|
+
this.allRoutes().map((route) => route.touches[0]).filter(Boolean)
|
|
1258
|
+
);
|
|
1259
|
+
this.allTouches().forEach((touch) => {
|
|
1260
|
+
if (touch.hasOutreach) return;
|
|
1261
|
+
const wait = touch.options.wait;
|
|
1262
|
+
const isFirst = this.touches[0] === touch;
|
|
1263
|
+
const isExplicitWait = !isFirst && !armHeads.has(touch) && (wait?.afterDays !== void 0 || !!wait?.when || wait?.filterId !== void 0);
|
|
1264
|
+
if (isExplicitWait) return;
|
|
1265
|
+
problems.push(
|
|
1266
|
+
`touch '${touch.name}' has no JOURNEY_STEP_EXECUTE action \u2014 a member reaching it gets nothing. Add one, or give it a wait so it is an explicit grace period.`
|
|
1267
|
+
);
|
|
1268
|
+
});
|
|
1269
|
+
for (const touch of this.allTouches()) {
|
|
1270
|
+
const branch = touch._branch;
|
|
1271
|
+
if (!branch) continue;
|
|
1272
|
+
const fallbacks = branch.routes.filter(
|
|
1273
|
+
(route) => !route.options.when?.trim() && route.options.filterId === void 0
|
|
1274
|
+
);
|
|
1275
|
+
if (branch.routes.length - fallbacks.length < 1) {
|
|
1276
|
+
problems.push(
|
|
1277
|
+
`branch after touch '${touch.name}' needs at least one conditional route \u2014 one declared with a \`when\`, a \`filterId\`, or both. Members wait at the branch until a route matches, so an Otherwise is optional.`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
if (fallbacks.length > 1) {
|
|
1281
|
+
problems.push(
|
|
1282
|
+
`branch after touch '${touch.name}' declares ${fallbacks.length} Otherwise routes; a branch has at most one unconditional fallback.`
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
if (fallbacks.length === 1 && branch.routes[branch.routes.length - 1] !== fallbacks[0]) {
|
|
1286
|
+
problems.push(
|
|
1287
|
+
`the Otherwise route of the branch after touch '${touch.name}' is not last; it is taken immediately, so any route after it can never be reached.`
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
for (const route of branch.routes) {
|
|
1291
|
+
if (!route.touches.length && !route.options.continueAt) {
|
|
1292
|
+
problems.push(
|
|
1293
|
+
`route '${route.name}' has no target \u2014 give it at least one touch, or a \`continueAt\` naming the touch it continues to.`
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
for (const ending of this.endings) {
|
|
1299
|
+
if (!ending.when?.trim() && ending.filterId === void 0) {
|
|
1300
|
+
problems.push(
|
|
1301
|
+
`ending '${ending.name}' has no condition \u2014 an Ending that always matches terminates every enrolment on evaluation order alone. Give it a \`when\` expression, a \`filterId\`, or both.`
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
return problems;
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Shapes this definition cannot express, found BEFORE anything is written.
|
|
1309
|
+
*
|
|
1310
|
+
* A different list from {@link validate}, and a different consequence:
|
|
1311
|
+
* those are warnings the orchestrator prints and then writes anyway, these
|
|
1312
|
+
* are refusals. For each one the payload has no way to say what the script
|
|
1313
|
+
* asked for, so serializing past it would write a DIFFERENT program from
|
|
1314
|
+
* the one the script wrote, with nothing afterwards to say so.
|
|
1315
|
+
*
|
|
1316
|
+
* It has to run before the first write rather than during serialization,
|
|
1317
|
+
* because {@link apply} creates the Journey — or pauses a live one — before
|
|
1318
|
+
* it builds the payload. A refusal raised from `definitionPayload()` has
|
|
1319
|
+
* therefore already left an empty journey shell behind, or taken a
|
|
1320
|
+
* production program off the air.
|
|
1321
|
+
*
|
|
1322
|
+
* PURE: reads only what the script declared, never what the server holds,
|
|
1323
|
+
* so it is correct at any point — before adoption included — and
|
|
1324
|
+
* `WorkspaceBuilder.preflight()` can stay a synchronous, side-effect-free
|
|
1325
|
+
* check a caller may run directly. Nothing here needs adoption to have run:
|
|
1326
|
+
* an anchor the server has not seen is addressed by its request-local key,
|
|
1327
|
+
* so "has this Ending been saved" is no longer a question a refusal
|
|
1328
|
+
* depends on.
|
|
1329
|
+
*/
|
|
1330
|
+
preflight() {
|
|
1331
|
+
const problems = [];
|
|
1332
|
+
this.allTouches().forEach((touch, index) => {
|
|
1333
|
+
const wait = touch.options.wait;
|
|
1334
|
+
if (wait?.afterDays !== void 0 && wait.when) {
|
|
1335
|
+
problems.push(waitExclusivityProblem(index));
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
const owned = this.ownedAnchors();
|
|
1339
|
+
this.goals.forEach((goal, index) => {
|
|
1340
|
+
const step = goal.step;
|
|
1341
|
+
if (step && !owned.has(step)) {
|
|
1342
|
+
problems.push(foreignAnchorProblem(index, anchorKind(step)));
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
const stepNames = [
|
|
1346
|
+
...this.allTouches().map((touch) => touch.name),
|
|
1347
|
+
...this.endings.map((ending) => ending.name)
|
|
1348
|
+
];
|
|
1349
|
+
const claims = /* @__PURE__ */ new Map();
|
|
1350
|
+
for (const name of stepNames) claims.set(name, (claims.get(name) ?? 0) + 1);
|
|
1351
|
+
const repeated = [...claims.values()].filter((count) => count > 1).length;
|
|
1352
|
+
if (repeated) problems.push(sharedStepNameProblem(repeated));
|
|
1353
|
+
const nameless = stepNames.filter((name) => !name).length;
|
|
1354
|
+
if (nameless) problems.push(namelessStepProblem(nameless));
|
|
1355
|
+
return problems;
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Every step a goal in THIS journey can anchor to, compared by object
|
|
1359
|
+
* identity — touches AND Endings, which is why the element type names both
|
|
1360
|
+
* rather than leaning on `V2EndingBuilder extends V2TouchBuilder` to smuggle
|
|
1361
|
+
* Endings in under a touch-only annotation.
|
|
1362
|
+
*
|
|
1363
|
+
* A goal anchored to some other journey's builder is not a near miss the
|
|
1364
|
+
* server can catch: an unsaved one sends a `stepKey` this definition never
|
|
1365
|
+
* declares (a 404 whose cause is invisible), and a SAVED one sends a real
|
|
1366
|
+
* `stepUuid` belonging to a live step somewhere else.
|
|
1367
|
+
*/
|
|
1368
|
+
ownedAnchors() {
|
|
1369
|
+
return /* @__PURE__ */ new Set([...this.allTouches(), ...this.endings]);
|
|
1370
|
+
}
|
|
1371
|
+
planLine() {
|
|
1372
|
+
const parts = [];
|
|
1373
|
+
const touchCount = this.allTouches().length;
|
|
1374
|
+
const routeCount = this.allRoutes().length;
|
|
1375
|
+
if (touchCount) parts.push(`${touchCount} touches`);
|
|
1376
|
+
if (routeCount) parts.push(`${routeCount} routes`);
|
|
1377
|
+
if (this.endings.length) parts.push(`${this.endings.length} endings`);
|
|
1378
|
+
if (this.goals.length) parts.push(`${this.goals.length} goals`);
|
|
1379
|
+
return `Journey (v2): ${this.name}${parts.length ? ` (${parts.join(", ")})` : ""}`;
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Re-attach every stable child identity the server already holds.
|
|
1383
|
+
*
|
|
1384
|
+
* The atomic endpoint is a FULL REPLACEMENT keyed on uuids: an omitted
|
|
1385
|
+
* uuid does not mean "unchanged", it means "create a new one and archive
|
|
1386
|
+
* whatever the old one was". So anything this misses is silently recreated
|
|
1387
|
+
* on every re-run of the same script — and a script is normally re-run,
|
|
1388
|
+
* because a program is built in stages. Each identity has a different thing
|
|
1389
|
+
* hanging off it, and losing it costs something specific:
|
|
1390
|
+
*
|
|
1391
|
+
* | identity | what re-creating it destroys |
|
|
1392
|
+
* |---|---|
|
|
1393
|
+
* | touch step uuid | the touch's `MemberJourneyStep` history |
|
|
1394
|
+
* | EventRule uuid | that action's delivery bookkeeping |
|
|
1395
|
+
* | Ending RULE uuid | which Ending completed enrollments latched |
|
|
1396
|
+
* | Ending anchor uuid | the Ending's status history and goal anchor |
|
|
1397
|
+
* | goal uuid | every member's satisfaction row for that goal |
|
|
1398
|
+
*
|
|
1399
|
+
* `existing` is the journey DETAIL or AUTHORING projection. Both carry
|
|
1400
|
+
* `steps[].eventRules`, `goals`, and `decisionRules` —
|
|
1401
|
+
* the read projection redacts CEL, not identity — so one fetch is enough
|
|
1402
|
+
* and no second round trip is needed. (The orchestrator asks for the
|
|
1403
|
+
* authoring projection anyway: this builder holds `journeys:write` because
|
|
1404
|
+
* it is about to PUT, and the superset costs nothing.)
|
|
1405
|
+
*
|
|
1406
|
+
* Names are the only key available on a FIRST adoption, and a duplicate
|
|
1407
|
+
* name makes adoption a coin flip between two live resources. That throws
|
|
1408
|
+
* rather than guessing: silently attaching a touch to the wrong step
|
|
1409
|
+
* archives real member history.
|
|
1410
|
+
*/
|
|
1411
|
+
adopt(existing) {
|
|
1412
|
+
super.adopt(existing);
|
|
1413
|
+
this._expectedUpdatedAt = existing.updatedAt;
|
|
1414
|
+
this._status = existing.status;
|
|
1415
|
+
this.assertUnambiguousNames();
|
|
1416
|
+
this.adoptChildIdentities(existing);
|
|
1417
|
+
}
|
|
1418
|
+
/** Reject a script whose own names cannot identify a resource. */
|
|
1419
|
+
assertUnambiguousNames() {
|
|
1420
|
+
assertUniqueNames(
|
|
1421
|
+
this.allTouches().map((touch) => touch.name),
|
|
1422
|
+
`journey '${this.name}'`,
|
|
1423
|
+
"touch"
|
|
1424
|
+
);
|
|
1425
|
+
assertUniqueNames(
|
|
1426
|
+
this.allRoutes().map((route) => route.name),
|
|
1427
|
+
`journey '${this.name}'`,
|
|
1428
|
+
"route"
|
|
1429
|
+
);
|
|
1430
|
+
assertUniqueNames(
|
|
1431
|
+
this.endings.map((ending) => ending.name),
|
|
1432
|
+
`journey '${this.name}'`,
|
|
1433
|
+
"ending"
|
|
1434
|
+
);
|
|
1435
|
+
assertUniqueNames(
|
|
1436
|
+
this.goals.map((goal) => goal.name),
|
|
1437
|
+
`journey '${this.name}'`,
|
|
1438
|
+
"goal"
|
|
1439
|
+
);
|
|
1440
|
+
for (const touch of [...this.allTouches(), ...this.endings]) {
|
|
1441
|
+
const names = touch.rules.map((rule) => rule.options.name).filter(Boolean);
|
|
1442
|
+
assertUniqueNames(names, `touch '${touch.name}'`, "action name");
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
adoptChildIdentities(payload) {
|
|
1446
|
+
const steps = payload.steps ?? [];
|
|
1447
|
+
const referencedStepNames = /* @__PURE__ */ new Set([
|
|
1448
|
+
...this.allTouches().map((touch) => touch.name),
|
|
1449
|
+
...this.endings.map((ending) => ending.name)
|
|
1450
|
+
]);
|
|
1451
|
+
const stepsByName = indexStoredByName(
|
|
1452
|
+
steps,
|
|
1453
|
+
referencedStepNames,
|
|
1454
|
+
`journey '${this.name}'`,
|
|
1455
|
+
"step"
|
|
1456
|
+
);
|
|
1457
|
+
const stepsById = new Map(steps.map((step) => [step.id, step]));
|
|
1458
|
+
for (const touch of this.allTouches()) {
|
|
1459
|
+
const match = stepsByName.get(touch.name);
|
|
1460
|
+
if (!match) continue;
|
|
1461
|
+
touch._uuid = match.uuid;
|
|
1462
|
+
touch.adoptRuleUuids(match.eventRules ?? []);
|
|
1463
|
+
}
|
|
1464
|
+
const routeRules = (payload.decisionRules ?? []).filter(
|
|
1465
|
+
(rule) => rule.sourceStepId !== null && rule.sourceStepId !== void 0
|
|
1466
|
+
);
|
|
1467
|
+
const declaredRoutes = this.allRoutes();
|
|
1468
|
+
const routesByName = indexStoredByName(
|
|
1469
|
+
routeRules,
|
|
1470
|
+
new Set(declaredRoutes.map((route) => route.name)),
|
|
1471
|
+
`journey '${this.name}'`,
|
|
1472
|
+
"route"
|
|
1473
|
+
);
|
|
1474
|
+
for (const route of declaredRoutes) {
|
|
1475
|
+
const stored = routesByName.get(route.name);
|
|
1476
|
+
if (stored) route._ruleUuid = stored.uuid;
|
|
1477
|
+
}
|
|
1478
|
+
const endingRules = (payload.decisionRules ?? []).filter(
|
|
1479
|
+
(rule) => rule.sourceStepId === null || rule.sourceStepId === void 0
|
|
1480
|
+
);
|
|
1481
|
+
const rulesByName = indexStoredByName(
|
|
1482
|
+
endingRules,
|
|
1483
|
+
new Set(this.endings.map((ending) => ending.name)),
|
|
1484
|
+
`journey '${this.name}'`,
|
|
1485
|
+
"ending"
|
|
1486
|
+
);
|
|
1487
|
+
for (const ending of this.endings) {
|
|
1488
|
+
const rule = rulesByName.get(ending.name);
|
|
1489
|
+
const anchor = rule ? stepsById.get(rule.targetStepId) : stepsByName.get(ending.name);
|
|
1490
|
+
if (rule) ending._ruleUuid = rule.uuid;
|
|
1491
|
+
if (!anchor) continue;
|
|
1492
|
+
ending._uuid = anchor.uuid;
|
|
1493
|
+
ending.adoptRuleUuids(anchor.eventRules ?? []);
|
|
1494
|
+
}
|
|
1495
|
+
const goalsByName = indexStoredByName(
|
|
1496
|
+
payload.goals ?? [],
|
|
1497
|
+
new Set(this.goals.map((goal) => goal.name)),
|
|
1498
|
+
`journey '${this.name}'`,
|
|
1499
|
+
"goal"
|
|
1500
|
+
);
|
|
1501
|
+
for (const goal of this.goals) {
|
|
1502
|
+
const match = goalsByName.get(goal.name);
|
|
1503
|
+
if (match) goal._uuid = match.uuid;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
/** The atomic request body. Exposed so a test can assert it without HTTP. */
|
|
1507
|
+
definitionPayload() {
|
|
1508
|
+
const owned = this.ownedAnchors();
|
|
1509
|
+
const positions = new Map(
|
|
1510
|
+
this.allTouches().map((touch, index) => [touch, index])
|
|
1511
|
+
);
|
|
1512
|
+
return {
|
|
1513
|
+
expectedUpdatedAt: this._expectedUpdatedAt ?? "",
|
|
1514
|
+
touches: this.touches.map((touch) => serializeV2Touch(touch, positions)),
|
|
1515
|
+
endings: this.endings.map((ending) => ({
|
|
1516
|
+
...ending._ruleUuid ? { uuid: ending._ruleUuid } : {},
|
|
1517
|
+
// Anchor address, exactly as `serializeV2Touch` sends a touch's:
|
|
1518
|
+
// the saved anchor's uuid, or the request-local `key` a goal in
|
|
1519
|
+
// this same save names it by. Both come from `_address`, so the
|
|
1520
|
+
// Ending and the goal cannot be sent under two spellings.
|
|
1521
|
+
...ending._uuid ? { anchorUuid: ending._uuid } : { key: ending._address },
|
|
1522
|
+
name: ending.name,
|
|
1523
|
+
// The two predicates are sent INDEPENDENTLY (rule-filters plan
|
|
1524
|
+
// §3.2). A filter-only Ending carries no `celCondition` at all
|
|
1525
|
+
// rather than an empty one the server would have to normalize.
|
|
1526
|
+
...ending.when ? { celCondition: ending.when } : {},
|
|
1527
|
+
...ending.filterId !== void 0 ? { filterId: ending.filterId } : {},
|
|
1528
|
+
eventRules: ending.eventRuleInputs()
|
|
1529
|
+
})),
|
|
1530
|
+
// ALWAYS sent, `[]` included. The field distinguishes omitted (leave
|
|
1531
|
+
// goals alone) from empty (delete them), and this builder owns the
|
|
1532
|
+
// goal list — so omitting an empty list would make goal REMOVAL the
|
|
1533
|
+
// one edit a script cannot express: delete the `.goal(...)` call, re-run,
|
|
1534
|
+
// and the goal stays live forever.
|
|
1535
|
+
goals: this.goals.map((goal, index) => ({
|
|
1536
|
+
...goal._uuid ? { uuid: goal._uuid } : {},
|
|
1537
|
+
name: goal.name,
|
|
1538
|
+
...goal.description ? { description: goal.description } : {},
|
|
1539
|
+
...serializeV2GoalAnchor(goal.step, index, owned),
|
|
1540
|
+
...goal.completionCel ? { completionCel: goal.completionCel } : {},
|
|
1541
|
+
...goal.value !== void 0 ? { value: goal.value } : {}
|
|
1542
|
+
}))
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
async apply(client) {
|
|
1546
|
+
const problems = this.preflight();
|
|
1547
|
+
if (problems.length) {
|
|
1548
|
+
throw new Error(
|
|
1549
|
+
`Journey (v2) has ${problems.length} problem(s) and was not applied:
|
|
1550
|
+
` + problems.map((problem) => ` - ${problem}`).join("\n")
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
if (!this._existed) {
|
|
1554
|
+
const created = await client.createJourney({
|
|
1555
|
+
name: this.name,
|
|
1556
|
+
description: this.description,
|
|
1557
|
+
// Explicit, always. An omitted executionModel means `legacy` on the
|
|
1558
|
+
// server, so a v2 builder that forgot it would quietly author a
|
|
1559
|
+
// legacy Journey that happens to look like a timeline.
|
|
1560
|
+
executionModel: "v2"
|
|
1561
|
+
});
|
|
1562
|
+
this._id = created.id;
|
|
1563
|
+
this._uuid = created.uuid;
|
|
1564
|
+
this._created = true;
|
|
1565
|
+
this._expectedUpdatedAt = created.updatedAt;
|
|
1566
|
+
} else if (this._status === "active" && this.activate) {
|
|
1567
|
+
const paused = await client.updateJourney(this.id, { status: "paused" });
|
|
1568
|
+
this._expectedUpdatedAt = paused.updatedAt;
|
|
1569
|
+
}
|
|
1570
|
+
const saved = await client.putJourneyV2Definition(this.id, this.definitionPayload());
|
|
1571
|
+
this._expectedUpdatedAt = saved.updatedAt;
|
|
1572
|
+
this.adoptWrittenUuids(saved);
|
|
1573
|
+
if (this.activate) {
|
|
1574
|
+
await client.updateJourney(this.id, { status: "active" });
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Re-attach the uuids the server assigned, so a follow-up write matches.
|
|
1579
|
+
*
|
|
1580
|
+
* The PUT returns the AUTHORING projection, which is the same shape
|
|
1581
|
+
* `adopt` reads — so this is the same operation against a payload we just
|
|
1582
|
+
* caused, and it uses the same code path rather than a second, thinner
|
|
1583
|
+
* copy that would drift.
|
|
1584
|
+
*/
|
|
1585
|
+
adoptWrittenUuids(saved) {
|
|
1586
|
+
this.adoptChildIdentities(saved);
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
function indexStoredByName(rows, referenced, owner, kind) {
|
|
1590
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1591
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1592
|
+
for (const row of rows) {
|
|
1593
|
+
const name = row.name;
|
|
1594
|
+
if (typeof name !== "string" || !name) continue;
|
|
1595
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
1596
|
+
if (!byName.has(name)) byName.set(name, row);
|
|
1597
|
+
}
|
|
1598
|
+
for (const [name, count] of counts) {
|
|
1599
|
+
if (count < 2 || !referenced.has(name)) continue;
|
|
1600
|
+
throw new Error(
|
|
1601
|
+
`${owner} already has ${count} ${kind}s named '${name}'. This script adopts by name, so it cannot tell which ${kind} it owns \u2014 and the one it does not pick is archived on the next save, taking its history with it. Rename them so each name identifies one ${kind}.`
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
return byName;
|
|
1605
|
+
}
|
|
1606
|
+
function assertUniqueNames(names, owner, kind) {
|
|
1607
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1608
|
+
for (const name of names) {
|
|
1609
|
+
if (seen.has(name)) {
|
|
1610
|
+
throw new Error(
|
|
1611
|
+
`${owner} has two ${kind}s named '${name}'. The v2 builder adopts an existing journey by name, so duplicates would attach a builder to either one at random \u2014 archiving the other's history on the next save. Rename one.`
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
seen.add(name);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
function serializeV2Touch(touch, positions) {
|
|
1618
|
+
return {
|
|
1619
|
+
...touch._uuid ? { uuid: touch._uuid } : { key: touch._address },
|
|
1620
|
+
name: touch.name,
|
|
1621
|
+
...touch.options.description ? { description: touch.options.description } : {},
|
|
1622
|
+
...touch.options.wait ? { wait: buildV2Wait(touch.options.wait, positions.get(touch)) } : {},
|
|
1623
|
+
eventRules: touch.eventRuleInputs(),
|
|
1624
|
+
...touch._branch ? {
|
|
1625
|
+
branch: {
|
|
1626
|
+
routes: touch._branch.routes.map((route) => ({
|
|
1627
|
+
...route._ruleUuid ? { uuid: route._ruleUuid } : {},
|
|
1628
|
+
name: route.name,
|
|
1629
|
+
// Omitted, not null, and INDEPENDENT of each other: a route
|
|
1630
|
+
// with neither predicate IS the Otherwise marker, one with
|
|
1631
|
+
// either is conditional, and one with both is ANDed
|
|
1632
|
+
// (rule-filters plan §3.2). The server normalizes "" to the
|
|
1633
|
+
// same thing as omitted.
|
|
1634
|
+
...route.options.when ? { celCondition: route.options.when } : {},
|
|
1635
|
+
...route.options.filterId !== void 0 ? { filterId: route.options.filterId } : {},
|
|
1636
|
+
touches: route.touches.map((armTouch) => serializeV2Touch(armTouch, positions)),
|
|
1637
|
+
...route.options.continueAt ? { continueAt: route.options.continueAt._address } : {}
|
|
1638
|
+
}))
|
|
1639
|
+
}
|
|
1640
|
+
} : {}
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
function serializeV2GoalAnchor(step, goalIndex, owned) {
|
|
1644
|
+
if (!step) return {};
|
|
1645
|
+
if (!owned.has(step)) throw new Error(foreignAnchorProblem(goalIndex, anchorKind(step)));
|
|
1646
|
+
if (step._uuid) return { stepUuid: step._uuid };
|
|
1647
|
+
return { stepKey: step._address };
|
|
1648
|
+
}
|
|
1649
|
+
function foreignAnchorProblem(goalIndex, kind) {
|
|
1650
|
+
return `goal at index ${goalIndex} is anchored to ${kind === "Ending" ? "an" : "a"} ${kind} that belongs to a different journey builder. A goal anchor is part of the same atomic definition as its step, so it can only name a touch or Ending this journey declares.`;
|
|
1651
|
+
}
|
|
1652
|
+
function waitExclusivityProblem(touchIndex) {
|
|
1653
|
+
const where = touchIndex === void 0 ? "this touch" : `touch at index ${touchIndex}`;
|
|
1654
|
+
return `${where} sets both afterDays and when; fold the day count into \`when\`. A v2 wait is either a whole-day count or Advanced CEL, not both.`;
|
|
1655
|
+
}
|
|
1656
|
+
function sharedStepNameProblem(count) {
|
|
1657
|
+
return `${count} step name(s) are claimed more than once. Touches and Endings are one namespace: until a save mints their uuids they address themselves by name and the definition resolves those keys through one field, and once saved they are stored steps the next run cannot tell apart. Names are canonical (trimmed), as the API stores them. Rename one.`;
|
|
1658
|
+
}
|
|
1659
|
+
function namelessStepProblem(count) {
|
|
1660
|
+
return `${count} step(s) have an empty name once trimmed. A step's name is its request-local address until a save mints its uuid, so a nameless one has nothing to be addressed by, and the API refuses it. Name them.`;
|
|
1661
|
+
}
|
|
1662
|
+
function anchorKind(step) {
|
|
1663
|
+
return step instanceof V2EndingBuilder ? "Ending" : "touch";
|
|
1664
|
+
}
|
|
1665
|
+
function buildV2Wait(wait, touchIndex) {
|
|
1666
|
+
if (wait.afterDays !== void 0 && wait.when) {
|
|
1667
|
+
throw new Error(waitExclusivityProblem(touchIndex));
|
|
1668
|
+
}
|
|
1669
|
+
const extras = {
|
|
1670
|
+
...wait.filterId !== void 0 ? { filterId: wait.filterId } : {}
|
|
1671
|
+
};
|
|
1672
|
+
if (wait.afterDays !== void 0) {
|
|
1673
|
+
return { kind: "wholeDays", days: wait.afterDays, ...extras };
|
|
1674
|
+
}
|
|
1675
|
+
if (wait.when) {
|
|
1676
|
+
return { kind: "advanced", cel: wait.when, ...extras };
|
|
1677
|
+
}
|
|
1678
|
+
return { kind: "none", ...extras };
|
|
1679
|
+
}
|
|
889
1680
|
|
|
890
1681
|
// src/workspace-builder/role-update.ts
|
|
891
1682
|
var RoleUpdate = class extends WorkspaceBuilderResource {
|
|
@@ -1121,6 +1912,7 @@ var AgentBuilder = class extends WorkspaceBuilderResource {
|
|
|
1121
1912
|
this.memberRoleId = options.roleId ?? options.role_id;
|
|
1122
1913
|
this.tools = options.tools ?? [];
|
|
1123
1914
|
this.codeInterpreterEnabled = options.codeInterpreterEnabled ?? options.code_interpreter_enabled ?? false;
|
|
1915
|
+
this.webBrowserEnabled = options.webBrowserEnabled ?? options.web_browser_enabled ?? false;
|
|
1124
1916
|
}
|
|
1125
1917
|
name;
|
|
1126
1918
|
chatModelId;
|
|
@@ -1128,6 +1920,7 @@ var AgentBuilder = class extends WorkspaceBuilderResource {
|
|
|
1128
1920
|
memberRoleId;
|
|
1129
1921
|
tools;
|
|
1130
1922
|
codeInterpreterEnabled;
|
|
1923
|
+
webBrowserEnabled;
|
|
1131
1924
|
planLine() {
|
|
1132
1925
|
const parts = [];
|
|
1133
1926
|
if (this.chatModelId) parts.push(`model=${this.chatModelId}`);
|
|
@@ -1148,7 +1941,8 @@ var AgentBuilder = class extends WorkspaceBuilderResource {
|
|
|
1148
1941
|
chatModelId: this.chatModelId,
|
|
1149
1942
|
systemPrompt: this.systemPrompt,
|
|
1150
1943
|
...this.tools.length ? { tools: this.tools } : {},
|
|
1151
|
-
...this.codeInterpreterEnabled ? { codeInterpreterEnabled: true } : {}
|
|
1944
|
+
...this.codeInterpreterEnabled ? { codeInterpreterEnabled: true } : {},
|
|
1945
|
+
...this.webBrowserEnabled ? { webBrowserEnabled: true } : {}
|
|
1152
1946
|
};
|
|
1153
1947
|
const result = await client.createAgent(payload);
|
|
1154
1948
|
this._id = result.id;
|
|
@@ -1169,6 +1963,9 @@ var PHASES = /* @__PURE__ */ new Map([
|
|
|
1169
1963
|
[RoutineBuilder, 6],
|
|
1170
1964
|
[NotificationRuleBuilder, 6],
|
|
1171
1965
|
[JourneyBuilder, 7],
|
|
1966
|
+
// Same phase as the legacy builder: a v2 Journey is written whole by its
|
|
1967
|
+
// own apply(), so it has no child resources to sequence after it.
|
|
1968
|
+
[JourneyV2Builder, 7],
|
|
1172
1969
|
[JourneyStepBuilder, 8]
|
|
1173
1970
|
]);
|
|
1174
1971
|
var LIST_PAGE_SIZE = 100;
|
|
@@ -1230,6 +2027,21 @@ var WorkspaceBuilder = class {
|
|
|
1230
2027
|
this.resources.push(journey);
|
|
1231
2028
|
return journey;
|
|
1232
2029
|
}
|
|
2030
|
+
/**
|
|
2031
|
+
* A Journey v2 timeline (plan §3.8): ordered touches, structured waits, and
|
|
2032
|
+
* ordered Endings, written through the atomic definition endpoint.
|
|
2033
|
+
*
|
|
2034
|
+
* A separate factory rather than an option on {@link journey}, because the
|
|
2035
|
+
* two return different builders with different vocabularies — `.touch()`
|
|
2036
|
+
* and `.ending()` versus `.step()` and `.connectsTo()`. The legacy builder
|
|
2037
|
+
* keeps its current behaviour untouched, which is the plan's explicit
|
|
2038
|
+
* compatibility requirement.
|
|
2039
|
+
*/
|
|
2040
|
+
journeyV2(name, options = {}) {
|
|
2041
|
+
const journey = new JourneyV2Builder(name, options);
|
|
2042
|
+
this.resources.push(journey);
|
|
2043
|
+
return journey;
|
|
2044
|
+
}
|
|
1233
2045
|
agent(name, options = {}) {
|
|
1234
2046
|
const agent = new AgentBuilder(name, options);
|
|
1235
2047
|
this.resources.push(agent);
|
|
@@ -1313,7 +2125,9 @@ ${"=".repeat(50)}`);
|
|
|
1313
2125
|
* surfaced before the write rather than discovered from a member's phone.
|
|
1314
2126
|
*/
|
|
1315
2127
|
reportJourneyWarnings() {
|
|
1316
|
-
const problems = this.resources.filter(
|
|
2128
|
+
const problems = this.resources.filter(
|
|
2129
|
+
(resource) => resource instanceof JourneyBuilder || resource instanceof JourneyV2Builder
|
|
2130
|
+
).flatMap((journey) => journey.validate());
|
|
1317
2131
|
if (!problems.length) return;
|
|
1318
2132
|
writeLine("\n Journey warnings:");
|
|
1319
2133
|
for (const problem of problems) writeLine(` ! ${problem}`);
|
|
@@ -1330,6 +2144,12 @@ ${"=".repeat(50)}`);
|
|
|
1330
2144
|
* Deliberately narrow: only cross-resource facts the builder can see with
|
|
1331
2145
|
* certainty. Per-field CEL schema checking stays server-side, where the
|
|
1332
2146
|
* real validator lives — duplicating it here would drift.
|
|
2147
|
+
*
|
|
2148
|
+
* Pure: reads only what the plan declares, never what the server holds, so
|
|
2149
|
+
* it is correct before adoption and safe for a caller to run directly. It
|
|
2150
|
+
* is also the WHOLE gate: every anchor a v2 definition can name has a
|
|
2151
|
+
* request-local address whether or not the server has seen it, so no
|
|
2152
|
+
* refusal has to wait for adoption to decide it.
|
|
1333
2153
|
*/
|
|
1334
2154
|
preflight() {
|
|
1335
2155
|
const problems = [];
|
|
@@ -1353,39 +2173,46 @@ ${"=".repeat(50)}`);
|
|
|
1353
2173
|
}
|
|
1354
2174
|
}
|
|
1355
2175
|
}
|
|
2176
|
+
for (const resource of this.resources) {
|
|
2177
|
+
if (resource instanceof JourneyV2Builder) problems.push(...resource.preflight());
|
|
2178
|
+
}
|
|
1356
2179
|
return [...new Set(problems)];
|
|
1357
2180
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
if (problems.length)
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
2181
|
+
/** The plan-rejection shape: every problem printed, nothing written. */
|
|
2182
|
+
rejectIfProblems(problems) {
|
|
2183
|
+
if (!problems.length) return;
|
|
2184
|
+
writeLine("\nPlan rejected \u2014 nothing was created:");
|
|
2185
|
+
for (const problem of problems) writeLine(` ! ${problem}`);
|
|
2186
|
+
throw new Error(
|
|
2187
|
+
`Plan has ${problems.length} problem(s) and was not applied:
|
|
1365
2188
|
` + problems.map((problem) => ` - ${problem}`).join("\n")
|
|
1366
|
-
|
|
1367
|
-
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
async apply() {
|
|
1368
2192
|
const resources = this.sortedResources();
|
|
2193
|
+
this.rejectIfProblems(this.preflight());
|
|
1369
2194
|
await this.resolveExisting(resources);
|
|
1370
2195
|
const summary = {};
|
|
1371
2196
|
writeLine("Applying...");
|
|
1372
2197
|
for (const resource of resources) {
|
|
1373
2198
|
try {
|
|
1374
2199
|
if (resource.isImplicit) continue;
|
|
1375
|
-
if (resource._existed) {
|
|
2200
|
+
if (resource._existed && !resource.appliesWhenExisting) {
|
|
1376
2201
|
const key = resourceKey(resource);
|
|
1377
2202
|
summary[key] ??= [];
|
|
1378
2203
|
summary[key].push(resource._id);
|
|
1379
2204
|
writeLine(` Exists ${resource.planLine()} -> id=${resource._id}`);
|
|
1380
2205
|
continue;
|
|
1381
2206
|
}
|
|
2207
|
+
const updating = resource._existed;
|
|
1382
2208
|
await resource.apply(this.client);
|
|
1383
2209
|
if (resource._created || resource._id !== void 0) {
|
|
1384
2210
|
const key = resourceKey(resource);
|
|
1385
2211
|
summary[key] ??= [];
|
|
1386
2212
|
summary[key].push(resource._id);
|
|
1387
2213
|
const startTag = resource instanceof TaskBuilder && resource.start ? " [start]" : "";
|
|
1388
|
-
|
|
2214
|
+
const verb = updating ? "Updated" : "Created";
|
|
2215
|
+
writeLine(` ${verb} ${resource.planLine()}${startTag} -> id=${resource._id}`);
|
|
1389
2216
|
} else {
|
|
1390
2217
|
writeLine(` Skipped: ${resource.planLine()}`);
|
|
1391
2218
|
}
|
|
@@ -1404,6 +2231,11 @@ Done! Created ${count} resources.`);
|
|
|
1404
2231
|
return summary;
|
|
1405
2232
|
}
|
|
1406
2233
|
async resolveExisting(resources) {
|
|
2234
|
+
assertUniqueJourneyNames(
|
|
2235
|
+
resources.filter(
|
|
2236
|
+
(resource) => resource instanceof JourneyBuilder || resource instanceof JourneyV2Builder
|
|
2237
|
+
)
|
|
2238
|
+
);
|
|
1407
2239
|
const formSlugs = new Map(
|
|
1408
2240
|
resources.filter((resource) => resource instanceof FormBuilder).map((resource) => [resource.slug, resource])
|
|
1409
2241
|
);
|
|
@@ -1442,14 +2274,33 @@ Done! Created ${count} resources.`);
|
|
|
1442
2274
|
}
|
|
1443
2275
|
}
|
|
1444
2276
|
const journeyNames = new Map(
|
|
1445
|
-
resources.filter(
|
|
2277
|
+
resources.filter(
|
|
2278
|
+
(resource) => resource instanceof JourneyBuilder || resource instanceof JourneyV2Builder
|
|
2279
|
+
).map((resource) => [resource.name, resource])
|
|
1446
2280
|
);
|
|
1447
2281
|
if (journeyNames.size) {
|
|
1448
|
-
const
|
|
1449
|
-
|
|
2282
|
+
const pageSize = 500;
|
|
2283
|
+
const firstPage = await this.client.getJourneys(false, { page: 1, pageSize });
|
|
2284
|
+
const allJourneys = [...firstPage.items];
|
|
2285
|
+
for (let page = 2; page <= firstPage.totalPages; page++) {
|
|
2286
|
+
const nextPage = await this.client.getJourneys(false, { page, pageSize });
|
|
2287
|
+
allJourneys.push(...nextPage.items);
|
|
2288
|
+
}
|
|
2289
|
+
const storedByName = /* @__PURE__ */ new Map();
|
|
2290
|
+
for (const existing of allJourneys) {
|
|
1450
2291
|
if (!existing.name || !journeyNames.has(existing.name)) continue;
|
|
1451
|
-
|
|
1452
|
-
|
|
2292
|
+
storedByName.set(existing.name, [...storedByName.get(existing.name) ?? [], existing]);
|
|
2293
|
+
}
|
|
2294
|
+
for (const [name, matches] of storedByName) {
|
|
2295
|
+
if (matches.length > 1) {
|
|
2296
|
+
throw new Error(
|
|
2297
|
+
`This workspace has ${matches.length} journeys named '${name}', and the script adopts an existing journey by name \u2014 so it cannot tell which one it owns. Adopting the wrong one replaces that journey whole on the next save. Rename the others in the app, or rename the journey in the script.`
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
2300
|
+
const existing = matches[0];
|
|
2301
|
+
const builder = journeyNames.get(name);
|
|
2302
|
+
const detail = builder instanceof JourneyV2Builder ? await this.client.getJourneyAuthoring(existing.id) : await this.client.getJourney(existing.id);
|
|
2303
|
+
builder.adopt(detail);
|
|
1453
2304
|
}
|
|
1454
2305
|
}
|
|
1455
2306
|
const agentNames = new Map(
|
|
@@ -1628,6 +2479,12 @@ Done! Created ${count} resources.`);
|
|
|
1628
2479
|
}
|
|
1629
2480
|
} else if (resource instanceof JourneyBuilder) {
|
|
1630
2481
|
for (const goal of resource.goals) lines.push(`Goal: ${goal.name}`);
|
|
2482
|
+
} else if (resource instanceof JourneyV2Builder) {
|
|
2483
|
+
resource.touches.forEach((touch, index) => {
|
|
2484
|
+
lines.push(`${index + 1}. ${touch.name}`);
|
|
2485
|
+
});
|
|
2486
|
+
for (const ending of resource.endings) lines.push(`Ending: ${ending.name}`);
|
|
2487
|
+
for (const goal of resource.goals) lines.push(`Goal: ${goal.name}`);
|
|
1631
2488
|
}
|
|
1632
2489
|
}
|
|
1633
2490
|
return lines;
|
|
@@ -1786,6 +2643,17 @@ var ApplyError = class extends Error {
|
|
|
1786
2643
|
return this.#cause;
|
|
1787
2644
|
}
|
|
1788
2645
|
};
|
|
2646
|
+
function assertUniqueJourneyNames(journeys) {
|
|
2647
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2648
|
+
for (const journey of journeys) {
|
|
2649
|
+
if (seen.has(journey.name)) {
|
|
2650
|
+
throw new Error(
|
|
2651
|
+
`This script declares two journeys named '${journey.name}'. A journey is adopted and re-applied by name, so only one of them could ever be written \u2014 and a v2 apply replaces the journey it adopted whole. Rename one.`
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
seen.add(journey.name);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
1789
2657
|
function resourceKey(resource) {
|
|
1790
2658
|
if (resource instanceof RoleUpdate) return "roleUpdates";
|
|
1791
2659
|
if (resource instanceof FormBuilder) return "forms";
|
|
@@ -1795,7 +2663,9 @@ function resourceKey(resource) {
|
|
|
1795
2663
|
if (resource instanceof MemberFilterBuilder) return "memberFilters";
|
|
1796
2664
|
if (resource instanceof RoutineBuilder) return "routines";
|
|
1797
2665
|
if (resource instanceof NotificationRuleBuilder) return "notificationRules";
|
|
1798
|
-
if (resource instanceof JourneyBuilder)
|
|
2666
|
+
if (resource instanceof JourneyBuilder || resource instanceof JourneyV2Builder) {
|
|
2667
|
+
return "journeys";
|
|
2668
|
+
}
|
|
1799
2669
|
if (resource instanceof JourneyStepBuilder) return "journeySteps";
|
|
1800
2670
|
if (resource instanceof AgentBuilder) return "agents";
|
|
1801
2671
|
return "resources";
|
|
@@ -1906,7 +2776,7 @@ function writeScopeForResource(resource) {
|
|
|
1906
2776
|
if (resource instanceof RoutineBuilder || resource instanceof NotificationRuleBuilder) {
|
|
1907
2777
|
return "automations:admin";
|
|
1908
2778
|
}
|
|
1909
|
-
if (resource instanceof JourneyBuilder || resource instanceof JourneyStepBuilder) {
|
|
2779
|
+
if (resource instanceof JourneyBuilder || resource instanceof JourneyStepBuilder || resource instanceof JourneyV2Builder) {
|
|
1910
2780
|
return "journeys:write";
|
|
1911
2781
|
}
|
|
1912
2782
|
if (resource instanceof RoleUpdate) return "member_roles:write";
|
|
@@ -1970,6 +2840,111 @@ async function runWorkspaceScript(client, build, mode = "plan") {
|
|
|
1970
2840
|
if (mode === "apply") return workspace.apply();
|
|
1971
2841
|
throw new Error(`Unknown mode '${mode}'. Use plan, preview, or apply.`);
|
|
1972
2842
|
}
|
|
2843
|
+
|
|
2844
|
+
// src/types/generated/event_type.ts
|
|
2845
|
+
var ActivityCapability = {
|
|
2846
|
+
"assignments:read": "assignments:read",
|
|
2847
|
+
"chats:read": "chats:read",
|
|
2848
|
+
"journeys:read": "journeys:read",
|
|
2849
|
+
"members:read": "members:read",
|
|
2850
|
+
"operator:read": "operator:read",
|
|
2851
|
+
"org:read": "org:read",
|
|
2852
|
+
"phones:read": "phones:read",
|
|
2853
|
+
"workflows:read": "workflows:read",
|
|
2854
|
+
"workspace:read": "workspace:read"
|
|
2855
|
+
};
|
|
2856
|
+
var ActivityEventType = {
|
|
2857
|
+
membercreated: "member.created",
|
|
2858
|
+
memberremoved: "member.removed",
|
|
2859
|
+
memberrole_changed: "member.role_changed",
|
|
2860
|
+
memberlabels_changed: "member.labels_changed",
|
|
2861
|
+
memberattributes_changed: "member.attributes_changed",
|
|
2862
|
+
importstarted: "import.started",
|
|
2863
|
+
importcompleted: "import.completed",
|
|
2864
|
+
assignmentstarted: "assignment.started",
|
|
2865
|
+
assignmentconcluded: "assignment.concluded",
|
|
2866
|
+
journeyenrolled: "journey.enrolled",
|
|
2867
|
+
journeyunenrolled: "journey.unenrolled",
|
|
2868
|
+
journeycompleted: "journey.completed",
|
|
2869
|
+
journeystep_transitioned: "journey.step_transitioned",
|
|
2870
|
+
journeyactivated: "journey.activated",
|
|
2871
|
+
chatstarted: "chat.started",
|
|
2872
|
+
chatawaiting_human: "chat.awaiting_human",
|
|
2873
|
+
chathuman_replied: "chat.human_replied",
|
|
2874
|
+
escalationopened: "escalation.opened",
|
|
2875
|
+
escalationresolved: "escalation.resolved",
|
|
2876
|
+
callstarted: "call.started",
|
|
2877
|
+
callcompleted: "call.completed",
|
|
2878
|
+
callmissed: "call.missed",
|
|
2879
|
+
presencewent_online: "presence.went_online",
|
|
2880
|
+
presencewent_offline: "presence.went_offline",
|
|
2881
|
+
operator_groupmember_added: "operator_group.member_added",
|
|
2882
|
+
operator_groupmember_removed: "operator_group.member_removed",
|
|
2883
|
+
voicemailreceived: "voicemail.received",
|
|
2884
|
+
voicemaillistened: "voicemail.listened",
|
|
2885
|
+
routinestarted: "routine.started",
|
|
2886
|
+
routinecompleted: "routine.completed",
|
|
2887
|
+
routinefailed: "routine.failed",
|
|
2888
|
+
workflowpublished: "workflow.published",
|
|
2889
|
+
subscriptionstarted: "subscription.started",
|
|
2890
|
+
subscriptionupdated: "subscription.updated",
|
|
2891
|
+
subscriptioncanceled: "subscription.canceled",
|
|
2892
|
+
member_subscriptionstarted: "member_subscription.started",
|
|
2893
|
+
member_subscriptioncanceled: "member_subscription.canceled",
|
|
2894
|
+
purchasecompleted: "purchase.completed",
|
|
2895
|
+
phone_numberprovisioned: "phone_number.provisioned",
|
|
2896
|
+
phone_numberarchived: "phone_number.archived",
|
|
2897
|
+
domainlinked: "domain.linked",
|
|
2898
|
+
domainverified: "domain.verified",
|
|
2899
|
+
configupdated: "config.updated"
|
|
2900
|
+
};
|
|
2901
|
+
var ActivityVisibility = {
|
|
2902
|
+
workspace: "workspace",
|
|
2903
|
+
org: "org",
|
|
2904
|
+
account_private: "account_private"
|
|
2905
|
+
};
|
|
2906
|
+
|
|
2907
|
+
// src/types/generated/ref.ts
|
|
2908
|
+
var ActorKind = {
|
|
2909
|
+
account: "account",
|
|
2910
|
+
agent: "agent",
|
|
2911
|
+
connection: "connection",
|
|
2912
|
+
member: "member",
|
|
2913
|
+
org_account: "org_account",
|
|
2914
|
+
system: "system"
|
|
2915
|
+
};
|
|
2916
|
+
var EntityKind = {
|
|
2917
|
+
account: "account",
|
|
2918
|
+
org: "org",
|
|
2919
|
+
org_account: "org_account",
|
|
2920
|
+
workspace: "workspace",
|
|
2921
|
+
agent: "agent",
|
|
2922
|
+
connection: "connection",
|
|
2923
|
+
member: "member",
|
|
2924
|
+
assignment: "assignment",
|
|
2925
|
+
journey: "journey",
|
|
2926
|
+
journey_step: "journey_step",
|
|
2927
|
+
routine: "routine",
|
|
2928
|
+
task: "task",
|
|
2929
|
+
workflow: "workflow",
|
|
2930
|
+
workflow_revision: "workflow_revision",
|
|
2931
|
+
call: "call",
|
|
2932
|
+
chat: "chat",
|
|
2933
|
+
escalation: "escalation",
|
|
2934
|
+
inbox: "inbox",
|
|
2935
|
+
phone_number: "phone_number",
|
|
2936
|
+
voicemail: "voicemail",
|
|
2937
|
+
domain: "domain",
|
|
2938
|
+
import: "import",
|
|
2939
|
+
invoice: "invoice",
|
|
2940
|
+
member_label: "member_label",
|
|
2941
|
+
member_subscription: "member_subscription",
|
|
2942
|
+
operator_group: "operator_group",
|
|
2943
|
+
purchase: "purchase",
|
|
2944
|
+
subscription: "subscription",
|
|
2945
|
+
data_type: "data_type",
|
|
2946
|
+
workspace_config: "workspace_config"
|
|
2947
|
+
};
|
|
1973
2948
|
export {
|
|
1974
2949
|
AGENT_NOTE_ORIGINS,
|
|
1975
2950
|
AbilityConfigIssueCode,
|
|
@@ -1979,6 +2954,16 @@ export {
|
|
|
1979
2954
|
AccessGrantStatus,
|
|
1980
2955
|
AccessGrantTrigger,
|
|
1981
2956
|
AccessLevel,
|
|
2957
|
+
ActivityCapability,
|
|
2958
|
+
ActivityEventFamily,
|
|
2959
|
+
ActivityEventType,
|
|
2960
|
+
ActivityGrain,
|
|
2961
|
+
ActivityGroupBy,
|
|
2962
|
+
ActivityOpenItemOrder,
|
|
2963
|
+
ActivityOutcome,
|
|
2964
|
+
ActivityVisibility,
|
|
2965
|
+
ActivityWindowBasis,
|
|
2966
|
+
ActorKind,
|
|
1982
2967
|
AgentBuilder,
|
|
1983
2968
|
ApiError,
|
|
1984
2969
|
ApplyError,
|
|
@@ -1999,8 +2984,15 @@ export {
|
|
|
1999
2984
|
CalendarScheduleRecurrence,
|
|
2000
2985
|
CalendarSource,
|
|
2001
2986
|
CapabilityClass,
|
|
2987
|
+
ChannelMessageDirection,
|
|
2988
|
+
ChannelMessageSource,
|
|
2989
|
+
ChannelMessagesUnavailableReason,
|
|
2990
|
+
ChatInteractionController,
|
|
2002
2991
|
ConsentMethod,
|
|
2003
2992
|
CriterionType,
|
|
2993
|
+
DEFAULT_AGENT_CHAT_MODEL,
|
|
2994
|
+
DEFAULT_AGENT_REALTIME_MODEL,
|
|
2995
|
+
DEFAULT_AGENT_REALTIME_MODEL_CONFIG,
|
|
2004
2996
|
DEFAULT_CHAT_MODEL,
|
|
2005
2997
|
DEFAULT_CUSTOM_VOICE_MODEL,
|
|
2006
2998
|
DEFAULT_THINKING_SPEECH_PHRASES,
|
|
@@ -2011,7 +3003,9 @@ export {
|
|
|
2011
3003
|
DataSensitivity,
|
|
2012
3004
|
DataTypeRecordScope,
|
|
2013
3005
|
DriverMode,
|
|
3006
|
+
EhrWritebackOutcomeResponseStatus,
|
|
2014
3007
|
EmailDnsRecordType,
|
|
3008
|
+
EntityKind,
|
|
2015
3009
|
EventRuleActionType,
|
|
2016
3010
|
EventRuleEventType,
|
|
2017
3011
|
EventRuleObjectType,
|
|
@@ -2022,6 +3016,11 @@ export {
|
|
|
2022
3016
|
FhirVendor,
|
|
2023
3017
|
FormBuilder,
|
|
2024
3018
|
GravityRailClient,
|
|
3019
|
+
ImportSkippedRuleResourceGroupResourceType,
|
|
3020
|
+
ImportSkippedRuleResponseNeededResourceTypesItem,
|
|
3021
|
+
ImportSkippedRuleResponseParentKind,
|
|
3022
|
+
ImportSkippedRuleResponseReasonCode,
|
|
3023
|
+
ImportSkippedRuleResponseStatus,
|
|
2025
3024
|
InboundFaxStatus as InboundFaxStatusValues,
|
|
2026
3025
|
IndexStatus,
|
|
2027
3026
|
InvitationStatus,
|
|
@@ -2029,6 +3028,7 @@ export {
|
|
|
2029
3028
|
JourneyBuilder,
|
|
2030
3029
|
JourneyStepBuilder,
|
|
2031
3030
|
MASKED_SECRET_SENTINEL,
|
|
3031
|
+
MEMBER_RELATIONSHIP_KINDS,
|
|
2032
3032
|
McpServerState,
|
|
2033
3033
|
MemberFeature,
|
|
2034
3034
|
MemberFilterBuilder,
|
|
@@ -2036,6 +3036,7 @@ export {
|
|
|
2036
3036
|
MemberImportPreviewRowAction,
|
|
2037
3037
|
MemberImportValidationError,
|
|
2038
3038
|
MemberLabelBuilder,
|
|
3039
|
+
MemberRelationshipKind,
|
|
2039
3040
|
MemberType,
|
|
2040
3041
|
MenuItemResponseItemType,
|
|
2041
3042
|
MenuItemResponseItemType as MenuItemType,
|
|
@@ -2071,8 +3072,11 @@ export {
|
|
|
2071
3072
|
REALTIME_MODEL_OPENAI,
|
|
2072
3073
|
REALTIME_MODEL_OPENAI_2,
|
|
2073
3074
|
REALTIME_MODEL_OPENAI_MINI,
|
|
3075
|
+
RefResolutionStatus,
|
|
2074
3076
|
Region,
|
|
2075
3077
|
RegistrationIdentifier,
|
|
3078
|
+
RelatedMemberRole,
|
|
3079
|
+
RelationshipCardinality,
|
|
2076
3080
|
Resource,
|
|
2077
3081
|
RoleUpdate,
|
|
2078
3082
|
RoutineBuilder,
|
|
@@ -2097,7 +3101,12 @@ export {
|
|
|
2097
3101
|
TTS_PREVIEW_PROVIDERS,
|
|
2098
3102
|
TaskBuilder,
|
|
2099
3103
|
TemplatesApi,
|
|
3104
|
+
TestChatRunLane,
|
|
3105
|
+
TestChatRunOutcome,
|
|
2100
3106
|
ToolkitState,
|
|
3107
|
+
UiPromptPresentedKind as UiPromptPresentedKindValues,
|
|
3108
|
+
UiPromptResponseKind as UiPromptResponseKindValues,
|
|
3109
|
+
UiPromptResponseStatus as UiPromptResponseStatusValues,
|
|
2101
3110
|
WIDGET_SLUGS,
|
|
2102
3111
|
WORKFLOW_IMPLICIT_USE_SCOPE,
|
|
2103
3112
|
WorkflowAccessWarningResourceType,
|