feat(04-02): use user context for project selection in generic commands
- Generic commands now use project from user context first - Fall back to context param or default if no user context - Add users_root parameter to Orchestrator for test isolation - Update integration tests to use tmp_users_root fixture Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,10 @@ This module provides the central command routing system that:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import Any, Dict, Optional, Tuple
|
from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from gsd_openclaw.tmux_session import TmuxSession, TmuxSessionManager
|
from gsd_openclaw.tmux_session import TmuxSession, TmuxSessionManager
|
||||||
from gsd_openclaw.command_executor import CommandExecutor, CommandResult
|
from gsd_openclaw.command_executor import CommandExecutor, CommandResult
|
||||||
@@ -47,7 +50,8 @@ class Orchestrator:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
tmux_manager: Optional[TmuxSessionManager] = None,
|
tmux_manager: Optional[TmuxSessionManager] = None,
|
||||||
formatter: Optional[FeishuResponseFormatter] = None
|
formatter: Optional[FeishuResponseFormatter] = None,
|
||||||
|
users_root: Optional["Path"] = None
|
||||||
):
|
):
|
||||||
"""Initialize Orchestrator.
|
"""Initialize Orchestrator.
|
||||||
|
|
||||||
@@ -56,6 +60,7 @@ class Orchestrator:
|
|||||||
If not provided, a new one will be created.
|
If not provided, a new one will be created.
|
||||||
formatter: Optional FeishuResponseFormatter instance.
|
formatter: Optional FeishuResponseFormatter instance.
|
||||||
If not provided, a new one will be created.
|
If not provided, a new one will be created.
|
||||||
|
users_root: Optional custom root for user contexts (useful for testing).
|
||||||
"""
|
"""
|
||||||
self.tmux_manager = tmux_manager or TmuxSessionManager()
|
self.tmux_manager = tmux_manager or TmuxSessionManager()
|
||||||
self.formatter = formatter or FeishuResponseFormatter()
|
self.formatter = formatter or FeishuResponseFormatter()
|
||||||
@@ -63,6 +68,7 @@ class Orchestrator:
|
|||||||
self.approval_handler = ApprovalHandler(self.formatter)
|
self.approval_handler = ApprovalHandler(self.formatter)
|
||||||
self.progress_monitor = ProgressMonitor(self.formatter)
|
self.progress_monitor = ProgressMonitor(self.formatter)
|
||||||
self.user_context_managers: Dict[str, UserContextManager] = {}
|
self.user_context_managers: Dict[str, UserContextManager] = {}
|
||||||
|
self._users_root = users_root
|
||||||
|
|
||||||
def _get_user_context_manager(self, user_id: str) -> UserContextManager:
|
def _get_user_context_manager(self, user_id: str) -> UserContextManager:
|
||||||
"""Get or create UserContextManager for a user.
|
"""Get or create UserContextManager for a user.
|
||||||
@@ -74,7 +80,7 @@ class Orchestrator:
|
|||||||
UserContextManager instance for the user.
|
UserContextManager instance for the user.
|
||||||
"""
|
"""
|
||||||
if user_id not in self.user_context_managers:
|
if user_id not in self.user_context_managers:
|
||||||
self.user_context_managers[user_id] = UserContextManager(user_id)
|
self.user_context_managers[user_id] = UserContextManager(user_id, self._users_root)
|
||||||
return self.user_context_managers[user_id]
|
return self.user_context_managers[user_id]
|
||||||
|
|
||||||
def _handle_new_project(self, user_id: str, project_name: str) -> Dict[str, Any]:
|
def _handle_new_project(self, user_id: str, project_name: str) -> Dict[str, Any]:
|
||||||
@@ -249,8 +255,13 @@ class Orchestrator:
|
|||||||
if command == 'use':
|
if command == 'use':
|
||||||
return self._handle_use_project(user_id, args)
|
return self._handle_use_project(user_id, args)
|
||||||
|
|
||||||
# Get project name from context
|
# Generic commands - get project from user context first
|
||||||
project_name = context.get("project", "default")
|
user_ctx = self._get_user_context_manager(user_id)
|
||||||
|
project_name = user_ctx.get_current_project()
|
||||||
|
|
||||||
|
# Fall back to context param or default
|
||||||
|
if not project_name:
|
||||||
|
project_name = context.get("project", "default")
|
||||||
|
|
||||||
# Get or create session (auto-create per CONTEXT.md)
|
# Get or create session (auto-create per CONTEXT.md)
|
||||||
session = self.tmux_manager.get_session(project_name)
|
session = self.tmux_manager.get_session(project_name)
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ class TestIntegrationFlows:
|
|||||||
"""Integration-style tests for complete flows."""
|
"""Integration-style tests for complete flows."""
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def orchestrator_with_real_components(self):
|
def orchestrator_with_real_components(self, tmp_users_root):
|
||||||
"""Create orchestrator with real components (mock only tmux)."""
|
"""Create orchestrator with real components (mock only tmux)."""
|
||||||
from gsd_openclaw.orchestrator import Orchestrator
|
from gsd_openclaw.orchestrator import Orchestrator
|
||||||
from gsd_openclaw.response_formatter import FeishuResponseFormatter
|
from gsd_openclaw.response_formatter import FeishuResponseFormatter
|
||||||
@@ -359,7 +359,8 @@ class TestIntegrationFlows:
|
|||||||
|
|
||||||
orchestrator = Orchestrator(
|
orchestrator = Orchestrator(
|
||||||
tmux_manager=mock_tmux_manager,
|
tmux_manager=mock_tmux_manager,
|
||||||
formatter=formatter
|
formatter=formatter,
|
||||||
|
users_root=tmp_users_root
|
||||||
)
|
)
|
||||||
orchestrator.approval_handler = approval_handler
|
orchestrator.approval_handler = approval_handler
|
||||||
orchestrator.executor = MagicMock()
|
orchestrator.executor = MagicMock()
|
||||||
|
|||||||
Reference in New Issue
Block a user