mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
78 lines
3.3 KiB
Python
78 lines
3.3 KiB
Python
"""A workflow's frozen Actions set has to actually restrict the run.
|
|
|
|
It did not. `AgentConfig.allowed_tools` was computed by the executor, passed to launch, and then
|
|
dropped on the floor: launch resolved tools purely from the mode. So the Actions page offered a
|
|
permission toggle that no dispatch code read, which is worse than offering nothing, because it
|
|
sells a boundary that is not there. An unattended 3am run with Bash and nobody to deny an approval
|
|
is exactly the case the toggle exists for.
|
|
|
|
None still means "whatever the mode allows", so an unfrozen workflow keeps the full surface.
|
|
|
|
Run:
|
|
cd backend && .venv/bin/python -m pytest tests/test_frozen_workflow_tools.py -v
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import List
|
|
|
|
from backend.apps.agents.core.models import AgentConfig
|
|
from backend.apps.agents.manager.AgentLaunch import resolve_launch_tools
|
|
|
|
|
|
def p_resolve(config: AgentConfig, mode_tools: List[str]) -> List[str]:
|
|
"""Drives the REAL launch-time resolver, not a copy of it. A mirrored implementation here would
|
|
keep passing even if launch went back to ignoring the frozen set entirely."""
|
|
return resolve_launch_tools(mode_tools, config.allowed_tools)
|
|
|
|
|
|
P_MODE = ["Read", "Edit", "Write", "Bash", "Glob", "Grep", "WebSearch", "AskUserQuestion"]
|
|
|
|
|
|
def test_default_is_unrestricted():
|
|
"""Every normal chat posts a config with no allowed_tools. If the default were a list instead of
|
|
None, honouring it would silently strip the whole app down to that list."""
|
|
assert AgentConfig(name="chat").allowed_tools is None
|
|
|
|
|
|
def test_unfrozen_workflow_keeps_the_full_mode_surface():
|
|
assert p_resolve(AgentConfig(name="wf"), P_MODE) == P_MODE
|
|
|
|
|
|
def test_frozen_set_actually_removes_tools():
|
|
config = AgentConfig(name="wf", allowed_tools=["Read", "Glob"])
|
|
resolved = p_resolve(config, P_MODE)
|
|
assert resolved == ["Read", "Glob"]
|
|
assert "Bash" not in resolved, "the whole point: a frozen set must be able to withhold Bash"
|
|
assert "Write" not in resolved
|
|
|
|
|
|
def test_a_frozen_set_cannot_widen_the_mode():
|
|
"""Intersect, never trust. A stale saved set naming a tool the mode does not grant must not
|
|
smuggle it back in."""
|
|
config = AgentConfig(name="wf", allowed_tools=["Read", "NotebookEdit", "BrowserClick"])
|
|
resolved = p_resolve(config, ["Read", "Bash"])
|
|
assert resolved == ["Read"]
|
|
|
|
|
|
def test_an_explicitly_empty_set_grants_nothing():
|
|
"""Distinct from None on purpose. This is why the workflow edit and scheduling chats had to move
|
|
off [] and onto None: under the old dead code [] was harmless, now it means zero tools."""
|
|
assert p_resolve(AgentConfig(name="wf", allowed_tools=[]), P_MODE) == []
|
|
|
|
|
|
def test_order_follows_the_mode_not_the_saved_set():
|
|
config = AgentConfig(name="wf", allowed_tools=["Grep", "Read"])
|
|
assert p_resolve(config, P_MODE) == ["Read", "Grep"]
|
|
|
|
|
|
def test_workflow_call_sites_do_not_pass_an_empty_list():
|
|
"""Guards the migration: an [] left behind at any AgentConfig site is now a silently toolless
|
|
agent, which reads to the user as the agent being broken."""
|
|
import re
|
|
from pathlib import Path
|
|
root = Path(__file__).resolve().parents[1]
|
|
for rel in ("apps/workflows/workflows.py", "apps/workflows/executor.py"):
|
|
src = (root / rel).read_text()
|
|
assert not re.search(r"allowed_tools=\[\]", src), f"{rel} still passes an empty allowed_tools"
|