feat(cli): serve the pre-warmed runtime over uipath-ipc alongside HTTP [ROBO-5779] - #1809
feat(cli): serve the pre-warmed runtime over uipath-ipc alongside HTTP [ROBO-5779]#1809eduard-dumitru wants to merge 14 commits into
Conversation
72b8383 to
952d047
Compare
c3a6d7d to
7e8f4fb
Compare
🚨 Heads up:
|
7e8f4fb to
f8eff08
Compare
There was a problem hiding this comment.
Pull request overview
Adds a uipath-ipc named-pipe transport to uipath server, served concurrently with the existing HTTP transport, and refactors shared job execution into a single core path used by both transports.
Changes:
- Introduces an IPC contract/service (
IPythonRuntimeServer, DTOs,start_ipc_server) and runs it alongside the HTTP server when--server-socketis provided. - Extracts shared job execution into
_run_command_isolatedand routes HTTP/startthrough it. - Adds unit/integration tests covering transport composition, IPC behavior, and job-core error branches; updates packaging/sonar config for the new IPC module.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| sonar-project.properties | Suppresses Sonar naming rules for PascalCase IPC contract/DTOs in cli_server_ipc.py only. |
| packages/uipath/uv.lock | Bumps uipath version and locks uipath-ipc dependency from internal feed. |
| packages/uipath/tests/cli/test_server_transport.py | Verifies _serve composes HTTP+IPC correctly and CLI wires args through. |
| packages/uipath/tests/cli/test_server_job_core.py | Unit tests for _run_command_isolated error/edge branches. |
| packages/uipath/tests/cli/test_server_ipc.py | IPC server tests (StartJob/StopJob + env isolation). |
| packages/uipath/src/uipath/_cli/cli_server.py | Adds IPC integration, shared job core, and updates HTTP handler to use it. |
| packages/uipath/src/uipath/_cli/cli_server_ipc.py | New IPC contract/DTOs/service and server bootstrap. |
| packages/uipath/pyproject.toml | Adds uipath-ipc to project dependencies and configures uv index pinning. |
Comments suppressed due to low confidence (1)
packages/uipath/src/uipath/_cli/cli_server.py:292
- When
_run_command_isolatedreports a request-shaped failure (e.g., invalid working directory), the HTTP response should keep a 4xx status instead of always defaulting to 200 for any nonzero ExitCode.
return web.json_response(
{"success": False, "job_key": job_key, "error": result["Error"]}
)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
radu-mocanu
left a comment
There was a problem hiding this comment.
Blocking: public install is broken
Please remove uipath-ipc from the required uipath dependencies, or publish it to public PyPI before merging. The PR note says the package currently resolves only from the internal Azure feed. That uv configuration is not included in the published wheel, so public pip install uipath cannot resolve this required dependency.
Also bound the runtime dependency’s minor version when it is released publicly, for example uipath-ipc>=2.5.1,<2.6.0, to protect consumers from incompatible minor releases.
…P [ROBO-5779] `uipath server` serves an HTTP channel and, when --server-socket is given, a uipath-ipc named-pipe channel concurrently — both behind one env/cwd-isolated job core. Adds the IPythonRuntimeServer contract (StartJob/StopJob) with typed PythonRunRequest/PythonRunResult dataclasses mirroring the .NET side, plus tests. Adds uipath-ipc as a dependency. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…OBO-5779] Move the wire-dictated PascalCase surface (PythonRunRequest/PythonRunResult, IPythonRuntimeServer, PythonRuntimeService, start_ipc_server) out of cli_server into cli_server_ipc, re-exported for backward compatibility. Scopes the Sonar S100/S116 suppression to that single file so the rest of the tree stays strict. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cover the missing/empty-Command rejection and the StopJob bool ack over the uipath-ipc channel, taking cli_server_ipc to full new-code coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Sonar: use resourceKey (not resourcePath) for the multicriteria issue ignore. - version: bump uipath 2.13.13 -> 2.13.14 (2.13.13 already on PyPI). - mypy: parametrize dict annotations in the server tests; ignore [type-abstract] on get_proxy(IPythonRuntimeServer) (proxying an interface is intended). - ruff: wrap over-long lines to the 88-col format. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add direct unit tests for _run_command_isolated's error paths (uninitialized state, bad working directory, SystemExit exit-code mapping, unexpected exception), lifting new-code coverage over the SonarCloud 90% gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- HTTP: request-shaped errors (bad working directory) return 400 again, not 200 — the shared job core flags them (ClientError) and handle_start maps that to 4xx; IPC keeps returning ExitCode/Error. - PythonRunRequest.Args typed str | list[str] | None to match parse_args and all callers (the .NET peer sends a string; HTTP/tests may send a list); add parse_args tests covering the string (shlex) path production uses. - test_server_ipc: replace the fixed time.sleep(0.5) startup delay with a bounded readiness poll (connect + no-op StopJob until the pipe answers). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
f8eff08 to
7157cbb
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
packages/uipath/src/uipath/_cli/cli_server.py:265
environmentVariablesis coerced with... or {}and then validated withif env_vars and not isinstance(env_vars, dict), which lets falsy non-dict values (e.g.[],"") silently pass as{}. This can mask malformed requests instead of returning 400.
env_vars = get_field(message, "environmentVariables", "EnvironmentVariables") or {}
working_dir = get_field(message, "workingDirectory", "WorkingDirectory")
if env_vars and not isinstance(env_vars, dict):
return web.json_response(
| os.environ.clear() | ||
| os.environ.update(_state.baseline_env) | ||
| if isinstance(env_vars, dict): | ||
| os.environ.update(env_vars) |
There was a problem hiding this comment.
this is a pre-existing condition despite all of the code moving
✅Done |
…[ROBO-5779] Move the transport-agnostic job core (server state, command table, arg parsing, and the isolated command runner) out of cli_server into a new _server_core module. Both the HTTP (cli_server) and uipath-ipc (cli_server_ipc) transports now import it at module top level, so cli_server_ipc no longer needs its function-local import of cli_server and the two modules no longer form an import cycle. Also switch the Windows branch of _run_server to asyncio.Runner(loop_factory=ProactorEventLoop): it runs the server on the Proactor loop without mutating the thread's current-loop slot and tears tasks down cleanly on exit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…OBO-5779] The previous commit moved COMMANDS, _state, parse_args and _run_command_isolated into _server_core, but the tests still reached for them through cli_server. With no_implicit_reexport enabled, mypy rejects that (attr-defined). Point each test at the owning module instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Decouple the uipath-ipc channel from --server-socket. Previously the presence of --server-socket both set the HTTP socket and implicitly enabled IPC (pipe name = its basename), overloading one flag with three meanings and surprising consumers of `uipath server` that only wanted to control the HTTP socket. --server-socket now means only "the HTTP socket." IPC is served only when --ipc-pipe names a pipe, on that name verbatim (both sides agree on it out of band; no basename derivation). Omitting --ipc-pipe is HTTP-only, silently. The .NET Handler must pass --ipc-pipe to keep serving IPC (paired change). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ict [ROBO-5779]
Preserve the raw environmentVariables value and validate it whenever the field
is present and not a dict, then default to {}. Previously `... or {}` coerced
falsy non-dicts ([], "", 0) to {} before the isinstance check ran, so those
values were silently accepted instead of returning 400.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cy [ROBO-5779] Drop uipath-ipc from the required runtime dependencies. It is now imported lazily inside start_ipc_server, so `pip install uipath` no longer pulls it and importing cli_server (HTTP-only serving) never touches it. The DTOs and the IPythonRuntimeServer contract are pure stdlib and don't reference it. If --ipc-pipe is requested but uipath-ipc is not installed, fail fast with a clear message rather than silently degrading — the .NET Handler only passes --ipc-pipe when the venv is IPC-capable, so the mismatch is a broken image that should halt loudly. Prebaked venvs that ship uipath-ipc get the IPC channel as before. uipath-ipc moves to the dev dependency group (tests + mypy still need it); the lock is regenerated accordingly. This also resolves the public-install concern at its root: uipath no longer requires uipath-ipc at all. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow up the previous change (uipath-ipc dropped from required deps) by declaring it honestly as an optional dependency: `pip install uipath[ipc]`. This documents the feature and hints the supported version range (>=2.5.1,<2.6.0) without making it a hard dependency — base `pip install uipath` stays free of it, so the public-install concern remains resolved. Runtime behaviour is unchanged: uipath-ipc is still imported lazily in start_ipc_server, and --ipc-pipe without it fails fast (the error now points at `uipath[ipc]`). It stays in the dev group so tests + mypy still install it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…5779] Hoist _unique_pipe() and the coroutine creation out of the pytest.raises block so only asyncio.run(coro) can raise inside it (SonarCloud S5778). This makes the test assert that start_ipc_server is what fails, not an incidental setup call. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bf9c244 to
5d69c8c
Compare
|



Adds a uipath-ipc named-pipe transport to
uipath server, served alongside the existing HTTP-over-socket path (ROBO-5779).What
uipath serverserves both channels concurrently: HTTP (aiohttp over a Unix socket, or TCP on Windows) and — when--server-socketis given — a uipath-ipc named pipe. HTTP is never torn down; the IPC pipe name is the socket's basename, so the executor spawns the server the same way regardless of channel.IPythonRuntimeServer(StartJob/StopJob) viaNamedPipeServerTransport; both edges run jobs through one env/cwd-isolated core (_run_command_isolated).PythonRunRequest/PythonRunResult) mirroring the .NET side.StopJobreturns aboolso CoreIPC treats it as request/response, not fire-and-forget — cancellation itself is a no-op for now (Phase 1).uipath-ipc>=2.5.1(internal Azure Artifacts feed for now — see thepyprojectnote; a public-PyPIuipath-ipcrelease is a prerequisite for a publicpip install uipath).test_server_ipc.py(IPC server) andtest_server_transport.py(channel composition /_run_serverarg resolution / CLI wiring).Notes
PythonRuntimePreferIpcFPS feature flag, Low-Code only) — separate PR inUiPath/hdens.main; pending green CI (cross-tests + Sonar) before ready.🤖 Generated with Claude Code