Przeglądaj źródła

fix(protocol): 未知持久化协议版本统一失败关闭

TaskProgress 只对 Recursive revision 3 启用;拒绝未来 Recursive revision 和非 1 的 Legacy revision,避免把 revision 4/99 按当前协议执行。

增加策略解析与 Runner 恢复恶意 Trace 的回归,证明拒绝发生在快照推导、状态更新或其他副作用之前,同时保留历史 Recursive revision 1 与只读 revision 2 兼容。
SamLee 15 godzin temu
rodzic
commit
9b898e676c
2 zmienionych plików z 70 dodań i 1 usunięć
  1. 19 1
      cyber_agent/core/agent_mode.py
  2. 51 0
      tests/test_agent_mode.py

+ 19 - 1
cyber_agent/core/agent_mode.py

@@ -25,6 +25,8 @@ RECURSIVE_MAX_PARALLEL_CHILDREN_CONTEXT_KEY = "_recursive_max_parallel_children"
 MAX_PARALLEL_RECURSIVE_CHILDREN = 2
 RECURSIVE_REVISION_READ_ONLY_ERROR = "recursive_revision_2_read_only"
 CURRENT_RECURSIVE_REVISION = 3
+SUPPORTED_RECURSIVE_REVISIONS = frozenset({1, 2, CURRENT_RECURSIVE_REVISION})
+UNSUPPORTED_AGENT_MODE_REVISION_ERROR = "unsupported_agent_mode_revision"
 
 
 class AgentMode(str, Enum):
@@ -59,7 +61,10 @@ class AgentPolicy:
     @property
     def requires_task_progress(self) -> bool:
         """Recursive revision 3 是否强制使用 TaskProgress。"""
-        return self.mode is AgentMode.RECURSIVE and self.revision >= 3
+        return (
+            self.mode is AgentMode.RECURSIVE
+            and self.revision == CURRENT_RECURSIVE_REVISION
+        )
 
     @property
     def is_read_only(self) -> bool:
@@ -138,6 +143,19 @@ def policy_from_context(context: Mapping[str, Any] | None) -> AgentPolicy:
     raw_revision = context.get(AGENT_MODE_REVISION_CONTEXT_KEY, 1)
     if not isinstance(raw_revision, int) or raw_revision < 1:
         raise ValueError(f"Invalid persisted agent mode revision: {raw_revision!r}")
+    if mode is AgentMode.LEGACY and raw_revision != 1:
+        raise ValueError(
+            f"{UNSUPPORTED_AGENT_MODE_REVISION_ERROR}: "
+            f"legacy revision {raw_revision}"
+        )
+    if (
+        mode is AgentMode.RECURSIVE
+        and raw_revision not in SUPPORTED_RECURSIVE_REVISIONS
+    ):
+        raise ValueError(
+            f"{UNSUPPORTED_AGENT_MODE_REVISION_ERROR}: "
+            f"recursive revision {raw_revision}"
+        )
     return _policy(mode, raw_revision)
 
 

+ 51 - 0
tests/test_agent_mode.py

@@ -152,6 +152,57 @@ class AgentModePolicyTest(unittest.IsolatedAsyncioTestCase):
                 RunConfig(trace_id=trace.trace_id, name="continue"),
             )
 
+    async def test_unknown_persisted_revisions_fail_closed(self):
+        for mode, revision in (("recursive", 4), ("recursive", 99), ("legacy", 2)):
+            with self.subTest(mode=mode, revision=revision):
+                context = {
+                    "agent_mode": mode,
+                    "agent_mode_revision": revision,
+                }
+                with self.assertRaisesRegex(
+                    ValueError,
+                    "unsupported_agent_mode_revision",
+                ):
+                    policy_from_context(context)
+
+        for revision in (4, 99):
+            trace = Trace(
+                trace_id=f"unknown-recursive-{revision}",
+                mode="agent",
+                task="must remain untouched",
+                status="stopped",
+                context={
+                    "agent_mode": "recursive",
+                    "agent_mode_revision": revision,
+                },
+            )
+            await self.store.create_trace(trace)
+            with self.assertRaisesRegex(
+                ValueError,
+                "unsupported_agent_mode_revision",
+            ):
+                await self.runner._prepare_trace(
+                    [],
+                    RunConfig(trace_id=trace.trace_id),
+                )
+            persisted = await self.store.get_trace(trace.trace_id)
+            self.assertEqual("stopped", persisted.status)
+            self.assertNotIn("run_config_snapshot", persisted.context)
+
+    async def test_only_recursive_revision_three_requires_task_progress(self):
+        self.assertFalse(policy_from_context({
+            "agent_mode": "recursive",
+            "agent_mode_revision": 1,
+        }).requires_task_progress)
+        self.assertFalse(policy_from_context({
+            "agent_mode": "recursive",
+            "agent_mode_revision": 2,
+        }).requires_task_progress)
+        self.assertTrue(policy_from_context({
+            "agent_mode": "recursive",
+            "agent_mode_revision": 3,
+        }).requires_task_progress)
+
     async def test_historical_trace_is_persisted_as_legacy_on_continue(self):
         trace = Trace(trace_id="historical", mode="agent", task="old", context={})
         await self.store.create_trace(trace)