v2.18.0 (#843)
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai> Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Cyprien <courtot.c@gmail.com> Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com> Co-authored-by: Jean Burellier <sheplu@users.noreply.github.com> Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@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: Quentin <quentin.torroba@mistral.ai> Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com> Co-authored-by: josephine-delas <57808586+josephine-delas@users.noreply.github.com> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
725d3a56ce
commit
e607ccbb00
242 changed files with 7372 additions and 1974 deletions
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, cast
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
|
|
@ -13,8 +14,12 @@ from tests.stubs.fake_connector_registry import FakeConnectorRegistry
|
|||
from tests.stubs.fake_mcp_registry import FakeMCPRegistry
|
||||
from vibe.core.config import ConnectorConfig, VibeConfig
|
||||
from vibe.core.tools.base import BaseToolConfig, ToolError
|
||||
from vibe.core.tools.connectors import compute_connector_counts
|
||||
from vibe.core.tools.connectors import (
|
||||
compute_connector_counts,
|
||||
connector_registry as connector_registry_module,
|
||||
)
|
||||
from vibe.core.tools.connectors.connector_registry import (
|
||||
_BOOTSTRAP_CACHE_TTL_SECONDS,
|
||||
ConnectorAuthAction,
|
||||
ConnectorRegistry,
|
||||
RemoteTool,
|
||||
|
|
@ -26,6 +31,8 @@ from vibe.core.tools.connectors.connector_registry import (
|
|||
from vibe.core.tools.manager import ToolManager
|
||||
from vibe.core.tools.mcp.tools import MCPTool, MCPToolResult
|
||||
|
||||
_BOOTSTRAP_CACHE_FILE_NAME = "connector_bootstrap_cache.json"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Unit tests for helper functions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -561,6 +568,202 @@ class TestBootstrapDiscovery:
|
|||
assert tools == {}
|
||||
assert registry.connector_count == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fresh_bootstrap_cache_avoids_http(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path
|
||||
) -> None:
|
||||
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
|
||||
payload = _make_bootstrap_response([
|
||||
_make_connector_payload(tools=[_make_tool_payload("cached")])
|
||||
])
|
||||
|
||||
with (
|
||||
patch.object(connector_registry_module.time, "time", return_value=1_000),
|
||||
respx.mock,
|
||||
):
|
||||
respx.get(_BOOTSTRAP_URL).mock(
|
||||
return_value=httpx.Response(200, json=payload)
|
||||
)
|
||||
await ConnectorRegistry(api_key="test-key").get_tools_async()
|
||||
|
||||
with (
|
||||
patch.object(connector_registry_module.time, "time", return_value=1_100),
|
||||
respx.mock(assert_all_called=False) as router,
|
||||
):
|
||||
route = router.get(_BOOTSTRAP_URL).mock(
|
||||
return_value=httpx.Response(500, text="should not be called")
|
||||
)
|
||||
tools = await ConnectorRegistry(api_key="test-key").get_tools_async()
|
||||
|
||||
assert "connector_wiki_cached" in tools
|
||||
assert not route.called
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stale_bootstrap_cache_falls_back_to_http(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path
|
||||
) -> None:
|
||||
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
|
||||
cached_payload = _make_bootstrap_response([
|
||||
_make_connector_payload(tools=[_make_tool_payload("cached")])
|
||||
])
|
||||
fresh_payload = _make_bootstrap_response([
|
||||
_make_connector_payload(tools=[_make_tool_payload("fresh")])
|
||||
])
|
||||
|
||||
with (
|
||||
patch.object(connector_registry_module.time, "time", return_value=1_000),
|
||||
respx.mock,
|
||||
):
|
||||
respx.get(_BOOTSTRAP_URL).mock(
|
||||
return_value=httpx.Response(200, json=cached_payload)
|
||||
)
|
||||
await ConnectorRegistry(api_key="test-key").get_tools_async()
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
connector_registry_module.time,
|
||||
"time",
|
||||
return_value=1_000 + _BOOTSTRAP_CACHE_TTL_SECONDS + 1,
|
||||
),
|
||||
respx.mock,
|
||||
):
|
||||
route = respx.get(_BOOTSTRAP_URL).mock(
|
||||
return_value=httpx.Response(200, json=fresh_payload)
|
||||
)
|
||||
tools = await ConnectorRegistry(api_key="test-key").get_tools_async()
|
||||
|
||||
assert route.called
|
||||
assert "connector_wiki_fresh" in tools
|
||||
assert "connector_wiki_cached" not in tools
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_malformed_bootstrap_cache_falls_back_to_http(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path
|
||||
) -> None:
|
||||
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
|
||||
(tmp_path / _BOOTSTRAP_CACHE_FILE_NAME).write_text("{bad toml")
|
||||
payload = _make_bootstrap_response([
|
||||
_make_connector_payload(tools=[_make_tool_payload("fresh")])
|
||||
])
|
||||
|
||||
with respx.mock:
|
||||
route = respx.get(_BOOTSTRAP_URL).mock(
|
||||
return_value=httpx.Response(200, json=payload)
|
||||
)
|
||||
tools = await ConnectorRegistry(api_key="test-key").get_tools_async()
|
||||
|
||||
assert route.called
|
||||
assert "connector_wiki_fresh" in tools
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_force_refresh_bypasses_fresh_bootstrap_cache(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path
|
||||
) -> None:
|
||||
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
|
||||
cached_payload = _make_bootstrap_response([
|
||||
_make_connector_payload(tools=[_make_tool_payload("cached")])
|
||||
])
|
||||
fresh_payload = _make_bootstrap_response([
|
||||
_make_connector_payload(tools=[_make_tool_payload("fresh")])
|
||||
])
|
||||
|
||||
with (
|
||||
patch.object(connector_registry_module.time, "time", return_value=1_000),
|
||||
respx.mock,
|
||||
):
|
||||
respx.get(_BOOTSTRAP_URL).mock(
|
||||
return_value=httpx.Response(200, json=cached_payload)
|
||||
)
|
||||
await ConnectorRegistry(api_key="test-key").get_tools_async()
|
||||
|
||||
with (
|
||||
patch.object(connector_registry_module.time, "time", return_value=1_100),
|
||||
respx.mock,
|
||||
):
|
||||
route = respx.get(_BOOTSTRAP_URL).mock(
|
||||
return_value=httpx.Response(200, json=fresh_payload)
|
||||
)
|
||||
tools = await ConnectorRegistry(api_key="test-key").get_tools_async(
|
||||
force_refresh=True
|
||||
)
|
||||
|
||||
assert route.called
|
||||
assert "connector_wiki_fresh" in tools
|
||||
assert "connector_wiki_cached" not in tools
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bootstrap_cache_does_not_store_raw_api_key(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path
|
||||
) -> None:
|
||||
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
|
||||
payload = _make_bootstrap_response([
|
||||
_make_connector_payload(tools=[_make_tool_payload("cached")])
|
||||
])
|
||||
|
||||
with respx.mock:
|
||||
respx.get(_BOOTSTRAP_URL).mock(
|
||||
return_value=httpx.Response(200, json=payload)
|
||||
)
|
||||
await ConnectorRegistry(api_key="secret-test-key").get_tools_async()
|
||||
|
||||
cache_text = (tmp_path / _BOOTSTRAP_CACHE_FILE_NAME).read_text()
|
||||
assert "secret-test-key" not in cache_text
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bootstrap_cache_stores_only_consumed_connector_fields(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path
|
||||
) -> None:
|
||||
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
|
||||
payload = _make_bootstrap_response([
|
||||
_make_connector_payload(
|
||||
tools=[{**_make_tool_payload("cached"), "secret_extra": "tool-secret"}],
|
||||
auth_action={"type": "oauth", "url": "https://secret.example.com"},
|
||||
)
|
||||
| {
|
||||
"display_name": "Private display name",
|
||||
"description": "Private connector description",
|
||||
"bootstrap_errors": ["private error detail"],
|
||||
}
|
||||
])
|
||||
|
||||
with respx.mock:
|
||||
respx.get(_BOOTSTRAP_URL).mock(
|
||||
return_value=httpx.Response(200, json=payload)
|
||||
)
|
||||
await ConnectorRegistry(api_key="test-key").get_tools_async()
|
||||
|
||||
cache_text = (tmp_path / _BOOTSTRAP_CACHE_FILE_NAME).read_text()
|
||||
assert "Private display name" not in cache_text
|
||||
assert "Private connector description" not in cache_text
|
||||
assert "private error detail" not in cache_text
|
||||
assert "https://secret.example.com" not in cache_text
|
||||
assert "tool-secret" not in cache_text
|
||||
|
||||
cache = json.loads(cache_text)
|
||||
entry = next(iter(cache.values()))
|
||||
connector = entry["payload"]["connectors"][0]
|
||||
assert set(connector) == {"id", "name", "status", "tools", "auth_action"}
|
||||
assert connector["auth_action"] == {"type": "oauth"}
|
||||
assert set(connector["tools"][0]) == {"name", "description", "inputSchema"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bootstrap_cache_uses_dedicated_file(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path
|
||||
) -> None:
|
||||
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
|
||||
payload = _make_bootstrap_response([
|
||||
_make_connector_payload(tools=[_make_tool_payload("cached")])
|
||||
])
|
||||
|
||||
with respx.mock:
|
||||
respx.get(_BOOTSTRAP_URL).mock(
|
||||
return_value=httpx.Response(200, json=payload)
|
||||
)
|
||||
await ConnectorRegistry(api_key="test-key").get_tools_async()
|
||||
|
||||
assert (tmp_path / _BOOTSTRAP_CACHE_FILE_NAME).exists()
|
||||
assert not (tmp_path / "cache.toml").exists()
|
||||
|
||||
@respx.mock
|
||||
@pytest.mark.asyncio
|
||||
async def test_deduplicates_connector_aliases(self) -> None:
|
||||
|
|
@ -821,6 +1024,51 @@ class TestAuthActionablediscovery:
|
|||
assert registry.is_connected("linear")
|
||||
assert registry.get_auth_action("linear") == ConnectorAuthAction.NONE
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_updates_bootstrap_file_cache(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path
|
||||
) -> None:
|
||||
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
|
||||
payload = _make_bootstrap_response([
|
||||
_make_connector_payload(
|
||||
connector_id="c-1",
|
||||
name="linear",
|
||||
is_ready=False,
|
||||
tools=[],
|
||||
auth_action={"type": "oauth"},
|
||||
)
|
||||
])
|
||||
|
||||
with respx.mock:
|
||||
respx.get(_BOOTSTRAP_URL).mock(
|
||||
return_value=httpx.Response(200, json=payload)
|
||||
)
|
||||
registry = ConnectorRegistry(api_key="test-key")
|
||||
await registry.get_tools_async()
|
||||
|
||||
refresh_payload = _make_bootstrap_response([
|
||||
_make_connector_payload(
|
||||
connector_id="c-1",
|
||||
name="linear",
|
||||
tools=[_make_tool_payload("search_issues")],
|
||||
)
|
||||
])
|
||||
with respx.mock:
|
||||
respx.get(_BOOTSTRAP_URL).mock(
|
||||
return_value=httpx.Response(200, json=refresh_payload)
|
||||
)
|
||||
await registry.refresh_connector_async("linear")
|
||||
|
||||
with respx.mock:
|
||||
route = respx.get(_BOOTSTRAP_URL).mock(return_value=httpx.Response(500))
|
||||
warm_registry = ConnectorRegistry(api_key="test-key")
|
||||
tools = await warm_registry.get_tools_async()
|
||||
|
||||
assert not route.called
|
||||
assert "connector_linear_search_issues" in tools
|
||||
assert warm_registry.is_connected("linear")
|
||||
assert warm_registry.get_auth_action("linear") == ConnectorAuthAction.NONE
|
||||
|
||||
def test_get_auth_action_unknown_alias_returns_none(self) -> None:
|
||||
registry = ConnectorRegistry(api_key="test-key")
|
||||
assert registry.get_auth_action("nobody") == ConnectorAuthAction.NONE
|
||||
|
|
|
|||
|
|
@ -63,6 +63,23 @@ class TestBashGranularPermissions:
|
|||
assert isinstance(result, PermissionContext)
|
||||
assert result.permission is ToolPermission.ASK
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"command",
|
||||
[
|
||||
"python3 << 'EOF'\nprint(42)\nEOF",
|
||||
"python3 - << 'EOF'\nprint(42)\nEOF",
|
||||
"python3 <<'PYEOF'\nimport sys\nprint('hello')\nPYEOF",
|
||||
"python3 < input.txt",
|
||||
],
|
||||
)
|
||||
def test_standalone_denylisted_with_redirect_not_denied(self, command):
|
||||
bash = self._bash()
|
||||
result = bash.resolve_permission(BashArgs(command=command))
|
||||
assert isinstance(result, PermissionContext)
|
||||
assert result.permission is not ToolPermission.NEVER, (
|
||||
f"Command with redirect should not be denied: {command!r}"
|
||||
)
|
||||
|
||||
def test_unknown_command_returns_permission_context(self):
|
||||
bash = self._bash()
|
||||
result = bash.resolve_permission(BashArgs(command="npm install"))
|
||||
|
|
|
|||
|
|
@ -10,15 +10,8 @@ from vibe.core.tools.manager import NoSuchToolError, ToolManager
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def config():
|
||||
return build_test_vibe_config(
|
||||
system_prompt_id="tests", include_project_context=False
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tool_manager(config):
|
||||
return ToolManager(lambda: config)
|
||||
def tool_manager(vibe_config):
|
||||
return ToolManager(lambda: vibe_config)
|
||||
|
||||
|
||||
def test_returns_default_config_when_no_overrides(tool_manager):
|
||||
|
|
@ -33,11 +26,7 @@ def test_returns_default_config_when_no_overrides(tool_manager):
|
|||
|
||||
|
||||
def test_merges_user_overrides_with_defaults():
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests",
|
||||
include_project_context=False,
|
||||
tools={"bash": {"permission": "always"}},
|
||||
)
|
||||
vibe_config = build_test_vibe_config(tools={"bash": {"permission": "always"}})
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
|
||||
config = manager.get_tool_config("bash")
|
||||
|
|
@ -50,11 +39,7 @@ def test_merges_user_overrides_with_defaults():
|
|||
|
||||
|
||||
def test_preserves_tool_specific_fields_from_overrides():
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests",
|
||||
include_project_context=False,
|
||||
tools={"bash": {"permission": "ask"}},
|
||||
)
|
||||
vibe_config = build_test_vibe_config(tools={"bash": {"permission": "ask"}})
|
||||
vibe_config.tools["bash"]["default_timeout"] = 600
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
|
||||
|
|
@ -73,9 +58,7 @@ def test_falls_back_to_base_config_for_unknown_tool(tool_manager):
|
|||
|
||||
def test_partial_override_preserves_tool_defaults():
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests",
|
||||
include_project_context=False,
|
||||
tools={"read": {"sensitive_patterns": ["**/*.key"]}},
|
||||
tools={"read": {"sensitive_patterns": ["**/*.key"]}}
|
||||
)
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
|
||||
|
|
@ -89,11 +72,7 @@ def test_partial_override_preserves_tool_defaults():
|
|||
|
||||
class TestToolManagerFiltering:
|
||||
def test_enabled_tools_filters_to_only_enabled(self):
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests",
|
||||
include_project_context=False,
|
||||
enabled_tools=["bash", "grep"],
|
||||
)
|
||||
vibe_config = build_test_vibe_config(enabled_tools=["bash", "grep"])
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
|
||||
tools = manager.available_tools
|
||||
|
|
@ -104,11 +83,7 @@ class TestToolManagerFiltering:
|
|||
assert "write_file" not in tools
|
||||
|
||||
def test_disabled_tools_excludes_disabled(self):
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests",
|
||||
include_project_context=False,
|
||||
disabled_tools=["bash", "write_file"],
|
||||
)
|
||||
vibe_config = build_test_vibe_config(disabled_tools=["bash", "write_file"])
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
|
||||
tools = manager.available_tools
|
||||
|
|
@ -120,8 +95,6 @@ class TestToolManagerFiltering:
|
|||
|
||||
def test_enabled_tools_takes_precedence_over_disabled(self):
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests",
|
||||
include_project_context=False,
|
||||
enabled_tools=["bash"],
|
||||
disabled_tools=["bash"], # Should be ignored
|
||||
)
|
||||
|
|
@ -133,9 +106,7 @@ class TestToolManagerFiltering:
|
|||
|
||||
def test_glob_pattern_matching(self):
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests",
|
||||
include_project_context=False,
|
||||
disabled_tools=["write_*"], # Matches write_file
|
||||
disabled_tools=["write_*"] # Matches write_file
|
||||
)
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
|
||||
|
|
@ -145,11 +116,7 @@ class TestToolManagerFiltering:
|
|||
assert "grep" in tools
|
||||
|
||||
def test_regex_pattern_matching(self):
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests",
|
||||
include_project_context=False,
|
||||
enabled_tools=["re:^(bash|grep)$"],
|
||||
)
|
||||
vibe_config = build_test_vibe_config(enabled_tools=["re:^(bash|grep)$"])
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
|
||||
tools = manager.available_tools
|
||||
|
|
@ -158,11 +125,7 @@ class TestToolManagerFiltering:
|
|||
assert "grep" in tools
|
||||
|
||||
def test_get_raises_for_disabled_tool(self):
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests",
|
||||
include_project_context=False,
|
||||
disabled_tools=["bash"],
|
||||
)
|
||||
vibe_config = build_test_vibe_config(disabled_tools=["bash"])
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
|
||||
assert "bash" not in manager.available_tools
|
||||
|
|
@ -170,11 +133,7 @@ class TestToolManagerFiltering:
|
|||
manager.get("bash")
|
||||
|
||||
def test_case_insensitive_matching(self):
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests",
|
||||
include_project_context=False,
|
||||
enabled_tools=["BASH", "GREP"],
|
||||
)
|
||||
vibe_config = build_test_vibe_config(enabled_tools=["BASH", "GREP"])
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
|
||||
tools = manager.available_tools
|
||||
|
|
@ -182,9 +141,7 @@ class TestToolManagerFiltering:
|
|||
assert "grep" in tools
|
||||
|
||||
def test_empty_enabled_tools_returns_all(self):
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests", include_project_context=False, enabled_tools=[]
|
||||
)
|
||||
vibe_config = build_test_vibe_config(enabled_tools=[])
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
|
||||
tools = manager.available_tools
|
||||
|
|
@ -242,11 +199,7 @@ class FileTool(BaseTool[FileToolArgs, FileToolResult, BaseToolConfig, BaseToolSt
|
|||
for k in to_remove:
|
||||
del sys.modules[k]
|
||||
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests",
|
||||
include_project_context=False,
|
||||
tool_paths=[tool_dir, file_tool],
|
||||
)
|
||||
vibe_config = build_test_vibe_config(tool_paths=[tool_dir, file_tool])
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
|
||||
tools = manager.available_tools
|
||||
|
|
@ -292,11 +245,7 @@ class ConditionalTool(BaseTool[ConditionalToolArgs, ConditionalToolResult, BaseT
|
|||
del sys.modules[k]
|
||||
|
||||
monkeypatch.delenv("TEST_VAR", raising=False)
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests",
|
||||
include_project_context=False,
|
||||
tool_paths=[tool_dir],
|
||||
)
|
||||
vibe_config = build_test_vibe_config(tool_paths=[tool_dir])
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
assert "conditional_tool" not in manager.available_tools
|
||||
|
||||
|
|
@ -310,9 +259,7 @@ class ConditionalTool(BaseTool[ConditionalToolArgs, ConditionalToolResult, BaseT
|
|||
|
||||
def test_default_is_available_returns_true(self):
|
||||
"""Tools without is_available() override should be available."""
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests", include_project_context=False
|
||||
)
|
||||
vibe_config = build_test_vibe_config()
|
||||
manager = ToolManager(lambda: vibe_config)
|
||||
|
||||
assert "bash" in manager.available_tools
|
||||
|
|
@ -329,9 +276,7 @@ class TestToolManagerModuleReuse:
|
|||
|
||||
def test_multiple_managers_share_tool_classes(self):
|
||||
"""Tool classes should be identical across multiple ToolManager instances."""
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests", include_project_context=False
|
||||
)
|
||||
vibe_config = build_test_vibe_config()
|
||||
|
||||
manager1 = ToolManager(lambda: vibe_config)
|
||||
manager2 = ToolManager(lambda: vibe_config)
|
||||
|
|
@ -347,9 +292,7 @@ class TestToolManagerModuleReuse:
|
|||
|
||||
def test_tool_state_classes_are_identical(self):
|
||||
"""Tool state classes should be identical across managers."""
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests", include_project_context=False
|
||||
)
|
||||
vibe_config = build_test_vibe_config()
|
||||
|
||||
manager1 = ToolManager(lambda: vibe_config)
|
||||
manager2 = ToolManager(lambda: vibe_config)
|
||||
|
|
@ -364,9 +307,7 @@ class TestToolManagerModuleReuse:
|
|||
|
||||
def test_tool_args_results_classes_are_identical(self):
|
||||
"""Tool args and result classes should be identical across managers."""
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests", include_project_context=False
|
||||
)
|
||||
vibe_config = build_test_vibe_config()
|
||||
|
||||
manager1 = ToolManager(lambda: vibe_config)
|
||||
manager2 = ToolManager(lambda: vibe_config)
|
||||
|
|
@ -386,9 +327,7 @@ class TestToolManagerModuleReuse:
|
|||
This ensures subagents have isolated state (e.g., separate todo lists)
|
||||
while still sharing class definitions for Pydantic validation.
|
||||
"""
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests", include_project_context=False
|
||||
)
|
||||
vibe_config = build_test_vibe_config()
|
||||
|
||||
manager1 = ToolManager(lambda: vibe_config)
|
||||
manager2 = ToolManager(lambda: vibe_config)
|
||||
|
|
@ -412,9 +351,7 @@ class TestToolManagerModuleReuse:
|
|||
|
||||
def test_class_shared_but_instances_isolated(self):
|
||||
"""Classes must be shared (for validation) but instances isolated (for state)."""
|
||||
vibe_config = build_test_vibe_config(
|
||||
system_prompt_id="tests", include_project_context=False
|
||||
)
|
||||
vibe_config = build_test_vibe_config()
|
||||
|
||||
manager1 = ToolManager(lambda: vibe_config)
|
||||
manager2 = ToolManager(lambda: vibe_config)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class MemoryKeyring(KeyringBackend):
|
|||
|
||||
def delete_password(self, service: str, username: str) -> None:
|
||||
if (service, username) not in self.store:
|
||||
raise keyring.errors.PasswordDeleteError(username)
|
||||
raise keyring.errors.PasswordDeleteError()
|
||||
del self.store[(service, username)]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -34,9 +34,7 @@ class TestTaskArgs:
|
|||
class TestTaskToolValidation:
|
||||
@pytest.fixture
|
||||
def ctx(self) -> InvokeContext:
|
||||
config = build_test_vibe_config(
|
||||
include_project_context=False, include_prompt_detail=False
|
||||
)
|
||||
config = build_test_vibe_config()
|
||||
manager = AgentManager(lambda: config)
|
||||
return InvokeContext(
|
||||
tool_call_id="test-call-id",
|
||||
|
|
@ -133,9 +131,7 @@ class TestTaskToolResolvePermission:
|
|||
class TestTaskToolExecution:
|
||||
@pytest.fixture
|
||||
def ctx(self) -> InvokeContext:
|
||||
config = build_test_vibe_config(
|
||||
include_project_context=False, include_prompt_detail=False
|
||||
)
|
||||
config = build_test_vibe_config()
|
||||
manager = AgentManager(lambda: config)
|
||||
return InvokeContext(
|
||||
tool_call_id="test-call-id",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue