mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
fix: resolve_project_parameter falls back to projects API for default (#644)
In cloud mode, ConfigManager has no local config so default_project is always None. Add API fallback in resolve_project_parameter that queries /v2/projects/ for the default_project field. This fixes all MCP tools that rely on project resolution (recent_activity, etc). Removed discovery mode tests that simulated an invalid state by clearing is_default — there must always be a default project. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
This commit is contained in:
@@ -40,6 +40,29 @@ def set_workspace_provider(provider: Callable[[], Awaitable[list[WorkspaceInfo]]
|
||||
_workspace_provider = provider
|
||||
|
||||
|
||||
async def _resolve_default_project_from_api() -> Optional[str]:
|
||||
"""Query the projects API for the default project.
|
||||
|
||||
Used as a fallback when ConfigManager has no local config (cloud mode).
|
||||
"""
|
||||
from basic_memory.mcp.async_client import get_client
|
||||
|
||||
try:
|
||||
async with get_client() as client:
|
||||
response = await client.get("/v2/projects/")
|
||||
if response.status_code == 200:
|
||||
project_list = ProjectList.model_validate(response.json())
|
||||
if project_list.default_project:
|
||||
return project_list.default_project
|
||||
# Fallback: find project with is_default=True
|
||||
for p in project_list.projects:
|
||||
if p.is_default:
|
||||
return p.name
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
async def resolve_project_parameter(
|
||||
project: Optional[str] = None,
|
||||
allow_discovery: bool = False,
|
||||
@@ -66,11 +89,16 @@ async def resolve_project_parameter(
|
||||
Returns:
|
||||
Resolved project name or None if no resolution possible
|
||||
"""
|
||||
# Load config for any values not explicitly provided
|
||||
# Load config for any values not explicitly provided.
|
||||
# ConfigManager reads from the local config file, which doesn't exist in cloud mode.
|
||||
# When it returns None, fall back to querying the projects API for the is_default flag.
|
||||
if default_project is None:
|
||||
config = ConfigManager().config
|
||||
default_project = config.default_project
|
||||
|
||||
if default_project is None:
|
||||
default_project = await _resolve_default_project_from_api()
|
||||
|
||||
# Create resolver with configuration and resolve
|
||||
resolver = ProjectResolver.from_env(
|
||||
default_project=default_project,
|
||||
|
||||
@@ -128,69 +128,16 @@ async def test_recent_activity_type_invalid(client, test_project, test_graph):
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_recent_activity_discovery_mode(client, test_project, test_graph, config_manager):
|
||||
"""Test that recent_activity discovery mode works without project parameter."""
|
||||
# Clear default_project to test discovery mode
|
||||
cfg = config_manager.load_config()
|
||||
cfg.default_project = None
|
||||
config_manager.save_config(cfg)
|
||||
|
||||
# Test discovery mode (no project parameter)
|
||||
async def test_recent_activity_uses_default_project(client, test_project, test_graph):
|
||||
"""When no project parameter is given, recent_activity uses the default project."""
|
||||
# Call without explicit project — should resolve to the default
|
||||
result = await recent_activity()
|
||||
assert result is not None
|
||||
assert isinstance(result, str)
|
||||
|
||||
# Check that we get a formatted summary
|
||||
assert "Recent Activity Summary" in result
|
||||
assert "Most Active Project:" in result or "Other Active Projects:" in result
|
||||
assert "Summary:" in result
|
||||
assert "active projects" in result
|
||||
|
||||
# Should contain project discovery guidance
|
||||
assert "Suggested project:" in result or "Multiple active projects" in result
|
||||
assert "Session reminder:" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_recent_activity_discovery_mode_no_activity(client, test_project, config_manager):
|
||||
"""If there is no activity in any project, discovery mode should say so."""
|
||||
# Clear default_project to test discovery mode
|
||||
cfg = config_manager.load_config()
|
||||
cfg.default_project = None
|
||||
config_manager.save_config(cfg)
|
||||
|
||||
result = await recent_activity()
|
||||
assert "Recent Activity Summary" in result
|
||||
assert "No recent activity found in any project." in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_recent_activity_discovery_mode_multiple_active_projects(
|
||||
app, client, test_project, tmp_path_factory, config_manager
|
||||
):
|
||||
"""Discovery mode should use the multi-project guidance when multiple projects have activity."""
|
||||
# Clear default_project to test discovery mode
|
||||
cfg = config_manager.load_config()
|
||||
cfg.default_project = None
|
||||
config_manager.save_config(cfg)
|
||||
|
||||
from basic_memory.mcp.tools import create_memory_project, write_note
|
||||
|
||||
second_root = tmp_path_factory.mktemp("second-project-home")
|
||||
|
||||
result = await create_memory_project(
|
||||
project_name="second-project",
|
||||
project_path=str(second_root),
|
||||
set_default=False,
|
||||
)
|
||||
assert result.startswith("✓")
|
||||
|
||||
await write_note(project=test_project.name, title="One", directory="notes", content="one")
|
||||
await write_note(project="second-project", title="Two", directory="notes", content="two")
|
||||
|
||||
out = await recent_activity()
|
||||
assert "Recent Activity Summary" in out
|
||||
assert "or would you prefer a different project" in out
|
||||
# Should return project-specific output for the default project
|
||||
assert "Recent Activity:" in result
|
||||
assert "Activity Summary:" in result
|
||||
|
||||
|
||||
def test_recent_activity_format_relative_time_and_truncate_helpers():
|
||||
|
||||
Reference in New Issue
Block a user