@wix/himalaya-cli 0.940.0 → 0.941.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 (50) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/genie-chat/src/index.d.ts +32 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-genie-chat.mjs +22 -1
  3. package/dist/cli.mjs +0 -2
  4. package/guides/sdk-reference.json +19 -1
  5. package/guides/tier-5-add-a-screen.md +113 -0
  6. package/package.json +2 -2
  7. package/templates/beauty-salon-owner/dev/index.ts +57 -0
  8. package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +367 -16
  9. package/templates/beauty-salon-owner/tier5-src/_shared/aria-widgets.ts +45 -0
  10. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -1
  11. package/templates/community-club-owner/dev/index.ts +57 -0
  12. package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +367 -16
  13. package/templates/community-club-owner/tier5-src/_shared/aria-widgets.ts +45 -0
  14. package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -1
  15. package/templates/events-meetups-owner/dev/index.ts +57 -0
  16. package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +367 -16
  17. package/templates/events-meetups-owner/tier5-src/_shared/aria-widgets.ts +45 -0
  18. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -1
  19. package/templates/gym-fitness-owner/dev/index.ts +57 -0
  20. package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +367 -16
  21. package/templates/gym-fitness-owner/tier5-src/_shared/aria-widgets.ts +45 -0
  22. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -1
  23. package/templates/nonprofit-cause-owner/dev/index.ts +57 -0
  24. package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +367 -16
  25. package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-widgets.ts +45 -0
  26. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -1
  27. package/templates/owner-lite/dev/index.ts +57 -0
  28. package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +367 -16
  29. package/templates/owner-lite/tier5-src/_shared/aria-widgets.ts +45 -0
  30. package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +15 -1
  31. package/templates/podcast-creator-owner/dev/index.ts +57 -0
  32. package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +367 -16
  33. package/templates/podcast-creator-owner/tier5-src/_shared/aria-widgets.ts +45 -0
  34. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -1
  35. package/templates/portfolio-creative-owner/dev/index.ts +57 -0
  36. package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +367 -16
  37. package/templates/portfolio-creative-owner/tier5-src/_shared/aria-widgets.ts +45 -0
  38. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -1
  39. package/templates/restaurant-owner/dev/index.ts +57 -0
  40. package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +367 -16
  41. package/templates/restaurant-owner/tier5-src/_shared/aria-widgets.ts +45 -0
  42. package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -1
  43. package/templates/retail-store-owner/dev/index.ts +57 -0
  44. package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +367 -16
  45. package/templates/retail-store-owner/tier5-src/_shared/aria-widgets.ts +45 -0
  46. package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -1
  47. package/templates/yoga-studio-owner/dev/index.ts +57 -0
  48. package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +367 -16
  49. package/templates/yoga-studio-owner/tier5-src/_shared/aria-widgets.ts +45 -0
  50. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -1
