feat(04-02): implement _handle_new_project with UserContextManager

- Add UserContextManager for per-user project tracking
- Add _handle_new_project method that pre-creates sessions
- Update user context after new-project creation
- Route new-project command in handle_message
- Add LONG_COMMANDS class attribute for timeout control

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 18:47:40 +08:00
parent acc52fbd8c
commit 9e8fc81469
2 changed files with 331 additions and 1 deletions
+82 -1
View File
@@ -16,7 +16,8 @@ from gsd_openclaw.tmux_session import TmuxSession, TmuxSessionManager
from gsd_openclaw.command_executor import CommandExecutor, CommandResult
from gsd_openclaw.approval_handler import ApprovalHandler
from gsd_openclaw.progress_monitor import ProgressMonitor
from gsd_openclaw.response_formatter import FeishuResponseFormatter
from gsd_openclaw.response_formatter import FeishuResponseFormatter, CardButton
from gsd_openclaw.user_context import UserContextManager
class Orchestrator:
@@ -40,6 +41,9 @@ class Orchestrator:
# Maximum characters for card content (Feishu limit)
MAX_OUTPUT_LENGTH = 2000
# Commands that require long-running sessions (no timeout)
LONG_COMMANDS = {'execute-phase', 'plan-phase', 'discuss-phase', 'new-project'}
def __init__(
self,
tmux_manager: Optional[TmuxSessionManager] = None,
@@ -58,6 +62,79 @@ class Orchestrator:
self.executor = CommandExecutor()
self.approval_handler = ApprovalHandler(self.formatter)
self.progress_monitor = ProgressMonitor(self.formatter)
self.user_context_managers: Dict[str, UserContextManager] = {}
def _get_user_context_manager(self, user_id: str) -> UserContextManager:
"""Get or create UserContextManager for a user.
Args:
user_id: The user identifier.
Returns:
UserContextManager instance for the user.
"""
if user_id not in self.user_context_managers:
self.user_context_managers[user_id] = UserContextManager(user_id)
return self.user_context_managers[user_id]
def _handle_new_project(self, user_id: str, project_name: str) -> Dict[str, Any]:
"""Handle /gsd:new-project command.
Pre-creates a tmux session before executing the command,
then updates user context to track the new project.
Args:
user_id: ID of the user sending the command.
project_name: Name of the project to create.
Returns:
Feishu Card JSON dict as response.
"""
# Validate project name
if not project_name or not project_name.strip():
return self.formatter.format_error(
title="Project name required",
content="Please provide a project name: /gsd:new-project <name>"
)
project_name = project_name.strip()
try:
# Create session first
session = self.tmux_manager.create_session(project_name)
# Build and execute the new-project command
full_command = f"/gsd:new-project {project_name}"
# Execute with no timeout for long-running commands
result = self.executor.execute(session, full_command, timeout=None)
# Update user context
user_ctx = self._get_user_context_manager(user_id)
user_ctx.set_current_project(project_name)
# Truncate output if needed
output = result.output
if len(output) > self.MAX_OUTPUT_LENGTH:
output = output[-self.MAX_OUTPUT_LENGTH:]
# Return appropriate response
if result.has_error:
return self.formatter.format_error(
title="Project creation failed",
content=output
)
else:
return self.formatter.format_success(
title="Project created",
content=f"Project '{project_name}' created successfully.\n\n{output}"
)
except Exception as e:
return self.formatter.format_error(
title="Project creation failed",
content=str(e)[-self.MAX_OUTPUT_LENGTH:]
)
def parse_command(self, message: str) -> Optional[Tuple[str, str]]:
"""Parse a message for /gsd:* command pattern.
@@ -112,6 +189,10 @@ class Orchestrator:
command, args = parsed
# Route special commands
if command == 'new-project':
return self._handle_new_project(user_id, args)
# Get project name from context
project_name = context.get("project", "default")