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

@ -0,0 +1,765 @@
from __future__ import annotations
import ast
from pathlib import Path
import pytest
from tests import TESTS_ROOT
from tests.conftest import build_test_agent_loop, build_test_vibe_config
from tests.stubs.fake_backend import FakeBackend
from vibe.core.agents.manager import AgentManager
from vibe.core.agents.models import (
BUILTIN_AGENTS,
CHAT,
AgentProfile,
AgentSafety,
AgentType,
BuiltinAgentName,
_deep_merge,
)
from vibe.core.config import VibeConfig
from vibe.core.prompts import UtilityPrompt
from vibe.core.tools.base import ToolPermission
from vibe.core.types import LLMChunk, LLMMessage, LLMUsage, Role
class TestDeepMerge:
def test_simple_merge(self) -> None:
base = {"a": 1, "b": 2}
override = {"c": 3}
result = _deep_merge(base, override)
assert result == {"a": 1, "b": 2, "c": 3}
def test_override_existing_key(self) -> None:
base = {"a": 1, "b": 2}
override = {"b": 3}
result = _deep_merge(base, override)
assert result == {"a": 1, "b": 3}
def test_nested_dict_merge(self) -> None:
base = {"a": {"x": 1, "y": 2}}
override = {"a": {"y": 3, "z": 4}}
result = _deep_merge(base, override)
assert result == {"a": {"x": 1, "y": 3, "z": 4}}
def test_deeply_nested_merge(self) -> None:
base = {"a": {"b": {"c": 1}}}
override = {"a": {"b": {"d": 2}}}
result = _deep_merge(base, override)
assert result == {"a": {"b": {"c": 1, "d": 2}}}
def test_override_dict_with_non_dict(self) -> None:
base = {"a": {"x": 1}}
override = {"a": "replaced"}
result = _deep_merge(base, override)
assert result == {"a": "replaced"}
def test_override_non_dict_with_dict(self) -> None:
base = {"a": "string"}
override = {"a": {"x": 1}}
result = _deep_merge(base, override)
assert result == {"a": {"x": 1}}
def test_preserves_original_base(self) -> None:
base = {"a": 1, "b": {"c": 2}}
override = {"b": {"d": 3}}
_deep_merge(base, override)
assert base == {"a": 1, "b": {"c": 2}}
def test_empty_override(self) -> None:
base = {"a": 1, "b": 2}
override: dict = {}
result = _deep_merge(base, override)
assert result == {"a": 1, "b": 2}
def test_empty_base(self) -> None:
base: dict = {}
override = {"a": 1}
result = _deep_merge(base, override)
assert result == {"a": 1}
def test_lists_are_overridden_not_merged(self) -> None:
"""Lists should be replaced entirely, not merged element-by-element."""
base = {"tools": ["read", "grep", "bash"]}
override = {"tools": ["write_file"]}
result = _deep_merge(base, override)
assert result == {"tools": ["write_file"]}
def test_nested_lists_are_overridden_not_merged(self) -> None:
"""Nested lists in dicts should also be replaced, not merged."""
base = {"config": {"enabled_tools": ["a", "b", "c"], "other": 1}}
override = {"config": {"enabled_tools": ["x", "y"]}}
result = _deep_merge(base, override)
assert result == {"config": {"enabled_tools": ["x", "y"], "other": 1}}
class TestAgentSafety:
def test_safety_enum_values(self) -> None:
assert AgentSafety.SAFE == "safe"
assert AgentSafety.NEUTRAL == "neutral"
assert AgentSafety.DESTRUCTIVE == "destructive"
assert AgentSafety.YOLO == "yolo"
def test_default_agent_is_neutral(self) -> None:
assert BUILTIN_AGENTS[BuiltinAgentName.DEFAULT].safety == AgentSafety.NEUTRAL
def test_auto_approve_agent_is_yolo(self) -> None:
assert BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE].safety == AgentSafety.YOLO
def test_plan_agent_is_safe(self) -> None:
assert BUILTIN_AGENTS[BuiltinAgentName.PLAN].safety == AgentSafety.SAFE
def test_accept_edits_agent_is_destructive(self) -> None:
assert (
BUILTIN_AGENTS[BuiltinAgentName.ACCEPT_EDITS].safety
== AgentSafety.DESTRUCTIVE
)
class TestAgentProfile:
def test_all_builtin_agents_have_valid_names(self) -> None:
acp_only = {BuiltinAgentName.CHAT}
assert set(BUILTIN_AGENTS.keys()) == set(BuiltinAgentName) - acp_only
def test_display_name_property(self) -> None:
assert BUILTIN_AGENTS[BuiltinAgentName.DEFAULT].display_name == "Default"
assert (
BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE].display_name == "Auto Approve"
)
assert BUILTIN_AGENTS[BuiltinAgentName.PLAN].display_name == "Plan"
assert (
BUILTIN_AGENTS[BuiltinAgentName.ACCEPT_EDITS].display_name == "Accept Edits"
)
def test_description_property(self) -> None:
assert (
"approval" in BUILTIN_AGENTS[BuiltinAgentName.DEFAULT].description.lower()
)
assert (
"auto" in BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE].description.lower()
)
assert "read-only" in BUILTIN_AGENTS[BuiltinAgentName.PLAN].description.lower()
assert (
"edits" in BUILTIN_AGENTS[BuiltinAgentName.ACCEPT_EDITS].description.lower()
)
def test_explore_is_subagent(self) -> None:
assert BUILTIN_AGENTS[BuiltinAgentName.EXPLORE].agent_type == AgentType.SUBAGENT
def test_agents(self) -> None:
agents = [
name
for name, profile in BUILTIN_AGENTS.items()
if profile.agent_type == AgentType.AGENT
]
assert set(agents) == {
BuiltinAgentName.DEFAULT,
BuiltinAgentName.PLAN,
BuiltinAgentName.ACCEPT_EDITS,
BuiltinAgentName.AUTO_APPROVE,
BuiltinAgentName.LEAN,
}
class TestAgentApplyToConfig:
def test_profile_disabled_tools_are_merged_with_base_config(self) -> None:
base = VibeConfig(
include_project_context=False,
include_prompt_detail=False,
disabled_tools=["ask_user_question"],
)
result = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT].apply_to_config(base)
assert set(result.disabled_tools) == {"ask_user_question", "exit_plan_mode"}
def test_profile_disabled_tools_preserve_user_disabled_tools(self) -> None:
base = VibeConfig(
include_project_context=False,
include_prompt_detail=False,
disabled_tools=["ask_user_question", "custom_tool"],
)
result = BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE].apply_to_config(base)
assert set(result.disabled_tools) == {
"ask_user_question",
"custom_tool",
"exit_plan_mode",
}
def test_base_disabled_tools_are_filtered_from_profile_enabled_tools(self) -> None:
base = VibeConfig(
include_project_context=False,
include_prompt_detail=False,
disabled_tools=["ask_user_question"],
)
result = CHAT.apply_to_config(base)
assert "ask_user_question" not in result.enabled_tools
assert "grep" in result.enabled_tools
assert "read" in result.enabled_tools
assert "task" in result.enabled_tools
def test_base_disabled_tools_filter_supports_glob_patterns(self) -> None:
base = VibeConfig(
include_project_context=False,
include_prompt_detail=False,
disabled_tools=["ask_*"],
)
agent = AgentProfile(
name="custom",
display_name="Custom",
description="",
safety=AgentSafety.NEUTRAL,
overrides={"enabled_tools": ["grep", "ask_user_question", "ask_extra"]},
)
result = agent.apply_to_config(base)
assert result.enabled_tools == ["grep"]
def test_empty_base_disabled_tools_leaves_enabled_tools_untouched(self) -> None:
base = VibeConfig(
include_project_context=False,
include_prompt_detail=False,
disabled_tools=[],
)
result = CHAT.apply_to_config(base)
assert "ask_user_question" in result.enabled_tools
def test_custom_prompt_found_in_global_when_missing_from_project(
self, mock_prompts_dirs: tuple[Path, Path]
) -> None:
_, global_prompts = mock_prompts_dirs
(global_prompts / "cc.md").write_text("Global custom prompt")
base = VibeConfig(include_project_context=False, include_prompt_detail=False)
agent = AgentProfile(
name="cc",
display_name="Cc",
description="",
safety=AgentSafety.NEUTRAL,
overrides={"system_prompt_id": "cc"},
)
result = agent.apply_to_config(base)
assert result.system_prompt_id == "cc"
assert result.system_prompt == "Global custom prompt"
def test_custom_prompt_overrides_builtin(
self, mock_prompts_dirs: tuple[Path, Path]
) -> None:
"""Custom prompts in .vibe/prompts/ should override built-in prompts.
A user-provided explore.md (or any built-in prompt name) in the
project or user prompts directory must take priority over the
bundled SystemPrompt enum.
"""
project_prompts, _ = mock_prompts_dirs
(project_prompts / "explore.md").write_text("My custom explore prompt")
config = VibeConfig(
system_prompt_id="explore",
include_project_context=False,
include_prompt_detail=False,
)
assert config.system_prompt == "My custom explore prompt"
def test_custom_compaction_prompt_found_in_global_when_missing_from_project(
self, mock_prompts_dirs: tuple[Path, Path]
) -> None:
_, global_prompts = mock_prompts_dirs
(global_prompts / "proofs.md").write_text("Global custom compaction prompt")
config = VibeConfig(
compaction_prompt_id="proofs",
include_project_context=False,
include_prompt_detail=False,
)
assert config.compaction_prompt == "Global custom compaction prompt"
def test_custom_compaction_prompt_overrides_builtin(
self, mock_prompts_dirs: tuple[Path, Path]
) -> None:
project_prompts, _ = mock_prompts_dirs
(project_prompts / "compact.md").write_text("My custom compact prompt")
config = VibeConfig(
compaction_prompt_id="compact",
include_project_context=False,
include_prompt_detail=False,
)
assert config.compaction_prompt == "My custom compact prompt"
def test_default_compaction_prompt_falls_back_to_builtin(
self, mock_prompts_dirs: tuple[Path, Path]
) -> None:
config = VibeConfig(include_project_context=False, include_prompt_detail=False)
assert config.compaction_prompt == UtilityPrompt.COMPACT.read()
def test_invalid_compaction_prompt_reports_setting_name(
self, mock_prompts_dirs: tuple[Path, Path]
) -> None:
project_prompts, user_prompts = mock_prompts_dirs
(project_prompts / "alpha.md").write_text("a")
(user_prompts / "beta.md").write_text("b")
with pytest.raises(ValueError) as exc_info:
VibeConfig(
compaction_prompt_id="unknown",
include_project_context=False,
include_prompt_detail=False,
)
error_text = str(exc_info.value)
assert "Invalid compaction_prompt_id value: 'unknown'" in error_text
assert 'available prompts ("compact")' in error_text
assert '(available: "alpha", "beta")' in error_text
@pytest.mark.parametrize(
"malicious_id",
["../../../etc/passwd", "..", ".", "subdir/compact", "back\\slash", ""],
)
def test_prompt_id_rejects_path_traversal(
self, mock_prompts_dirs: tuple[Path, Path], malicious_id: str
) -> None:
with pytest.raises(ValueError, match="must be a bare filename"):
VibeConfig(
compaction_prompt_id=malicious_id,
include_project_context=False,
include_prompt_detail=False,
)
class TestAgentProfileOverrides:
def test_default_agent_disables_exit_plan_mode(self) -> None:
overrides = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT].overrides
assert "exit_plan_mode" in overrides.get("base_disabled", [])
def test_auto_approve_agent_sets_bypass_tool_permissions(self) -> None:
overrides = BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE].overrides
assert overrides.get("bypass_tool_permissions") is True
def test_plan_agent_restricts_tools(self) -> None:
overrides = BUILTIN_AGENTS[BuiltinAgentName.PLAN].overrides
assert "tools" in overrides
tools = overrides["tools"]
assert "write_file" in tools
assert "edit" in tools
assert tools["write_file"]["permission"] == "never"
assert tools["edit"]["permission"] == "never"
assert len(tools["write_file"]["allowlist"]) > 0
assert len(tools["edit"]["allowlist"]) > 0
def test_accept_edits_agent_sets_tool_permissions(self) -> None:
overrides = BUILTIN_AGENTS[BuiltinAgentName.ACCEPT_EDITS].overrides
assert "tools" in overrides
tools_config = overrides["tools"]
assert "write_file" in tools_config
assert "edit" in tools_config
assert tools_config["write_file"]["permission"] == "always"
assert tools_config["edit"]["permission"] == "always"
class TestAgentManagerCycling:
@pytest.fixture
def base_config(self) -> VibeConfig:
return build_test_vibe_config(
include_project_context=False, include_prompt_detail=False
)
@pytest.fixture
def backend(self) -> FakeBackend:
return FakeBackend([
LLMChunk(
message=LLMMessage(role=Role.assistant, content="Test response"),
usage=LLMUsage(prompt_tokens=10, completion_tokens=5),
)
])
def test_get_agent_order_includes_primary_agents(
self, base_config: VibeConfig, backend: FakeBackend
) -> None:
agent = build_test_agent_loop(
config=base_config, agent_name=BuiltinAgentName.DEFAULT, backend=backend
)
order = agent.agent_manager.get_agent_order()
assert len(order) == 4
assert BuiltinAgentName.DEFAULT in order
assert BuiltinAgentName.AUTO_APPROVE in order
assert BuiltinAgentName.PLAN in order
assert BuiltinAgentName.ACCEPT_EDITS in order
def test_next_agent_cycles_through_all(
self, base_config: VibeConfig, backend: FakeBackend
) -> None:
agent = build_test_agent_loop(
config=base_config, agent_name=BuiltinAgentName.DEFAULT, backend=backend
)
order = agent.agent_manager.get_agent_order()
current = agent.agent_manager.active_profile
visited = [current.name]
for _ in range(len(order) - 1):
current = agent.agent_manager.next_agent(current)
visited.append(current.name)
assert len(set(visited)) == len(order)
def test_next_agent_wraps_around(
self, base_config: VibeConfig, backend: FakeBackend
) -> None:
agent = build_test_agent_loop(
config=base_config, agent_name=BuiltinAgentName.DEFAULT, backend=backend
)
order = agent.agent_manager.get_agent_order()
last_profile = agent.agent_manager.get_agent(order[-1])
first_profile = agent.agent_manager.get_agent(order[0])
assert agent.agent_manager.next_agent(last_profile).name == first_profile.name
class TestAgentProfileConfig:
def test_agent_profile_frozen(self) -> None:
profile = AgentProfile(
name="test",
display_name="Test",
description="Test agent",
safety=AgentSafety.NEUTRAL,
)
with pytest.raises(AttributeError):
profile.name = "changed" # pyright: ignore[reportAttributeAccessIssue]
class TestAgentSwitchAgent:
@pytest.fixture
def base_config(self) -> VibeConfig:
return build_test_vibe_config(
include_project_context=False, include_prompt_detail=False
)
@pytest.fixture
def backend(self) -> FakeBackend:
return FakeBackend([
LLMChunk(
message=LLMMessage(role=Role.assistant, content="Test response"),
usage=LLMUsage(prompt_tokens=10, completion_tokens=5),
)
])
@pytest.mark.asyncio
async def test_switch_to_plan_agent_has_tools_with_restricted_permissions(
self, base_config: VibeConfig, backend: FakeBackend
) -> None:
agent = build_test_agent_loop(
config=base_config, agent_name=BuiltinAgentName.DEFAULT, backend=backend
)
await agent.switch_agent(BuiltinAgentName.PLAN)
plan_tool_names = set(agent.tool_manager.available_tools.keys())
# Plan mode now has all tools available but with restricted permissions
assert "write_file" in plan_tool_names
assert "edit" in plan_tool_names
assert "grep" in plan_tool_names
assert "read" in plan_tool_names
assert agent.agent_profile.name == BuiltinAgentName.PLAN
# Verify write tools have "never" base permission
write_config = agent.tool_manager.get_tool_config("write_file")
assert write_config.permission == ToolPermission.NEVER
@pytest.mark.asyncio
async def test_switch_from_plan_to_default_restores_tools(
self, base_config: VibeConfig, backend: FakeBackend
) -> None:
agent = build_test_agent_loop(
config=base_config, agent_name=BuiltinAgentName.PLAN, backend=backend
)
await agent.switch_agent(BuiltinAgentName.DEFAULT)
# Write tools should revert to default ASK permission
write_config = agent.tool_manager.get_tool_config("write_file")
assert write_config.permission == ToolPermission.ASK
assert agent.agent_profile.name == BuiltinAgentName.DEFAULT
@pytest.mark.asyncio
async def test_switch_agent_preserves_conversation_history(
self, base_config: VibeConfig, backend: FakeBackend
) -> None:
agent = build_test_agent_loop(
config=base_config, agent_name=BuiltinAgentName.DEFAULT, backend=backend
)
user_msg = LLMMessage(role=Role.user, content="Hello")
assistant_msg = LLMMessage(role=Role.assistant, content="Hi there")
agent.messages.append(user_msg)
agent.messages.append(assistant_msg)
await agent.switch_agent(BuiltinAgentName.PLAN)
assert len(agent.messages) == 3 # system + user + assistant
assert agent.messages[1].content == "Hello"
assert agent.messages[2].content == "Hi there"
@pytest.mark.asyncio
async def test_switch_to_same_agent_is_noop(
self, base_config: VibeConfig, backend: FakeBackend
) -> None:
agent = build_test_agent_loop(
config=base_config, agent_name=BuiltinAgentName.DEFAULT, backend=backend
)
original_config = agent.config
await agent.switch_agent(BuiltinAgentName.DEFAULT)
assert agent.config is original_config
assert agent.agent_profile.name == BuiltinAgentName.DEFAULT
class TestAcceptEditsAgent:
def test_accept_edits_config_sets_write_file_always(self) -> None:
overrides = BUILTIN_AGENTS[BuiltinAgentName.ACCEPT_EDITS].overrides
assert overrides["tools"]["write_file"]["permission"] == "always"
def test_accept_edits_config_sets_edit_always(self) -> None:
overrides = BUILTIN_AGENTS[BuiltinAgentName.ACCEPT_EDITS].overrides
assert overrides["tools"]["edit"]["permission"] == "always"
@pytest.mark.asyncio
async def test_accept_edits_agent_auto_approves_write_file(self) -> None:
backend = FakeBackend([])
config = build_test_vibe_config(enabled_tools=["write_file"])
agent = build_test_agent_loop(
config=config, agent_name=BuiltinAgentName.ACCEPT_EDITS, backend=backend
)
perm = agent.tool_manager.get_tool_config("write_file").permission
assert perm == ToolPermission.ALWAYS
@pytest.mark.asyncio
async def test_accept_edits_agent_requires_approval_for_other_tools(self) -> None:
backend = FakeBackend([])
config = build_test_vibe_config(enabled_tools=["bash"])
agent = build_test_agent_loop(
config=config, agent_name=BuiltinAgentName.ACCEPT_EDITS, backend=backend
)
perm = agent.tool_manager.get_tool_config("bash").permission
assert perm == ToolPermission.ASK
class TestPlanAgentToolRestriction:
@pytest.mark.asyncio
async def test_plan_agent_has_all_tools_with_restricted_write_permissions(
self,
) -> None:
backend = FakeBackend([
LLMChunk(
message=LLMMessage(role=Role.assistant, content="ok"),
usage=LLMUsage(prompt_tokens=10, completion_tokens=5),
)
])
config = build_test_vibe_config()
agent = build_test_agent_loop(
config=config, agent_name=BuiltinAgentName.PLAN, backend=backend
)
tool_names = set(agent.tool_manager.available_tools.keys())
# Plan mode now has all tools available
assert "grep" in tool_names
assert "read" in tool_names
assert "write_file" in tool_names
assert "edit" in tool_names
# But write tools have restricted permissions
write_config = agent.tool_manager.get_tool_config("write_file")
assert write_config.permission == ToolPermission.NEVER
assert len(write_config.allowlist) > 0
edit_config = agent.tool_manager.get_tool_config("edit")
assert edit_config.permission == ToolPermission.NEVER
assert len(edit_config.allowlist) > 0
class TestAgentManagerFiltering:
def test_enabled_agents_filters_to_only_enabled(self) -> None:
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
enabled_agents=["default", "plan"],
)
manager = AgentManager(lambda: config)
agents = manager.available_agents
assert len(agents) < len(manager._discovered)
assert "default" in agents
assert "plan" in agents
assert "auto-approve" not in agents
assert "accept-edits" not in agents
def test_disabled_agents_excludes_disabled(self) -> None:
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
disabled_agents=["auto-approve", "accept-edits"],
)
manager = AgentManager(lambda: config)
agents = manager.available_agents
assert len(agents) < len(manager._discovered)
assert "default" in agents
assert "plan" in agents
assert "auto-approve" not in agents
assert "accept-edits" not in agents
def test_enabled_agents_takes_precedence_over_disabled(self) -> None:
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
enabled_agents=["default"],
disabled_agents=["default"], # Should be ignored
)
manager = AgentManager(lambda: config)
agents = manager.available_agents
assert len(agents) == 1
assert "default" in agents
def test_glob_pattern_matching(self) -> None:
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
disabled_agents=["auto-*", "accept-*"],
)
manager = AgentManager(lambda: config)
agents = manager.available_agents
assert "default" in agents
assert "plan" in agents
assert "auto-approve" not in agents
assert "accept-edits" not in agents
def test_regex_pattern_matching(self) -> None:
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
enabled_agents=["re:^(default|plan)$"],
)
manager = AgentManager(lambda: config)
agents = manager.available_agents
assert len(agents) == 2
assert "default" in agents
assert "plan" in agents
def test_empty_enabled_agents_returns_all(self) -> None:
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
enabled_agents=[],
)
manager = AgentManager(lambda: config)
agents = manager.available_agents
assert "default" in agents
assert "plan" in agents
assert "auto-approve" in agents
assert "explore" in agents
def test_install_required_agents_hidden_by_default(self) -> None:
config = build_test_vibe_config(
include_project_context=False, include_prompt_detail=False
)
manager = AgentManager(lambda: config)
agents = manager.available_agents
assert "lean" not in agents
def test_install_required_agents_visible_when_installed(self) -> None:
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
installed_agents=["lean"],
)
manager = AgentManager(lambda: config)
agents = manager.available_agents
assert "lean" in agents
def test_get_subagents_respects_filtering(self) -> None:
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
disabled_agents=["explore"],
)
manager = AgentManager(lambda: config)
subagents = manager.get_subagents()
names = [a.name for a in subagents]
assert "explore" not in names
class TestAgentLoopInitialization:
def test_agent_system_prompt_id_is_applied_on_init(
self, mock_prompts_dirs: tuple[Path, Path], monkeypatch: pytest.MonkeyPatch
) -> None:
_, global_prompts = mock_prompts_dirs
custom_prompt_content = "CUSTOM_AGENT_PROMPT_MARKER"
(global_prompts / "custom_agent.md").write_text(custom_prompt_content)
custom_agent = AgentProfile(
name="custom_test_agent",
display_name="Custom Test",
description="Test agent with custom system prompt",
safety=AgentSafety.NEUTRAL,
overrides={"system_prompt_id": "custom_agent"},
)
patched_agents = {**BUILTIN_AGENTS, "custom_test_agent": custom_agent}
monkeypatch.setattr("vibe.core.agents.models.BUILTIN_AGENTS", patched_agents)
monkeypatch.setattr("vibe.core.agents.manager.BUILTIN_AGENTS", patched_agents)
config = build_test_vibe_config(
include_project_context=False, include_prompt_detail=False
)
assert config.system_prompt_id == "cli", (
"Base config should use default 'cli' prompt"
)
agent_loop = build_test_agent_loop(
config=config, agent_name="custom_test_agent"
)
assert agent_loop.config.system_prompt_id == "custom_agent", (
"Merged config should have the agent's system_prompt_id override"
)
system_message = agent_loop.messages[0]
assert system_message.role == Role.system
assert system_message.content is not None
assert custom_prompt_content in system_message.content, (
f"System message should contain custom prompt content. "
f"Expected '{custom_prompt_content}' to be in system message."
)
class TestActConsumersUseAclosing:
def test_no_bare_async_for_over_act(self) -> None:
vibe_pkg = TESTS_ROOT.parent / "vibe"
violations: list[str] = []
for path in vibe_pkg.rglob("*.py"):
tree = ast.parse(path.read_text(), filename=str(path))
for node in ast.walk(tree):
if not isinstance(node, ast.AsyncFor):
continue
match node.iter:
case ast.Call(func=ast.Attribute(attr="act")):
violations.append(f"{path}:{node.lineno}")
assert not violations, (
"Bare `async for ... in .act()` found — wrap in "
"contextlib.aclosing(). See issue #569.\n" + "\n".join(violations)
)