@@ -78,7 +78,21 @@ export interface GetConversationsResponse {
78
78
  export interface SendMessageResponse {
79
79
  assistantMessages?: ConversationMessage[];
80
80
  conversationMessage?: ConversationMessage;
81
+ /** The user's own turn AS STORED by the service — present on step 1's reply
82
+ * (`conversationUserMessage`), which is where the id actually comes from. */
83
+ userMessage?: ConversationMessage;
84
+ /** The id the owner's turn was stored under, carried out by `sendMessage`.
85
+ *
86
+ * The only handle the abort route accepts, and there is no route that looks
87
+ * it up afterwards — so a caller that wants to stop the reply has to keep it
88
+ * from the send. `sendMessage` resolves with step 2's response, which does
89
+ * NOT carry `userMessage`, so this field is the one reliable source; read it
90
+ * through `userMessageIdOf` rather than directly. */
91
+ userMessageId?: string;
81
92
  }
93
+ /** Empty on success — the service answers `{}`. Named rather than `void` so a
94
+ * future field has somewhere to land without changing every call site. */
95
+ export type AbortAssistantMessageGenerationResponse = Record<string, never>;
82
96
  export interface GetConversationMessagesResponse {
83
97
  messages?: ConversationMessage[];
84
98
  conversationMessages?: ConversationMessage[];
@@ -149,10 +163,28 @@ export declare function sendMessage(conversationId: string, content: string, mod
149
163
  * failed — see `sendMessage`, which attaches the id to its error for exactly
150
164
  * this call. Carries no content: the turn it names is the content. */
151
165
  export declare function triggerAssistantMessage(conversationId: string, userMessageId: string, opts?: SendMessageOptions): Promise<SendMessageResponse>;
166
+ /** Ask the service to abandon the assistant reply it is generating for
167
+ * `userMessageId` — the "stop" a chat surface puts beside a reply in progress.
168
+ *
169
+ * Addressed by USER message, not by conversation: the thing being abandoned is
170
+ * one turn, and the id comes from that turn's own send response
171
+ * (`userMessageIdOf`). There is no route that looks it up afterwards, so a
172
+ * caller that did not keep it cannot abort.
173
+ *
174
+ * Resolving means the service ACCEPTED the abort, not that generation had not
175
+ * already finished — a reply that was already written still lands in the
176
+ * transcript. Rejecting means the turn is still running: treat it as "could
177
+ * not stop", never as "stopped". */
178
+ export declare function abortAssistantMessageGeneration(userMessageId: string): Promise<AbortAssistantMessageGenerationResponse>;
152
179
  /** Fetch the messages of a conversation (poll after sendMessage). */
153
180
  export declare function getConversationMessages(conversationId: string): Promise<GetConversationMessagesResponse>;
154
181
  /** The transcript from a getConversationMessages response, under either key. */
155
182
  export declare function messagesOf(res: GetConversationMessagesResponse | null | undefined): ConversationMessage[];
183
+ /** The id of the user turn a send response stored, or "" when the service did
184
+ * not name one. Empty is a real answer, not a parse failure: it means this turn
185
+ * cannot be aborted, and a caller must degrade rather than send `undefined` to
186
+ * the abort route. */
187
+ export declare function userMessageIdOf(res: SendMessageResponse | null | undefined): string;
156
188
  /** The conversation id from a createConversation response, under either key. */
157
189
  export declare function conversationIdOf(res: CreateConversationResponse | null | undefined): string;
158
190
  /** A message's text. `content` is normally a string, but can arrive as an object
@@ -56,7 +56,19 @@ function sendMessage(conversationId, content, modeOrOptions = "POLLING") {
56
56
  "@himalaya/wix-genie-chat: conversation-user-message returned no userMessage.id, so the agent would have no turn to answer"
57
57
  );
58
58
  }
59
- return triggerAssistantMessage(conversationId, userMessageId, opts).catch((cause) => {
59
+ return triggerAssistantMessage(conversationId, userMessageId, opts).then((res) => ({
60
+ ...res,
61
+ // The id the turn was STORED under, carried out on the response.
62
+ //
63
+ // It only exists on step 1's reply, which callers never see, and there is
64
+ // no route that looks it up afterwards — so a caller that wants to abort
65
+ // this turn later has exactly one chance to learn it, here. Before the
66
+ // send became two calls it could be read off this very response
67
+ // (`userMessage.id`); it cannot now, and a caller doing that silently got
68
+ // `""` — which reads as "nothing to abort" rather than as an error, so the
69
+ // Stop control would simply never appear. Hence carrying it explicitly.
70
+ userMessageId
71
+ })).catch((cause) => {
60
72
  const e = genieChatError(
61
73
  "request",
62
74
  `@himalaya/wix-genie-chat: the turn was stored but the agent could not be triggered: ${String(cause?.message ?? cause)}`,
@@ -81,6 +93,10 @@ function triggerAssistantMessage(conversationId, userMessageId, opts = {}) {
81
93
  ...opts.enforceAccountScopedConversations === void 0 ? {} : { enforceAccountScopedConversations: opts.enforceAccountScopedConversations }
82
94
  });
83
95
  }
96
+ function abortAssistantMessageGeneration(userMessageId) {
97
+ if (!userMessageId) throw new Error("abortAssistantMessageGeneration: userMessageId is required");
98
+ return wixCall("POST", `${BASE}/abort-assistant-message-generation`, { userMessageId });
99
+ }
84
100
  function getConversationMessages(conversationId) {
85
101
  if (!conversationId) throw new Error("getConversationMessages: conversationId is required");
86
102
  return wixCall("GET", `${BASE}/conversation-messages?conversationId=${encodeURIComponent(conversationId)}`);
@@ -88,6 +104,9 @@ function getConversationMessages(conversationId) {
88
104
  function messagesOf(res) {
89
105
  return res?.messages ?? res?.conversationMessages ?? [];
90
106
  }
107
+ function userMessageIdOf(res) {
108
+ return (res?.userMessageId ?? res?.userMessage?.id ?? "").trim();
109
+ }
91
110
  function conversationIdOf(res) {
92
111
  return (res?.conversationId || res?.conversation?.id || "").trim();
93
112
  }
@@ -114,6 +133,7 @@ function lastAssistantMessage(msgs) {
114
133
  export {
115
134
  ARIA_ORIGIN,
116
135
  ARIA_UNIFIED_ASSISTANT_ID,
136
+ abortAssistantMessageGeneration,
117
137
  ariaAssistantConfig,
118
138
  conversationIdOf,
119
139
  conversationUserMessage,
@@ -127,6 +147,7 @@ export {
127
147
  messagesOf,
128
148
  sendMessage,
129
149
  triggerAssistantMessage,
150
+ userMessageIdOf,
130
151
  wixCall
131
152
  };
132
153
  //# sourceMappingURL=wix-genie-chat.mjs.map
package/dist/cli.mjs CHANGED
@@ -18541,7 +18541,6 @@ function shouldRevealError(args) {
18541
18541
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
18542
18542
  var init_validation = __esm({
18543
18543
  "../../core/ts/src/validation.ts"() {
18544
- "use strict";
18545
18544
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
18546
18545
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
18547
18546
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -21965,7 +21964,6 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
21965
21964
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
21966
21965
  var init_worker_subtree = __esm({
21967
21966
  "../../core/ts/src/worker-subtree.ts"() {
21968
- "use strict";
21969
21967
  init_worker_subtree_safe_types_generated();
21970
21968
  WORKER_SUBTREE_CEILINGS = Object.freeze({
21971
21969
  maxEncodedBytes: 65536,
@@ -16721,7 +16721,13 @@
16721
16721
  "name": "SendMessageResponse",
16722
16722
  "kind": "interface",
16723
16723
  "jsdoc": "",
16724
- "decl": "export interface SendMessageResponse { assistantMessages?: ConversationMessage[]; conversationMessage?: ConversationMessage }"
16724
+ "decl": "export interface SendMessageResponse {\n assistantMessages?: ConversationMessage[];\n conversationMessage?: ConversationMessage;\n /** The user's own turn AS STORED by the service — present on step 1's reply\n * (`conversationUserMessage`), which is where the id actually comes from. */\n userMessage?: ConversationMessage;\n /** The id the owner's turn was stored under, carried out by `sendMessage`.\n *\n * The only handle the abort route accepts, and there is no route that looks\n * it up afterwards — so a caller that wants to stop the reply has to keep it\n * from the send. `sendMessage` resolves with step 2's response, which does\n * NOT carry `userMessage`, so this field is the one reliable source; read it\n * through `userMessageIdOf` rather than directly. */\n userMessageId?: string;\n}"
16725
+ },
16726
+ {
16727
+ "name": "AbortAssistantMessageGenerationResponse",
16728
+ "kind": "type",
16729
+ "jsdoc": "/** Empty on success — the service answers `{}`. Named rather than `void` so a\n * future field has somewhere to land without changing every call site. */",
16730
+ "decl": "export type AbortAssistantMessageGenerationResponse = Record<string, never>;"
16725
16731
  },
16726
16732
  {
16727
16733
  "name": "GetConversationMessagesResponse",
@@ -16777,6 +16783,12 @@
16777
16783
  "jsdoc": "/** Step 2 of sending a turn on its own: ask the agent to answer a user message\n * that `conversationUserMessage` has ALREADY stored.\n *\n * Exported because it is the only safe retry when step 2 is the half that\n * failed — see `sendMessage`, which attaches the id to its error for exactly\n * this call. Carries no content: the turn it names is the content. */",
16778
16784
  "decl": "export function triggerAssistantMessage(\n conversationId: string,\n userMessageId: string,\n opts: SendMessageOptions = {},\n): Promise<SendMessageResponse>;"
16779
16785
  },
16786
+ {
16787
+ "name": "abortAssistantMessageGeneration",
16788
+ "kind": "function",
16789
+ "jsdoc": "/** Ask the service to abandon the assistant reply it is generating for\n * `userMessageId` — the \"stop\" a chat surface puts beside a reply in progress.\n *\n * Addressed by USER message, not by conversation: the thing being abandoned is\n * one turn, and the id comes from that turn's own send response\n * (`userMessageIdOf`). There is no route that looks it up afterwards, so a\n * caller that did not keep it cannot abort.\n *\n * Resolving means the service ACCEPTED the abort, not that generation had not\n * already finished — a reply that was already written still lands in the\n * transcript. Rejecting means the turn is still running: treat it as \"could\n * not stop\", never as \"stopped\". */",
16790
+ "decl": "export function abortAssistantMessageGeneration(userMessageId: string): Promise<AbortAssistantMessageGenerationResponse>;"
16791
+ },
16780
16792
  {
16781
16793
  "name": "getConversationMessages",
16782
16794
  "kind": "function",
@@ -16789,6 +16801,12 @@
16789
16801
  "jsdoc": "/** The transcript from a getConversationMessages response, under either key. */",
16790
16802
  "decl": "export function messagesOf(res: GetConversationMessagesResponse | null | undefined): ConversationMessage[];"
16791
16803
  },
16804
+ {
16805
+ "name": "userMessageIdOf",
16806
+ "kind": "function",
16807
+ "jsdoc": "/** The id of the user turn a send response stored, or \"\" when the service did\n * not name one. Empty is a real answer, not a parse failure: it means this turn\n * cannot be aborted, and a caller must degrade rather than send `undefined` to\n * the abort route. */",
16808
+ "decl": "export function userMessageIdOf(res: SendMessageResponse | null | undefined): string;"
16809
+ },
16792
16810
  {
16793
16811
  "name": "conversationIdOf",
16794
16812
  "kind": "function",
@@ -891,6 +891,119 @@ Branch in the action body. Server tells the client which segment via descriptor
891
891
 
892
892
  ---
893
893
 
894
+ ## A reducer must return a WHOLE state, never a patch
895
+
896
+ A worker action returns the **entire** next state, not the fields it changed:
897
+
898
+ ```ts
899
+ // WRONG — reads as "I only changed status", and on some hosts means
900
+ // "I deleted everything else"
901
+ stop: async (s) => {
902
+ await abort(s.jobId);
903
+ return { status: "ready" };
904
+ }
905
+
906
+ // RIGHT
907
+ stop: async (s) => {
908
+ await abort(s.jobId);
909
+ return { ...s, status: "ready" };
910
+ }
911
+ ```
912
+
913
+ **Why this is worth its own section.** The hosts do not agree about what a
914
+ returned record means. One merges it onto the state it already holds, so the
915
+ wrong version above looks perfectly fine; another assigns it, so the same code
916
+ silently drops every field you left out. A partial return therefore passes on
917
+ the surface you tested and loses data on the one you did not — with no error, no
918
+ warning, and nothing in the log. "Return only what you changed" is the normal
919
+ instinct, and it is the one this platform punishes.
920
+
921
+ **When an action awaits, it holds a stale snapshot.** Spreading `s` after an
922
+ `await` writes back the values as they were *before* it, so anything that
923
+ changed meanwhile is overwritten. There are two different problems in that
924
+ sentence, and they need two different answers.
925
+
926
+ ### Should this result be committed at all?
927
+
928
+ If the await crossed a point where the *whole* state moved on — the person
929
+ started a new item, cancelled the one in flight, reset the screen — then nothing
930
+ this action computed is worth keeping. Do not merge a repaired version of it;
931
+ stand down by returning **`undefined`**, so the newer state stands and the next
932
+ action reconciles.
933
+
934
+ **`undefined`, and nothing else.** Not `null`, not `false`, not `{}`. Only
935
+ `undefined` is read as "I have no opinion" everywhere; the other falsy values
936
+ are read as a *state* by at least one host and will replace the service's
937
+ state with them, taking every binding down with it. One host is lenient about
938
+ this and another is not, so the wrong spelling is a bug that passes wherever you
939
+ happen to test it:
940
+
941
+ ```ts
942
+ // Module scope, beside the service: a latch, not state. It must not be
943
+ // persisted, and reducers cannot see each other's locals any other way.
944
+ let turnSeq = 0; // bumped by EVERY action that starts,
945
+ // cancels or replaces the unit of work
946
+
947
+ defineService("feed", {
948
+ state: { data: [], status: "ready" },
949
+ actions: {
950
+ start: async (s) => { turnSeq += 1; return { ...s, status: "loading" }; },
951
+
952
+ refresh: async (s) => {
953
+ const seq = turnSeq; // read BEFORE the await
954
+ const data = await load();
955
+ // A boundary moved while we were reading: `s` predates it, and returning
956
+ // anything built from `s` would undo whatever moved it.
957
+ if (turnSeq !== seq) return undefined;
958
+ return { ...s, data };
959
+ },
960
+ },
961
+ });
962
+ ```
963
+
964
+ Put that check **before every return in the block, including the error path** —
965
+ a read that threw is exactly as stale as one that succeeded, and `return s` in a
966
+ `catch` is just as much a stale write.
967
+
968
+ ### Or committed, but with one field kept current?
969
+
970
+ If the await crossed an *ordinary* edit instead — typing into a field, say —
971
+ the result is still wanted; only that one field must not be rolled back. Carry
972
+ it across explicitly, and only when it actually moved:
973
+
974
+ ```ts
975
+ let draft = "";
976
+ let draftSeq = 0; // bumped by the action that owns it
977
+
978
+ defineService("composer", {
979
+ state: { input: "", status: "ready" },
980
+ actions: {
981
+ onType: (s, v) => {
982
+ draft = v;
983
+ draftSeq += 1;
984
+ return { ...s, input: v };
985
+ },
986
+
987
+ confirm: async (s) => {
988
+ const typedAt = draftSeq; // read BEFORE the await
989
+ await somethingSlow();
990
+ const next = { ...s, status: "ready" };
991
+ // Only re-apply a draft typed DURING the await.
992
+ return draftSeq === typedAt ? next : { ...next, input: draft };
993
+ },
994
+ },
995
+ });
996
+ ```
997
+
998
+ The counter is what keeps this honest. Re-applying whenever the latch merely
999
+ *differs* from the snapshot resurrects a value the action legitimately cleared —
1000
+ a submit that empties the box, followed by a slow confirm, puts the submitted
1001
+ text straight back into it.
1002
+
1003
+ **The two are not alternatives.** An action that awaits usually needs both: the
1004
+ boundary check to decide whether to commit, and the carry-across for the fields
1005
+ an ordinary edit may have moved underneath it.
1006
+
894
1007
  ## Anti-patterns
895
1008
 
896
1009
  Don't use Tier 5 for:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.940.0",
3
+ "version": "0.941.0",
4
4
  "type": "module",
5
5
  "description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
6
6
  "keywords": [
@@ -64,5 +64,5 @@
64
64
  "artifactId": "himalaya-cli"
65
65
  }
66
66
  },
67
- "falconPackageHash": "9cbb37281f079a04b648edee13a6f4aad84789cd285a2a200c8e6a55"
67
+ "falconPackageHash": "f6ef221b31d3e6cb04fc7f80011d74de1d766b5da1d85c61f7a68530"
68
68
  }
