From d68fe1687a37ced6abdfad28d1565e3f7ba85b1e Mon Sep 17 00:00:00 2001 From: Josh Park <50765702+JoshParkSJ@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:04:18 -0400 Subject: [PATCH 1/3] chore: bump uipath floor to 2.10.57 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tightens the uipath dependency to >=2.10.57 so this repo installs cleanly alongside the recently-merged CAS tool-confirmation migration in uipath-python. The only source change required at this floor is dropping the now-removed `interrupts` kwarg from `UiPathConversationMessage` constructor calls in models/chat.py. The full migration of the interrupt → event-based confirmation flow in the dev console is tracked separately (see closed reference PR #105 on josh/cas-tc-migration). --- pyproject.toml | 4 ++-- src/uipath/dev/models/chat.py | 2 -- uv.lock | 28 ++++++++++++++-------------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1286fbc..98e123c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-dev" -version = "0.0.78" +version = "0.0.79" description = "UiPath Developer Console" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" @@ -10,7 +10,7 @@ dependencies = [ "pyperclip>=1.11.0, <2.0.0", "fastapi>=0.128.8", "uvicorn[standard]>=0.40.0", - "uipath>=2.10.0, <2.11.0", + "uipath>=2.10.57, <2.11.0", "aiosqlite>=0.20.0", "pywinpty>=2.0.0; sys_platform == 'win32'", "mcp[cli]>=1.0.0", diff --git a/src/uipath/dev/models/chat.py b/src/uipath/dev/models/chat.py index ef14139..920fb96 100644 --- a/src/uipath/dev/models/chat.py +++ b/src/uipath/dev/models/chat.py @@ -40,7 +40,6 @@ def add( role=self.get_role(event), content_parts=[], tool_calls=[], - interrupts=[], created_at=self.get_timestamp(event), updated_at=self.get_timestamp(event), ) @@ -205,7 +204,6 @@ def get_user_message(user_text: str) -> UiPathConversationMessage: ) ], tool_calls=[], - interrupts=[], role="user", ) diff --git a/uv.lock b/uv.lock index 23f0d6a..e45bdcd 100644 --- a/uv.lock +++ b/uv.lock @@ -2511,7 +2511,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.10.31" +version = "2.10.60" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "applicationinsights" }, @@ -2534,28 +2534,28 @@ dependencies = [ { name = "uipath-platform" }, { name = "uipath-runtime" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/07/ba72ccad8e41416d9ba40ef7617cb2fa296b2b3b1807c9023b1d10c8bb82/uipath-2.10.31.tar.gz", hash = "sha256:8d71edfab76226eb5106d27bf43950b8863aaae6f1a88e0b251251c4929d4ed1", size = 2885044, upload-time = "2026-03-25T16:50:04.962Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/47/c551ec9ba09b105860ee9597d8d87b8b4298b9415a25f1a00a7f9902c39a/uipath-2.10.60.tar.gz", hash = "sha256:be1833249c18cd8105dcd1959f0d1ca00c834d3e7bdd9329b194722e3a979678", size = 2933329, upload-time = "2026-04-30T12:18:34.187Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/23/d47328741b6872426c50a5e79427c141dd729aa99be26187261448005f1a/uipath-2.10.31-py3-none-any.whl", hash = "sha256:eca52f4ad5c36779931b9695a3f24d69dbb76e8ec5e08df8e0b9b5e3538264ca", size = 366322, upload-time = "2026-03-25T16:50:03Z" }, + { url = "https://files.pythonhosted.org/packages/85/1b/ae8f3eb7db8bb54cef311b6eab6c24c1df643da940a3a02d641442630905/uipath-2.10.60-py3-none-any.whl", hash = "sha256:4fde5a4a59ec0a209433c023dc113033082fc51494138aa1ceef11594e474c07", size = 389319, upload-time = "2026-04-30T12:18:31.732Z" }, ] [[package]] name = "uipath-core" -version = "0.5.7" +version = "0.5.15" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-sdk" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/67/cea2367246d8332bbcc4a4410a7287824d89a6b23795ef1a238f215c1c55/uipath_core-0.5.7.tar.gz", hash = "sha256:977b00a80dd38cd6abd49329861c6155f523079d0645341fead9e5cb195cdd9d", size = 112660, upload-time = "2026-03-13T16:32:31.136Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/61/945ed075095ab2b4e5e4a43f3724f7d7d2e8897267e11e34bce290df96de/uipath_core-0.5.15.tar.gz", hash = "sha256:dc1049bff52029313f213ab87a6b39acae76c462543729fcea80ea5ac23366b5", size = 117892, upload-time = "2026-04-30T07:39:01.092Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/a9/37c9f603dd6ba72e8c6fab9fc0c0d6f2aebe78280e831138917671c255ce/uipath_core-0.5.7-py3-none-any.whl", hash = "sha256:ab42306028245d333b2e08e6a8bbf5cffe00caf7a5cb5d7aa40f05e698173ed2", size = 42045, upload-time = "2026-03-13T16:32:29.823Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e4/e763bb94dd08ea93e98937b1a229a29f2907bee75a273a0594d929528133/uipath_core-0.5.15-py3-none-any.whl", hash = "sha256:8aa50b1f1531151ef827d29454d63e409c38699cd5a5d63f943094f2b7d5ddd7", size = 44575, upload-time = "2026-04-30T07:38:59.427Z" }, ] [[package]] name = "uipath-dev" -version = "0.0.78" +version = "0.0.79" source = { editable = "." } dependencies = [ { name = "aiosqlite" }, @@ -2595,7 +2595,7 @@ requires-dist = [ { name = "pyperclip", specifier = ">=1.11.0,<2.0.0" }, { name = "pywinpty", marker = "sys_platform == 'win32'", specifier = ">=2.0.0" }, { name = "textual", specifier = ">=7.5.0,<8.0.0" }, - { name = "uipath", specifier = ">=2.10.0,<2.11.0" }, + { name = "uipath", specifier = ">=2.10.57,<2.11.0" }, { name = "uipath-runtime", specifier = ">=0.10.0,<0.11.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.40.0" }, ] @@ -2620,7 +2620,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.8" +version = "0.1.42" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -2630,21 +2630,21 @@ dependencies = [ { name = "truststore" }, { name = "uipath-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/2c/eb7cec36eb96aea845a621e97d6149f38e06d8d41f9012400a0e514e20f1/uipath_platform-0.1.8.tar.gz", hash = "sha256:b8ef2f1f6d04e4568a278a01b84cd0d32df17f0d37444633aa0b04afae5fa71b", size = 285358, upload-time = "2026-03-24T14:11:07.996Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/d0/b815b95164294138f679da5a4f703f5f0ba27bbda4cc07c0b6486a325dce/uipath_platform-0.1.42.tar.gz", hash = "sha256:44f3230fb61e63bce73c6151afc6d5f1156a96646eed220b89a8409866e0b315", size = 334236, upload-time = "2026-04-30T16:36:39.946Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/66/873bee9a829ea673f2251babe2e0a1140e6a41022b48df6e64d146e4962e/uipath_platform-0.1.8-py3-none-any.whl", hash = "sha256:91ce82613133a61f72cadaa0f23e6d74f6b2049bb0258bbccc06862b8f8ab321", size = 176303, upload-time = "2026-03-24T14:11:06.319Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a5/db4ecf46424cedddc57fdd2f8edf17b8dab51458a125e53b7f13a25b20db/uipath_platform-0.1.42-py3-none-any.whl", hash = "sha256:9f908c523ab2c9c2dca0542a1afd499e5d049c76deb58a4f4e1df30ba9ad2bb1", size = 219840, upload-time = "2026-04-30T16:36:38.48Z" }, ] [[package]] name = "uipath-runtime" -version = "0.10.0" +version = "0.10.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "uipath-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/64/69462ee01a5607ce36b1fa152c52ac72fb28abe0aa049394406fc0b31525/uipath_runtime-0.10.0.tar.gz", hash = "sha256:d27d58e2252f506c8c0e00f814b37c3863150e8ffcde8e4c6ab14bd98febd3df", size = 139626, upload-time = "2026-03-24T19:42:43.738Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/a5/3643d567b291a584de3b9ff1b39734db8041e5c09a97256426212bbe1f33/uipath_runtime-0.10.2.tar.gz", hash = "sha256:885183183f658aa0af785d2cc268426a583b1a6f12276f5bbafa797323217167", size = 141197, upload-time = "2026-04-29T08:10:33.261Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/ed/9c0e97a078b96e4d3742ea3515cb30886b08579cd08077cd42a159adf70d/uipath_runtime-0.10.0-py3-none-any.whl", hash = "sha256:4f52df0b56f54e70fcf34fbf74e223d02b97b5a6fd6d8f64bc06782bb5484b07", size = 42097, upload-time = "2026-03-24T19:42:42.359Z" }, + { url = "https://files.pythonhosted.org/packages/e4/52/b50ad909e7025f8731422f0cf62c1143d674c445fe128e94d7f652bdfaa3/uipath_runtime-0.10.2-py3-none-any.whl", hash = "sha256:394c78d1666fc049ceab30d65faaf8f9f51c97831ef67468a174bf2ffd7d2217", size = 43058, upload-time = "2026-04-29T08:10:31.315Z" }, ] [[package]] From 4ac3045f96397f0a37d4747800efa4831fedb373 Mon Sep 17 00:00:00 2001 From: Josh Park <50765702+JoshParkSJ@users.noreply.github.com> Date: Fri, 1 May 2026 11:37:40 -0400 Subject: [PATCH 2/3] fix: replace removed UiPathConversationToolCallConfirmationValue with new event type Co-Authored-By: Claude Sonnet 4.6 --- src/uipath/dev/services/run_service.py | 81 +++++--------------------- 1 file changed, 15 insertions(+), 66 deletions(-) diff --git a/src/uipath/dev/services/run_service.py b/src/uipath/dev/services/run_service.py index 7f79a3f..ac2e74e 100644 --- a/src/uipath/dev/services/run_service.py +++ b/src/uipath/dev/services/run_service.py @@ -9,7 +9,7 @@ from pydantic import BaseModel from uipath.core.chat import ( UiPathConversationMessageEvent, - UiPathConversationToolCallConfirmationValue, + UiPathConversationToolCallConfirmationEvent, ) from uipath.core.tracing import UiPathTraceManager from uipath.runtime import ( @@ -32,12 +32,10 @@ UiPathErrorContract, ) from uipath.runtime.events import UiPathRuntimeStateEvent -from uipath.runtime.resumable.trigger import UiPathResumeTrigger from uipath.dev.infrastructure import RunContextExporter, RunContextLogHandler from uipath.dev.models.data import ( ChatData, - InterruptData, LogData, StateData, TraceData, @@ -52,7 +50,6 @@ TraceCallback = Callable[[TraceData], None] ChatCallback = Callable[[ChatData], None] StateCallback = Callable[[StateData], None] -InterruptCallback = Callable[[InterruptData], None] class DebugBridgeProtocol(UiPathDebugProtocol, Protocol): @@ -98,7 +95,6 @@ def __init__( on_trace: TraceCallback | None = None, on_chat: ChatCallback | None = None, on_state: StateCallback | None = None, - on_interrupt: InterruptCallback | None = None, debug_bridge_factory: DebugBridgeFactory | None = None, on_run_removed: Callable[[str], None] | None = None, ) -> None: @@ -112,7 +108,6 @@ def __init__( self.on_trace = on_trace self.on_chat = on_chat self.on_state = on_state - self.on_interrupt = on_interrupt self._debug_bridge_factory = debug_bridge_factory self._on_run_removed = on_run_removed @@ -208,9 +203,6 @@ async def execute(self, run: ExecutionRun) -> None: chat_bridge.on_message = lambda evt: self._handle_chat_message_event( run, evt ) - chat_bridge.on_interrupt = lambda trigger: self._handle_interrupt( - run, trigger - ) self.chat_bridges[run.id] = chat_bridge # ChatRuntime handles suspend/resume internally @@ -442,13 +434,20 @@ def get_chat_bridge(self, run_id: str) -> WebChatBridge | None: """Get the chat bridge for a run.""" return self.chat_bridges.get(run_id) - def resume_chat(self, run: ExecutionRun, data: dict[str, Any]) -> None: - """Resume a suspended chat run with interrupt response data.""" - chat_bridge = self.chat_bridges.get(run.id) - if chat_bridge: - run.status = "running" - self._emit_run_updated(run) - chat_bridge.resume(data) + def confirm_tool_call( + self, run_id: str, approved: bool, input: Any | None = None + ) -> None: + """Deliver a tool call confirmation to a chat-mode run. + + Forwarded as ``UiPathConversationToolCallConfirmationEvent`` to the + ``WebChatBridge``, which unblocks the runtime's ``wait_for_resume``. + """ + chat_bridge = self.chat_bridges.get(run_id) + if chat_bridge is None: + return + chat_bridge.set_tool_confirmation( + UiPathConversationToolCallConfirmationEvent(approved=approved, input=input) + ) def get_debug_bridge(self, run_id: str) -> DebugBridgeProtocol | None: """Get the debug bridge for a run.""" @@ -466,56 +465,6 @@ def _handle_chat_message_event( ) self.on_chat(chat_data) - def _handle_interrupt( - self, run: ExecutionRun, trigger: UiPathResumeTrigger - ) -> None: - """Handle an interrupt event from the chat bridge.""" - payload = trigger.payload - interrupt_id = trigger.interrupt_id or "" - interrupt_data: InterruptData - - # Try strongly-typed tool call confirmation first - tc = self._try_parse_tool_call_confirmation(payload) - if tc is not None: - interrupt_data = InterruptData( - run_id=run.id, - interrupt_id=interrupt_id, - interrupt_type="tool_call_confirmation", - tool_call_id=tc.tool_call_id, - tool_name=tc.tool_name, - input_schema=tc.input_schema, - input_value=tc.input_value, - ) - else: - interrupt_data = InterruptData( - run_id=run.id, - interrupt_id=interrupt_id, - interrupt_type="generic", - content=payload, - ) - - run.status = "suspended" - self._emit_run_updated(run) - - if self.on_interrupt is not None: - self.on_interrupt(interrupt_data) - - @staticmethod - def _try_parse_tool_call_confirmation( - payload: Any, - ) -> UiPathConversationToolCallConfirmationValue | None: - """Try to parse payload as a tool call confirmation value.""" - if isinstance(payload, UiPathConversationToolCallConfirmationValue): - return payload - if isinstance(payload, dict): - try: - return UiPathConversationToolCallConfirmationValue.model_validate( - payload - ) - except Exception: - return None - return None - def _handle_state_update(self, run_id: str, state: UiPathRuntimeStateEvent) -> None: """Handle state update from debug runtime.""" run = self.runs.get(run_id) From db9630eb5ed4af3afcdb4f39394ae91160e6c8b0 Mon Sep 17 00:00:00 2001 From: Josh Park <50765702+JoshParkSJ@users.noreply.github.com> Date: Fri, 1 May 2026 11:39:35 -0400 Subject: [PATCH 3/3] fix: migrate confirmation flow from interrupt-based to event-based (cas-tc) Co-Authored-By: Claude Sonnet 4.6 --- src/uipath/dev/server/__init__.py | 6 --- src/uipath/dev/server/ws/handler.py | 22 +++++---- src/uipath/dev/server/ws/manager.py | 9 ---- src/uipath/dev/server/ws/protocol.py | 3 +- src/uipath/dev/services/chat_bridge.py | 64 +++++++++++++++++--------- 5 files changed, 55 insertions(+), 49 deletions(-) diff --git a/src/uipath/dev/server/__init__.py b/src/uipath/dev/server/__init__.py index 80b3a17..af3d30d 100644 --- a/src/uipath/dev/server/__init__.py +++ b/src/uipath/dev/server/__init__.py @@ -20,7 +20,6 @@ from uipath.dev.models.data import ( ChatData, - InterruptData, LogData, StateData, TraceData, @@ -85,7 +84,6 @@ def __init__( on_trace=self._on_trace, on_chat=self._on_chat, on_state=self._on_state, - on_interrupt=self._on_interrupt, debug_bridge_factory=lambda mode: WebDebugBridge(mode=mode), on_run_removed=self.connection_manager.remove_run_subscriptions, ) @@ -303,10 +301,6 @@ def _on_chat(self, chat_data: ChatData) -> None: """Broadcast chat message to subscribed WebSocket clients.""" self.connection_manager.broadcast_chat(chat_data) - def _on_interrupt(self, interrupt_data: InterruptData) -> None: - """Broadcast chat interrupt to subscribed WebSocket clients.""" - self.connection_manager.broadcast_interrupt(interrupt_data) - def _on_state(self, state_data: StateData) -> None: """Broadcast state transition to subscribed WebSocket clients.""" self.connection_manager.broadcast_state(state_data) diff --git a/src/uipath/dev/server/ws/handler.py b/src/uipath/dev/server/ws/handler.py index 1e928fd..72a1afb 100644 --- a/src/uipath/dev/server/ws/handler.py +++ b/src/uipath/dev/server/ws/handler.py @@ -58,11 +58,10 @@ async def websocket_endpoint(websocket: WebSocket) -> None: if run_id and text: await _handle_chat_message(server, run_id, text) - elif command == ClientCommand.CHAT_INTERRUPT_RESPONSE.value: + elif command == ClientCommand.CHAT_CONFIRM_TOOL_CALL.value: run_id = payload.get("run_id", "") - data = payload.get("data", {}) if run_id: - _handle_interrupt_response(server, run_id, data) + _handle_confirm_tool_call(server, run_id, payload) elif command == ClientCommand.DEBUG_STEP.value: run_id = payload.get("run_id", "") @@ -136,15 +135,18 @@ async def websocket_endpoint(websocket: WebSocket) -> None: manager.disconnect(websocket) -def _handle_interrupt_response(server: Any, run_id: str, data: Any) -> None: - """Process an interrupt response from a WebSocket client.""" +def _handle_confirm_tool_call( + server: Any, run_id: str, payload: dict[str, Any] +) -> None: + """Process a tool call confirmation (approve/reject) from a WebSocket client.""" run = server.run_service.get_run(run_id) - if run is None or run.status != "suspended": + if run is None: return - - chat_bridge = server.run_service.get_chat_bridge(run_id) - if chat_bridge: - server.run_service.resume_chat(run, data if isinstance(data, dict) else {}) + server.run_service.confirm_tool_call( + run_id, + approved=bool(payload.get("approved", False)), + input=payload.get("input"), + ) async def _handle_chat_message(server: Any, run_id: str, text: str) -> None: diff --git a/src/uipath/dev/server/ws/manager.py b/src/uipath/dev/server/ws/manager.py index 5d3f03a..579ba20 100644 --- a/src/uipath/dev/server/ws/manager.py +++ b/src/uipath/dev/server/ws/manager.py @@ -10,7 +10,6 @@ from uipath.dev.models.data import ( ChatData, - InterruptData, LogData, StateData, TraceData, @@ -19,7 +18,6 @@ from uipath.dev.models.execution import ExecutionRun from uipath.dev.server.serializers import ( serialize_chat, - serialize_interrupt, serialize_log, serialize_run, serialize_state, @@ -154,13 +152,6 @@ def broadcast_chat(self, chat_data: ChatData) -> None: msg = server_message(ServerEvent.CHAT, serialize_chat(chat_data)) self._schedule_broadcast(chat_data.run_id, msg) - def broadcast_interrupt(self, interrupt_data: InterruptData) -> None: - """Broadcast a chat interrupt to run subscribers.""" - msg = server_message( - ServerEvent.CHAT_INTERRUPT, serialize_interrupt(interrupt_data) - ) - self._schedule_broadcast(interrupt_data.run_id, msg) - def broadcast_state(self, state_data: StateData) -> None: """Broadcast a state transition to all connected clients. diff --git a/src/uipath/dev/server/ws/protocol.py b/src/uipath/dev/server/ws/protocol.py index 4106f02..ccf61e4 100644 --- a/src/uipath/dev/server/ws/protocol.py +++ b/src/uipath/dev/server/ws/protocol.py @@ -13,7 +13,6 @@ class ServerEvent(str, Enum): LOG = "log" TRACE = "trace" CHAT = "chat" - CHAT_INTERRUPT = "chat.interrupt" STATE = "state" RELOAD = "reload" FILES_CHANGED = "files.changed" @@ -31,7 +30,7 @@ class ClientCommand(str, Enum): SUBSCRIBE = "subscribe" UNSUBSCRIBE = "unsubscribe" CHAT_MESSAGE = "chat.message" - CHAT_INTERRUPT_RESPONSE = "chat.interrupt_response" + CHAT_CONFIRM_TOOL_CALL = "chat.confirm_tool_call" DEBUG_STEP = "debug.step" DEBUG_CONTINUE = "debug.continue" DEBUG_STOP = "debug.stop" diff --git a/src/uipath/dev/services/chat_bridge.py b/src/uipath/dev/services/chat_bridge.py index 0f87d94..e0dd39f 100644 --- a/src/uipath/dev/services/chat_bridge.py +++ b/src/uipath/dev/services/chat_bridge.py @@ -4,7 +4,10 @@ import logging from typing import Any, Callable -from uipath.core.chat import UiPathConversationMessageEvent +from uipath.core.chat import ( + UiPathConversationMessageEvent, + UiPathConversationToolCallConfirmationEvent, +) from uipath.runtime.resumable.trigger import UiPathResumeTrigger logger = logging.getLogger(__name__) @@ -13,19 +16,23 @@ class WebChatBridge: """Bridge between the web server and UiPathChatRuntime. - Implements UiPathChatProtocol. Broadcasts message and interrupt events - to WebSocket clients via callbacks, and blocks on wait_for_resume until - the user responds. + Implements UiPathChatProtocol. Forwards message events to WebSocket clients + and resumes runtime execution when the user confirms a tool call. + + Tool confirmation flows through ``startToolCall`` events with + ``requireConfirmation: true`` and ``inputSchema``; the user's decision is + delivered back as a ``UiPathConversationToolCallConfirmationEvent`` via + ``set_tool_confirmation`` and consumed by ``wait_for_resume``. """ def __init__(self) -> None: """Initialize the web chat bridge.""" - self._resume_event = asyncio.Event() - self._resume_data: dict[str, Any] = {} + self._tool_confirmation_event = asyncio.Event() + self._tool_confirmation_value: ( + UiPathConversationToolCallConfirmationEvent | None + ) = None - # Callbacks (wired by RunService / server) self.on_message: Callable[[UiPathConversationMessageEvent], None] | None = None - self.on_interrupt: Callable[[UiPathResumeTrigger], None] | None = None self.on_exchange_end: Callable[[], None] | None = None async def connect(self) -> None: @@ -33,8 +40,8 @@ async def connect(self) -> None: logger.debug("WebChatBridge connected") async def disconnect(self) -> None: - """Close connection and send exchange end event.""" - self._resume_event.set() + """Unblock any waiting coroutine and close.""" + self._tool_confirmation_event.set() logger.debug("WebChatBridge disconnected") async def emit_message_event( @@ -45,9 +52,13 @@ async def emit_message_event( self.on_message(message_event) async def emit_interrupt_event(self, resume_trigger: UiPathResumeTrigger) -> None: - """Forward an interrupt event via callback.""" - if self.on_interrupt: - self.on_interrupt(resume_trigger) + """No-op. + + Tool confirmations now flow on the tool call event itself + (``requireConfirmation`` + ``confirmToolCall``); generic agent + interrupts have no near-term consumer in the dev console. + """ + return None async def emit_exchange_end_event(self) -> None: """Send an exchange end event.""" @@ -59,12 +70,21 @@ async def emit_exchange_error_event(self, error: Exception) -> None: logger.error(f"Exchange error: {error}") async def wait_for_resume(self) -> dict[str, Any]: - """Wait for the user to respond to an interrupt.""" - self._resume_event.clear() - await self._resume_event.wait() - return self._resume_data - - def resume(self, data: dict[str, Any]) -> None: - """Store resume data and signal the waiting coroutine.""" - self._resume_data = data - self._resume_event.set() + """Wait for a confirmToolCall event to be received.""" + self._tool_confirmation_event.clear() + self._tool_confirmation_value = None + + await self._tool_confirmation_event.wait() + + if self._tool_confirmation_value is not None: + return self._tool_confirmation_value.model_dump( + mode="python", by_alias=False + ) + return {} + + def set_tool_confirmation( + self, confirmation: UiPathConversationToolCallConfirmationEvent + ) -> None: + """Deliver the user's tool call confirmation and unblock the runtime.""" + self._tool_confirmation_value = confirmation + self._tool_confirmation_event.set()