@gravity-rail/sdk 0.21.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.
Files changed (55) hide show
  1. package/README.md +57 -8
  2. package/dist/agui.d.cts +1 -1
  3. package/dist/agui.d.ts +1 -1
  4. package/dist/audio.d.cts +1 -1
  5. package/dist/audio.d.ts +1 -1
  6. package/dist/{client-B0eh2BEY.d.cts → chat-interaction-BKdImgZ5.d.ts} +8006 -3570
  7. package/dist/{client-CFz-BGmY.d.ts → chat-interaction-DLfC_h_P.d.cts} +8006 -3570
  8. package/dist/{chunk-3HAV5XUJ.js → chunk-HRUMZ46V.js} +1 -1
  9. package/dist/chunk-HRUMZ46V.js.map +1 -0
  10. package/dist/{chunk-24L4RD4Z.js → chunk-N446FGCT.js} +1247 -203
  11. package/dist/chunk-N446FGCT.js.map +1 -0
  12. package/dist/{chunk-7OLBLLV7.js → chunk-P2E6LU7I.js} +57 -7
  13. package/dist/chunk-P2E6LU7I.js.map +1 -0
  14. package/dist/helpers.cjs +54 -1
  15. package/dist/helpers.cjs.map +1 -1
  16. package/dist/helpers.d.cts +3 -2
  17. package/dist/helpers.d.ts +3 -2
  18. package/dist/helpers.js +11 -3
  19. package/dist/helpers.js.map +1 -1
  20. package/dist/index.cjs +2302 -144
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +509 -6
  23. package/dist/index.d.ts +509 -6
  24. package/dist/index.js +1032 -23
  25. package/dist/index.js.map +1 -1
  26. package/dist/internal.cjs +1337 -222
  27. package/dist/internal.cjs.map +1 -1
  28. package/dist/internal.d.cts +33 -49
  29. package/dist/internal.d.ts +33 -49
  30. package/dist/internal.js +73 -33
  31. package/dist/internal.js.map +1 -1
  32. package/dist/realtime.cjs +279 -14
  33. package/dist/realtime.cjs.map +1 -1
  34. package/dist/realtime.d.cts +169 -17
  35. package/dist/realtime.d.ts +169 -17
  36. package/dist/realtime.js +264 -13
  37. package/dist/realtime.js.map +1 -1
  38. package/dist/{revision-state-fJ9PM15N.d.cts → revision-state-Bsq5dKwi.d.cts} +236 -5
  39. package/dist/{revision-state-KfOztBkY.d.ts → revision-state-CQTYcIRG.d.ts} +236 -5
  40. package/dist/schemas.cjs +3 -1
  41. package/dist/schemas.cjs.map +1 -1
  42. package/dist/schemas.d.cts +31 -13
  43. package/dist/schemas.d.ts +31 -13
  44. package/dist/schemas.js +4 -2
  45. package/dist/schemas.js.map +1 -1
  46. package/dist/{tasks-WH_VfzMv.d.cts → tasks-B-31JPuq.d.cts} +298 -114
  47. package/dist/{tasks-WH_VfzMv.d.ts → tasks-B-31JPuq.d.ts} +298 -114
  48. package/dist/types-0c6_NGU4.d.cts +90 -0
  49. package/dist/types-0c6_NGU4.d.ts +90 -0
  50. package/package.json +3 -3
  51. package/dist/chunk-24L4RD4Z.js.map +0 -1
  52. package/dist/chunk-3HAV5XUJ.js.map +0 -1
  53. package/dist/chunk-7OLBLLV7.js.map +0 -1
  54. package/dist/types-DkYyrvoC.d.cts +0 -19
  55. 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-24L4RD4Z.js";
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-3HAV5XUJ.js";
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-7OLBLLV7.js";
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((resource) => resource instanceof JourneyBuilder).flatMap((journey) => journey.validate());
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
- async apply() {
1359
- const problems = this.preflight();
1360
- if (problems.length) {
1361
- writeLine("\nPlan rejected \u2014 nothing was created:");
1362
- for (const problem of problems) writeLine(` ! ${problem}`);
1363
- throw new Error(
1364
- `Plan has ${problems.length} problem(s) and was not applied:
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
- writeLine(` Created ${resource.planLine()}${startTag} -> id=${resource._id}`);
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((resource) => resource instanceof JourneyBuilder).map((resource) => [resource.name, resource])
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 existingJourneys = await this.client.getJourneys();
1449
- for (const existing of existingJourneys.items) {
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
- const detail = await this.client.getJourney(existing.id);
1452
- journeyNames.get(existing.name).adopt(detail);
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) return "journeys";
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,