From 99a46487f5725aac47e0e7d7c4a3199882b264c8 Mon Sep 17 00:00:00 2001 From: Paul Lizer Date: Thu, 15 Oct 2026 10:20:36 -0500 Subject: [PATCH 1/2] Fix unrelated M365 warnings and preserve chat during session recovery Keep optional outgoing-action errors contextual and distinguish app-session expiry from provider auth and connectivity failures. Add bounded read-only verification and a shared recovery notice without replaying failed operations or discarding drafts. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- application/single_app/config.py | 2 +- .../components/chat/PendingActionSlots.tsx | 11 +- .../v2_ui/src/components/layout/AppShell.tsx | 2 + .../src/components/layout/SessionNotice.tsx | 74 +++++ application/v2_ui/src/lib/apiClient.ts | 120 +++++++- .../v2_ui/src/lib/workflowPlanReplay.ts | 6 +- .../v2_ui/src/stores/appSessionStore.ts | 37 +++ .../v2_ui/src/stores/bootstrapStore.ts | 18 +- .../src/stores/m365PendingActionsStore.ts | 25 +- .../features/V2_M365_PENDING_ACTION_CARDS.md | 8 +- ...M365_UNRELATED_CHAT_SESSION_WARNING_FIX.md | 74 +++++ docs/guides/getting-started.md | 15 + docs/reference/chat-controls.md | 23 +- .../test_v2_app_session_recovery.mjs | 270 ++++++++++++++++++ .../test_v2_bootstrap_refresh_logic.mjs | 6 +- .../test_v2_m365_pending_actions.py | 10 +- .../fixtures/orchestration/harness_entry.tsx | 13 + ui_tests/test_v2_m365_pending_action_cards.py | 49 +++- ui_tests/test_v2_workflow_plan_replay_card.py | 122 +++++++- 19 files changed, 856 insertions(+), 29 deletions(-) create mode 100644 application/v2_ui/src/components/layout/SessionNotice.tsx create mode 100644 application/v2_ui/src/stores/appSessionStore.ts create mode 100644 docs/explanation/fixes/M365_UNRELATED_CHAT_SESSION_WARNING_FIX.md create mode 100644 functional_tests/test_v2_app_session_recovery.mjs diff --git a/application/single_app/config.py b/application/single_app/config.py index 276949d40..028a6ea81 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -101,7 +101,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.261.337" +VERSION = "0.261.338" IS_DEVELOPMENT = is_development_env_enabled() # Opt-out for deployments where App Service Easy Auth is active but the platform diff --git a/application/v2_ui/src/components/chat/PendingActionSlots.tsx b/application/v2_ui/src/components/chat/PendingActionSlots.tsx index 5b01d248c..e6972a81d 100644 --- a/application/v2_ui/src/components/chat/PendingActionSlots.tsx +++ b/application/v2_ui/src/components/chat/PendingActionSlots.tsx @@ -34,6 +34,7 @@ import type { ChatMessage } from '../../lib/types'; import { useChatStore } from '../../stores/chatStore'; import { usePendingActions, usePendingActionsStoreApi } from '../../stores/m365PendingActionsStore'; import { useUiStore } from '../../stores/uiStore'; +import { SESSION_REQUIRED_MESSAGE } from '../../stores/appSessionStore'; import { Notice } from '../approvals/ApprovalParts'; import { PendingActionCard } from '../approvals/PendingActionCard'; import { GlassButton, GlassPanel } from '../ui/primitives'; @@ -282,6 +283,10 @@ export function PendingActionsConversationSection({ const continuationToken = usePendingActions((state) => state.continuationToken); const loadingMore = usePendingActions((state) => state.loadingMore); const listLoading = usePendingActions((state) => state.listStatus === 'loading'); + const hasActionContext = usePendingActions((state) => + Object.keys(state.entries).length > 0 || state.referenceLoading || Boolean(state.referenceError) || + state.focusRequest?.conversationId === state.conversationId); + const contextualListError = hasActionContext && listError !== SESSION_REQUIRED_MESSAGE ? listError : ''; usePendingActionsLifecycle(activeConversationId); const { unavailable, dismissUnavailable } = usePendingActionFocus(scrollRef, pauseFollowing); @@ -290,7 +295,7 @@ export function PendingActionsConversationSection({ // under its message. const showSection = !messagesLoading && - (orphanIds.length > 0 || Boolean(listError) || Boolean(referenceError) || Boolean(continuationToken)); + (orphanIds.length > 0 || Boolean(contextualListError) || Boolean(referenceError) || Boolean(continuationToken)); if (!unavailable && !showSection) { return null; } @@ -328,7 +333,9 @@ export function PendingActionsConversationSection({

- {listError && {listError}} + {contextualListError && ( + {contextualListError} + )} {referenceError && ( {referenceError} )} diff --git a/application/v2_ui/src/components/layout/AppShell.tsx b/application/v2_ui/src/components/layout/AppShell.tsx index 4475a065e..d4421d729 100644 --- a/application/v2_ui/src/components/layout/AppShell.tsx +++ b/application/v2_ui/src/components/layout/AppShell.tsx @@ -16,6 +16,7 @@ import { WorkflowAlertLiveRegion } from '../notifications/WorkflowAlertLiveRegio import { useBootstrapStore } from '../../stores/bootstrapStore'; import { useUiStore } from '../../stores/uiStore'; import { useCollaborationStore } from '../../stores/collaborationStore'; +import { SessionNotice } from './SessionNotice'; function ClassificationBanner() { const banner = useBootstrapStore((state) => state.data?.branding?.classification_banner); @@ -69,6 +70,7 @@ export function AppShell({ children }: { children: ReactNode }) {
+
{mobile && mobileNavOpen && (