@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.
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-C10qSd6b.d.ts → chat-interaction-DJ9-Qtg2.d.cts} +8016 -3571
  7. package/dist/{client-Ct0soeip.d.cts → chat-interaction-dd6F3uT4.d.ts} +8016 -3571
  8. package/dist/{chunk-3HAV5XUJ.js → chunk-HRUMZ46V.js} +1 -1
  9. package/dist/chunk-HRUMZ46V.js.map +1 -0
  10. package/dist/{chunk-7OLBLLV7.js → chunk-P2E6LU7I.js} +57 -7
  11. package/dist/chunk-P2E6LU7I.js.map +1 -0
  12. package/dist/{chunk-WMA6YCAS.js → chunk-WDYHAQDY.js} +1364 -216
  13. package/dist/chunk-WDYHAQDY.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 +2414 -149
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +550 -6
  23. package/dist/index.d.ts +550 -6
  24. package/dist/index.js +1037 -23
  25. package/dist/index.js.map +1 -1
  26. package/dist/internal.cjs +1456 -235
  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 +77 -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-C6aM-PHu.d.ts → revision-state-BD39fW5X.d.ts} +236 -5
  39. package/dist/{revision-state-CCPTiW-i.d.cts → revision-state-Cgmy_Cds.d.cts} +236 -5
  40. package/dist/schemas.cjs +35 -10
  41. package/dist/schemas.cjs.map +1 -1
  42. package/dist/schemas.d.cts +54 -33
  43. package/dist/schemas.d.ts +54 -33
  44. package/dist/schemas.js +35 -10
  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-3HAV5XUJ.js.map +0 -1
  52. package/dist/chunk-7OLBLLV7.js.map +0 -1
  53. package/dist/chunk-WMA6YCAS.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,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-WMA6YCAS.js";
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-3HAV5XUJ.js";
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-7OLBLLV7.js";
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((resource) => resource instanceof JourneyBuilder).flatMap((journey) => journey.validate());
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
- 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:
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
- writeLine(` Created ${resource.planLine()}${startTag} -> id=${resource._id}`);
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((resource) => resource instanceof JourneyBuilder).map((resource) => [resource.name, resource])
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 existingJourneys = await this.client.getJourneys();
1449
- for (const existing of existingJourneys.items) {
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
- const detail = await this.client.getJourney(existing.id);
1452
- journeyNames.get(existing.name).adopt(detail);
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) return "journeys";
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,