Skip to content

Commit 0e16bbb

Browse files
feat: support for IS resume triggers
1 parent 521c20e commit 0e16bbb

7 files changed

Lines changed: 64 additions & 10 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-runtime"
3-
version = "0.10.2"
3+
version = "0.10.3"
44
description = "Runtime abstractions and interfaces for building agents and automation scripts in the UiPath ecosystem"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath/runtime/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from uipath.core.triggers import (
44
UiPathApiTrigger,
5+
UiPathIntegrationTrigger,
56
UiPathResumeTrigger,
67
UiPathResumeTriggerName,
78
UiPathResumeTriggerType,
@@ -60,6 +61,7 @@
6061
"UiPathResumableStorageProtocol",
6162
"UiPathResumeTriggerProtocol",
6263
"UiPathApiTrigger",
64+
"UiPathIntegrationTrigger",
6365
"UiPathResumeTrigger",
6466
"UiPathResumeTriggerType",
6567
"UiPathResumableRuntime",

src/uipath/runtime/resumable/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from uipath.core.triggers import (
44
UiPathApiTrigger,
5+
UiPathIntegrationTrigger,
56
UiPathResumeTrigger,
67
UiPathResumeTriggerType,
78
)
@@ -19,6 +20,7 @@
1920
"UiPathResumeTriggerReaderProtocol",
2021
"UiPathResumeTriggerProtocol",
2122
"UiPathApiTrigger",
23+
"UiPathIntegrationTrigger",
2224
"UiPathResumeTrigger",
2325
"UiPathResumeTriggerType",
2426
]

src/uipath/runtime/resumable/runtime.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,10 @@ async def stream(
145145
options.resume = True
146146

147147
async def _get_fired_triggers(self) -> dict[str, Any] | None:
148-
"""Check stored triggers for any that have already fired (excluding API triggers).
148+
"""Check stored triggers for any that have already fired.
149149
150-
API triggers cannot be completed before suspending the job, so they are skipped.
150+
Skips async-external triggers (API, Inbox) whose payloads only arrive
151+
asynchronously and cannot be polled at suspend time.
151152
152153
Returns:
153154
A resume map of {interrupt_id: resume_data} for fired triggers, or None.
@@ -156,10 +157,13 @@ async def _get_fired_triggers(self) -> dict[str, Any] | None:
156157
if not triggers:
157158
return None
158159

159-
non_api_triggers = [
160-
t for t in triggers if t.trigger_type != UiPathResumeTriggerType.API
160+
pollable_triggers = [
161+
t
162+
for t in triggers
163+
if t.trigger_type
164+
not in (UiPathResumeTriggerType.API, UiPathResumeTriggerType.INBOX)
161165
]
162-
return await self._build_resume_map(non_api_triggers)
166+
return await self._build_resume_map(pollable_triggers)
163167

164168
async def _restore_resume_input(
165169
self,

src/uipath/runtime/resumable/trigger.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
from uipath.core.triggers import (
44
UiPathApiTrigger,
5+
UiPathIntegrationTrigger,
56
UiPathResumeTrigger,
67
UiPathResumeTriggerName,
78
UiPathResumeTriggerType,
89
)
910

1011
__all__ = [
1112
"UiPathApiTrigger",
13+
"UiPathIntegrationTrigger",
1214
"UiPathResumeTrigger",
1315
"UiPathResumeTriggerName",
1416
"UiPathResumeTriggerType",

tests/test_resumable.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,50 @@ async def read_trigger_impl(trigger: UiPathResumeTrigger) -> dict[str, Any]:
499499
# Delegate should have been executed only once (no auto-resume)
500500
assert runtime_impl.execution_count == 1
501501

502+
@pytest.mark.asyncio
503+
async def test_resumable_skips_inbox_triggers_on_auto_resume_check(self) -> None:
504+
"""Inbox triggers should be skipped when checking for auto-resume after suspension.
505+
506+
Inbox triggers are async-external (payload delivered via Integration
507+
Services), so calling read_trigger on them at suspend time would hit a
508+
404 and fault the run. They should behave like API triggers here.
509+
"""
510+
511+
runtime_impl = MultiTriggerMockRuntime()
512+
storage = StatefulStorageMock()
513+
trigger_manager = make_trigger_manager_mock()
514+
515+
def create_inbox_trigger(data: dict[str, Any]) -> UiPathResumeTrigger:
516+
return UiPathResumeTrigger(
517+
interrupt_id="", # Will be set by resumable runtime
518+
trigger_type=UiPathResumeTriggerType.INBOX,
519+
payload=data,
520+
)
521+
522+
trigger_manager.create_trigger = AsyncMock(side_effect=create_inbox_trigger) # type: ignore[method-assign]
523+
524+
# Track whether read_trigger is ever called — it must NOT be, otherwise
525+
# the filter is broken and we'd hit the payload endpoint prematurely.
526+
trigger_manager.read_trigger = AsyncMock(side_effect=AssertionError("read_trigger must not be called for Inbox triggers pre-resume")) # type: ignore[method-assign]
527+
528+
resumable = UiPathResumableRuntime(
529+
delegate=runtime_impl,
530+
storage=storage,
531+
trigger_manager=trigger_manager,
532+
runtime_id="runtime-1",
533+
)
534+
535+
result = await resumable.execute({})
536+
537+
assert result.status == UiPathRuntimeStatus.SUSPENDED
538+
assert result.triggers is not None
539+
assert len(result.triggers) == 2
540+
assert all(
541+
t.trigger_type == UiPathResumeTriggerType.INBOX for t in result.triggers
542+
)
543+
trigger_manager.read_trigger.assert_not_called()
544+
assert runtime_impl.execution_count == 1
545+
502546
@pytest.mark.asyncio
503547
async def test_resumable_auto_resumes_task_triggers_but_not_api_triggers(
504548
self,

uv.lock

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

0 commit comments

Comments
 (0)