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>
This commit is contained in:
Clément Drouin 2026-06-19 11:01:24 +02:00 committed by GitHub
parent 564a14365e
commit 6bedf271ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
223 changed files with 10533 additions and 6947 deletions

View file

@ -1,13 +1,11 @@
from __future__ import annotations
from pathlib import Path
from unittest.mock import AsyncMock
from acp import RequestError
from acp.schema import (
AgentMessageChunk,
AgentThoughtChunk,
ClientCapabilities,
ToolCallProgress,
ToolCallStart,
UserMessageChunk,
@ -17,7 +15,8 @@ import pytest
from tests.conftest import build_test_vibe_config
from tests.stubs.fake_backend import FakeBackend
from tests.stubs.fake_client import FakeClient
from vibe.acp.acp_agent_loop import WORKSPACE_TRUST_CAPABILITY, VibeAcpAgentLoop
from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
from vibe.acp.user_display_content import USER_DISPLAY_CONTENT_META_KEY
from vibe.core.agent_loop import AgentLoop
from vibe.core.agents.models import BuiltinAgentName
from vibe.core.config import ModelConfig, SessionLoggingConfig
@ -126,35 +125,42 @@ class TestLoadSession:
assert response.config_options[2].current_value == "off"
@pytest.mark.asyncio
async def test_load_session_resolves_workspace_trust_before_loading_config(
async def test_load_session_returns_trust_details_without_loading_project_docs(
self,
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
temp_session_dir: Path,
create_test_session,
tmp_working_directory: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
acp_agent, client = acp_agent_with_session_config
acp_agent.client_capabilities = ClientCapabilities(
field_meta={WORKSPACE_TRUST_CAPABILITY: True}
)
request_trust = AsyncMock(return_value={"decision": "trust_cwd"})
monkeypatch.setattr(client, "ext_method", request_trust)
acp_agent, _client = acp_agent_with_session_config
(tmp_working_directory / "AGENTS.md").write_text(
"Loaded session project instructions", encoding="utf-8"
)
session_id = "test-sess-trust"
create_test_session(temp_session_dir, session_id, str(tmp_working_directory))
await acp_agent.load_session(
response = await acp_agent.load_session(
cwd=str(tmp_working_directory), mcp_servers=[], session_id=session_id
)
request_trust.assert_awaited_once()
assert trusted_folders_manager.is_trusted(tmp_working_directory) is True
assert response is not None
payload = response.model_dump(mode="json", by_alias=True)
assert payload.get("_meta") == {
"workspace_trust": {
"status": "untrusted",
"details": {
"cwd": str(tmp_working_directory.resolve()),
"repoRoot": None,
"ignoredFiles": ["AGENTS.md"],
"availableDecisions": ["trust_cwd", "trust_session", "decline"],
},
}
}
assert trusted_folders_manager.is_trusted(tmp_working_directory) is None
await acp_agent.sessions[session_id].agent_loop.wait_until_ready()
system_prompt = acp_agent.sessions[session_id].agent_loop.messages[0].content
assert system_prompt is not None
assert "Loaded session project instructions" in system_prompt
assert "Loaded session project instructions" not in system_prompt
@pytest.mark.asyncio
async def test_load_session_registers_session_with_original_id(
@ -223,6 +229,51 @@ class TestLoadSession:
]
assert len(user_updates) == 1
assert user_updates[0].update.content.text == "Hello world"
assert user_updates[0].update.field_meta is None
@pytest.mark.asyncio
async def test_load_session_replays_user_display_content(
self,
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
temp_session_dir: Path,
create_test_session,
) -> None:
acp_agent, client = acp_agent_with_session_config
session_id = "replay-dsp-123456"
cwd = str(Path.cwd())
user_display_content = {
"version": "1.0.0",
"host": "mistral-vscode",
"content": [
{"type": "text", "text": "Look at "},
{
"type": "workspace_mention",
"kind": "file",
"uri": "file:///repo/src/app.ts",
"name": "app.ts",
},
],
}
messages = [
{
"role": "user",
"content": "Look at app.ts",
"user_display_content": user_display_content,
}
]
create_test_session(temp_session_dir, session_id, cwd, messages=messages)
await acp_agent.load_session(cwd=cwd, mcp_servers=[], session_id=session_id)
user_updates = [
u for u in client._session_updates if isinstance(u.update, UserMessageChunk)
]
assert len(user_updates) == 1
assert user_updates[0].update.content.text == "Look at app.ts"
assert user_updates[0].update.field_meta == {
USER_DISPLAY_CONTENT_META_KEY: user_display_content
}
@pytest.mark.asyncio
async def test_load_session_replays_assistant_messages(
@ -278,7 +329,12 @@ class TestLoadSession:
}
],
},
{"role": "tool", "tool_call_id": "call_123", "content": "file contents"},
{
"role": "tool",
"tool_call_id": "call_123",
"name": "read",
"content": "file contents",
},
]
create_test_session(temp_session_dir, session_id, cwd, messages=messages)
@ -288,15 +344,58 @@ class TestLoadSession:
u for u in client._session_updates if isinstance(u.update, ToolCallStart)
]
assert len(tool_call_starts) == 1
assert tool_call_starts[0].update.title == "read"
assert tool_call_starts[0].update.tool_call_id == "call_123"
start = tool_call_starts[0].update
assert start.tool_call_id == "call_123"
assert start.kind == "read"
# The host drops created events without a status; replayed calls are
# historical, so they must carry one.
assert start.status == "completed"
assert start.field_meta is not None
assert start.field_meta["tool_name"] == "read"
tool_results = [
u for u in client._session_updates if isinstance(u.update, ToolCallProgress)
]
assert len(tool_results) == 1
assert tool_results[0].update.tool_call_id == "call_123"
assert tool_results[0].update.status == "completed"
result = tool_results[0].update
assert result.tool_call_id == "call_123"
assert result.status == "completed"
# The host drops tool_call_update events without a kind.
assert result.kind == "read"
assert result.field_meta is not None
assert result.field_meta["tool_name"] == "read"
@pytest.mark.asyncio
async def test_load_session_skips_result_whose_call_was_not_replayed(
self,
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
temp_session_dir: Path,
create_test_session,
) -> None:
acp_agent, client = acp_agent_with_session_config
session_id = "replay-orphan-12345"
cwd = str(Path.cwd())
# A tool result whose call was not replayed (here no matching tool call
# at all, mirroring a hidden tool whose call replay returns None).
# Emitting it would orphan a tool_call_update with no preceding call.
messages = [
{"role": "user", "content": "Do something"},
{
"role": "tool",
"tool_call_id": "call_orphan",
"name": "read",
"content": "file contents",
},
]
create_test_session(temp_session_dir, session_id, cwd, messages=messages)
await acp_agent.load_session(cwd=cwd, mcp_servers=[], session_id=session_id)
tool_results = [
u for u in client._session_updates if isinstance(u.update, ToolCallProgress)
]
assert tool_results == []
@pytest.mark.asyncio
async def test_load_session_replays_reasoning_content(