@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
package/lib/epic.js CHANGED
@@ -53,10 +53,16 @@ const acceptMasterTOSEpic_1 = require("./view/aiCfoView/epics/acceptMasterTOSEpi
53
53
  const createSessionAndSubmitEpic_1 = require("./view/aiCfoView/epics/createSessionAndSubmitEpic");
54
54
  const createSessionEpic_1 = require("./view/aiCfoView/epics/createSessionEpic");
55
55
  const deleteChatSessionEpic_1 = require("./view/aiCfoView/epics/deleteChatSessionEpic");
56
+ const deleteSkillEpic_1 = require("./view/aiCfoView/epics/deleteSkillEpic");
56
57
  const fetchChatHistoryEpic_1 = require("./view/aiCfoView/epics/fetchChatHistoryEpic");
57
58
  const fetchChatSessionsForUserEpic_1 = require("./view/aiCfoView/epics/fetchChatSessionsForUserEpic");
59
+ const fetchSkillDetailEpic_1 = require("./view/aiCfoView/epics/fetchSkillDetailEpic");
60
+ const fetchSkillVersionBodyEpic_1 = require("./view/aiCfoView/epics/fetchSkillVersionBodyEpic");
61
+ const fetchSkillVersionsEpic_1 = require("./view/aiCfoView/epics/fetchSkillVersionsEpic");
58
62
  const fetchSkillsEpic_1 = require("./view/aiCfoView/epics/fetchSkillsEpic");
59
63
  const fetchSuggestedQuestionsEpic_1 = require("./view/aiCfoView/epics/fetchSuggestedQuestionsEpic");
64
+ const saveSkillEpic_1 = require("./view/aiCfoView/epics/saveSkillEpic");
65
+ const setSkillVisibilityEpic_1 = require("./view/aiCfoView/epics/setSkillVisibilityEpic");
60
66
  const stopSubmitEpic_1 = require("./view/aiCfoView/epics/stopSubmitEpic");
61
67
  const stopSubmitQuestionEpic_1 = require("./view/aiCfoView/epics/stopSubmitQuestionEpic");
62
68
  const submitFeedbackEpic_1 = require("./view/aiCfoView/epics/submitFeedbackEpic");
@@ -802,6 +808,7 @@ const ALL_EPICS = [
802
808
  deleteRemiEpic_1.deleteRemiEpic,
803
809
  deleteScheduleAccruedDetailEpic_1.deleteScheduleAccruedDetailEpic,
804
810
  deleteScheduleDetailEpic_1.deleteScheduleDetailEpic,
811
+ deleteSkillEpic_1.deleteSkillEpic,
805
812
  deleteTagEpic_1.deleteTagEpic,
806
813
  deleteTaskEpic_1.deleteTaskEpic,
807
814
  deleteTaskGroupEpic_1.deleteTaskGroupEpic,
@@ -1118,7 +1125,10 @@ const ALL_EPICS = [
1118
1125
  fetchScheduleDetailsPageEpic_1.fetchScheduleDetailsPageEpic,
1119
1126
  fetchScheduleListEpic_1.fetchScheduleListEpic,
1120
1127
  fetchSchedulesAccountEpic_1.fetchSchedulesAccountEpic,
1128
+ fetchSkillDetailEpic_1.fetchSkillDetailEpic,
1121
1129
  fetchSkillsEpic_1.fetchSkillsEpic,
1130
+ fetchSkillVersionBodyEpic_1.fetchSkillVersionBodyEpic,
1131
+ fetchSkillVersionsEpic_1.fetchSkillVersionsEpic,
1122
1132
  fetchSubscriptionAddOnsEpic_1.fetchSubscriptionAddOnsEpic,
1123
1133
  fetchSubscriptionCouponsEpic_1.fetchSubscriptionCouponsEpic,
1124
1134
  fetchSubscriptionCreateEstimateEpic_1.fetchSubscriptionCreateEstimateEpic,
@@ -1319,6 +1329,7 @@ const ALL_EPICS = [
1319
1329
  saveRemiSetupApproverViewUpdatesEpic_1.saveRemiSetupApproverViewUpdatesEpic,
1320
1330
  saveScheduleAccruedDetailsEpic_1.saveScheduleAccruedDetailsEpic,
1321
1331
  saveScheduleDetailsEpic_1.saveScheduleDetailsEpic,
1332
+ saveSkillEpic_1.saveSkillEpic,
1322
1333
  saveSubscriptionNotesUpdatesEpic_1.saveSubscriptionNotesUpdatesEpic,
1323
1334
  saveSubscriptionUpdatesEpic_1.saveSubscriptionUpdatesEpic,
1324
1335
  saveTaskDetailEpic_1.saveTaskDetailEpic,
@@ -1339,6 +1350,7 @@ const ALL_EPICS = [
1339
1350
  sendOtpEpic_1.sendOtpEpic,
1340
1351
  sendReferralInviteEpic_1.sendReferralInviteEpic,
1341
1352
  sessionHeartbeatEpic_1.sessionHeartbeatEpic,
1353
+ setSkillVisibilityEpic_1.setSkillVisibilityEpic,
1342
1354
  snoozeTaskEpic_1.snoozeTaskEpic,
1343
1355
  startInvoicingQboBackfillEpic_1.startInvoicingQboBackfillEpic,
1344
1356
  statementCloseDayEpic_1.statementCloseDayEpic,
package/lib/esm/epic.js CHANGED
@@ -50,10 +50,16 @@ import { acceptMasterTOSEpic, } from './view/aiCfoView/epics/acceptMasterTOSEpic
50
50
  import { createSessionAndSubmitEpic, } from './view/aiCfoView/epics/createSessionAndSubmitEpic';
51
51
  import { createSessionEpic, } from './view/aiCfoView/epics/createSessionEpic';
52
52
  import { deleteChatSessionEpic, } from './view/aiCfoView/epics/deleteChatSessionEpic';
53
+ import { deleteSkillEpic } from './view/aiCfoView/epics/deleteSkillEpic';
53
54
  import { fetchChatHistoryEpic, } from './view/aiCfoView/epics/fetchChatHistoryEpic';
54
55
  import { fetchChatSessionsForUserEpic, } from './view/aiCfoView/epics/fetchChatSessionsForUserEpic';
56
+ import { fetchSkillDetailEpic } from './view/aiCfoView/epics/fetchSkillDetailEpic';
57
+ import { fetchSkillVersionBodyEpic } from './view/aiCfoView/epics/fetchSkillVersionBodyEpic';
58
+ import { fetchSkillVersionsEpic } from './view/aiCfoView/epics/fetchSkillVersionsEpic';
55
59
  import { fetchSkillsEpic, } from './view/aiCfoView/epics/fetchSkillsEpic';
56
60
  import { fetchSuggestedQuestionsEpic, } from './view/aiCfoView/epics/fetchSuggestedQuestionsEpic';
61
+ import { saveSkillEpic } from './view/aiCfoView/epics/saveSkillEpic';
62
+ import { setSkillVisibilityEpic } from './view/aiCfoView/epics/setSkillVisibilityEpic';
57
63
  import { stopSubmitEpic, } from './view/aiCfoView/epics/stopSubmitEpic';
58
64
  import { stopSubmitQuestionEpic, } from './view/aiCfoView/epics/stopSubmitQuestionEpic';
59
65
  import { submitFeedbackEpic, } from './view/aiCfoView/epics/submitFeedbackEpic';
@@ -799,6 +805,7 @@ const ALL_EPICS = [
799
805
  deleteRemiEpic,
800
806
  deleteScheduleAccruedDetailEpic,
801
807
  deleteScheduleDetailEpic,
808
+ deleteSkillEpic,
802
809
  deleteTagEpic,
803
810
  deleteTaskEpic,
804
811
  deleteTaskGroupEpic,
@@ -1115,7 +1122,10 @@ const ALL_EPICS = [
1115
1122
  fetchScheduleDetailsPageEpic,
1116
1123
  fetchScheduleListEpic,
1117
1124
  fetchSchedulesAccountEpic,
1125
+ fetchSkillDetailEpic,
1118
1126
  fetchSkillsEpic,
1127
+ fetchSkillVersionBodyEpic,
1128
+ fetchSkillVersionsEpic,
1119
1129
  fetchSubscriptionAddOnsEpic,
1120
1130
  fetchSubscriptionCouponsEpic,
1121
1131
  fetchSubscriptionCreateEstimateEpic,
@@ -1316,6 +1326,7 @@ const ALL_EPICS = [
1316
1326
  saveRemiSetupApproverViewUpdatesEpic,
1317
1327
  saveScheduleAccruedDetailsEpic,
1318
1328
  saveScheduleDetailsEpic,
1329
+ saveSkillEpic,
1319
1330
  saveSubscriptionNotesUpdatesEpic,
1320
1331
  saveSubscriptionUpdatesEpic,
1321
1332
  saveTaskDetailEpic,
@@ -1336,6 +1347,7 @@ const ALL_EPICS = [
1336
1347
  sendOtpEpic,
1337
1348
  sendReferralInviteEpic,
1338
1349
  sessionHeartbeatEpic,
1350
+ setSkillVisibilityEpic,
1339
1351
  snoozeTaskEpic,
1340
1352
  startInvoicingQboBackfillEpic,
1341
1353
  statementCloseDayEpic,
package/lib/esm/index.js CHANGED
@@ -683,8 +683,8 @@ export { updateAiAccountantCustomers, updateAiAccountantCustomer, updateAiAccoun
683
683
  export { getAiAccountantCustomers, getAiAccountantJobsByTenantId, } from './entity/aiAccountantCustomer/aiAccountantCustomerSelector';
684
684
  export { cancelAiAccountantOnboarding, fetchAiAccountantCustomers, fetchAiAccountantJobs, setSelectedTenantIdsForJobTrigger, triggerAiAccountantJob, updateAiAccountantUIState, } from './view/aiAccountantView/aiAccountantViewReducer';
685
685
  export { getAiAccountantCockpitView, } from './view/aiAccountantView/aiAccountantViewSelector';
686
- export { fetchChatSessionsForUser, createSession, submitQuestion, updateAiCfoViewScrollPosition, updateCurrentInput, updateCotCollapsedState, setSession, clearCurrentSessionId, clearDeleteChatSessionStatus, clearLastContextMessage, createSessionAndSubmit, stopSubmit, stopSubmitQuestion, fetchChatHistory, acceptMasterTOS, deleteChatSession, submitFeedback, updateResponseState, fetchSuggestedQuestions, fetchSuggestedQuestionsSuccess, fetchSuggestedQuestionsFailure, fetchSkills, fetchSkillsSuccess, fetchSkillsFailure, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, } from './view/aiCfoView/aiCfoViewReducer';
687
- export { getAiCfoView, getSuggestedQuestionsForPageContext, getSkills, } from './view/aiCfoView/aiCfoViewSelector';
686
+ export { fetchChatSessionsForUser, createSession, submitQuestion, updateAiCfoViewScrollPosition, updateCurrentInput, updateCotCollapsedState, setSession, clearCurrentSessionId, clearDeleteChatSessionStatus, clearLastContextMessage, createSessionAndSubmit, stopSubmit, stopSubmitQuestion, fetchChatHistory, acceptMasterTOS, deleteChatSession, submitFeedback, updateResponseState, fetchSuggestedQuestions, fetchSuggestedQuestionsSuccess, fetchSuggestedQuestionsFailure, fetchSkills, fetchSkillsSuccess, fetchSkillsFailure, fetchSkillDetail, clearSkillDetail, clearSkillMutation, deleteSkill, fetchSkillVersionBody, fetchSkillVersions, saveSkill, setSkillVisibility, openSkillsBrowser, closeSkillsBrowser, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, } from './view/aiCfoView/aiCfoViewReducer';
687
+ export { getAiCfoView, getSuggestedQuestionsForPageContext, getSkills, getSkillDetail, getSkillMutation, getSkillVersionBody, getSkillVersions, getSkillsEnabled, } from './view/aiCfoView/aiCfoViewSelector';
688
688
  export { setNewSession, setSessions, updateAiCfoAnswerCardPolicyWizardPlan, upsertAnswerPayload, upsertOrAddQuestionAnswerPayload, addQuestionPayload, clearSession, toAiCfoExports, toAiCfoVisualization, appendSyntheticAiCfoAnswer, updateSyntheticAiCfoAnswer, removeSyntheticAiCfoAnswer, } from './entity/aiCfo/aiCfoReducer';
689
689
  export { getAllQuestionAnswersForChatSession, getQuestionAnswerByIdForChatSession, getAllQuestionsForChatSession, getAiCfoSelectorView, getSyntheticAiCfoAnswersForChatSession, getSyntheticAiCfoAnswerByQuestionAnswerId, } from './entity/aiCfo/aiCfoSelector';
690
690
  export { ALL_AI_CFO_CHARTS_TYPES, ALL_AI_CFO_VISUALIZATION_TYPES, ALL_AI_CFO_ANSWER_STATE_TYPES, ALL_AI_CFO_ANSWER_RESPONSE_TYPES, ALL_INTERACTIVE_FORM_TYPES, toAiCfoVisualizationType, toAiCfoVisualizationTypeStrict, toAiCfoChartType, toAiCfoChartTypeStrict, toAiCfoAnswerStateType, toAiCfoAnswerStateTypeStrict, toAiCfoAnswerResponseType, toAiCfoAnswerResponseTypeStrict, toInteractiveFormType, toInteractiveFormTypeStrict, ALL_SYNTHETIC_AI_CFO_ANSWER_KINDS, toMessageSender, toMessageType, } from './entity/aiCfo/aiCfoState';
@@ -48,8 +48,81 @@ export const initialAiCfoViewState = {
48
48
  responseStateByChatSessionId: {},
49
49
  stopSubmitStateByChatSessionId: {},
50
50
  suggestedQuestionsByPageContext: {},
51
- skills: { fetchState: 'Not-Started', error: undefined, skills: [] },
51
+ skillDetail: {
52
+ fetchState: 'Not-Started',
53
+ error: undefined,
54
+ detail: undefined,
55
+ macroId: undefined,
56
+ },
57
+ skillMutation: {
58
+ fetchState: 'Not-Started',
59
+ error: undefined,
60
+ savedMacroId: undefined,
61
+ },
62
+ skillVersionBody: {
63
+ fetchState: 'Not-Started',
64
+ error: undefined,
65
+ instructions: undefined,
66
+ macroId: undefined,
67
+ version: undefined,
68
+ },
69
+ skillVersions: {
70
+ fetchState: 'Not-Started',
71
+ error: undefined,
72
+ versions: [],
73
+ macroId: undefined,
74
+ },
75
+ skills: {
76
+ fetchState: 'Not-Started',
77
+ error: undefined,
78
+ deletedMacroIds: [],
79
+ skills: [],
80
+ skillsEnabled: false,
81
+ canPublish: false,
82
+ },
52
83
  };
84
+ /**
85
+ * Forget the skill that was open — prompt AND history together.
86
+ *
87
+ * Both, always. They are two slices describing one selection, so clearing only the
88
+ * prompt leaves `skillVersions.macroId` matching: an in-flight history response then
89
+ * passes its own stale-response guard and renders the previous skill's versions under
90
+ * whatever the user opened next.
91
+ */
92
+ function clearOpenSkillState(draft) {
93
+ draft.skillDetail = {
94
+ fetchState: 'Not-Started',
95
+ error: undefined,
96
+ detail: undefined,
97
+ macroId: undefined,
98
+ };
99
+ draft.skillVersions = {
100
+ fetchState: 'Not-Started',
101
+ error: undefined,
102
+ versions: [],
103
+ macroId: undefined,
104
+ };
105
+ // Part of the same selection: a pinned body left behind would seed a restore form for
106
+ // a skill the user has left.
107
+ draft.skillVersionBody = {
108
+ fetchState: 'Not-Started',
109
+ error: undefined,
110
+ instructions: undefined,
111
+ macroId: undefined,
112
+ version: undefined,
113
+ };
114
+ }
115
+ /**
116
+ * Take the Skills browser down and drop the prompt it had open.
117
+ *
118
+ * Both halves together, always. Closing without clearing leaves the next open landing on
119
+ * the previous skill instead of the list, and leaves a `macroId` that makes an in-flight
120
+ * detail response look current to the stale-response guard.
121
+ */
122
+ function closeSkillsBrowserState(draft) {
123
+ draft.uiState.isSkillsBrowserOpen = false;
124
+ clearOpenSkillState(draft);
125
+ }
53
126
  const aiCfoView = createSlice({
54
127
  name: 'aiCfoView',
55
128
  initialState: initialAiCfoViewState,
@@ -57,6 +130,16 @@ const aiCfoView = createSlice({
57
130
  submitQuestion: {
58
131
  reducer(draft, action) {
59
132
  const { sessionId } = action.payload;
133
+ // A turn is being sent, so the chat body has to be what the user sees. Doing this
134
+ // here rather than at each call site is deliberate: the composer, the sample
135
+ // chips, Run, and the `/` menu all submit, and every one of them was a separate
136
+ // chance to forget and leave the answer hidden behind the browser.
137
+ //
138
+ // Guarded so a submit with the browser already closed — i.e. almost every submit
139
+ // — does not produce a new `uiState` for every subscriber to re-render on.
140
+ if (draft.uiState.isSkillsBrowserOpen === true) {
141
+ closeSkillsBrowserState(draft);
142
+ }
60
143
  draft.responseStateByChatSessionId[sessionId] = {
61
144
  thought: { fetchState: 'In-Progress', error: undefined },
62
145
  analysis: { fetchState: 'In-Progress', error: undefined },
@@ -183,6 +266,13 @@ const aiCfoView = createSlice({
183
266
  setSession(draft, action) {
184
267
  draft.uiState.currentChatSessionId = action.payload.sessionId;
185
268
  draft.uiState.agentId = action.payload.agentId;
269
+ // Picking a session means wanting to see that chat, so the browser cannot stay over
270
+ // it. In the reducer because there are six dispatch sites across three separate
271
+ // rails, and covering them one at a time missed half of them.
272
+ if (action.payload.isRestore !== true &&
273
+ draft.uiState.isSkillsBrowserOpen === true) {
274
+ closeSkillsBrowserState(draft);
275
+ }
186
276
  mirrorCurrentSessionToPageKeyMapIfActive(draft);
187
277
  },
188
278
  createSession: {
@@ -202,6 +292,12 @@ const aiCfoView = createSlice({
202
292
  draft.uiState.agentId = agentId;
203
293
  draft.createSessionState.fetchState = 'Completed';
204
294
  draft.createSessionState.error = undefined;
295
+ // Same invariant as submitQuestion and initializeNewSessionState: a new chat exists
296
+ // and is now current, so the browser cannot stay over it. This is the fifth entry
297
+ // point to that rule — the others are submit, new-session, New Chat and setSession.
298
+ if (draft.uiState.isSkillsBrowserOpen === true) {
299
+ closeSkillsBrowserState(draft);
300
+ }
205
301
  draft.uiState.currentChatSessionId = sessionId;
206
302
  draft.uiState.lastContextMessage = contextMessage;
207
303
  draft.uiState.lastContextStatus = contextStatus;
@@ -243,6 +339,10 @@ const aiCfoView = createSlice({
243
339
  },
244
340
  initializeNewSessionState(draft, action) {
245
341
  const { agentId, sessionId } = action.payload;
342
+ // Same invariant as submitQuestion — this is the new-session half of submitting.
343
+ if (draft.uiState.isSkillsBrowserOpen === true) {
344
+ closeSkillsBrowserState(draft);
345
+ }
246
346
  draft.uiState.currentChatSessionId = sessionId;
247
347
  draft.uiState.agentId = agentId;
248
348
  draft.uiState.chatHistoryNextPageToken[sessionId] = {
@@ -363,6 +463,10 @@ const aiCfoView = createSlice({
363
463
  };
364
464
  draft.allChatSessionIds = draft.allChatSessionIds.filter((id) => id !== chatSessionId);
365
465
  if (draft.uiState.currentChatSessionId === chatSessionId) {
466
+ // Deliberately does NOT close the Skills browser, unlike the other writers of
467
+ // this field. Deleting a chat from the history list is housekeeping: if the
468
+ // browser is open the user is not looking at that chat, and closing it would
469
+ // yank them out of what they were doing to reveal an empty chat area.
366
470
  draft.uiState.currentChatSessionId = undefined;
367
471
  draft.uiState.agentId = undefined;
368
472
  }
@@ -415,6 +519,10 @@ const aiCfoView = createSlice({
415
519
  },
416
520
  },
417
521
  clearCurrentSessionId(draft) {
522
+ // New Chat: the empty composer is the point, so the browser cannot stay over it.
523
+ if (draft.uiState.isSkillsBrowserOpen === true) {
524
+ closeSkillsBrowserState(draft);
525
+ }
418
526
  draft.uiState.currentChatSessionId = undefined;
419
527
  const key = draft.uiState.aiCfoSidePanelHostPageKey;
420
528
  if (key != null) {
@@ -431,8 +539,24 @@ const aiCfoView = createSlice({
431
539
  draft.uiState.chatSessionIdByPageKey[prevKey] = curId;
432
540
  }
433
541
  draft.uiState.aiCfoSidePanelHostPageKey = newAiCfoSidePanelHostPageKey;
434
- draft.uiState.currentChatSessionId =
435
- nextCurrentChatSessionId != null ? nextCurrentChatSessionId : undefined;
542
+ const nextId = nextCurrentChatSessionId != null ? nextCurrentChatSessionId : undefined;
543
+ // The sixth entry point to the same invariant as submitQuestion,
544
+ // initializeNewSessionState, clearCurrentSessionId, setSession and
545
+ // createSessionSuccess: a different chat is now visible, so the browser cannot stay
546
+ // over it. Missing here, this path swapped the session underneath an open browser
547
+ // and left its macroId looking current to the stale-response guards.
548
+ //
549
+ // Only when the session actually CHANGES, and only when there WAS one. A host page
550
+ // resolving back to the same chat has not changed what is on screen. And
551
+ // `undefined` becoming a saved id is a hydrate, not a navigation — the same
552
+ // settle/restore race `setSession` exempts via `isRestore`: opening Skills on a
553
+ // host page whose chat has not resolved yet would otherwise close the browser
554
+ // under the user the moment the sessions list arrives.
555
+ const hadASession = draft.uiState.currentChatSessionId != null;
556
+ if (hadASession && nextId !== draft.uiState.currentChatSessionId) {
557
+ closeSkillsBrowserState(draft);
558
+ }
559
+ draft.uiState.currentChatSessionId = nextId;
436
560
  draft.uiState.currentInput = '';
437
561
  mirrorCurrentSessionToPageKeyMapIfActive(draft);
438
562
  },
@@ -477,28 +601,306 @@ const aiCfoView = createSlice({
477
601
  };
478
602
  },
479
603
  fetchSkills(draft) {
480
- // Optional-chain: persisted/rehydrated state from before this slice existed
481
- // may have no `skills`, so don't assume draft.skills is set.
604
+ // Read straight off `draft.skills` — no optional chain. This store is never
605
+ // persisted or rehydrated (no redux-persist anywhere in the repo, and no
606
+ // `preloadedState` in any consumer), and `initialAiCfoViewState` gives every skills
607
+ // slice a concrete value, so an undefined slice is unreachable here rather than
608
+ // merely unlikely. An earlier version of this comment claimed the opposite and
609
+ // justified a guard by a mechanism that does not exist — which then got applied to
610
+ // some accesses and not others, so the file read as if the slice were sometimes
611
+ // missing and sometimes not. The one guard that IS meaningful lives in the
612
+ // selectors, where `state.aiCfoViewState` may genuinely be absent for a host that
613
+ // composes a partial root reducer; that is a boundary, and this is not.
482
614
  draft.skills = {
483
615
  fetchState: 'In-Progress',
484
616
  error: undefined,
485
- skills: draft.skills?.skills ?? [],
617
+ skills: draft.skills.skills,
618
+ skillsEnabled: draft.skills.skillsEnabled,
619
+ // Carried for the same reason as skillsEnabled: a refetch in flight is not a
620
+ // reason to hide the publish control the user was just looking at.
621
+ canPublish: draft.skills.canPublish,
622
+ // Carried, not cleared: the response this is about to fetch may still contain a
623
+ // skill deleted moments ago, which is the whole reason the list is kept.
624
+ deletedMacroIds: draft.skills.deletedMacroIds,
486
625
  };
487
626
  },
488
627
  fetchSkillsSuccess(draft, action) {
628
+ // A list response can predate a delete — a save emits fetchSkills, and a delete can
629
+ // land while that is still outstanding — so replacing the array wholesale would put
630
+ // the deleted row back and read as the delete having failed.
489
631
  draft.skills = {
490
632
  fetchState: 'Completed',
491
633
  error: undefined,
492
- skills: action.payload.skills,
634
+ skills: action.payload.skills.filter((skill) => !draft.skills.deletedMacroIds.includes(skill.macroId)),
635
+ skillsEnabled: action.payload.skillsEnabled,
636
+ canPublish: action.payload.canPublish,
637
+ // Applied ONCE, then forgotten — the tombstone covers a race window, not a
638
+ // permanent state.
639
+ //
640
+ // The previous rule (keep it until a response omits that skill) was wrong in the
641
+ // case it was written for: nothing refetches after a delete, and a create emits
642
+ // fetchSkills whose response DOES contain the id, so the tombstone was kept and
643
+ // reapplied forever. A skill recreated under the same id stayed invisible with no
644
+ // way to clear it.
645
+ deletedMacroIds: [],
493
646
  };
494
647
  },
495
648
  fetchSkillsFailure(draft, action) {
496
649
  draft.skills = {
497
650
  fetchState: 'Error',
498
651
  error: action.payload,
499
- skills: draft.skills?.skills ?? [],
652
+ skills: draft.skills.skills,
653
+ // Kept, not reset: a failed refresh is not evidence the feature was turned
654
+ // off, and dropping it to false would make the Skills entry vanish on any
655
+ // transient error.
656
+ skillsEnabled: draft.skills.skillsEnabled,
657
+ // Same reasoning again: a transient error must not revoke a privilege the server
658
+ // already granted, or the control flickers away on every failed refresh.
659
+ canPublish: draft.skills.canPublish,
660
+ // Same reasoning: a failed refresh is not confirmation the delete was undone.
661
+ deletedMacroIds: draft.skills.deletedMacroIds,
662
+ };
663
+ },
664
+ fetchSkillDetail(draft, action) {
665
+ // Opening a skill IS the selection change, so forget the previous one whole —
666
+ // history included. Resetting only the prompt left `skillVersions.macroId` pointing
667
+ // at the skill just navigated away from, so a history response still in flight for
668
+ // it passed its own guard and rendered under the new title. Close, back and delete
669
+ // all go through the same helper for the same reason.
670
+ clearOpenSkillState(draft);
671
+ draft.skillDetail = {
672
+ fetchState: 'In-Progress',
673
+ error: undefined,
674
+ // Cleared rather than kept: holding the previous skill's prompt while a new
675
+ // one loads would show the wrong instructions under the right title.
676
+ detail: undefined,
677
+ macroId: action.payload.macroId,
678
+ };
679
+ },
680
+ fetchSkillDetailSuccess(draft, action) {
681
+ // Ignore a response for a skill the user has already navigated away from.
682
+ if (draft.skillDetail.macroId !== action.payload.macroId) {
683
+ return;
684
+ }
685
+ draft.skillDetail = {
686
+ fetchState: 'Completed',
687
+ error: undefined,
688
+ detail: action.payload.detail,
689
+ macroId: action.payload.macroId,
690
+ };
691
+ },
692
+ fetchSkillDetailFailure(draft, action) {
693
+ if (draft.skillDetail.macroId !== action.payload.macroId) {
694
+ return;
695
+ }
696
+ draft.skillDetail = {
697
+ fetchState: 'Error',
698
+ error: action.payload.error,
699
+ detail: undefined,
700
+ macroId: action.payload.macroId,
500
701
  };
501
702
  },
703
+ clearSkillDetail(draft) {
704
+ // Back to the list. History belongs to the detail view, so it goes too.
705
+ clearOpenSkillState(draft);
706
+ },
707
+ fetchSkillVersions: {
708
+ reducer(draft, action) {
709
+ draft.skillVersions = {
710
+ fetchState: 'In-Progress',
711
+ error: undefined,
712
+ // Cleared, not kept: showing the previous skill's history under a new title
713
+ // is worse than showing none while this loads.
714
+ versions: [],
715
+ macroId: action.payload.macroId,
716
+ };
717
+ },
718
+ prepare(payload) {
719
+ return { payload };
720
+ },
721
+ },
722
+ fetchSkillVersionsSuccess(draft, action) {
723
+ // Same stale-response guard as skillDetail: a switchMap cancels the request but
724
+ // cannot un-dispatch one already in flight.
725
+ if (draft.skillVersions.macroId !== action.payload.macroId) {
726
+ return;
727
+ }
728
+ draft.skillVersions.fetchState = 'Completed';
729
+ draft.skillVersions.error = undefined;
730
+ draft.skillVersions.versions = action.payload.versions;
731
+ },
732
+ fetchSkillVersionsFailure(draft, action) {
733
+ if (draft.skillVersions.macroId !== action.payload.macroId) {
734
+ return;
735
+ }
736
+ draft.skillVersions.fetchState = 'Error';
737
+ draft.skillVersions.error = action.payload.error;
738
+ },
739
+ fetchSkillVersionBody: {
740
+ reducer(draft, action) {
741
+ draft.skillVersionBody = {
742
+ fetchState: 'In-Progress',
743
+ error: undefined,
744
+ // Cleared: seeding a restore form from the PREVIOUS version's body is the same
745
+ // class of mistake as showing one skill's prompt under another's title.
746
+ instructions: undefined,
747
+ macroId: action.payload.macroId,
748
+ version: action.payload.version,
749
+ };
750
+ },
751
+ prepare(payload) {
752
+ return { payload };
753
+ },
754
+ },
755
+ fetchSkillVersionBodySuccess(draft, action) {
756
+ // Both parts of the key: a user can jump between versions of the same skill, so
757
+ // macroId alone would let an earlier version's body land on a later request.
758
+ if (draft.skillVersionBody.macroId !== action.payload.macroId ||
759
+ draft.skillVersionBody.version !== action.payload.version) {
760
+ return;
761
+ }
762
+ draft.skillVersionBody.fetchState = 'Completed';
763
+ draft.skillVersionBody.error = undefined;
764
+ draft.skillVersionBody.instructions = action.payload.instructions;
765
+ },
766
+ fetchSkillVersionBodyFailure(draft, action) {
767
+ if (draft.skillVersionBody.macroId !== action.payload.macroId ||
768
+ draft.skillVersionBody.version !== action.payload.version) {
769
+ return;
770
+ }
771
+ draft.skillVersionBody.fetchState = 'Error';
772
+ draft.skillVersionBody.error = action.payload.error;
773
+ },
774
+ saveSkill: {
775
+ reducer(draft) {
776
+ draft.skillMutation = {
777
+ fetchState: 'In-Progress',
778
+ error: undefined,
779
+ savedMacroId: undefined,
780
+ };
781
+ },
782
+ prepare(payload) {
783
+ return { payload };
784
+ },
785
+ },
786
+ saveSkillSuccess(draft, action) {
787
+ draft.skillMutation.fetchState = 'Completed';
788
+ draft.skillMutation.error = undefined;
789
+ draft.skillMutation.savedMacroId = action.payload.macroId;
790
+ // ONLY when the saved skill is still the one on screen. The save is a mergeMap —
791
+ // deliberately not cancellable, because cancelling a write only hides its outcome —
792
+ // so it can land after the user opened another skill, went back to the list, closed
793
+ // the browser, or deleted it. Writing unconditionally would drop one skill's prompt
794
+ // on top of another's, which is precisely what the fetch guards exist to prevent.
795
+ //
796
+ // A create lands here with nothing open, and is deliberately NOT force-opened:
797
+ // `savedMacroId` is the signal for "what did I just create", and the caller decides
798
+ // whether to open it.
799
+ if (draft.skillDetail.macroId !== action.payload.macroId) {
800
+ return;
801
+ }
802
+ // The save response IS a detail read, so adopt it. Discarding it left an editor
803
+ // sitting on the same skill showing the prompt and card it had BEFORE the save —
804
+ // the user's own change missing from the thing they just saved.
805
+ draft.skillDetail = {
806
+ fetchState: 'Completed',
807
+ error: undefined,
808
+ detail: action.payload.detail,
809
+ macroId: action.payload.macroId,
810
+ };
811
+ // History is now a version behind; the epic refetches it.
812
+ draft.skillVersions = {
813
+ fetchState: 'Not-Started',
814
+ error: undefined,
815
+ versions: [],
816
+ macroId: undefined,
817
+ };
818
+ },
819
+ saveSkillFailure(draft, action) {
820
+ draft.skillMutation.fetchState = 'Error';
821
+ draft.skillMutation.error = action.payload;
822
+ },
823
+ setSkillVisibility: {
824
+ reducer(draft) {
825
+ // Shares skillMutation with save and delete. One in-flight write at a time is the
826
+ // existing contract here, and publishing is a write like any other.
827
+ draft.skillMutation = {
828
+ fetchState: 'In-Progress',
829
+ error: undefined,
830
+ savedMacroId: undefined,
831
+ };
832
+ },
833
+ prepare(payload) {
834
+ return { payload };
835
+ },
836
+ },
837
+ setSkillVisibilitySuccess(draft, action) {
838
+ draft.skillMutation.fetchState = 'Completed';
839
+ draft.skillMutation.error = undefined;
840
+ draft.skillMutation.savedMacroId = action.payload.macroId;
841
+ // Same staleness guard as saveSkillSuccess, for the same reason: this is a mergeMap
842
+ // write, so it can land after the user opened another skill.
843
+ if (draft.skillDetail.macroId !== action.payload.macroId) {
844
+ return;
845
+ }
846
+ draft.skillDetail = {
847
+ fetchState: 'Completed',
848
+ error: undefined,
849
+ detail: action.payload.detail,
850
+ macroId: action.payload.macroId,
851
+ };
852
+ // Deliberately NOT clearing skillVersions, unlike saveSkillSuccess. No version was
853
+ // created, so the history the user is looking at is still correct — refetching it
854
+ // would flash a loading state for a list that did not change.
855
+ },
856
+ setSkillVisibilityFailure(draft, action) {
857
+ draft.skillMutation.fetchState = 'Error';
858
+ draft.skillMutation.error = action.payload;
859
+ },
860
+ deleteSkill: {
861
+ reducer(draft) {
862
+ draft.skillMutation = {
863
+ fetchState: 'In-Progress',
864
+ error: undefined,
865
+ savedMacroId: undefined,
866
+ };
867
+ },
868
+ prepare(payload) {
869
+ return { payload };
870
+ },
871
+ },
872
+ deleteSkillSuccess(draft, action) {
873
+ draft.skillMutation.fetchState = 'Completed';
874
+ draft.skillMutation.error = undefined;
875
+ // Drop it from the list immediately rather than waiting for a refetch: the row
876
+ // the user just deleted staying on screen reads as the delete having failed.
877
+ draft.skills.skills = draft.skills.skills.filter((skill) => skill.macroId !== action.payload.macroId);
878
+ if (!draft.skills.deletedMacroIds.includes(action.payload.macroId)) {
879
+ draft.skills.deletedMacroIds.push(action.payload.macroId);
880
+ }
881
+ if (draft.skillDetail.macroId === action.payload.macroId) {
882
+ clearOpenSkillState(draft);
883
+ }
884
+ },
885
+ deleteSkillFailure(draft, action) {
886
+ draft.skillMutation.fetchState = 'Error';
887
+ draft.skillMutation.error = action.payload;
888
+ },
889
+ clearSkillMutation(draft) {
890
+ draft.skillMutation = {
891
+ fetchState: 'Not-Started',
892
+ error: undefined,
893
+ savedMacroId: undefined,
894
+ };
895
+ },
896
+ openSkillsBrowser(draft) {
897
+ draft.uiState.isSkillsBrowserOpen = true;
898
+ },
899
+ closeSkillsBrowser(draft) {
900
+ // Unguarded, unlike the implicit closes: an explicit close should clear a lingering
901
+ // detail even if the flag is already false.
902
+ closeSkillsBrowserState(draft);
903
+ },
502
904
  updateCotCollapsedState(draft, action) {
503
905
  const { isCollapsed, questionAnswerId } = action.payload;
504
906
  draft.uiState.cotCollapsedByQuestionAnswerId[questionAnswerId] =
@@ -511,5 +913,5 @@ const aiCfoView = createSlice({
511
913
  });
512
914
  },
513
915
  });
514
- export const { submitQuestion, submitQuestionSuccess, submitQuestionFailure, updateResponseState, updateAiCfoViewScrollPosition, updateCurrentInput, submitFeedback, submitFeedbackSuccess, submitFeedbackFailure, setSession, createSession, createSessionSuccess, createSessionFailure, createSessionAndSubmit, initializeNewSessionState, createSessionAndSubmitSuccess, createSessionAndSubmitFailure, fetchChatSessionsForUser, fetchChatSessionsForUserSuccess, fetchChatSessionsForUserFailure, fetchChatHistory, fetchChatHistorySuccess, fetchChatHistoryFailure, clearCurrentSessionId, clearLastContextMessage, stopSubmit, stopSubmitSuccess, stopSubmitFailure, stopSubmitQuestion, stopSubmitQuestionSuccess, stopSubmitQuestionFailure, acceptMasterTOS, acceptMasterTOSSuccess, acceptMasterTOSFailure, deleteChatSession, deleteChatSessionSuccess, deleteChatSessionFailure, clearDeleteChatSessionStatus, updateCotCollapsedState, fetchSuggestedQuestions, fetchSuggestedQuestionsSuccess, fetchSuggestedQuestionsFailure, fetchSkills, fetchSkillsSuccess, fetchSkillsFailure, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, } = aiCfoView.actions;
916
+ export const { submitQuestion, submitQuestionSuccess, submitQuestionFailure, updateResponseState, updateAiCfoViewScrollPosition, updateCurrentInput, submitFeedback, submitFeedbackSuccess, submitFeedbackFailure, setSession, createSession, createSessionSuccess, createSessionFailure, createSessionAndSubmit, initializeNewSessionState, createSessionAndSubmitSuccess, createSessionAndSubmitFailure, fetchChatSessionsForUser, fetchChatSessionsForUserSuccess, fetchChatSessionsForUserFailure, fetchChatHistory, fetchChatHistorySuccess, fetchChatHistoryFailure, clearCurrentSessionId, clearLastContextMessage, stopSubmit, stopSubmitSuccess, stopSubmitFailure, stopSubmitQuestion, stopSubmitQuestionSuccess, stopSubmitQuestionFailure, acceptMasterTOS, acceptMasterTOSSuccess, acceptMasterTOSFailure, deleteChatSession, deleteChatSessionSuccess, deleteChatSessionFailure, clearDeleteChatSessionStatus, updateCotCollapsedState, fetchSuggestedQuestions, fetchSuggestedQuestionsSuccess, fetchSuggestedQuestionsFailure, fetchSkills, fetchSkillsSuccess, fetchSkillsFailure, fetchSkillDetail, fetchSkillDetailSuccess, fetchSkillDetailFailure, clearSkillDetail, clearSkillMutation, deleteSkill, deleteSkillSuccess, deleteSkillFailure, fetchSkillVersionBody, fetchSkillVersionBodySuccess, fetchSkillVersionBodyFailure, fetchSkillVersions, fetchSkillVersionsSuccess, fetchSkillVersionsFailure, saveSkill, saveSkillSuccess, saveSkillFailure, setSkillVisibility, setSkillVisibilitySuccess, setSkillVisibilityFailure, openSkillsBrowser, closeSkillsBrowser, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, } = aiCfoView.actions;
515
917
  export default aiCfoView.reducer;