Просмотр исходного кода

增加 recursive 上下文引用安全读取工具

新增 read_context_ref,仅允许 Recursive revision 2 从当前 Trace 的本地授权目录读取精确版本快照。

读取时校验 root、UID、内容哈希和引用版本,并让 get_current_context 复用统一渲染入口;Legacy、Validator 与 Remote 不获得该工具。
SamLee 20 часов назад
Родитель
Сommit
a01fd0cad2
2 измененных файлов с 101 добавлено и 34 удалено
  1. 2 1
      cyber_agent/tools/builtin/__init__.py
  2. 99 33
      cyber_agent/tools/builtin/context.py

+ 2 - 1
cyber_agent/tools/builtin/__init__.py

@@ -25,7 +25,7 @@ from cyber_agent.tools.builtin.memory import dream
 #   agent(agent_type="remote_librarian", task=...)         # 查询
 #   agent(agent_type="remote_librarian", task=...)         # 查询
 #   agent(agent_type="remote_librarian_ingest", task=...)  # 上传(异步)
 #   agent(agent_type="remote_librarian_ingest", task=...)  # 上传(异步)
 #   agent(agent_type="remote_research", task=...)          # 深度调研
 #   agent(agent_type="remote_research", task=...)          # 深度调研
-from cyber_agent.tools.builtin.context import get_current_context
+from cyber_agent.tools.builtin.context import get_current_context, read_context_ref
 from cyber_agent.tools.builtin.toolhub import toolhub_health, toolhub_search, toolhub_call
 from cyber_agent.tools.builtin.toolhub import toolhub_health, toolhub_search, toolhub_call
 from cyber_agent.tools.builtin.resource import resource_list_tools, resource_get_tool
 from cyber_agent.tools.builtin.resource import resource_list_tools, resource_get_tool
 from cyber_agent.tools.builtin.content import (
 from cyber_agent.tools.builtin.content import (
@@ -70,6 +70,7 @@ __all__ = [
     "content_suggest",
     "content_suggest",
     # 上下文工具
     # 上下文工具
     "get_current_context",
     "get_current_context",
+    "read_context_ref",
     # ToolHub 远程工具库
     # ToolHub 远程工具库
     "toolhub_health",
     "toolhub_health",
     "toolhub_search",
     "toolhub_search",

+ 99 - 33
cyber_agent/tools/builtin/context.py

@@ -1,5 +1,5 @@
 """
 """
-上下文工具 - 获取当前执行上下文
+上下文工具 - 刷新当前执行状态并读取授权协议快照
 
 
 提供 get_current_context 工具,让 Agent 可以主动获取:
 提供 get_current_context 工具,让 Agent 可以主动获取:
 - 当前计划(GoalTree)
 - 当前计划(GoalTree)
@@ -9,7 +9,15 @@
 框架也会在特定轮次自动调用此工具进行周期性上下文刷新。
 框架也会在特定轮次自动调用此工具进行周期性上下文刷新。
 """
 """
 
 
+import json
+
 from cyber_agent.tools import tool, ToolResult, ToolContext
 from cyber_agent.tools import tool, ToolResult, ToolContext
+from cyber_agent.core.agent_mode import policy_from_context
+from cyber_agent.core.context_policy import (
+    ContextPolicyError,
+    get_authorized_context_snapshot,
+    require_matching_root_task_anchor,
+)
 
 
 
 
 @tool(
 @tool(
@@ -42,38 +50,8 @@ async def get_current_context(
     if runner.trace_store and trace_id:
     if runner.trace_store and trace_id:
         trace = await runner.trace_store.get_trace(trace_id)
         trace = await runner.trace_store.get_trace(trace_id)
 
 
-    # 构建上下文内容(复用 runner 的 _build_context_injection 方法)
-    if hasattr(runner, '_build_context_injection'):
-        context_content = runner._build_context_injection(trace, goal_tree)
-    else:
-        # Fallback:只返回 GoalTree
-        if goal_tree and goal_tree.goals:
-            context_content = f"## Current Plan\n\n{goal_tree.to_prompt()}"
-        else:
-            context_content = "暂无计划信息"
-
-        # Fallback 也检查 IM 通知
-        im_config = trace.context.get("im_config") if trace else None
-        if im_config:
-            contact_id = im_config.get("contact_id")
-            chat_id = im_config.get("chat_id")
-            if contact_id and chat_id:
-                try:
-                    from cyber_agent.tools.builtin.im import chat as im_chat
-                    notification = im_chat._notifications.get((contact_id, chat_id))
-                    if notification:
-                        count = notification.get("count", 0)
-                        senders = notification.get("from", [])
-                        senders_str = ", ".join(senders)
-                        context_content += (
-                            f"\n\n## IM 消息通知\n\n"
-                            f"你有 {count} 条新消息,来自: {senders_str}\n"
-                            f"使用 `im_receive_messages(contact_id=\"{contact_id}\", chat_id=\"{chat_id}\")` 查看消息内容。"
-                        )
-                    else:
-                        context_content += "\n\n## IM 消息通知\n\n暂无新消息"
-                except (ImportError, AttributeError):
-                    pass
+    # Runner 是工具的必需上下文,统一由它渲染 Goal、通知和 Recursive 摘要。
+    context_content = runner._build_context_injection(trace, goal_tree)
 
 
     if not context_content:
     if not context_content:
         context_content = "当前无需要刷新的上下文信息"
         context_content = "当前无需要刷新的上下文信息"
@@ -83,3 +61,91 @@ async def get_current_context(
         output=context_content,
         output=context_content,
         long_term_memory="已刷新执行上下文",
         long_term_memory="已刷新执行上下文",
     )
     )
+
+
+@tool(
+    description="读取当前 Recursive Trace 已获授权的一份不可变协议上下文引用。",
+    hidden_params=["context"],
+    groups=["core"],
+)
+async def read_context_ref(
+    ref_id: str,
+    version: str,
+    context: ToolContext,
+) -> ToolResult:
+    """读取当前 Trace本地持有的精确 ContextRef版本。
+
+    Agent按需调用;工具重新加载持久化 Trace并校验模式、所有者、树归属和内容哈希,
+    不接受任意 Trace ID,也不遍历父级、祖先或兄弟。
+    """
+    store = context.get("store")
+    trace_id = context.get("trace_id")
+    if not store or not trace_id:
+        return ToolResult(
+            title="上下文引用读取失败",
+            output="",
+            error="store and trace_id are required",
+        )
+    trace = await store.get_trace(trace_id)
+    if not trace:
+        return ToolResult(
+            title="上下文引用读取失败",
+            output="",
+            error=f"Trace not found: {trace_id}",
+        )
+    policy = policy_from_context(trace.context)
+    if (
+        not policy.requires_task_protocol
+        or trace.agent_type == "validator"
+        or trace.context.get("created_by_tool") == "validator"
+    ):
+        return ToolResult(
+            title="上下文引用读取失败",
+            output="",
+            error="read_context_ref is only available to Recursive revision 2 business Agents",
+        )
+    root_trace_id = trace.context.get("root_trace_id") or trace.trace_id
+    root = trace if root_trace_id == trace.trace_id else await store.get_trace(
+        root_trace_id
+    )
+    if not root:
+        return ToolResult(
+            title="上下文引用读取失败",
+            output="",
+            error=f"Recursive root Trace not found: {root_trace_id}",
+        )
+    try:
+        require_matching_root_task_anchor(root.context, trace.context)
+        snapshot = get_authorized_context_snapshot(
+            trace.context,
+            ref_id=ref_id,
+            version=version,
+            root_trace_id=root_trace_id,
+            uid=trace.uid,
+        )
+    except ContextPolicyError as exc:
+        return ToolResult(
+            title="上下文引用读取失败",
+            output="",
+            error=str(exc),
+        )
+    payload = {
+        "ref_id": snapshot.ref_id,
+        "version": snapshot.version,
+        "kind": snapshot.kind,
+        "summary": snapshot.summary,
+        "source_trace_id": snapshot.source_trace_id,
+        "content": snapshot.content,
+    }
+    return ToolResult(
+        title="授权上下文引用",
+        output=json.dumps(
+            payload,
+            ensure_ascii=False,
+            sort_keys=True,
+            separators=(",", ":"),
+        ),
+        long_term_memory=(
+            f"已读取 {snapshot.kind} 引用 {snapshot.ref_id}@{snapshot.version[:12]}"
+        ),
+    )