oclaw/runtime/plan_agent_v2/tool_policy.py
oliver 37522f492a feat: plan agent v2, global chat mode, and user-mode v2 flag sync
- Add runtime/plan_agent_v2 package and shims; gateway/direct_loop/WS wiring

- Admin chat: interaction mode and specialist only in user menu; session API stores memory_mode and execution_mode only

- POST /admin/api/chat/user-mode mirrors plan_agent_version to AIA_EXPERT_PLAN_AGENT_V2_ENABLED (v2 to 1, v1 to 0)

- Composer cleanup (hidden mode select, no reasoning toggle in meta bar); tests and _local/system.env.example

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-02 18:41:45 +08:00

52 lines
1.3 KiB
Python

from __future__ import annotations
from typing import Iterable
from .models import PLAN_MODE_PLAN
from oclaw.runtime.tools.base import ToolRegistry, ToolSpec
_DEFAULT_PLAN_ALLOWLIST = frozenset(
{
"read_file",
"search_files",
"glob",
"list_directory",
"list_workspace_tree",
"search_files_context",
"system_time",
# Plan-mode control tools are non-read-only by design, but must stay callable.
"enter_plan_mode_v2",
"exit_plan_mode_v2",
}
)
def plan_mode_allowed_tool_names(extra_allowed: Iterable[str] | None = None) -> set[str]:
out = set(_DEFAULT_PLAN_ALLOWLIST)
for x in (extra_allowed or []):
n = str(x or "").strip()
if n:
out.add(n)
return out
def filter_tools_for_mode(
*,
registry: ToolRegistry,
mode: str,
extra_allowed: Iterable[str] | None = None,
) -> list[ToolSpec]:
tools = list(registry.list())
if str(mode or "").strip().lower() != PLAN_MODE_PLAN:
return tools
allow = plan_mode_allowed_tool_names(extra_allowed=extra_allowed)
out: list[ToolSpec] = []
for t in tools:
if t.name in allow or bool(t.is_read_only()):
out.append(t)
return out
__all__ = ["filter_tools_for_mode", "plan_mode_allowed_tool_names"]