Skip to content

Commit d3d7a09

Browse files
authored
Merge branch 'main' into feat/add-memory-space-resource-overwrite
2 parents 368044c + 28ad47c commit d3d7a09

6 files changed

Lines changed: 199 additions & 13 deletions

File tree

packages/uipath/docs/cli/index.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,92 @@ Processing: uipath.json
308308
File 'uipath.json' is up to date
309309
✓ Project pulled successfully
310310
```
311+
---
312+
313+
::: mkdocs-click
314+
:module: uipath._cli
315+
:command: debug
316+
:depth: 1
317+
:style: table
318+
319+
Runs your agent under the debug runtime, with a debug bridge attached. Locally, the bridge is the interactive **console** (read commands from stdin, stop at breakpoints). In the cloud, the bridge is **SignalR** (driven by Studio Web / Orchestrator). The `--attach` flag lets you override that default, including `none` for executors that need the debug command's surrounding behavior (bindings fetch, state streaming) but cannot speak the interactive debug protocol.
320+
321+
### Attach modes
322+
323+
| Mode | When to use |
324+
|------|-------------|
325+
| `signalr` | Remote runs driven by Studio Web / Orchestrator. Default when `job_id` is set. |
326+
| `console` | Local interactive debugging from the terminal. Default when no `job_id`. |
327+
| `none` | Run under the debug command without attaching a debugger. No wait-for-start gate, no breakpoints, no step mode. |
328+
329+
/// info
330+
`--attach` selects the **debug bridge**. It's unrelated to `--debug`, which starts a `debugpy` server for Python-level breakpoints in your IDE. The two can be combined.
331+
///
332+
333+
<!-- termynal -->
334+
335+
```shell
336+
> uipath debug main '{"message": "test"}'
337+
Debug Mode Commands
338+
c, continue Continue until next breakpoint
339+
s, step Step to next node
340+
b <node> Set breakpoint at <node>
341+
l, list List all breakpoints
342+
r <node> Remove breakpoint at <node>
343+
h, help Show help
344+
q, quit Exit debugger
345+
▶ START
346+
> b analyze_sentiment
347+
✓ Breakpoint set at: analyze_sentiment
348+
> c
349+
────────────────────────────────────────
350+
■ BREAKPOINT analyze_sentiment (before)
351+
Next: analyze_sentiment
352+
────────────────────────────────────────
353+
> s
354+
● analyze_sentiment
355+
> c
356+
✓ Execution completed
357+
```
358+
---
359+
360+
::: mkdocs-click
361+
:module: uipath._cli
362+
:command: eval
363+
:depth: 1
364+
:style: table
365+
366+
Runs an evaluation set against your agent. Entry point and eval set are auto-discovered from the project if not passed explicitly. Evaluations run in parallel (see `--workers`) and, unless `--no-report` is passed, results are reported back to Studio Web when `UIPATH_PROJECT_ID` is set.
367+
368+
### Common flags
369+
370+
| Flag | Purpose |
371+
|------|---------|
372+
| `--eval-ids` | Run only a subset of evaluations by id. |
373+
| `--workers` | Parallel workers for running evaluations (default 1). |
374+
| `--no-report` | Skip reporting results back to UiPath. |
375+
| `--enable-mocker-cache` | Cache LLM mocker responses across runs. |
376+
| `--input-overrides` | Per-eval input overrides, merged into the eval's input. |
377+
| `--trace-file` | Write OpenTelemetry traces to a JSONL file for offline inspection. |
378+
| `--resume` | Resume evaluation from a previous suspended state. |
379+
380+
<!-- termynal -->
381+
382+
```shell
383+
> uipath eval
384+
⠋ Running evaluations ...
385+
Weather in Paris
386+
LLM Judge Output 0.7
387+
Tool Call Arguments 1.0
388+
Tool Call Count 1.0
389+
Tool Call Order 1.0
390+
391+
Evaluation Results
392+
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓
393+
┃ Evaluation ┃ LLM Judge Output ┃ Tool Call Args ┃ Tool Call Count ┃ Tool Call Order ┃
394+
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩
395+
│ Weather in Paris │ 0.7 │ 1.0 │ 1.0 │ 1.0 │
396+
├────────────────────┼────────────────────┼────────────────────┼────────────────────┼────────────────────┤
397+
│ Average │ 0.7 │ 1.0 │ 1.0 │ 1.0 │
398+
└────────────────────┴────────────────────┴────────────────────┴────────────────────┴────────────────────┘
399+
```

packages/uipath/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
[project]
22
name = "uipath"
3-
version = "2.10.53"
3+
version = "2.10.54"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"
77
dependencies = [
88
"uipath-core>=0.5.8, <0.6.0",
9-
"uipath-runtime>=0.10.0, <0.11.0",
9+
"uipath-runtime>=0.10.1, <0.11.0",
1010
"uipath-platform>=0.1.13, <0.2.0",
1111
"click>=8.3.1",
1212
"httpx>=0.28.1",

packages/uipath/src/uipath/_cli/_debug/_bridge.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,15 @@
1919
UiPathRuntimeResult,
2020
UiPathRuntimeStatus,
2121
)
22-
from uipath.runtime.debug import UiPathDebugProtocol, UiPathDebugQuitError
22+
from uipath.runtime.debug import (
23+
DetachedDebugBridge,
24+
UiPathDebugProtocol,
25+
UiPathDebugQuitError,
26+
)
2327
from uipath.runtime.events import UiPathRuntimeStateEvent, UiPathRuntimeStatePhase
2428

29+
DebugAttachMode = Literal["signalr", "console", "none"]
30+
2531
logger = logging.getLogger(__name__)
2632

2733

@@ -871,18 +877,27 @@ def get_remote_debug_bridge(context: UiPathRuntimeContext) -> UiPathDebugProtoco
871877

872878

873879
def get_debug_bridge(
874-
context: UiPathRuntimeContext, verbose: bool = True
880+
context: UiPathRuntimeContext,
881+
verbose: bool = True,
882+
attach: DebugAttachMode | None = None,
875883
) -> UiPathDebugProtocol:
876884
"""Factory to get appropriate debug bridge based on context.
877885
878886
Args:
879887
context: The runtime context containing debug configuration.
880888
verbose: If True, console bridge shows all state updates. If False, only breakpoints.
889+
attach: Explicit attach mode. When None, falls back to
890+
``context.job_id``-based selection.
881891
882892
Returns:
883893
An instance of UiPathDebugBridge suitable for the context.
884894
"""
885-
if context.job_id:
895+
if attach == "none":
896+
return DetachedDebugBridge()
897+
if attach == "signalr":
886898
return get_remote_debug_bridge(context)
887-
else:
899+
if attach == "console":
888900
return ConsoleDebugBridge(verbose=verbose)
901+
if context.job_id:
902+
return get_remote_debug_bridge(context)
903+
return ConsoleDebugBridge(verbose=verbose)

packages/uipath/src/uipath/_cli/cli_debug.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import asyncio
22
import logging
3+
from typing import cast, get_args
34

45
import click
56

67
from uipath._cli._chat._bridge import get_chat_bridge
7-
from uipath._cli._debug._bridge import get_debug_bridge
8+
from uipath._cli._debug._bridge import DebugAttachMode, get_debug_bridge
89
from uipath._cli._utils._debug import setup_debugging
910
from uipath._cli._utils._studio_project import StudioClient
1011
from uipath.core.tracing import UiPathTraceManager
@@ -64,6 +65,15 @@
6465
default=5678,
6566
help="Port for the debug server (default: 5678)",
6667
)
68+
@click.option(
69+
"--attach",
70+
type=click.Choice(list(get_args(DebugAttachMode)), case_sensitive=False),
71+
default=None,
72+
help=(
73+
"Debugger attach mode. Defaults to 'signalr' for cloud runs, "
74+
"'console' for local runs."
75+
),
76+
)
6777
@track_command("debug")
6878
def debug(
6979
entrypoint: str | None,
@@ -74,13 +84,18 @@ def debug(
7484
output_file: str | None,
7585
debug: bool,
7686
debug_port: int,
87+
attach: str | None,
7788
) -> None:
7889
"""Debug the project."""
7990
input_file = file or input_file
8091
# Setup debugging if requested
8192
if not setup_debugging(debug, debug_port):
8293
console.error(f"Failed to start debug server on port {debug_port}")
8394

95+
attach_mode: DebugAttachMode | None = (
96+
cast(DebugAttachMode, attach.lower()) if attach else None
97+
)
98+
8499
result = Middlewares.next(
85100
"debug",
86101
entrypoint,
@@ -90,6 +105,7 @@ def debug(
90105
output_file=output_file,
91106
debug=debug,
92107
debug_port=debug_port,
108+
attach=attach_mode,
93109
)
94110

95111
if result.error_message:
@@ -141,7 +157,9 @@ async def execute_debug_runtime():
141157

142158
async def execute_debug_runtime():
143159
chat_runtime: UiPathRuntimeProtocol | None = None
144-
debug_bridge: UiPathDebugProtocol = get_debug_bridge(ctx)
160+
debug_bridge: UiPathDebugProtocol = get_debug_bridge(
161+
ctx, attach=attach_mode
162+
)
145163

146164
runtime = await factory.new_runtime(
147165
entrypoint,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Tests for `get_debug_bridge()` selection matrix.
2+
3+
Locks in the non-breaking-change contract: absence of `attach` preserves the
4+
legacy `job_id`-based selection. Explicit `attach` overrides that selection.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import pytest
10+
11+
from uipath._cli._debug._bridge import (
12+
ConsoleDebugBridge,
13+
SignalRDebugBridge,
14+
get_debug_bridge,
15+
)
16+
from uipath.runtime import UiPathRuntimeContext
17+
from uipath.runtime.debug import DetachedDebugBridge
18+
19+
20+
def _ctx(**overrides) -> UiPathRuntimeContext:
21+
return UiPathRuntimeContext(**overrides)
22+
23+
24+
def test_attach_none_returns_detached_bridge_without_job_id():
25+
bridge = get_debug_bridge(_ctx(), attach="none")
26+
assert isinstance(bridge, DetachedDebugBridge)
27+
28+
29+
def test_attach_none_returns_detached_bridge_even_when_job_id_set(monkeypatch):
30+
"""'none' wins over job_id — this is the whole point of the flag."""
31+
monkeypatch.setenv("UIPATH_URL", "https://cloud.uipath.com")
32+
bridge = get_debug_bridge(_ctx(job_id="job-123"), attach="none")
33+
assert isinstance(bridge, DetachedDebugBridge)
34+
35+
36+
def test_attach_signalr_forces_signalr_bridge(monkeypatch):
37+
monkeypatch.setenv("UIPATH_URL", "https://cloud.uipath.com")
38+
bridge = get_debug_bridge(_ctx(job_id="job-123"), attach="signalr")
39+
assert isinstance(bridge, SignalRDebugBridge)
40+
41+
42+
def test_attach_console_forces_console_bridge_even_when_job_id_set(monkeypatch):
43+
monkeypatch.setenv("UIPATH_URL", "https://cloud.uipath.com")
44+
bridge = get_debug_bridge(_ctx(job_id="job-123"), attach="console")
45+
assert isinstance(bridge, ConsoleDebugBridge)
46+
47+
48+
def test_legacy_selection_signalr_when_job_id_set_and_no_attach(monkeypatch):
49+
"""Non-breaking change assertion: absence of `attach` preserves today's behavior."""
50+
monkeypatch.setenv("UIPATH_URL", "https://cloud.uipath.com")
51+
bridge = get_debug_bridge(_ctx(job_id="job-123"))
52+
assert isinstance(bridge, SignalRDebugBridge)
53+
54+
55+
def test_legacy_selection_console_when_no_job_id_and_no_attach():
56+
"""Non-breaking change assertion: absence of `attach` preserves today's behavior."""
57+
bridge = get_debug_bridge(_ctx())
58+
assert isinstance(bridge, ConsoleDebugBridge)
59+
60+
61+
def test_attach_signalr_without_job_id_raises():
62+
"""Explicit signalr without job_id is a user error — surface it loudly."""
63+
with pytest.raises(ValueError, match="UIPATH_URL and UIPATH_JOB_KEY"):
64+
get_debug_bridge(_ctx(), attach="signalr")

packages/uipath/uv.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)