@zeniai/client-epic-state 5.2.4 → 5.2.5

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 (38) hide show
  1. package/lib/epic.js +12 -0
  2. package/lib/esm/epic.js +12 -0
  3. package/lib/esm/index.js +2 -2
  4. package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +411 -9
  5. package/lib/esm/view/aiCfoView/aiCfoViewSelector.js +49 -0
  6. package/lib/esm/view/aiCfoView/epics/deleteSkillEpic.js +23 -0
  7. package/lib/esm/view/aiCfoView/epics/fetchSkillDetailEpic.js +35 -0
  8. package/lib/esm/view/aiCfoView/epics/fetchSkillVersionBodyEpic.js +45 -0
  9. package/lib/esm/view/aiCfoView/epics/fetchSkillVersionsEpic.js +37 -0
  10. package/lib/esm/view/aiCfoView/epics/fetchSkillsEpic.js +11 -6
  11. package/lib/esm/view/aiCfoView/epics/saveSkillEpic.js +72 -0
  12. package/lib/esm/view/aiCfoView/epics/setSkillVisibilityEpic.js +32 -0
  13. package/lib/esm/view/aiCfoView/epics/toSkillDetail.js +102 -0
  14. package/lib/index.d.ts +3 -3
  15. package/lib/index.js +26 -11
  16. package/lib/view/aiCfoView/aiCfoViewPayload.d.ts +85 -0
  17. package/lib/view/aiCfoView/aiCfoViewReducer.d.ts +67 -2
  18. package/lib/view/aiCfoView/aiCfoViewReducer.js +413 -10
  19. package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +7 -1
  20. package/lib/view/aiCfoView/aiCfoViewSelector.js +54 -0
  21. package/lib/view/aiCfoView/aiCfoViewState.d.ts +143 -1
  22. package/lib/view/aiCfoView/epics/deleteSkillEpic.d.ts +8 -0
  23. package/lib/view/aiCfoView/epics/deleteSkillEpic.js +27 -0
  24. package/lib/view/aiCfoView/epics/fetchSkillDetailEpic.d.ts +8 -0
  25. package/lib/view/aiCfoView/epics/fetchSkillDetailEpic.js +39 -0
  26. package/lib/view/aiCfoView/epics/fetchSkillVersionBodyEpic.d.ts +13 -0
  27. package/lib/view/aiCfoView/epics/fetchSkillVersionBodyEpic.js +49 -0
  28. package/lib/view/aiCfoView/epics/fetchSkillVersionsEpic.d.ts +8 -0
  29. package/lib/view/aiCfoView/epics/fetchSkillVersionsEpic.js +41 -0
  30. package/lib/view/aiCfoView/epics/fetchSkillsEpic.js +11 -6
  31. package/lib/view/aiCfoView/epics/saveSkillEpic.d.ts +14 -0
  32. package/lib/view/aiCfoView/epics/saveSkillEpic.js +76 -0
  33. package/lib/view/aiCfoView/epics/setSkillVisibilityEpic.d.ts +14 -0
  34. package/lib/view/aiCfoView/epics/setSkillVisibilityEpic.js +36 -0
  35. package/lib/view/aiCfoView/epics/toSkillDetail.d.ts +88 -0
  36. package/lib/view/aiCfoView/epics/toSkillDetail.js +110 -0
  37. package/lib/view/taskManager/taskListView/taskList.d.ts +1 -1
  38. package/package.json +1 -1
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  var _a;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.clearAiCfoSidePanelHostPageContext = exports.applyAiCfoSidePanelHostPageTransition = exports.fetchSkillsFailure = exports.fetchSkillsSuccess = exports.fetchSkills = exports.fetchSuggestedQuestionsFailure = exports.fetchSuggestedQuestionsSuccess = exports.fetchSuggestedQuestions = exports.updateCotCollapsedState = exports.clearDeleteChatSessionStatus = exports.deleteChatSessionFailure = exports.deleteChatSessionSuccess = exports.deleteChatSession = exports.acceptMasterTOSFailure = exports.acceptMasterTOSSuccess = exports.acceptMasterTOS = exports.stopSubmitQuestionFailure = exports.stopSubmitQuestionSuccess = exports.stopSubmitQuestion = exports.stopSubmitFailure = exports.stopSubmitSuccess = exports.stopSubmit = exports.clearLastContextMessage = exports.clearCurrentSessionId = exports.fetchChatHistoryFailure = exports.fetchChatHistorySuccess = exports.fetchChatHistory = exports.fetchChatSessionsForUserFailure = exports.fetchChatSessionsForUserSuccess = exports.fetchChatSessionsForUser = exports.createSessionAndSubmitFailure = exports.createSessionAndSubmitSuccess = exports.initializeNewSessionState = exports.createSessionAndSubmit = exports.createSessionFailure = exports.createSessionSuccess = exports.createSession = exports.setSession = exports.submitFeedbackFailure = exports.submitFeedbackSuccess = exports.submitFeedback = exports.updateCurrentInput = exports.updateAiCfoViewScrollPosition = exports.updateResponseState = exports.submitQuestionFailure = exports.submitQuestionSuccess = exports.submitQuestion = exports.initialAiCfoViewState = void 0;
4
+ exports.clearSkillDetail = exports.fetchSkillDetailFailure = exports.fetchSkillDetailSuccess = exports.fetchSkillDetail = exports.fetchSkillsFailure = exports.fetchSkillsSuccess = exports.fetchSkills = exports.fetchSuggestedQuestionsFailure = exports.fetchSuggestedQuestionsSuccess = exports.fetchSuggestedQuestions = exports.updateCotCollapsedState = exports.clearDeleteChatSessionStatus = exports.deleteChatSessionFailure = exports.deleteChatSessionSuccess = exports.deleteChatSession = exports.acceptMasterTOSFailure = exports.acceptMasterTOSSuccess = exports.acceptMasterTOS = exports.stopSubmitQuestionFailure = exports.stopSubmitQuestionSuccess = exports.stopSubmitQuestion = exports.stopSubmitFailure = exports.stopSubmitSuccess = exports.stopSubmit = exports.clearLastContextMessage = exports.clearCurrentSessionId = exports.fetchChatHistoryFailure = exports.fetchChatHistorySuccess = exports.fetchChatHistory = exports.fetchChatSessionsForUserFailure = exports.fetchChatSessionsForUserSuccess = exports.fetchChatSessionsForUser = exports.createSessionAndSubmitFailure = exports.createSessionAndSubmitSuccess = exports.initializeNewSessionState = exports.createSessionAndSubmit = exports.createSessionFailure = exports.createSessionSuccess = exports.createSession = exports.setSession = exports.submitFeedbackFailure = exports.submitFeedbackSuccess = exports.submitFeedback = exports.updateCurrentInput = exports.updateAiCfoViewScrollPosition = exports.updateResponseState = exports.submitQuestionFailure = exports.submitQuestionSuccess = exports.submitQuestion = exports.initialAiCfoViewState = void 0;
5
+ exports.clearAiCfoSidePanelHostPageContext = exports.applyAiCfoSidePanelHostPageTransition = exports.closeSkillsBrowser = exports.openSkillsBrowser = exports.setSkillVisibilityFailure = exports.setSkillVisibilitySuccess = exports.setSkillVisibility = exports.saveSkillFailure = exports.saveSkillSuccess = exports.saveSkill = exports.fetchSkillVersionsFailure = exports.fetchSkillVersionsSuccess = exports.fetchSkillVersions = exports.fetchSkillVersionBodyFailure = exports.fetchSkillVersionBodySuccess = exports.fetchSkillVersionBody = exports.deleteSkillFailure = exports.deleteSkillSuccess = exports.deleteSkill = exports.clearSkillMutation = void 0;
5
6
  const toolkit_1 = require("@reduxjs/toolkit");
