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:
2026-03-11 18:51:50 +08:00
parent 3047605d14
commit 87068339df
2 changed files with 19 additions and 7 deletions
+16 -5
View File
@@ -10,7 +10,10 @@ This module provides the central command routing system that:
from __future__ import annotations
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.command_executor import CommandExecutor, CommandResult
@@ -47,7 +50,8 @@ class Orchestrator:
def __init__(
self,
tmux_manager: Optional[TmuxSessionManager] = None,
formatter: Optional[FeishuResponseFormatter] = None
formatter: Optional[FeishuResponseFormatter] = None,
users_root: Optional["Path"] = None
):
"""Initialize Orchestrator.
@@ -56,6 +60,7 @@ class Orchestrator:
If not provided, a new one will be created.
formatter: Optional FeishuResponseFormatter instance.
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.formatter = formatter or FeishuResponseFormatter()
@@ -63,6 +68,7 @@ class Orchestrator:
self.approval_handler = ApprovalHandler(self.formatter)
self.progress_monitor = ProgressMonitor(self.formatter)
self.user_context_managers: Dict[str, UserContextManager] = {}
self._users_root = users_root
def _get_user_context_manager(self, user_id: str) -> UserContextManager:
"""Get or create UserContextManager for a user.
@@ -74,7 +80,7 @@ class Orchestrator:
UserContextManager instance for the user.
"""
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]
def _handle_new_project(self, user_id: str, project_name: str) -> Dict[str, Any]:
@@ -249,8 +255,13 @@ class Orchestrator:
if command == 'use':
return self._handle_use_project(user_id, args)
# Get project name from context
project_name = context.get("project", "default")
# Generic commands - get project from user context first
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)
session = self.tmux_manager.get_session(project_name)
+3 -2
View File
@@ -345,7 +345,7 @@ class TestIntegrationFlows:
"""Integration-style tests for complete flows."""
@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)."""
from gsd_openclaw.orchestrator import Orchestrator
from gsd_openclaw.response_formatter import FeishuResponseFormatter
@@ -359,7 +359,8 @@ class TestIntegrationFlows:
orchestrator = Orchestrator(
tmux_manager=mock_tmux_manager,
formatter=formatter
formatter=formatter,
users_root=tmp_users_root
)
orchestrator.approval_handler = approval_handler
orchestrator.executor = MagicMock()