Python: Improve DevUI, add Context Inspector view as new tab under traces (#2742)

* Improve DevUI, add Context Inspector view as new tab under traces

* fix mypy errors

* fix: Handle stale MCP connections in DevUI executor

MCP tools can become stale when HTTP streaming responses end - the underlying
stdio streams close but `is_connected` remains True. This causes subsequent
requests to fail with `ClosedResourceError`.

Add `_ensure_mcp_connections()` to detect and reconnect stale MCP tools before
agent execution. This is a workaround for an upstream Agent Framework issue
where connection state isn't properly tracked.

Fixes MCP tools failing on second HTTP request in DevUI.

fixes  #1476 #1515 #2865

* fix #1572 report import dependency errors more clearly

* Ensure there is streaming toggle where users can select streaming vs non streaming mode in devui . Fixes .NET: [Python] DevUI tool call rendering in non-streaming mode?

* remove unused dead code

* improve ux - workflows with agents show a chat component in execution timelien, also ensure magentic final output shows correctly

* update ui build

* update devui to use instrumentation instead of tracing, other instrumentation and type/instance check fixes
This commit is contained in:
Victor Dibia
2026-01-07 00:26:08 -08:00
committed by GitHub
Unverified
parent db283cd396
commit 2e1189ca65
36 changed files with 7430 additions and 1662 deletions
@@ -7,6 +7,8 @@ import type {
import type {
ExtendedResponseStreamEvent,
ResponseWorkflowEventComplete,
ResponseOutputItemAddedEvent,
ResponseOutputItemDoneEvent,
JSONSchemaProperty,
} from "@/types";
import type { Workflow } from "@/types/workflow";
@@ -389,9 +391,10 @@ export function processWorkflowEvents(
events.forEach((event) => {
// Handle new standard OpenAI events
if (event.type === "response.output_item.added" || event.type === "response.output_item.done") {
const item = (event as any).item;
if (item && item.type === "executor_action" && item.executor_id) {
const executorId = item.executor_id;
const outputEvent = event as ResponseOutputItemAddedEvent | ResponseOutputItemDoneEvent;
const item = outputEvent.item;
if (item && item.type === "executor_action" && "executor_id" in item) {
const executorId = item.executor_id as string;
const itemId = item.id;
// Track the latest item ID for this executor
@@ -492,16 +495,17 @@ export function processWorkflowEvents(
// This prevents setting to "running" after the executor has already completed
const hasCompletionEvent = events.some((event) => {
if (event.type === "response.output_item.done") {
const item = (event as any).item;
return item && item.type === "executor_action" && item.executor_id === startExecutorId;
const outputEvent = event as ResponseOutputItemDoneEvent;
const item = outputEvent.item;
return item && item.type === "executor_action" && "executor_id" in item && item.executor_id === startExecutorId;
}
if (event.type === "response.workflow_event.completed" && "data" in event && event.data) {
const data = event.data as any;
const data = event.data as Record<string, unknown>;
return data.executor_id === startExecutorId &&
(data.event_type === "ExecutorCompletedEvent" ||
data.event_type === "ExecutorFailedEvent" ||
data.event_type?.includes("Error") ||
data.event_type?.includes("Failed"));
(typeof data.event_type === "string" && data.event_type.includes("Error")) ||
(typeof data.event_type === "string" && data.event_type.includes("Failed")));
}
return false;
});
@@ -565,9 +569,10 @@ export function getCurrentlyExecutingExecutors(
events.forEach((event) => {
// Handle new standard OpenAI events
if (event.type === "response.output_item.added" || event.type === "response.output_item.done") {
const item = (event as any).item;
if (item && item.type === "executor_action" && item.executor_id) {
const executorId = item.executor_id;
const outputEvent = event as ResponseOutputItemAddedEvent | ResponseOutputItemDoneEvent;
const item = outputEvent.item;
if (item && item.type === "executor_action" && "executor_id" in item) {
const executorId = item.executor_id as string;
executorTimeline[executorId] = {
lastEvent: event.type === "response.output_item.added" ? "ExecutorInvokedEvent" : "ExecutorCompletedEvent",