6
7
  const rootActions_1 = require("../../rootActions");
7
8
  function removeSessionIdFromPageKeyMap(draft, sessionId) {
@@ -52,8 +53,81 @@ exports.initialAiCfoViewState = {
52
53
  responseStateByChatSessionId: {},
53
54
  stopSubmitStateByChatSessionId: {},
54
55
  suggestedQuestionsByPageContext: {},
55
- skills: { fetchState: 'Not-Started', error: undefined, skills: [] },
56
+ skillDetail: {
57
+ fetchState: 'Not-Started',
58
+ error: undefined,
59
+ detail: undefined,
60
+ macroId: undefined,
61
+ },
62
+ skillMutation: {
63
+ fetchState: 'Not-Started',
64
+ error: undefined,
65
+ savedMacroId: undefined,
66
+ },
67
+ skillVersionBody: {
68
+ fetchState: 'Not-Started',
69
+ error: undefined,
70
+ instructions: undefined,
71
+ macroId: undefined,
72
+ version: undefined,
73
+ },
74
+ skillVersions: {
75
+ fetchState: 'Not-Started',
76
+ error: undefined,
77
+ versions: [],
78
+ macroId: undefined,
79
+ },
80
+ skills: {
81
+ fetchState: 'Not-Started',
82
+ error: undefined,
83
+ deletedMacroIds: [],
84
+ skills: [],
85
+ skillsEnabled: false,
86
+ canPublish: false,
87
+ },
56
88
  };
89
+ /**
90
+ * Forget the skill that was open — prompt AND history together.
91
+ *
92
+ * Both, always. They are two slices describing one selection, so clearing only the
93
+ * prompt leaves `skillVersions.macroId` matching: an in-flight history response then
94
+ * passes its own stale-response guard and renders the previous skill's versions under
95
+ * whatever the user opened next.
96
+ */
97
+ function clearOpenSkillState(draft) {
98
+ draft.skillDetail = {
99
+ fetchState: 'Not-Started',
100
+ error: undefined,
101
+ detail: undefined,
102
+ macroId: undefined,
103
+ };
104
+ draft.skillVersions = {
105
+ fetchState: 'Not-Started',
106
+ error: undefined,
107
+ versions: [],
108
+ macroId: undefined,
109
+ };
110
+ // Part of the same selection: a pinned body left behind would seed a restore form for
111
+ // a skill the user has left.
112
+ draft.skillVersionBody = {
113
+ fetchState: 'Not-Started',
114
+ error: undefined,
115
+ instructions: undefined,
116
+ macroId: undefined,
117
+ version: undefined,
118
+ };
119
+ }
120
+ /**
121
+ * Take the Skills browser down and drop the prompt it had open.
122
+ *
123
+ * Both halves together, always. Closing without clearing leaves the next open landing on
124
+ * the previous skill instead of the list, and leaves a `macroId` that makes an in-flight
125
+ * detail response look current to the stale-response guard.
126
+ */
127
+ function closeSkillsBrowserState(draft) {
128
+ draft.uiState.isSkillsBrowserOpen = false;
129
+ clearOpenSkillState(draft);
130
+ }
57
131
  const aiCfoView = (0, toolkit_1.createSlice)({
58
132
  name: 'aiCfoView',
59
133
  initialState: exports.initialAiCfoViewState,
@@ -61,6 +135,16 @@ const aiCfoView = (0, toolkit_1.createSlice)({
61
135
  submitQuestion: {
62
136
  reducer(draft, action) {
63
137
  const { sessionId } = action.payload;
138
+ // A turn is being sent, so the chat body has to be what the user sees. Doing this
139
+ // here rather than at each call site is deliberate: the composer, the sample
140
+ // chips, Run, and the `/` menu all submit, and every one of them was a separate
141
+ // chance to forget and leave the answer hidden behind the browser.
142
+ //
143
+ // Guarded so a submit with the browser already closed — i.e. almost every submit
144
+ // — does not produce a new `uiState` for every subscriber to re-render on.
145
+ if (draft.uiState.isSkillsBrowserOpen === true) {
146
+ closeSkillsBrowserState(draft);
147
+ }
64
148
  draft.responseStateByChatSessionId[sessionId] = {
65
149
  thought: { fetchState: 'In-Progress', error: undefined },
66
150
  analysis: { fetchState: 'In-Progress', error: undefined },
@@ -187,6 +271,13 @@ const aiCfoView = (0, toolkit_1.createSlice)({
187
271
  setSession(draft, action) {
188
272
  draft.uiState.currentChatSessionId = action.payload.sessionId;
189
273
  draft.uiState.agentId = action.payload.agentId;
274
+ // Picking a session means wanting to see that chat, so the browser cannot stay over
275
+ // it. In the reducer because there are six dispatch sites across three separate
276
+ // rails, and covering them one at a time missed half of them.
277
+ if (action.payload.isRestore !== true &&
278
+ draft.uiState.isSkillsBrowserOpen === true) {
279
+ closeSkillsBrowserState(draft);
280
+ }
190
281
  mirrorCurrentSessionToPageKeyMapIfActive(draft);
191
282
  },
192
283
  createSession: {
@@ -206,6 +297,12 @@ const aiCfoView = (0, toolkit_1.createSlice)({
206
297
  draft.uiState.agentId = agentId;
207
298
  draft.createSessionState.fetchState = 'Completed';
208
299
  draft.createSessionState.error = undefined;
300
+ // Same invariant as submitQuestion and initializeNewSessionState: a new chat exists
301
+ // and is now current, so the browser cannot stay over it. This is the fifth entry
302
+ // point to that rule — the others are submit, new-session, New Chat and setSession.
303
+ if (draft.uiState.isSkillsBrowserOpen === true) {
304
+ closeSkillsBrowserState(draft);
305
+ }
209
306
  draft.uiState.currentChatSessionId = sessionId;
210
307
  draft.uiState.lastContextMessage = contextMessage;
211
308
  draft.uiState.lastContextStatus = contextStatus;
@@ -247,6 +344,10 @@ const aiCfoView = (0, toolkit_1.createSlice)({
247
344
  },
248
345
  initializeNewSessionState(draft, action) {
249
346
  const { agentId, sessionId } = action.payload;
347
+ // Same invariant as submitQuestion — this is the new-session half of submitting.
348
+ if (draft.uiState.isSkillsBrowserOpen === true) {
349
+ closeSkillsBrowserState(draft);
350
+ }
250
351
  draft.uiState.currentChatSessionId = sessionId;
251
352
  draft.uiState.agentId = agentId;
252
353
  draft.uiState.chatHistoryNextPageToken[sessionId] = {
@@ -367,6 +468,10 @@ const aiCfoView = (0, toolkit_1.createSlice)({
367
468
  };
368
469
  draft.allChatSessionIds = draft.allChatSessionIds.filter((id) => id !== chatSessionId);
369
470
  if (draft.uiState.currentChatSessionId === chatSessionId) {
471
+ // Deliberately does NOT close the Skills browser, unlike the other writers of
472
+ // this field. Deleting a chat from the history list is housekeeping: if the
473
+ // browser is open the user is not looking at that chat, and closing it would
474
+ // yank them out of what they were doing to reveal an empty chat area.
370
475
  draft.uiState.currentChatSessionId = undefined;
371
476
  draft.uiState.agentId = undefined;
372
477
  }
@@ -419,6 +524,10 @@ const aiCfoView = (0, toolkit_1.createSlice)({
419
524
  },
420
525
  },
421
526
  clearCurrentSessionId(draft) {
527
+ // New Chat: the empty composer is the point, so the browser cannot stay over it.
528
+ if (draft.uiState.isSkillsBrowserOpen === true) {
529
+ closeSkillsBrowserState(draft);
530
+ }
422
531
  draft.uiState.currentChatSessionId = undefined;
423
532
  const key = draft.uiState.aiCfoSidePanelHostPageKey;
424
533
  if (key != null) {
@@ -435,8 +544,24 @@ const aiCfoView = (0, toolkit_1.createSlice)({
435
544
  draft.uiState.chatSessionIdByPageKey[prevKey] = curId;
436
545
  }
437
546
  draft.uiState.aiCfoSidePanelHostPageKey = newAiCfoSidePanelHostPageKey;
438
- draft.uiState.currentChatSessionId =
439
- nextCurrentChatSessionId != null ? nextCurrentChatSessionId : undefined;
547
+ const nextId = nextCurrentChatSessionId != null ? nextCurrentChatSessionId : undefined;
548
+ // The sixth entry point to the same invariant as submitQuestion,
549
+ // initializeNewSessionState, clearCurrentSessionId, setSession and
550
+ // createSessionSuccess: a different chat is now visible, so the browser cannot stay
551
+ // over it. Missing here, this path swapped the session underneath an open browser
552
+ // and left its macroId looking current to the stale-response guards.
553
+ //
554
+ // Only when the session actually CHANGES, and only when there WAS one. A host page
555
+ // resolving back to the same chat has not changed what is on screen. And
556
+ // `undefined` becoming a saved id is a hydrate, not a navigation — the same
557
+ // settle/restore race `setSession` exempts via `isRestore`: opening Skills on a
558
+ // host page whose chat has not resolved yet would otherwise close the browser
559
+ // under the user the moment the sessions list arrives.
560
+ const hadASession = draft.uiState.currentChatSessionId != null;
561
+ if (hadASession && nextId !== draft.uiState.currentChatSessionId) {
562
+ closeSkillsBrowserState(draft);
563
+ }
564
+ draft.uiState.currentChatSessionId = nextId;
440
565
  draft.uiState.currentInput = '';
441
566
  mirrorCurrentSessionToPageKeyMapIfActive(draft);
442
567
  },
@@ -481,28 +606,306 @@ const aiCfoView = (0, toolkit_1.createSlice)({
481
606
  };
482
607
  },
483
608
  fetchSkills(draft) {
484
- // Optional-chain: persisted/rehydrated state from before this slice existed
485
- // may have no `skills`, so don't assume draft.skills is set.
609
+ // Read straight off `draft.skills` — no optional chain. This store is never
610
+ // persisted or rehydrated (no redux-persist anywhere in the repo, and no
611
+ // `preloadedState` in any consumer), and `initialAiCfoViewState` gives every skills
612
+ // slice a concrete value, so an undefined slice is unreachable here rather than
613
+ // merely unlikely. An earlier version of this comment claimed the opposite and
614
+ // justified a guard by a mechanism that does not exist — which then got applied to
615
+ // some accesses and not others, so the file read as if the slice were sometimes
616
+ // missing and sometimes not. The one guard that IS meaningful lives in the
617
+ // selectors, where `state.aiCfoViewState` may genuinely be absent for a host that
618
+ // composes a partial root reducer; that is a boundary, and this is not.
486
619
  draft.skills = {
487
620
  fetchState: 'In-Progress',
488
621
  error: undefined,
489
- skills: draft.skills?.skills ?? [],
622
+ skills: draft.skills.skills,
623
+ skillsEnabled: draft.skills.skillsEnabled,
624
+ // Carried for the same reason as skillsEnabled: a refetch in flight is not a
625
+ // reason to hide the publish control the user was just looking at.
626
+ canPublish: draft.skills.canPublish,
627
+ // Carried, not cleared: the response this is about to fetch may still contain a
628
+ // skill deleted moments ago, which is the whole reason the list is kept.
629
+ deletedMacroIds: draft.skills.deletedMacroIds,
490
630
  };
491
631
  },
492
632
  fetchSkillsSuccess(draft, action) {
633
+ // A list response can predate a delete — a save emits fetchSkills, and a delete can
634
+ // land while that is still outstanding — so replacing the array wholesale would put
635
+ // the deleted row back and read as the delete having failed.
493
636
  draft.skills = {
494
637
  fetchState: 'Completed',
495
638
  error: undefined,
496
- skills: action.payload.skills,
639
+ skills: action.payload.skills.filter((skill) => !draft.skills.deletedMacroIds.includes(skill.macroId)),
640
+ skillsEnabled: action.payload.skillsEnabled,
641
+ canPublish: action.payload.canPublish,
642
+ // Applied ONCE, then forgotten — the tombstone covers a race window, not a
643
+ // permanent state.
644
+ //
645
+ // The previous rule (keep it until a response omits that skill) was wrong in the
646
+ // case it was written for: nothing refetches after a delete, and a create emits
647
+ // fetchSkills whose response DOES contain the id, so the tombstone was kept and
648
+ // reapplied forever. A skill recreated under the same id stayed invisible with no
649
+ // way to clear it.
650
+ deletedMacroIds: [],
497
651
  };
498
652
  },
499
653
  fetchSkillsFailure(draft, action) {
500
654
  draft.skills = {
501
655
  fetchState: 'Error',
502
656
  error: action.payload,
503
- skills: draft.skills?.skills ?? [],
657
+ skills: draft.skills.skills,
658
+ // Kept, not reset: a failed refresh is not evidence the feature was turned
659
+ // off, and dropping it to false would make the Skills entry vanish on any
660
+ // transient error.
661
+ skillsEnabled: draft.skills.skillsEnabled,
662
+ // Same reasoning again: a transient error must not revoke a privilege the server
663
+ // already granted, or the control flickers away on every failed refresh.
664
+ canPublish: draft.skills.canPublish,
665
+ // Same reasoning: a failed refresh is not confirmation the delete was undone.
666
+ deletedMacroIds: draft.skills.deletedMacroIds,
667
+ };
668
+ },
669
+ fetchSkillDetail(draft, action) {
670
+ // Opening a skill IS the selection change, so forget the previous one whole —
671
+ // history included. Resetting only the prompt left `skillVersions.macroId` pointing
672
+ // at the skill just navigated away from, so a history response still in flight for
673
+ // it passed its own guard and rendered under the new title. Close, back and delete
674
+ // all go through the same helper for the same reason.
675
+ clearOpenSkillState(draft);
676
+ draft.skillDetail = {
677
+ fetchState: 'In-Progress',
678
+ error: undefined,
679
+ // Cleared rather than kept: holding the previous skill's prompt while a new
680
+ // one loads would show the wrong instructions under the right title.
681
+ detail: undefined,
682
+ macroId: action.payload.macroId,
683
+ };
684
+ },
685
+ fetchSkillDetailSuccess(draft, action) {
686
+ // Ignore a response for a skill the user has already navigated away from.
687
+ if (draft.skillDetail.macroId !== action.payload.macroId) {
688
+ return;
689
+ }
690
+ draft.skillDetail = {
691
+ fetchState: 'Completed',
692
+ error: undefined,
693
+ detail: action.payload.detail,
694
+ macroId: action.payload.macroId,
695
+ };
696
+ },
697
+ fetchSkillDetailFailure(draft, action) {
698
+ if (draft.skillDetail.macroId !== action.payload.macroId) {
699
+ return;
700
+ }
701
+ draft.skillDetail = {
702
+ fetchState: 'Error',
703
+ error: action.payload.error,
704
+ detail: undefined,
705
+ macroId: action.payload.macroId,
504
706
  };
505
707
  },
708
+ clearSkillDetail(draft) {
709
+ // Back to the list. History belongs to the detail view, so it goes too.
710
+ clearOpenSkillState(draft);
711
+ },
712
+ fetchSkillVersions: {
713
+ reducer(draft, action) {
714
+ draft.skillVersions = {
715
+ fetchState: 'In-Progress',
716
+ error: undefined,
717
+ // Cleared, not kept: showing the previous skill's history under a new title
718
+ // is worse than showing none while this loads.
719
+ versions: [],
720
+ macroId: action.payload.macroId,
721
+ };
722
+ },
723
+ prepare(payload) {
724
+ return { payload };
725
+ },
726
+ },
727
+ fetchSkillVersionsSuccess(draft, action) {
728
+ // Same stale-response guard as skillDetail: a switchMap cancels the request but
729
+ // cannot un-dispatch one already in flight.
730
+ if (draft.skillVersions.macroId !== action.payload.macroId) {
731
+ return;
732
+ }
733
+ draft.skillVersions.fetchState = 'Completed';
734
+ draft.skillVersions.error = undefined;
735
+ draft.skillVersions.versions = action.payload.versions;
736
+ },
737
+ fetchSkillVersionsFailure(draft, action) {
738
+ if (draft.skillVersions.macroId !== action.payload.macroId) {
739
+ return;
740
+ }
741
+ draft.skillVersions.fetchState = 'Error';
742
+ draft.skillVersions.error = action.payload.error;
743
+ },
744
+ fetchSkillVersionBody: {
745
+ reducer(draft, action) {
746
+ draft.skillVersionBody = {
747
+ fetchState: 'In-Progress',
748
+ error: undefined,
749
+ // Cleared: seeding a restore form from the PREVIOUS version's body is the same
750
+ // class of mistake as showing one skill's prompt under another's title.
751
+ instructions: undefined,
752
+ macroId: action.payload.macroId,
753
+ version: action.payload.version,
754
+ };
755
+ },
756
+ prepare(payload) {
757
+ return { payload };
758
+ },
759
+ },
760
+ fetchSkillVersionBodySuccess(draft, action) {
761
+ // Both parts of the key: a user can jump between versions of the same skill, so
762
+ // macroId alone would let an earlier version's body land on a later request.
763
+ if (draft.skillVersionBody.macroId !== action.payload.macroId ||
764
+ draft.skillVersionBody.version !== action.payload.version) {
765
+ return;
766
+ }
767
+ draft.skillVersionBody.fetchState = 'Completed';
768
+ draft.skillVersionBody.error = undefined;
769
+ draft.skillVersionBody.instructions = action.payload.instructions;
770
+ },
771
+ fetchSkillVersionBodyFailure(draft, action) {
772
+ if (draft.skillVersionBody.macroId !== action.payload.macroId ||
773
+ draft.skillVersionBody.version !== action.payload.version) {
774
+ return;
775
+ }
776
+ draft.skillVersionBody.fetchState = 'Error';
777
+ draft.skillVersionBody.error = action.payload.error;
778
+ },
779
+ saveSkill: {
780
+ reducer(draft) {
781
+ draft.skillMutation = {
782
+ fetchState: 'In-Progress',
783
+ error: undefined,
784
+ savedMacroId: undefined,
785
+ };
786
+ },
787
+ prepare(payload) {
788
+ return { payload };
789
+ },
790
+ },
791
+ saveSkillSuccess(draft, action) {
792
+ draft.skillMutation.fetchState = 'Completed';
793
+ draft.skillMutation.error = undefined;
794
+ draft.skillMutation.savedMacroId = action.payload.macroId;
795
+ // ONLY when the saved skill is still the one on screen. The save is a mergeMap —
796
+ // deliberately not cancellable, because cancelling a write only hides its outcome —
797
+ // so it can land after the user opened another skill, went back to the list, closed
798
+ // the browser, or deleted it. Writing unconditionally would drop one skill's prompt
799
+ // on top of another's, which is precisely what the fetch guards exist to prevent.
800
+ //
801
+ // A create lands here with nothing open, and is deliberately NOT force-opened:
802
+ // `savedMacroId` is the signal for "what did I just create", and the caller decides
803
+ // whether to open it.
804
+ if (draft.skillDetail.macroId !== action.payload.macroId) {
805
+ return;
806
+ }
807
+ // The save response IS a detail read, so adopt it. Discarding it left an editor
808
+ // sitting on the same skill showing the prompt and card it had BEFORE the save —
809
+ // the user's own change missing from the thing they just saved.
810
+ draft.skillDetail = {
811
+ fetchState: 'Completed',
812
+ error: undefined,
813
+ detail: action.payload.detail,
814
+ macroId: action.payload.macroId,
815
+ };
816
+ // History is now a version behind; the epic refetches it.
817
+ draft.skillVersions = {
818
+ fetchState: 'Not-Started',
819
+ error: undefined,
820
+ versions: [],
821
+ macroId: undefined,
822
+ };
823
+ },
824
+ saveSkillFailure(draft, action) {
825
+ draft.skillMutation.fetchState = 'Error';
826
+ draft.skillMutation.error = action.payload;
827
+ },
828
+ setSkillVisibility: {
829
+ reducer(draft) {
830
+ // Shares skillMutation with save and delete. One in-flight write at a time is the
831
+ // existing contract here, and publishing is a write like any other.
832
+ draft.skillMutation = {
833
+ fetchState: 'In-Progress',
834
+ error: undefined,
835
+ savedMacroId: undefined,
836
+ };
837
+ },
838
+ prepare(payload) {
839
+ return { payload };
840
+ },
841
+ },
842
+ setSkillVisibilitySuccess(draft, action) {
843
+ draft.skillMutation.fetchState = 'Completed';
844
+ draft.skillMutation.error = undefined;
845
+ draft.skillMutation.savedMacroId = action.payload.macroId;
846
+ // Same staleness guard as saveSkillSuccess, for the same reason: this is a mergeMap
847
+ // write, so it can land after the user opened another skill.
848
+ if (draft.skillDetail.macroId !== action.payload.macroId) {
849
+ return;
850
+ }
851
+ draft.skillDetail = {
852
+ fetchState: 'Completed',
853
+ error: undefined,
854
+ detail: action.payload.detail,
855
+ macroId: action.payload.macroId,
856
+ };
857
+ // Deliberately NOT clearing skillVersions, unlike saveSkillSuccess. No version was
858
+ // created, so the history the user is looking at is still correct — refetching it
859
+ // would flash a loading state for a list that did not change.
860
+ },
861
+ setSkillVisibilityFailure(draft, action) {
862
+ draft.skillMutation.fetchState = 'Error';
863
+ draft.skillMutation.error = action.payload;
864
+ },
865
+ deleteSkill: {
866
+ reducer(draft) {
867
+ draft.skillMutation = {
868
+ fetchState: 'In-Progress',
869
+ error: undefined,
870
+ savedMacroId: undefined,
871
+ };
872
+ },
873
+ prepare(payload) {
874
+ return { payload };
875
+ },
876
+ },
877
+ deleteSkillSuccess(draft, action) {
878
+ draft.skillMutation.fetchState = 'Completed';
879
+ draft.skillMutation.error = undefined;
880
+ // Drop it from the list immediately rather than waiting for a refetch: the row
881
+ // the user just deleted staying on screen reads as the delete having failed.
882
+ draft.skills.skills = draft.skills.skills.filter((skill) => skill.macroId !== action.payload.macroId);
883
+ if (!draft.skills.deletedMacroIds.includes(action.payload.macroId)) {
884
+ draft.skills.deletedMacroIds.push(action.payload.macroId);
885
+ }
886
+ if (draft.skillDetail.macroId === action.payload.macroId) {
887
+ clearOpenSkillState(draft);
888
+ }
889
+ },
890
+ deleteSkillFailure(draft, action) {
891
+ draft.skillMutation.fetchState = 'Error';
892
+ draft.skillMutation.error = action.payload;
893
+ },
894
+ clearSkillMutation(draft) {
895
+ draft.skillMutation = {
896
+ fetchState: 'Not-Started',
897
+ error: undefined,
898
+ savedMacroId: undefined,
899
+ };
900
+ },
901
+ openSkillsBrowser(draft) {
902
+ draft.uiState.isSkillsBrowserOpen = true;
903
+ },
904
+ closeSkillsBrowser(draft) {
905
+ // Unguarded, unlike the implicit closes: an explicit close should clear a lingering
906
+ // detail even if the flag is already false.
907
+ closeSkillsBrowserState(draft);
908
+ },
506
909
  updateCotCollapsedState(draft, action) {
507
910
  const { isCollapsed, questionAnswerId } = action.payload;
508
911
  draft.uiState.cotCollapsedByQuestionAnswerId[questionAnswerId] =
@@ -515,5 +918,5 @@ const aiCfoView = (0, toolkit_1.createSlice)({
515
918
  });
516
919
  },
517
920
  });
518
- _a = aiCfoView.actions, exports.submitQuestion = _a.submitQuestion, exports.submitQuestionSuccess = _a.submitQuestionSuccess, exports.submitQuestionFailure = _a.submitQuestionFailure, exports.updateResponseState = _a.updateResponseState, exports.updateAiCfoViewScrollPosition = _a.updateAiCfoViewScrollPosition, exports.updateCurrentInput = _a.updateCurrentInput, exports.submitFeedback = _a.submitFeedback, exports.submitFeedbackSuccess = _a.submitFeedbackSuccess, exports.submitFeedbackFailure = _a.submitFeedbackFailure, exports.setSession = _a.setSession, exports.createSession = _a.createSession, exports.createSessionSuccess = _a.createSessionSuccess, exports.createSessionFailure = _a.createSessionFailure, exports.createSessionAndSubmit = _a.createSessionAndSubmit, exports.initializeNewSessionState = _a.initializeNewSessionState, exports.createSessionAndSubmitSuccess = _a.createSessionAndSubmitSuccess, exports.createSessionAndSubmitFailure = _a.createSessionAndSubmitFailure, exports.fetchChatSessionsForUser = _a.fetchChatSessionsForUser, exports.fetchChatSessionsForUserSuccess = _a.fetchChatSessionsForUserSuccess, exports.fetchChatSessionsForUserFailure = _a.fetchChatSessionsForUserFailure, exports.fetchChatHistory = _a.fetchChatHistory, exports.fetchChatHistorySuccess = _a.fetchChatHistorySuccess, exports.fetchChatHistoryFailure = _a.fetchChatHistoryFailure, exports.clearCurrentSessionId = _a.clearCurrentSessionId, exports.clearLastContextMessage = _a.clearLastContextMessage, exports.stopSubmit = _a.stopSubmit, exports.stopSubmitSuccess = _a.stopSubmitSuccess, exports.stopSubmitFailure = _a.stopSubmitFailure, exports.stopSubmitQuestion = _a.stopSubmitQuestion, exports.stopSubmitQuestionSuccess = _a.stopSubmitQuestionSuccess, exports.stopSubmitQuestionFailure = _a.stopSubmitQuestionFailure, exports.acceptMasterTOS = _a.acceptMasterTOS, exports.acceptMasterTOSSuccess = _a.acceptMasterTOSSuccess, exports.acceptMasterTOSFailure = _a.acceptMasterTOSFailure, exports.deleteChatSession = _a.deleteChatSession, exports.deleteChatSessionSuccess = _a.deleteChatSessionSuccess, exports.deleteChatSessionFailure = _a.deleteChatSessionFailure, exports.clearDeleteChatSessionStatus = _a.clearDeleteChatSessionStatus, exports.updateCotCollapsedState = _a.updateCotCollapsedState, exports.fetchSuggestedQuestions = _a.fetchSuggestedQuestions, exports.fetchSuggestedQuestionsSuccess = _a.fetchSuggestedQuestionsSuccess, exports.fetchSuggestedQuestionsFailure = _a.fetchSuggestedQuestionsFailure, exports.fetchSkills = _a.fetchSkills, exports.fetchSkillsSuccess = _a.fetchSkillsSuccess, exports.fetchSkillsFailure = _a.fetchSkillsFailure, exports.applyAiCfoSidePanelHostPageTransition = _a.applyAiCfoSidePanelHostPageTransition, exports.clearAiCfoSidePanelHostPageContext = _a.clearAiCfoSidePanelHostPageContext;
921
+ _a = aiCfoView.actions, exports.submitQuestion = _a.submitQuestion, exports.submitQuestionSuccess = _a.submitQuestionSuccess, exports.submitQuestionFailure = _a.submitQuestionFailure, exports.updateResponseState = _a.updateResponseState, exports.updateAiCfoViewScrollPosition = _a.updateAiCfoViewScrollPosition, exports.updateCurrentInput = _a.updateCurrentInput, exports.submitFeedback = _a.submitFeedback, exports.submitFeedbackSuccess = _a.submitFeedbackSuccess, exports.submitFeedbackFailure = _a.submitFeedbackFailure, exports.setSession = _a.setSession, exports.createSession = _a.createSession, exports.createSessionSuccess = _a.createSessionSuccess, exports.createSessionFailure = _a.createSessionFailure, exports.createSessionAndSubmit = _a.createSessionAndSubmit, exports.initializeNewSessionState = _a.initializeNewSessionState, exports.createSessionAndSubmitSuccess = _a.createSessionAndSubmitSuccess, exports.createSessionAndSubmitFailure = _a.createSessionAndSubmitFailure, exports.fetchChatSessionsForUser = _a.fetchChatSessionsForUser, exports.fetchChatSessionsForUserSuccess = _a.fetchChatSessionsForUserSuccess, exports.fetchChatSessionsForUserFailure = _a.fetchChatSessionsForUserFailure, exports.fetchChatHistory = _a.fetchChatHistory, exports.fetchChatHistorySuccess = _a.fetchChatHistorySuccess, exports.fetchChatHistoryFailure = _a.fetchChatHistoryFailure, exports.clearCurrentSessionId = _a.clearCurrentSessionId, exports.clearLastContextMessage = _a.clearLastContextMessage, exports.stopSubmit = _a.stopSubmit, exports.stopSubmitSuccess = _a.stopSubmitSuccess, exports.stopSubmitFailure = _a.stopSubmitFailure, exports.stopSubmitQuestion = _a.stopSubmitQuestion, exports.stopSubmitQuestionSuccess = _a.stopSubmitQuestionSuccess, exports.stopSubmitQuestionFailure = _a.stopSubmitQuestionFailure, exports.acceptMasterTOS = _a.acceptMasterTOS, exports.acceptMasterTOSSuccess = _a.acceptMasterTOSSuccess, exports.acceptMasterTOSFailure = _a.acceptMasterTOSFailure, exports.deleteChatSession = _a.deleteChatSession, exports.deleteChatSessionSuccess = _a.deleteChatSessionSuccess, exports.deleteChatSessionFailure = _a.deleteChatSessionFailure, exports.clearDeleteChatSessionStatus = _a.clearDeleteChatSessionStatus, exports.updateCotCollapsedState = _a.updateCotCollapsedState, exports.fetchSuggestedQuestions = _a.fetchSuggestedQuestions, exports.fetchSuggestedQuestionsSuccess = _a.fetchSuggestedQuestionsSuccess, exports.fetchSuggestedQuestionsFailure = _a.fetchSuggestedQuestionsFailure, exports.fetchSkills = _a.fetchSkills, exports.fetchSkillsSuccess = _a.fetchSkillsSuccess, exports.fetchSkillsFailure = _a.fetchSkillsFailure, exports.fetchSkillDetail = _a.fetchSkillDetail, exports.fetchSkillDetailSuccess = _a.fetchSkillDetailSuccess, exports.fetchSkillDetailFailure = _a.fetchSkillDetailFailure, exports.clearSkillDetail = _a.clearSkillDetail, exports.clearSkillMutation = _a.clearSkillMutation, exports.deleteSkill = _a.deleteSkill, exports.deleteSkillSuccess = _a.deleteSkillSuccess, exports.deleteSkillFailure = _a.deleteSkillFailure, exports.fetchSkillVersionBody = _a.fetchSkillVersionBody, exports.fetchSkillVersionBodySuccess = _a.fetchSkillVersionBodySuccess, exports.fetchSkillVersionBodyFailure = _a.fetchSkillVersionBodyFailure, exports.fetchSkillVersions = _a.fetchSkillVersions, exports.fetchSkillVersionsSuccess = _a.fetchSkillVersionsSuccess, exports.fetchSkillVersionsFailure = _a.fetchSkillVersionsFailure, exports.saveSkill = _a.saveSkill, exports.saveSkillSuccess = _a.saveSkillSuccess, exports.saveSkillFailure = _a.saveSkillFailure, exports.setSkillVisibility = _a.setSkillVisibility, exports.setSkillVisibilitySuccess = _a.setSkillVisibilitySuccess, exports.setSkillVisibilityFailure = _a.setSkillVisibilityFailure, exports.openSkillsBrowser = _a.openSkillsBrowser, exports.closeSkillsBrowser = _a.closeSkillsBrowser, exports.applyAiCfoSidePanelHostPageTransition = _a.applyAiCfoSidePanelHostPageTransition, exports.clearAiCfoSidePanelHostPageContext = _a.clearAiCfoSidePanelHostPageContext;
519
922
  exports.default = aiCfoView.reducer;
@@ -4,7 +4,7 @@ import { FetchStateAndError } from '../../commonStateTypes/common';
4
4
  import { SelectorView } from '../../commonStateTypes/viewAndReport/viewAndReport';
5
5
  import { AiCfoSelectorView } from '../../entity/aiCfo/aiCfoSelector';
6
6
  import { RootState } from '../../rootStateTypes';
7
- import { AiCfoViewUIState, SkillsState, SuggestedQuestionsForPageContext } from './aiCfoViewState';
7
+ import { AiCfoViewUIState, SkillDetailState, SkillMutationState, SkillVersionBodyState, SkillVersionsState, SkillsState, SuggestedQuestionsForPageContext } from './aiCfoViewState';
8
8
  export interface AiCfoViewSelector extends SelectorView, AiCfoSelectorView {
9
9
  createSessionAndSubmitState: FetchStateAndError;
10
10
  createSessionState: FetchStateAndError;
@@ -24,3 +24,9 @@ export interface AiCfoViewSelector extends SelectorView, AiCfoSelectorView {
24
24
  export declare const getAiCfoView: (state: RootState) => AiCfoViewSelector;
25
25
  export declare function getSuggestedQuestionsForPageContext(state: RootState, pageContext: AiCfoSuggestedQuestionsPageContext): SuggestedQuestionsForPageContext | undefined;
26
26
  export declare function getSkills(state: RootState): SkillsState;
27
+ export declare function getSkillDetail(state: RootState): SkillDetailState;
28
+ export declare function getSkillVersions(state: RootState): SkillVersionsState;
29
+ export declare function getSkillVersionBody(state: RootState): SkillVersionBodyState;
30
+ export declare function getSkillMutation(state: RootState): SkillMutationState;
31
+ /** True only when the server says the feature is on for this tenant. */
32
+ export declare function getSkillsEnabled(state: RootState): boolean;
@@ -3,6 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getAiCfoView = void 0;
4
4
  exports.getSuggestedQuestionsForPageContext = getSuggestedQuestionsForPageContext;
5
5
  exports.getSkills = getSkills;
6
+ exports.getSkillDetail = getSkillDetail;
7
+ exports.getSkillVersions = getSkillVersions;
8
+ exports.getSkillVersionBody = getSkillVersionBody;
9
+ exports.getSkillMutation = getSkillMutation;
10
+ exports.getSkillsEnabled = getSkillsEnabled;
6
11
  const aiCfoReducer_1 = require("../../entity/aiCfo/aiCfoReducer");
7
12
  const aiCfoSelector_1 = require("../../entity/aiCfo/aiCfoSelector");
8
13
  const aiCfoViewReducer_1 = require("./aiCfoViewReducer");
@@ -35,7 +40,56 @@ const EMPTY_SKILLS_STATE = {
35
40
  fetchState: 'Not-Started',
36
41
  error: undefined,
37
42
  skills: [],
43
+ // false, so the Skills entry stays hidden until the server confirms the feature
44
+ // is on. Defaulting true would flash a menu at tenants who never get one.
45
+ skillsEnabled: false,
46
+ // false, for the same reason as skillsEnabled: withhold the publish control until the
47
+ // server says this user may use it.
48
+ canPublish: false,
49
+ deletedMacroIds: [],
50
+ };
51
+ const EMPTY_SKILL_DETAIL_STATE = {
52
+ fetchState: 'Not-Started',
53
+ error: undefined,
54
+ detail: undefined,
55
+ macroId: undefined,
56
+ };
57
+ // Same stable-fallback reason as the two above: a selector that built a fresh object
58
+ // each call would re-render every subscriber on every unrelated state change.
59
+ const EMPTY_SKILL_VERSIONS_STATE = {
60
+ fetchState: 'Not-Started',
61
+ error: undefined,
62
+ versions: [],
63
+ macroId: undefined,
64
+ };
65
+ const EMPTY_SKILL_VERSION_BODY_STATE = {
66
+ fetchState: 'Not-Started',
67
+ error: undefined,
68
+ instructions: undefined,
69
+ macroId: undefined,
70
+ version: undefined,
71
+ };
72
+ const EMPTY_SKILL_MUTATION_STATE = {
73
+ fetchState: 'Not-Started',
74
+ error: undefined,
75
+ savedMacroId: undefined,
38
76
  };
39
77
  function getSkills(state) {
40
78
  return state.aiCfoViewState?.skills ?? EMPTY_SKILLS_STATE;
41
79
  }
80
+ function getSkillDetail(state) {
81
+ return state.aiCfoViewState?.skillDetail ?? EMPTY_SKILL_DETAIL_STATE;
82
+ }
83
+ function getSkillVersions(state) {
84
+ return state.aiCfoViewState?.skillVersions ?? EMPTY_SKILL_VERSIONS_STATE;
85
+ }
86
+ function getSkillVersionBody(state) {
87
+ return (state.aiCfoViewState?.skillVersionBody ?? EMPTY_SKILL_VERSION_BODY_STATE);
88
+ }
89
+ function getSkillMutation(state) {
90
+ return state.aiCfoViewState?.skillMutation ?? EMPTY_SKILL_MUTATION_STATE;
91
+ }
92
+ /** True only when the server says the feature is on for this tenant. */
93
+ function getSkillsEnabled(state) {
94
+ return getSkills(state).skillsEnabled;
95
+ }