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