From f9d6dc1ac96b3ae4114af0df95381ae60318bd27 Mon Sep 17 00:00:00 2001
From: Jackp0t <16182712+jackkp0t@user.noreply.gitee.com>
Date: Tue, 22 Sep 2026 19:37:15 +0800
Subject: [PATCH] fix: harden orchestrator parsing defaults
---
tests/agents/test_orchestrator_chain.py | 17 +++++++++++++++++
utu/agents/common.py | 2 +-
utu/agents/orchestrator/chain.py | 2 +-
3 files changed, 19 insertions(+), 2 deletions(-)
create mode 100644 tests/agents/test_orchestrator_chain.py
diff --git a/tests/agents/test_orchestrator_chain.py b/tests/agents/test_orchestrator_chain.py
new file mode 100644
index 00000000..6d4a20f9
--- /dev/null
+++ b/tests/agents/test_orchestrator_chain.py
@@ -0,0 +1,17 @@
+import pytest
+
+from utu.agents.common import TaskRecorder
+from utu.agents.orchestrator.chain import ChainPlanner
+from utu.agents.orchestrator.common import Recorder
+
+
+def test_parse_reports_missing_plan_block() -> None:
+ planner = object.__new__(ChainPlanner)
+ recorder = Recorder(input="summarize the report")
+
+ with pytest.raises(AssertionError, match="No tasks parsed from plan"):
+ planner._parse("the response was truncated", recorder)
+
+
+def test_task_recorder_defaults_input_to_list() -> None:
+ assert TaskRecorder().input == []
diff --git a/utu/agents/common.py b/utu/agents/common.py
index 9f816729..33e04e33 100644
--- a/utu/agents/common.py
+++ b/utu/agents/common.py
@@ -92,7 +92,7 @@ def to_dict(self):
class TaskRecorder(DataClassWithStreamEvents):
task: str = ""
trace_id: str = ""
- input: str | list[TResponseInputItem] = field(default_factory=dict)
+ input: str | list[TResponseInputItem] = field(default_factory=list)
# from RunResultStreaming
final_output: str = ""
diff --git a/utu/agents/orchestrator/chain.py b/utu/agents/orchestrator/chain.py
index c8236ceb..b91888b6 100644
--- a/utu/agents/orchestrator/chain.py
+++ b/utu/agents/orchestrator/chain.py
@@ -82,7 +82,7 @@ def _parse(self, text: str, recorder: Recorder) -> Plan:
analysis = match.group(1).strip() if match else ""
match = re.search(r"\s*\[(.*?)\]\s*", text, re.DOTALL)
- plan_content = match.group(1).strip()
+ plan_content = match.group(1).strip() if match else ""
tasks: list[Task] = []
task_pattern = r'\{"name":\s*"([^"]+)",\s*"task":\s*"([^"]+)"\s*\}'
task_matches = re.findall(task_pattern, plan_content, re.IGNORECASE)