@@ -55035,6 +55035,63 @@ const SCREENS = runtimeSetting({
55035
55035
  }
55036
55036
  }
55037
55037
  },
55038
+ {
55039
+ "id": "himi_owner_lite_aria.stop",
55040
+ "body": {
55041
+ "package": "himalaya",
55042
+ "type": "HStack",
55043
+ "props": {
55044
+ "spacing": 6,
55045
+ "children": [
55046
+ {
55047
+ "id": "",
55048
+ "body": {
55049
+ "package": "himalaya",
55050
+ "type": "Spacer",
55051
+ "props": {}
55052
+ }
55053
+ },
55054
+ {
55055
+ "id": "",
55056
+ "body": {
55057
+ "package": "himalaya",
55058
+ "type": "Icon",
55059
+ "props": {
55060
+ "name": "close",
55061
+ "size": 14,
55062
+ "tint": "accentPrimary"
55063
+ }
55064
+ }
55065
+ },
55066
+ {
55067
+ "id": "",
55068
+ "body": {
55069
+ "package": "himalaya",
55070
+ "type": "Text",
55071
+ "props": {
55072
+ "text": "Stop",
55073
+ "style": "footnote",
55074
+ "color": "accentPrimary",
55075
+ "maxLines": 1
55076
+ }
55077
+ }
55078
+ }
55079
+ ]
55080
+ }
55081
+ },
55082
+ "modifiers": {
55083
+ "padding": {
55084
+ "leading": 0,
55085
+ "trailing": 0,
55086
+ "top": 4,
55087
+ "bottom": 0
55088
+ },
55089
+ "onTap": {
55090
+ "actionId": "tier5.himiOwnerLiteAria.stop"
55091
+ },
55092
+ "hiddenPath": "$service:himiOwnerLiteAria.stopHidden"
55093
+ }
55094
+ },
55038
55095
  {
55039
55096
  "id": "",
55040
55097
  "body": {