From a60577d117d8f377397c5a6d0643942ceebc01de Mon Sep 17 00:00:00 2001 From: andrewwan-uipath Date: Mon, 15 Jun 2026 14:59:54 -0700 Subject: [PATCH 1/3] feat: pass end_exchange through to UiPathChatRuntime --- packages/uipath/pyproject.toml | 4 +- packages/uipath/src/uipath/_cli/cli_debug.py | 4 +- packages/uipath/src/uipath/_cli/cli_run.py | 4 +- packages/uipath/tests/cli/test_debug_chat.py | 94 +++++++++++++++++ packages/uipath/tests/cli/test_run.py | 102 +++++++++++++++++++ 5 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 packages/uipath/tests/cli/test_debug_chat.py diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index ffaa7f881..f5dafedb9 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,12 +1,12 @@ [project] name = "uipath" -version = "2.10.81" +version = "2.10.82" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" dependencies = [ "uipath-core>=0.5.17, <0.6.0", - "uipath-runtime>=0.11.0, <0.12.0", + "uipath-runtime>=0.12.0, <0.13.0", "uipath-platform>=0.1.63, <0.2.0", "click>=8.3.1", "httpx>=0.28.1", diff --git a/packages/uipath/src/uipath/_cli/cli_debug.py b/packages/uipath/src/uipath/_cli/cli_debug.py index cd9042b78..3d711d350 100644 --- a/packages/uipath/src/uipath/_cli/cli_debug.py +++ b/packages/uipath/src/uipath/_cli/cli_debug.py @@ -172,7 +172,9 @@ async def execute_debug_runtime(): context=ctx ) chat_runtime = UiPathChatRuntime( - delegate=delegate, chat_bridge=chat_bridge + delegate=delegate, + chat_bridge=chat_bridge, + end_exchange=ctx.end_exchange, ) delegate = chat_runtime diff --git a/packages/uipath/src/uipath/_cli/cli_run.py b/packages/uipath/src/uipath/_cli/cli_run.py index 48f42018b..a7205d433 100644 --- a/packages/uipath/src/uipath/_cli/cli_run.py +++ b/packages/uipath/src/uipath/_cli/cli_run.py @@ -267,7 +267,9 @@ async def execute() -> None: context=ctx ) chat_runtime = UiPathChatRuntime( - delegate=runtime, chat_bridge=chat_bridge + delegate=runtime, + chat_bridge=chat_bridge, + end_exchange=ctx.end_exchange, ) ctx.result = await execute_runtime( diff --git a/packages/uipath/tests/cli/test_debug_chat.py b/packages/uipath/tests/cli/test_debug_chat.py new file mode 100644 index 000000000..9d1a02290 --- /dev/null +++ b/packages/uipath/tests/cli/test_debug_chat.py @@ -0,0 +1,94 @@ +# type: ignore +"""Tests for chat runtime wiring in the debug command.""" + +import json +from unittest.mock import AsyncMock, Mock, patch + +from click.testing import CliRunner + +from uipath._cli import cli +from uipath._cli.middlewares import MiddlewareResult +from uipath.runtime import UiPathRuntimeResult, UiPathRuntimeStatus + + +class TestDebugConversationalExchangeEnd: + """Debug runs pass the endExchange fps property to UiPathChatRuntime.""" + + def _invoke_debug(self, runner: CliRunner, chat_runtime_cls): + mock_runtime = Mock() + mock_runtime.dispose = AsyncMock() + mock_runtime.get_schema = AsyncMock(return_value=Mock(metadata=None)) + + mock_factory = Mock() + mock_factory.new_runtime = AsyncMock(return_value=mock_runtime) + mock_factory.get_settings = AsyncMock(return_value=Mock(trace_settings=None)) + mock_factory.dispose = AsyncMock() + + mock_debug_runtime = Mock() + mock_debug_runtime.dispose = AsyncMock() + + mock_mock_runtime = Mock() + mock_mock_runtime.execute = AsyncMock( + return_value=UiPathRuntimeResult(status=UiPathRuntimeStatus.SUCCESSFUL) + ) + mock_mock_runtime.dispose = AsyncMock() + + with ( + patch( + "uipath._cli.cli_debug.Middlewares.next", + return_value=MiddlewareResult( + should_continue=True, + error_message=None, + should_include_stacktrace=False, + ), + ), + patch( + "uipath._cli.cli_debug.UiPathRuntimeFactoryRegistry.get", + return_value=mock_factory, + ), + patch("uipath._cli.cli_debug.get_debug_bridge"), + patch("uipath._cli.cli_debug.get_chat_bridge"), + patch( + "uipath._cli.cli_debug.UiPathDebugRuntime", + return_value=mock_debug_runtime, + ), + patch( + "uipath._cli.cli_debug.UiPathMockRuntime", + return_value=mock_mock_runtime, + ), + ): + return runner.invoke(cli, ["debug", "main", "{}"]) + + def test_end_exchange_false_passed_to_chat_runtime( + self, runner: CliRunner, temp_dir: str, monkeypatch + ): + """endExchange=false in fpsProperties reaches the UiPathChatRuntime constructor.""" + monkeypatch.setenv("UIPATH_TRACING_ENABLED", "false") + monkeypatch.delenv("UIPATH_CONFIG_PATH", raising=False) + + with runner.isolated_filesystem(temp_dir=temp_dir): + with open("uipath.json", "w") as f: + json.dump( + { + "fpsProperties": { + "conversationalService.conversationId": "conv-1", + "conversationalService.exchangeId": "ex-1", + "conversationalService.endExchange": False, + } + }, + f, + ) + + mock_chat_runtime = Mock() + mock_chat_runtime.dispose = AsyncMock() + with patch( + "uipath._cli.cli_debug.UiPathChatRuntime", + return_value=mock_chat_runtime, + ) as chat_runtime_cls: + result = self._invoke_debug(runner, chat_runtime_cls) + + assert result.exit_code == 0, ( + f"output: {result.output!r}, exception: {result.exception}" + ) + assert chat_runtime_cls.called + assert chat_runtime_cls.call_args.kwargs.get("end_exchange") is False diff --git a/packages/uipath/tests/cli/test_run.py b/packages/uipath/tests/cli/test_run.py index aa182c7c5..4789943a8 100644 --- a/packages/uipath/tests/cli/test_run.py +++ b/packages/uipath/tests/cli/test_run.py @@ -9,6 +9,7 @@ from uipath._cli import cli from uipath._cli.middlewares import MiddlewareResult +from uipath.runtime import UiPathRuntimeResult, UiPathRuntimeStatus def _middleware_continue(): @@ -559,3 +560,104 @@ def test_simulation_disabled_does_not_wrap_runtime( ) assert not mock_cls.called + + +def _make_chat_bridge_mock(): + """Create a chat bridge mock with all methods that UiPathChatRuntime uses.""" + bridge = Mock() + bridge.connect = AsyncMock() + bridge.disconnect = AsyncMock() + bridge.emit_message_event = AsyncMock() + bridge.emit_interrupt_event = AsyncMock() + bridge.emit_executing_tool_call_event = AsyncMock() + bridge.emit_exchange_end_event = AsyncMock() + bridge.emit_exchange_error_event = AsyncMock() + bridge.wait_for_resume = AsyncMock() + return bridge + + +async def _successful_result_gen(*args, **kwargs): + """An async generator yielding a single successful runtime result.""" + yield UiPathRuntimeResult(status=UiPathRuntimeStatus.SUCCESSFUL) + + +class TestRunConversationalExchangeEnd: + """Conversational runs honor the conversationalService.endExchange fps property.""" + + def _write_uipath_json(self, fps_properties: dict): + with open("uipath.json", "w") as f: + json.dump({"fpsProperties": fps_properties}, f) + + def _invoke_conversational_run(self, runner: CliRunner, bridge): + mock_factory = _make_mock_factory(["main"]) + mock_factory.new_runtime.return_value.stream = Mock( + side_effect=_successful_result_gen + ) + + with ( + patch( + "uipath._cli.cli_run.Middlewares.next", + return_value=_middleware_continue(), + ), + patch( + "uipath._cli.cli_run.UiPathRuntimeFactoryRegistry.get", + return_value=mock_factory, + ), + patch( + "uipath._cli.cli_run.ResourceOverwritesContext", + side_effect=_mock_resource_overwrites_context, + ), + patch("uipath._cli.cli_run.get_chat_bridge", return_value=bridge), + ): + return runner.invoke(cli, ["run", "main", "{}"]) + + def test_exchange_end_emitted_by_default( + self, runner: CliRunner, temp_dir: str, monkeypatch + ): + """Without the endExchange fps property, the exchange end event is emitted.""" + monkeypatch.setenv("UIPATH_JOB_KEY", "job-123") + monkeypatch.setenv("UIPATH_TRACING_ENABLED", "false") + monkeypatch.delenv("UIPATH_CONFIG_PATH", raising=False) + + with runner.isolated_filesystem(temp_dir=temp_dir): + self._write_uipath_json( + { + "conversationalService.conversationId": "conv-1", + "conversationalService.exchangeId": "ex-1", + } + ) + bridge = _make_chat_bridge_mock() + + result = self._invoke_conversational_run(runner, bridge) + + assert result.exit_code == 0, ( + f"output: {result.output!r}, exception: {result.exception}" + ) + bridge.connect.assert_awaited_once() + bridge.emit_exchange_end_event.assert_awaited_once() + + def test_exchange_end_skipped_when_end_exchange_false( + self, runner: CliRunner, temp_dir: str, monkeypatch + ): + """endExchange=false in fpsProperties suppresses the exchange end event.""" + monkeypatch.setenv("UIPATH_JOB_KEY", "job-123") + monkeypatch.setenv("UIPATH_TRACING_ENABLED", "false") + monkeypatch.delenv("UIPATH_CONFIG_PATH", raising=False) + + with runner.isolated_filesystem(temp_dir=temp_dir): + self._write_uipath_json( + { + "conversationalService.conversationId": "conv-1", + "conversationalService.exchangeId": "ex-1", + "conversationalService.endExchange": False, + } + ) + bridge = _make_chat_bridge_mock() + + result = self._invoke_conversational_run(runner, bridge) + + assert result.exit_code == 0, ( + f"output: {result.output!r}, exception: {result.exception}" + ) + bridge.connect.assert_awaited_once() + bridge.emit_exchange_end_event.assert_not_awaited() From 7decc18019e957805f84ffcfef8d4b3847622e87 Mon Sep 17 00:00:00 2001 From: andrewwan-uipath Date: Wed, 17 Jun 2026 10:40:17 -0700 Subject: [PATCH 2/3] fix: implement end exchange check in bridge --- .../uipath/src/uipath/_cli/_chat/_bridge.py | 14 +++ packages/uipath/src/uipath/_cli/cli_debug.py | 3 +- packages/uipath/src/uipath/_cli/cli_run.py | 3 +- packages/uipath/tests/cli/chat/test_bridge.py | 85 +++++++++++++++ packages/uipath/tests/cli/test_debug_chat.py | 94 ---------------- packages/uipath/tests/cli/test_run.py | 102 ------------------ 6 files changed, 101 insertions(+), 200 deletions(-) delete mode 100644 packages/uipath/tests/cli/test_debug_chat.py diff --git a/packages/uipath/src/uipath/_cli/_chat/_bridge.py b/packages/uipath/src/uipath/_cli/_chat/_bridge.py index 96566e898..2b5fa5a2f 100644 --- a/packages/uipath/src/uipath/_cli/_chat/_bridge.py +++ b/packages/uipath/src/uipath/_cli/_chat/_bridge.py @@ -106,6 +106,7 @@ def __init__( exchange_id: str, headers: dict[str, str], auth: dict[str, Any] | None = None, + end_exchange: bool = True, ): """Initialize the WebSocket chat bridge. @@ -115,6 +116,8 @@ def __init__( exchange_id: The exchange ID for this session headers: HTTP headers to send during connection auth: Optional authentication data to send during connection + end_exchange: Whether to send the exchange-end event to CAS on + completion. """ self.websocket_url = websocket_url self.websocket_path = websocket_path @@ -122,6 +125,7 @@ def __init__( self.exchange_id = exchange_id self.auth = auth self.headers = headers + self.end_exchange = end_exchange self._client: Any | None = None self._connected_event = asyncio.Event() @@ -283,9 +287,18 @@ async def emit_message_event( async def emit_exchange_end_event(self) -> None: """Send an exchange end event. + When end_exchange is False the exchange is left open — the event is not + sent to CAS so a downstream consumer can continue and end it later. + Raises: RuntimeError: If client is not connected """ + if not self.end_exchange: + logger.info( + "end_exchange is False; leaving the exchange open." + ) + return + if self._client is None: raise RuntimeError("WebSocket client not connected. Call connect() first.") @@ -531,6 +544,7 @@ def get_chat_bridge( conversation_id=context.conversation_id, exchange_id=context.exchange_id, headers=headers, + end_exchange=context.end_exchange, ) diff --git a/packages/uipath/src/uipath/_cli/cli_debug.py b/packages/uipath/src/uipath/_cli/cli_debug.py index 3d711d350..6057b117b 100644 --- a/packages/uipath/src/uipath/_cli/cli_debug.py +++ b/packages/uipath/src/uipath/_cli/cli_debug.py @@ -173,8 +173,7 @@ async def execute_debug_runtime(): ) chat_runtime = UiPathChatRuntime( delegate=delegate, - chat_bridge=chat_bridge, - end_exchange=ctx.end_exchange, + chat_bridge=chat_bridge ) delegate = chat_runtime diff --git a/packages/uipath/src/uipath/_cli/cli_run.py b/packages/uipath/src/uipath/_cli/cli_run.py index a7205d433..fa82b5c86 100644 --- a/packages/uipath/src/uipath/_cli/cli_run.py +++ b/packages/uipath/src/uipath/_cli/cli_run.py @@ -268,8 +268,7 @@ async def execute() -> None: ) chat_runtime = UiPathChatRuntime( delegate=runtime, - chat_bridge=chat_bridge, - end_exchange=ctx.end_exchange, + chat_bridge=chat_bridge ) ctx.result = await execute_runtime( diff --git a/packages/uipath/tests/cli/chat/test_bridge.py b/packages/uipath/tests/cli/chat/test_bridge.py index bbd385def..12db61375 100644 --- a/packages/uipath/tests/cli/chat/test_bridge.py +++ b/packages/uipath/tests/cli/chat/test_bridge.py @@ -22,11 +22,13 @@ def __init__( exchange_id: str = "test-exchange-id", tenant_id: str = "test-tenant-id", org_id: str = "test-org-id", + end_exchange: bool = True, ): self.conversation_id = conversation_id self.exchange_id = exchange_id self.tenant_id = tenant_id self.org_id = org_id + self.end_exchange = end_exchange class TestSocketIOChatBridgeDebugMode: @@ -310,6 +312,89 @@ async def test_emit_exchange_end_raises_without_client(self) -> None: assert "not connected" in str(exc_info.value).lower() +class TestSocketIOChatBridgeEndExchange: + """The bridge owns whether to honor the exchange-end event (CAS-specific).""" + + def _make_connected_bridge(self, end_exchange: bool) -> SocketIOChatBridge: + bridge = SocketIOChatBridge( + websocket_url="wss://test.example.com", + websocket_path="/socket.io", + conversation_id="conv-123", + exchange_id="exch-456", + headers={}, + end_exchange=end_exchange, + ) + bridge._websocket_disabled = False + bridge._client = AsyncMock() + bridge._connected_event.set() + return bridge + + def test_end_exchange_defaults_true(self) -> None: + bridge = SocketIOChatBridge( + websocket_url="wss://test.example.com", + websocket_path="/socket.io", + conversation_id="conv-123", + exchange_id="exch-456", + headers={}, + ) + assert bridge.end_exchange is True + + @pytest.mark.anyio + async def test_emit_exchange_end_sends_when_end_exchange_true(self) -> None: + bridge = self._make_connected_bridge(end_exchange=True) + + await bridge.emit_exchange_end_event() + + cast(AsyncMock, bridge._client).emit.assert_awaited_once() + assert ( + cast(AsyncMock, bridge._client).emit.await_args.args[0] + == "ConversationEvent" + ) + + @pytest.mark.anyio + async def test_emit_exchange_end_suppressed_when_end_exchange_false(self) -> None: + bridge = self._make_connected_bridge(end_exchange=False) + + await bridge.emit_exchange_end_event() + + cast(AsyncMock, bridge._client).emit.assert_not_awaited() + + @pytest.mark.anyio + async def test_emit_exchange_end_false_does_not_require_client(self) -> None: + """With the exchange kept open, suppression happens before the connection check.""" + bridge = SocketIOChatBridge( + websocket_url="wss://test.example.com", + websocket_path="/socket.io", + conversation_id="conv-123", + exchange_id="exch-456", + headers={}, + end_exchange=False, + ) + + # Should not raise even though _client is None. + await bridge.emit_exchange_end_event() + + def test_get_chat_bridge_propagates_end_exchange_false( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.setenv("UIPATH_URL", "https://cloud.uipath.com") + context = MockRuntimeContext(end_exchange=False) + + bridge = cast(SocketIOChatBridge, get_chat_bridge(cast(Any, context))) + + assert bridge.end_exchange is False + + def test_get_chat_bridge_defaults_end_exchange_true( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.setenv("UIPATH_URL", "https://cloud.uipath.com") + context = MockRuntimeContext() + + bridge = cast(SocketIOChatBridge, get_chat_bridge(cast(Any, context))) + + assert bridge.end_exchange is True + + class TestSignalRDebugBridgeSendMethod: """Tests for SignalRDebugBridge.""" diff --git a/packages/uipath/tests/cli/test_debug_chat.py b/packages/uipath/tests/cli/test_debug_chat.py deleted file mode 100644 index 9d1a02290..000000000 --- a/packages/uipath/tests/cli/test_debug_chat.py +++ /dev/null @@ -1,94 +0,0 @@ -# type: ignore -"""Tests for chat runtime wiring in the debug command.""" - -import json -from unittest.mock import AsyncMock, Mock, patch - -from click.testing import CliRunner - -from uipath._cli import cli -from uipath._cli.middlewares import MiddlewareResult -from uipath.runtime import UiPathRuntimeResult, UiPathRuntimeStatus - - -class TestDebugConversationalExchangeEnd: - """Debug runs pass the endExchange fps property to UiPathChatRuntime.""" - - def _invoke_debug(self, runner: CliRunner, chat_runtime_cls): - mock_runtime = Mock() - mock_runtime.dispose = AsyncMock() - mock_runtime.get_schema = AsyncMock(return_value=Mock(metadata=None)) - - mock_factory = Mock() - mock_factory.new_runtime = AsyncMock(return_value=mock_runtime) - mock_factory.get_settings = AsyncMock(return_value=Mock(trace_settings=None)) - mock_factory.dispose = AsyncMock() - - mock_debug_runtime = Mock() - mock_debug_runtime.dispose = AsyncMock() - - mock_mock_runtime = Mock() - mock_mock_runtime.execute = AsyncMock( - return_value=UiPathRuntimeResult(status=UiPathRuntimeStatus.SUCCESSFUL) - ) - mock_mock_runtime.dispose = AsyncMock() - - with ( - patch( - "uipath._cli.cli_debug.Middlewares.next", - return_value=MiddlewareResult( - should_continue=True, - error_message=None, - should_include_stacktrace=False, - ), - ), - patch( - "uipath._cli.cli_debug.UiPathRuntimeFactoryRegistry.get", - return_value=mock_factory, - ), - patch("uipath._cli.cli_debug.get_debug_bridge"), - patch("uipath._cli.cli_debug.get_chat_bridge"), - patch( - "uipath._cli.cli_debug.UiPathDebugRuntime", - return_value=mock_debug_runtime, - ), - patch( - "uipath._cli.cli_debug.UiPathMockRuntime", - return_value=mock_mock_runtime, - ), - ): - return runner.invoke(cli, ["debug", "main", "{}"]) - - def test_end_exchange_false_passed_to_chat_runtime( - self, runner: CliRunner, temp_dir: str, monkeypatch - ): - """endExchange=false in fpsProperties reaches the UiPathChatRuntime constructor.""" - monkeypatch.setenv("UIPATH_TRACING_ENABLED", "false") - monkeypatch.delenv("UIPATH_CONFIG_PATH", raising=False) - - with runner.isolated_filesystem(temp_dir=temp_dir): - with open("uipath.json", "w") as f: - json.dump( - { - "fpsProperties": { - "conversationalService.conversationId": "conv-1", - "conversationalService.exchangeId": "ex-1", - "conversationalService.endExchange": False, - } - }, - f, - ) - - mock_chat_runtime = Mock() - mock_chat_runtime.dispose = AsyncMock() - with patch( - "uipath._cli.cli_debug.UiPathChatRuntime", - return_value=mock_chat_runtime, - ) as chat_runtime_cls: - result = self._invoke_debug(runner, chat_runtime_cls) - - assert result.exit_code == 0, ( - f"output: {result.output!r}, exception: {result.exception}" - ) - assert chat_runtime_cls.called - assert chat_runtime_cls.call_args.kwargs.get("end_exchange") is False diff --git a/packages/uipath/tests/cli/test_run.py b/packages/uipath/tests/cli/test_run.py index 4789943a8..aa182c7c5 100644 --- a/packages/uipath/tests/cli/test_run.py +++ b/packages/uipath/tests/cli/test_run.py @@ -9,7 +9,6 @@ from uipath._cli import cli from uipath._cli.middlewares import MiddlewareResult -from uipath.runtime import UiPathRuntimeResult, UiPathRuntimeStatus def _middleware_continue(): @@ -560,104 +559,3 @@ def test_simulation_disabled_does_not_wrap_runtime( ) assert not mock_cls.called - - -def _make_chat_bridge_mock(): - """Create a chat bridge mock with all methods that UiPathChatRuntime uses.""" - bridge = Mock() - bridge.connect = AsyncMock() - bridge.disconnect = AsyncMock() - bridge.emit_message_event = AsyncMock() - bridge.emit_interrupt_event = AsyncMock() - bridge.emit_executing_tool_call_event = AsyncMock() - bridge.emit_exchange_end_event = AsyncMock() - bridge.emit_exchange_error_event = AsyncMock() - bridge.wait_for_resume = AsyncMock() - return bridge - - -async def _successful_result_gen(*args, **kwargs): - """An async generator yielding a single successful runtime result.""" - yield UiPathRuntimeResult(status=UiPathRuntimeStatus.SUCCESSFUL) - - -class TestRunConversationalExchangeEnd: - """Conversational runs honor the conversationalService.endExchange fps property.""" - - def _write_uipath_json(self, fps_properties: dict): - with open("uipath.json", "w") as f: - json.dump({"fpsProperties": fps_properties}, f) - - def _invoke_conversational_run(self, runner: CliRunner, bridge): - mock_factory = _make_mock_factory(["main"]) - mock_factory.new_runtime.return_value.stream = Mock( - side_effect=_successful_result_gen - ) - - with ( - patch( - "uipath._cli.cli_run.Middlewares.next", - return_value=_middleware_continue(), - ), - patch( - "uipath._cli.cli_run.UiPathRuntimeFactoryRegistry.get", - return_value=mock_factory, - ), - patch( - "uipath._cli.cli_run.ResourceOverwritesContext", - side_effect=_mock_resource_overwrites_context, - ), - patch("uipath._cli.cli_run.get_chat_bridge", return_value=bridge), - ): - return runner.invoke(cli, ["run", "main", "{}"]) - - def test_exchange_end_emitted_by_default( - self, runner: CliRunner, temp_dir: str, monkeypatch - ): - """Without the endExchange fps property, the exchange end event is emitted.""" - monkeypatch.setenv("UIPATH_JOB_KEY", "job-123") - monkeypatch.setenv("UIPATH_TRACING_ENABLED", "false") - monkeypatch.delenv("UIPATH_CONFIG_PATH", raising=False) - - with runner.isolated_filesystem(temp_dir=temp_dir): - self._write_uipath_json( - { - "conversationalService.conversationId": "conv-1", - "conversationalService.exchangeId": "ex-1", - } - ) - bridge = _make_chat_bridge_mock() - - result = self._invoke_conversational_run(runner, bridge) - - assert result.exit_code == 0, ( - f"output: {result.output!r}, exception: {result.exception}" - ) - bridge.connect.assert_awaited_once() - bridge.emit_exchange_end_event.assert_awaited_once() - - def test_exchange_end_skipped_when_end_exchange_false( - self, runner: CliRunner, temp_dir: str, monkeypatch - ): - """endExchange=false in fpsProperties suppresses the exchange end event.""" - monkeypatch.setenv("UIPATH_JOB_KEY", "job-123") - monkeypatch.setenv("UIPATH_TRACING_ENABLED", "false") - monkeypatch.delenv("UIPATH_CONFIG_PATH", raising=False) - - with runner.isolated_filesystem(temp_dir=temp_dir): - self._write_uipath_json( - { - "conversationalService.conversationId": "conv-1", - "conversationalService.exchangeId": "ex-1", - "conversationalService.endExchange": False, - } - ) - bridge = _make_chat_bridge_mock() - - result = self._invoke_conversational_run(runner, bridge) - - assert result.exit_code == 0, ( - f"output: {result.output!r}, exception: {result.exception}" - ) - bridge.connect.assert_awaited_once() - bridge.emit_exchange_end_event.assert_not_awaited() From a131998f6756a69ed55f21ab3b563fade8ad55e3 Mon Sep 17 00:00:00 2001 From: andrewwan-uipath Date: Wed, 17 Jun 2026 11:39:44 -0700 Subject: [PATCH 3/3] fix: formatting --- packages/uipath/src/uipath/_cli/cli_debug.py | 3 +-- packages/uipath/src/uipath/_cli/cli_run.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/uipath/src/uipath/_cli/cli_debug.py b/packages/uipath/src/uipath/_cli/cli_debug.py index 6057b117b..cd9042b78 100644 --- a/packages/uipath/src/uipath/_cli/cli_debug.py +++ b/packages/uipath/src/uipath/_cli/cli_debug.py @@ -172,8 +172,7 @@ async def execute_debug_runtime(): context=ctx ) chat_runtime = UiPathChatRuntime( - delegate=delegate, - chat_bridge=chat_bridge + delegate=delegate, chat_bridge=chat_bridge ) delegate = chat_runtime diff --git a/packages/uipath/src/uipath/_cli/cli_run.py b/packages/uipath/src/uipath/_cli/cli_run.py index fa82b5c86..48f42018b 100644 --- a/packages/uipath/src/uipath/_cli/cli_run.py +++ b/packages/uipath/src/uipath/_cli/cli_run.py @@ -267,8 +267,7 @@ async def execute() -> None: context=ctx ) chat_runtime = UiPathChatRuntime( - delegate=runtime, - chat_bridge=chat_bridge + delegate=runtime, chat_bridge=chat_bridge ) ctx.result = await execute_runtime(