Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
[project]
name = "uipath"
version = "2.2.15"
version = "2.2.16"
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.0.9, <0.1.0",
"uipath-runtime>=0.1.1, <0.2.0",
"uipath-runtime>=0.2.0, <0.3.0",
"click>=8.3.1",
"httpx>=0.28.1",
"pyjwt>=2.10.1",
Expand Down Expand Up @@ -144,3 +143,4 @@ name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true

96 changes: 95 additions & 1 deletion src/uipath/_cli/_debug/_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
)
from uipath.runtime.debug import UiPathDebugBridgeProtocol, UiPathDebugQuitError
from uipath.runtime.events import UiPathRuntimeStateEvent
from uipath.runtime.resumable import UiPathResumeTriggerType

from uipath._cli._utils._common import serialize_object

Expand Down Expand Up @@ -79,6 +80,7 @@ def __init__(self, verbose: bool = True):

self._stdin_executor = ThreadPoolExecutor(max_workers=1)
self._terminate_event: asyncio.Event | None = None
self._waiting_for_resume_data = False

async def connect(self) -> None:
"""Initialize the console debugger."""
Expand Down Expand Up @@ -155,6 +157,41 @@ async def emit_execution_completed(
output_data = runtime_result.output
self._print_json(output_data, label="output")

async def emit_execution_suspended(
self,
runtime_result: UiPathRuntimeResult,
) -> None:
"""Print execution suspended."""
self.console.print("[yellow]●[/yellow] [bold]<suspended>[/bold]")
if runtime_result.trigger is None:
return

if runtime_result.trigger.trigger_type == UiPathResumeTriggerType.API:
if runtime_result.output is None:
self._print_json({}, label="output")
elif isinstance(runtime_result.output, BaseModel):
self._print_json(runtime_result.output.model_dump(), label="output")
else:
self._print_json(runtime_result.output, label="output")
self.console.print("[dim]Please provide your input:[/dim]")

self._waiting_for_resume_data = True
else:
self._print_json(
runtime_result.trigger.model_dump() if runtime_result.trigger else {},
label="trigger",
)

async def emit_execution_resumed(self, resume_data: Any) -> None:
"""Send execution resumed event."""
self.console.print("[yellow]●[/yellow] [bold]<resumed>[/bold]")
self._print_json(
resume_data.model_dump()
if resume_data and isinstance(resume_data, BaseModel)
else resume_data or {},
label="data",
)

async def emit_execution_error(
self,
error: str,
Expand All @@ -173,7 +210,7 @@ async def emit_execution_error(
self.console.print(f"[red]{error_display}[/red]")
self.console.print("[red]─" * 40)

async def wait_for_resume(self) -> dict[str, Any]:
async def wait_for_resume(self) -> Any:
"""Wait for user to press Enter or type commands."""
while True: # Keep looping until we get a resume command
self.console.print()
Expand All @@ -188,6 +225,17 @@ async def wait_for_resume(self) -> dict[str, Any]:
except asyncio.CancelledError:
return {"command": DebugCommand.CONTINUE, "args": None}

if self._waiting_for_resume_data:
self._waiting_for_resume_data = False

stripped_input = user_input.strip()

# Try parse JSON, otherwise return raw string
try:
return json.loads(stripped_input)
except Exception:
return stripped_input

command_result = self._parse_command(user_input.strip())

# Handle commands that need another prompt
Expand Down Expand Up @@ -550,6 +598,52 @@ async def emit_execution_completed(
},
)

async def emit_execution_suspended(
self,
runtime_result: UiPathRuntimeResult,
) -> None:
"""Send execution suspended event."""
logger.info(f"Execution suspended: {runtime_result.status}")
if runtime_result.trigger is None:
return

if runtime_result.trigger.trigger_type == UiPathResumeTriggerType.API:
await self._send(
"OnBreakpointHit",
{
"node": "<suspended>",
"type": "before",
"state": runtime_result.output.model_dump()
if runtime_result.output
and isinstance(runtime_result.output, BaseModel)
else runtime_result.output or {},
"nextNodes": [],
},
)
else:
await self._send(
"OnStateUpdate",
{
"nodeName": "<suspended>",
"state": runtime_result.trigger.model_dump()
if runtime_result.trigger
else {},
},
)

async def emit_execution_resumed(self, resume_data: Any) -> None:
"""Send execution resumed event."""
logger.info("Execution resumed")
await self._send(
"OnStateUpdate",
{
"nodeName": "<resumed>",
"state": resume_data.model_dump()
if resume_data and isinstance(resume_data, BaseModel)
else resume_data or {},
},
)

async def emit_execution_error(
self,
error: str,
Expand Down
Loading
Loading