Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com> Co-authored-by: Hdandria <henri.dandria@mistral.ai> Co-authored-by: Ivana Dunisijevic <ivana.dunisijevic@mistral.ai> Co-authored-by: Jean Burellier <sheplu@users.noreply.github.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Mert Unsal <mert.unsal@mistral.ai> Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com> Co-authored-by: Paul VEZIA <166131032+le-codeur-rapide@users.noreply.github.com> Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai> Co-authored-by: Val <102326092+vdeva@users.noreply.github.com> Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com> Co-authored-by: renovate-mistral[bot] <253709520+renovate-mistral[bot]@users.noreply.github.com> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
140 lines
5 KiB
Python
140 lines
5 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from tests.acp.conftest import _create_acp_agent
|
|
from tests.conftest import build_test_vibe_config
|
|
from tests.stubs.fake_backend import FakeBackend
|
|
from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
|
|
from vibe.acp.exceptions import InvalidRequestError, SessionNotFoundError
|
|
from vibe.core.agent_loop import AgentLoop
|
|
from vibe.core.config import ModelConfig
|
|
from vibe.core.trusted_folders import trusted_folders_manager
|
|
|
|
|
|
async def _system_prompt(acp_agent_loop: VibeAcpAgentLoop, session_id: str) -> str:
|
|
session = acp_agent_loop.sessions[session_id]
|
|
await session.agent_loop.wait_until_ready()
|
|
return session.agent_loop.messages[0].content or ""
|
|
|
|
|
|
@pytest.fixture
|
|
def acp_agent_loop(
|
|
backend: FakeBackend, monkeypatch: pytest.MonkeyPatch
|
|
) -> VibeAcpAgentLoop:
|
|
config = build_test_vibe_config(
|
|
active_model="devstral-latest",
|
|
models=[
|
|
ModelConfig(
|
|
name="devstral-latest", provider="mistral", alias="devstral-latest"
|
|
),
|
|
ModelConfig(
|
|
name="devstral-small", provider="mistral", alias="devstral-small"
|
|
),
|
|
],
|
|
)
|
|
|
|
class PatchedAgentLoop(AgentLoop):
|
|
def __init__(self, *args, **kwargs) -> None:
|
|
super().__init__(*args, **{**kwargs, "backend": backend})
|
|
self._base_config = config
|
|
self.agent_manager.invalidate_config()
|
|
|
|
monkeypatch.setattr("vibe.acp.acp_agent_loop.AgentLoop", PatchedAgentLoop)
|
|
return _create_acp_agent()
|
|
|
|
|
|
class TestWorkspaceTrustExtMethods:
|
|
@pytest.mark.asyncio
|
|
async def test_workspace_trust_status_returns_details_even_after_decline(
|
|
self, acp_agent_loop: VibeAcpAgentLoop, tmp_working_directory: Path
|
|
) -> None:
|
|
(tmp_working_directory / "AGENTS.md").write_text(
|
|
"Trust me later", encoding="utf-8"
|
|
)
|
|
trusted_folders_manager.add_untrusted(tmp_working_directory)
|
|
|
|
response = await acp_agent_loop.ext_method(
|
|
"trust/status", {"cwd": str(tmp_working_directory)}
|
|
)
|
|
|
|
assert response == {
|
|
"trust_status": "untrusted",
|
|
"details": {
|
|
"cwd": str(tmp_working_directory.resolve()),
|
|
"repoRoot": None,
|
|
"ignoredFiles": ["AGENTS.md"],
|
|
"availableDecisions": ["trust_cwd", "trust_session", "decline"],
|
|
},
|
|
}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_workspace_trust_decision_trusts_session_and_reloads_session(
|
|
self, acp_agent_loop: VibeAcpAgentLoop, tmp_working_directory: Path
|
|
) -> None:
|
|
(tmp_working_directory / "AGENTS.md").write_text(
|
|
"Reloaded session instructions", encoding="utf-8"
|
|
)
|
|
|
|
session_response = await acp_agent_loop.new_session(
|
|
cwd=str(tmp_working_directory), mcp_servers=[]
|
|
)
|
|
assert session_response.session_id is not None
|
|
assert "Reloaded session instructions" not in await _system_prompt(
|
|
acp_agent_loop, session_response.session_id
|
|
)
|
|
|
|
response = await acp_agent_loop.ext_method(
|
|
"trust/decision",
|
|
{
|
|
"cwd": str(tmp_working_directory),
|
|
"decision": "trust_session",
|
|
"session_id": session_response.session_id,
|
|
},
|
|
)
|
|
|
|
assert response == {"trust_status": "session", "details": None}
|
|
normalized = str(tmp_working_directory.resolve())
|
|
assert normalized in trusted_folders_manager._session_trusted
|
|
assert normalized not in trusted_folders_manager._trusted
|
|
assert "Reloaded session instructions" in await _system_prompt(
|
|
acp_agent_loop, session_response.session_id
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_workspace_trust_decision_rejects_unavailable_decision(
|
|
self, acp_agent_loop: VibeAcpAgentLoop, tmp_working_directory: Path
|
|
) -> None:
|
|
(tmp_working_directory / "AGENTS.md").write_text(
|
|
"No repo decision here", encoding="utf-8"
|
|
)
|
|
|
|
with pytest.raises(InvalidRequestError):
|
|
await acp_agent_loop.ext_method(
|
|
"trust/decision",
|
|
{"cwd": str(tmp_working_directory), "decision": "trust_repo"},
|
|
)
|
|
|
|
assert trusted_folders_manager.is_trusted(tmp_working_directory) is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_workspace_trust_decision_rejects_unknown_session_id(
|
|
self, acp_agent_loop: VibeAcpAgentLoop, tmp_working_directory: Path
|
|
) -> None:
|
|
(tmp_working_directory / "AGENTS.md").write_text(
|
|
"Unknown session", encoding="utf-8"
|
|
)
|
|
|
|
with pytest.raises(SessionNotFoundError):
|
|
await acp_agent_loop.ext_method(
|
|
"trust/decision",
|
|
{
|
|
"cwd": str(tmp_working_directory),
|
|
"decision": "trust_session",
|
|
"session_id": "missing-session",
|
|
},
|
|
)
|
|
|
|
assert trusted_folders_manager.is_trusted(tmp_working_directory) is None
|