Skip to content

Commit 6e744e7

Browse files
Merge pull request #985 from microsoft/psl-fix-43544
fix: Add toaster message of failure in logs
2 parents 4450802 + 723cb0c commit 6e744e7

6 files changed

Lines changed: 114 additions & 24 deletions

File tree

src/App/src/components/content/streaming/StreamingAgentMessage.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,13 @@ const renderAgentMessages = (
176176
<Body1 className={styles.agentName}>
177177
{getAgentDisplayName(msg.agent)}
178178
</Body1>
179-
<Tag
180-
appearance="brand"
181-
>
182-
AI Agent
183-
</Tag>
179+
{msg.agent_type !== AgentMessageType.SYSTEM_AGENT && msg.agent?.toLowerCase() !== 'system' && (
180+
<Tag
181+
appearance="brand"
182+
>
183+
AI Agent
184+
</Tag>
185+
)}
184186
</div>
185187
)}
186188

src/App/src/hooks/usePlanWebSocket.tsx

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
selectPlanApproved,
1818
approvalRequestReceived,
1919
planCompletedFinal,
20+
planFailedFinal,
2021
} from '@/store/slices/planSlice';
2122
import {
2223
setSubmittingChatDisableInput,
@@ -178,16 +179,18 @@ export function usePlanWebSocket({
178179
WebsocketMessageType.FINAL_RESULT_MESSAGE,
179180
(finalMessage: any) => {
180181
if (!finalMessage) return;
181-
const agentMessageData: AgentMessageData = {
182-
agent: AgentType.GROUP_CHAT_MANAGER,
183-
agent_type: AgentMessageType.AI_AGENT,
184-
timestamp: Date.now(),
185-
steps: [],
186-
next_steps: [],
187-
content: '\u{1F389}\u{1F389} ' + (finalMessage.data?.content || ''),
188-
raw_data: finalMessage,
189-
};
190-
if (finalMessage?.data?.status === PlanStatus.COMPLETED) {
182+
const messageStatus = finalMessage?.data?.status;
183+
184+
if (messageStatus === PlanStatus.COMPLETED) {
185+
const agentMessageData: AgentMessageData = {
186+
agent: AgentType.GROUP_CHAT_MANAGER,
187+
agent_type: AgentMessageType.AI_AGENT,
188+
timestamp: Date.now(),
189+
steps: [],
190+
next_steps: [],
191+
content: '\u{1F389}\u{1F389} ' + (finalMessage.data?.content || ''),
192+
raw_data: finalMessage,
193+
};
191194
dispatch(setShowBufferingText(true));
192195
dispatch(addAgentMessage(agentMessageData));
193196
dispatch(setSelectedTeam(planData?.team || null));
@@ -196,11 +199,29 @@ export function usePlanWebSocket({
196199
scrollToBottom();
197200
webSocketService.disconnect();
198201
persistAgentMessage(agentMessageData, planData, dispatch, true, streamingMessageBuffer);
202+
} else if (messageStatus === 'error') {
203+
// Safety net: handle error status sent as FINAL_RESULT_MESSAGE
204+
const errorContent = finalMessage.data?.content || 'An unexpected error occurred. Please try again later.';
205+
const errorAgent: AgentMessageData = {
206+
agent: 'system',
207+
agent_type: AgentMessageType.SYSTEM_AGENT,
208+
timestamp: Date.now(),
209+
steps: [],
210+
next_steps: [],
211+
content: formatErrorMessage(errorContent),
212+
raw_data: finalMessage || '',
213+
};
214+
dispatch(addAgentMessage(errorAgent));
215+
dispatch(planFailedFinal());
216+
dispatch(setShowBufferingText(false));
217+
scrollToBottom();
218+
showToast(errorContent, 'error');
219+
webSocketService.disconnect();
199220
}
200221
},
201222
);
202223
return unsub;
203-
}, [dispatch, scrollToBottom, planData, streamingMessageBuffer]);
224+
}, [dispatch, scrollToBottom, planData, streamingMessageBuffer, formatErrorMessage, showToast]);
204225

205226
// ── ERROR_MESSAGE ─────────────────────────────────────────────
206227
useEffect(() => {
@@ -231,11 +252,11 @@ export function usePlanWebSocket({
231252
raw_data: errorMessage || '',
232253
};
233254
dispatch(addAgentMessage(errorAgent));
234-
dispatch(setShowProcessingPlanSpinner(false));
255+
dispatch(planFailedFinal());
235256
dispatch(setShowBufferingText(false));
236-
dispatch(setSubmittingChatDisableInput(false));
237257
scrollToBottom();
238258
showToast(errorContent, 'error');
259+
webSocketService.disconnect();
239260
},
240261
);
241262
return unsub;

src/App/src/store/slices/planSlice.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ const planSlice = createSlice({
161161
}
162162
},
163163

164+
/** Single dispatch when an error occurs during plan execution */
165+
planFailedFinal(state) {
166+
state.showProcessingPlanSpinner = false;
167+
if (state.planData?.plan) {
168+
(state.planData as any).plan.overall_status = PlanStatus.FAILED;
169+
}
170+
},
171+
164172
/** Reset everything back to initial state (used when navigating to a new plan) */
165173
resetPlan() {
166174
return { ...initialState };
@@ -229,6 +237,7 @@ export const {
229237
planApprovalRejected,
230238
approvalRequestReceived,
231239
planCompletedFinal,
240+
planFailedFinal,
232241
resetPlan,
233242
} = planSlice.actions;
234243

src/backend/v4/api/router.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,20 @@ async def process_request(
371371
try:
372372

373373
async def run_orchestration_task():
374-
await OrchestrationManager().run_orchestration(user_id, input_task)
374+
try:
375+
await OrchestrationManager().run_orchestration(user_id, input_task, plan_id=plan_id)
376+
except Exception as orch_error:
377+
logger.error("Background orchestration failed for plan '%s': %s", plan_id, orch_error)
378+
track_event_if_configured(
379+
"Error_Orchestration_Failed",
380+
{
381+
"plan_id": plan_id,
382+
"session_id": input_task.session_id,
383+
"user_id": user_id,
384+
"error": str(orch_error),
385+
"error_type": type(orch_error).__name__,
386+
},
387+
)
375388

376389
background_tasks.add_task(run_orchestration_task)
377390

src/backend/v4/orchestration/orchestration_manager.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
from v4.models.messages import WebsocketMessageType
3939
from v4.orchestration.human_approval_manager import HumanApprovalMagenticManager
4040
from v4.magentic_agents.magentic_agent_factory import MagenticAgentFactory
41+
from common.database.database_factory import DatabaseFactory
42+
from v4.models.models import PlanStatus
4143

4244

4345
class OrchestrationManager:
@@ -47,6 +49,7 @@ class OrchestrationManager:
4749

4850
def __init__(self):
4951
self.user_id: Optional[str] = None
52+
self._plan_id: Optional[str] = None
5053
self.logger = self.__class__.logger
5154

5255
def _extract_response_text(self, data) -> str:
@@ -293,10 +296,11 @@ async def get_current_or_new_orchestration(
293296
# ---------------------------
294297
# Execution
295298
# ---------------------------
296-
async def run_orchestration(self, user_id: str, input_task) -> None:
299+
async def run_orchestration(self, user_id: str, input_task, plan_id: str = None) -> None:
297300
"""
298301
Execute the Magentic workflow for the provided user and task description.
299302
"""
303+
self._plan_id = plan_id
300304
job_id = str(uuid.uuid4())
301305
orchestration_config.set_approval_pending(job_id)
302306
self.logger.info(
@@ -545,19 +549,50 @@ async def run_orchestration(self, user_id: str, input_task) -> None:
545549
self.logger.error("Error attributes: %s", e.__dict__)
546550
self.logger.info("=" * 50)
547551

548-
# Send error status to user
552+
# Build a user-friendly error message
553+
error_str = str(e)
554+
if "Too Many Requests" in error_str or "429" in error_str:
555+
user_error_message = (
556+
"The service is currently experiencing high demand (rate limit exceeded). "
557+
"Please wait a moment and try again."
558+
)
559+
elif "timeout" in error_str.lower():
560+
user_error_message = (
561+
"The request timed out while processing. Please try again."
562+
)
563+
elif "conflict" in error_str.lower() or "modified concurrently" in error_str.lower():
564+
user_error_message = (
565+
"A conflict occurred while processing your request. "
566+
"The resource was modified by another operation. Please start a new task and try again."
567+
)
568+
else:
569+
user_error_message = "An error occurred while processing your request. Please start a new task and try again."
570+
571+
# Update plan status to failed in the database
572+
try:
573+
if self._plan_id:
574+
memory_store = await DatabaseFactory.get_database(user_id=user_id)
575+
plan = await memory_store.get_plan_by_plan_id(plan_id=self._plan_id)
576+
if plan:
577+
plan.overall_status = PlanStatus.FAILED
578+
await memory_store.update_plan(plan)
579+
self.logger.info("Plan '%s' status updated to FAILED", self._plan_id)
580+
except Exception as db_error:
581+
self.logger.error("Failed to update plan status to FAILED: %s", db_error)
582+
583+
# Send error status to user via ERROR_MESSAGE type
549584
try:
550585
await connection_config.send_status_update_async(
551586
{
552-
"type": WebsocketMessageType.FINAL_RESULT_MESSAGE,
587+
"type": WebsocketMessageType.ERROR_MESSAGE,
553588
"data": {
554-
"content": f"Error during orchestration: {str(e)}",
589+
"content": user_error_message,
555590
"status": "error",
556591
"timestamp": asyncio.get_event_loop().time(),
557592
},
558593
},
559594
user_id,
560-
message_type=WebsocketMessageType.FINAL_RESULT_MESSAGE,
595+
message_type=WebsocketMessageType.ERROR_MESSAGE,
561596
)
562597
except Exception as send_error:
563598
self.logger.error("Failed to send error status: %s", send_error)

src/tests/backend/v4/orchestration/test_orchestration_manager.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ class MockDatabaseBase:
277277

278278
sys.modules['common.database'] = Mock()
279279
sys.modules['common.database.database_base'] = Mock(DatabaseBase=MockDatabaseBase)
280+
sys.modules['common.database.database_factory'] = Mock(DatabaseFactory=Mock())
280281

281282
# Mock v4 modules
282283
class MockTeamService:
@@ -315,9 +316,18 @@ def __init__(self):
315316
class MockWebsocketMessageType:
316317
"""Mock WebsocketMessageType."""
317318
FINAL_RESULT_MESSAGE = "final_result_message"
319+
ERROR_MESSAGE = "error_message"
320+
AGENT_MESSAGE = "agent_message"
321+
322+
class MockPlanStatus:
323+
"""Mock PlanStatus."""
324+
FAILED = "failed"
325+
COMPLETED = "completed"
326+
IN_PROGRESS = "in_progress"
318327

319328
sys.modules['v4.models'] = Mock()
320329
sys.modules['v4.models.messages'] = Mock(WebsocketMessageType=MockWebsocketMessageType)
330+
sys.modules['v4.models.models'] = Mock(PlanStatus=MockPlanStatus)
321331

322332
# Mock v4.orchestration.human_approval_manager
323333
class MockHumanApprovalMagenticManager:

0 commit comments

Comments
 (0)