CI / test (push) Canceled after 0s
## Summary epic r04 - begin refactor ## Change Type - [x] Cowork feature - [ ] Bug fix - [ ] Core AI contribution - [ ] Test / hardening - [ ] Performance - [ ] Documentation ## Related Work Cowork Task: Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets Core AI Issue: Core Task: Related PR: ## Scope What is intentionally included? What is intentionally NOT included? ## Validation - [ ] Unit tests - [ ] Integration tests - [ ] Manual verification - [ ] Regression check Commands / evidence: ## Security Impact Permission / credential / network / customer data impact: ## Compatibility - [ ] No breaking change - [ ] Breaking change documented ## Reviewer Notes Anything Cowork reviewers should pay attention to. --------- Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com> Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com> Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com> Co-authored-by: Vu Dam Tuan <vudt15@fpt.com> Co-authored-by: Hiep Ha Van <hiephv3@fpt.com> Co-authored-by: Lam Hoang Van <lamhv7@fpt.com> Reviewed-on: #7 Co-authored-by: Duy Le Huu <duylh19@fpt.com>
87 lines
3.1 KiB
Python
87 lines
3.1 KiB
Python
"""EPIC R05-T04: before this change, ``core/chat_agent.py::run_cowork`` called
|
|
``extra_executor(name, args)`` directly for any MCP/connector tool — no
|
|
permission check at all, regardless of the "confirm before running commands"
|
|
setting. This pins the fix: an extra tool now goes through the same
|
|
``ToolPolicyGateway`` as ``run_command``, using the conservative default
|
|
capability (``UNKNOWN_SOURCE_CAPABILITIES``) since MCP tools carry no
|
|
standard risk metadata.
|
|
|
|
Runs the real engine via :class:`FakeProvider`, matching
|
|
``tests/characterization/test_run_cowork.py``'s approach.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, List
|
|
|
|
from cowork_local.core.chat_agent import run_cowork
|
|
from cowork_local.providers.base import ToolSpec
|
|
from tests.fakes import FakeProvider, ScriptedTurn
|
|
|
|
|
|
class _RecordingGate:
|
|
def __init__(self, approve: bool):
|
|
self.approve = approve
|
|
self.calls: List[Dict[str, Any]] = []
|
|
|
|
def request(self, payload: Dict[str, Any]) -> bool:
|
|
self.calls.append(payload)
|
|
return self.approve
|
|
|
|
|
|
_EXTRA_SPEC = ToolSpec(name="github__delete_repo", description="", parameters={"type": "object"})
|
|
|
|
|
|
def _run(tmp_path, provider, gate, executed: List[str]):
|
|
events: List[Dict[str, Any]] = []
|
|
messages: List[Dict[str, Any]] = [{"role": "user", "content": "hi"}]
|
|
|
|
def extra_executor(name: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
executed.append(name)
|
|
return {"ok": True, "output": "done"}
|
|
|
|
run_cowork(provider, messages, tmp_path, events.append, gate=gate,
|
|
extra_tools=[_EXTRA_SPEC], extra_executor=extra_executor)
|
|
return events
|
|
|
|
|
|
def test_mcp_style_tool_is_rejected_without_ever_calling_the_executor(tmp_path):
|
|
provider = FakeProvider([
|
|
ScriptedTurn(tool_calls=[("github__delete_repo", {})]),
|
|
ScriptedTurn(text="done"),
|
|
])
|
|
gate = _RecordingGate(approve=False)
|
|
executed: List[str] = []
|
|
events = _run(tmp_path, provider, gate, executed)
|
|
|
|
assert len(gate.calls) == 1 and gate.calls[0]["name"] == "github__delete_repo"
|
|
assert executed == [] # rejected BEFORE the extra_executor ever ran
|
|
results = [e for e in events if e.get("type") == "tool_result"]
|
|
assert results[0]["ok"] is False
|
|
|
|
|
|
def test_mcp_style_tool_runs_once_approved(tmp_path):
|
|
provider = FakeProvider([
|
|
ScriptedTurn(tool_calls=[("github__delete_repo", {})]),
|
|
ScriptedTurn(text="done"),
|
|
])
|
|
gate = _RecordingGate(approve=True)
|
|
executed: List[str] = []
|
|
events = _run(tmp_path, provider, gate, executed)
|
|
|
|
assert executed == ["github__delete_repo"]
|
|
results = [e for e in events if e.get("type") == "tool_result"]
|
|
assert results[0]["ok"] is True
|
|
|
|
|
|
def test_no_gate_preserves_auto_run_for_extra_tools(tmp_path):
|
|
"""``gate=None`` is Cowork's existing "no confirmation configured" state —
|
|
must still auto-run, exactly like before this EPIC."""
|
|
provider = FakeProvider([
|
|
ScriptedTurn(tool_calls=[("github__delete_repo", {})]),
|
|
ScriptedTurn(text="done"),
|
|
])
|
|
executed: List[str] = []
|
|
events = _run(tmp_path, provider, None, executed)
|
|
|
|
assert executed == ["github__delete_repo"]
|