feat(python/omp-rpc): added blocker field and blocked status to todo items

- Add blocked todo status and optional blocker attribute to todo items.
- Update RPC client and worker serialization logic to support todo blockers.
- Add test coverage for parsing session state with blocked todos.
This commit is contained in:
can1357
2026-07-27 05:22:10 +02:00
parent d32545874d
commit e21f7c18ea
4 changed files with 42 additions and 2 deletions
+3
View File
@@ -1673,6 +1673,7 @@ class RpcClient:
"status": cast(JsonValue, seed.status),
"notes": seed.notes,
"details": seed.details,
"blocker": seed.blocker,
}
content = seed.get("content")
@@ -1683,6 +1684,7 @@ class RpcClient:
raw_status = seed.get("status")
raw_notes = seed.get("notes")
raw_details = seed.get("details")
raw_blocker = seed.get("blocker")
if isinstance(raw_status, str):
if raw_status not in _TODO_STATUS_VALUES:
raise RpcError(f"Unsupported todo status: {raw_status}")
@@ -1697,6 +1699,7 @@ class RpcClient:
"status": cast(JsonValue, status),
"notes": raw_notes if isinstance(raw_notes, str) else None,
"details": raw_details if isinstance(raw_details, str) else None,
"blocker": raw_blocker if isinstance(raw_blocker, str) else None,
}
def is_phase_seed(seed: TodoSeed | TodoPhaseSeed) -> bool:
+7 -2
View File
@@ -21,7 +21,9 @@ InterruptMode: TypeAlias = Literal["immediate", "wait"]
StopReason: TypeAlias = Literal["stop", "length", "toolUse", "error", "aborted"]
NotifyType: TypeAlias = Literal["info", "warning", "error"]
WidgetPlacement: TypeAlias = Literal["aboveEditor", "belowEditor"]
TodoStatus: TypeAlias = Literal["pending", "in_progress", "completed", "abandoned"]
TodoStatus: TypeAlias = Literal[
"pending", "in_progress", "completed", "abandoned", "blocked"
]
ExtensionUiMethod: TypeAlias = Literal[
"select",
"confirm",
@@ -65,7 +67,7 @@ _WIDGET_PLACEMENT_VALUES: Final[frozenset[str]] = frozenset(
{"aboveEditor", "belowEditor"}
)
_TODO_STATUS_VALUES: Final[frozenset[str]] = frozenset(
{"pending", "in_progress", "completed", "abandoned"}
{"pending", "in_progress", "completed", "abandoned", "blocked"}
)
_EXTENSION_UI_METHOD_VALUES: Final[frozenset[str]] = frozenset(
{
@@ -745,6 +747,8 @@ class TodoItem:
status: TodoStatus
notes: str | None = None
details: str | None = None
# What a `blocked` task is waiting on; None for all other statuses.
blocker: str | None = None
@dataclass(slots=True, frozen=True)
@@ -1260,6 +1264,7 @@ def parse_todo_item(payload: JsonObject) -> TodoItem:
),
notes=_optional_str(payload, "notes"),
details=_optional_str(payload, "details"),
blocker=_optional_str(payload, "blocker"),
)
+31
View File
@@ -176,6 +176,37 @@ class ProtocolParsingTests(unittest.TestCase):
self.assertEqual(notification.todos[0].content, "Map tools")
self.assertEqual(notification.todos[0].status, "pending")
def test_parse_session_state_accepts_blocked_todo(self) -> None:
# Regression: the TS agent added a `blocked` todo status (with a
# `blocker` note); resuming a session whose todos were blocked must
# not fail state parsing.
state = parse_session_state(
{
"sessionId": "session-123",
"steeringMode": "one-at-a-time",
"followUpMode": "one-at-a-time",
"interruptMode": "immediate",
"todoPhases": [
{
"id": "phase-1",
"name": "Fix",
"tasks": [
{
"id": "task-1",
"content": "Open PR",
"status": "blocked",
"blocker": "waiting on maintainer go-ahead",
}
],
}
],
}
)
task = state.todo_phases[0].tasks[0]
self.assertEqual(task.status, "blocked")
self.assertEqual(task.blocker, "waiting on maintainer go-ahead")
def test_assistant_text_excludes_thinking_by_default(self) -> None:
message = {
"role": "assistant",
+1
View File
@@ -645,6 +645,7 @@ def _run_rpc_blocking(
"status": t.status,
"notes": t.notes,
"details": t.details,
"blocker": t.blocker,
}
for t in p.tasks
],