mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
fix(backend): show name of created branch in conversation list. (#10208)
This commit is contained in:
@@ -2,7 +2,7 @@ import asyncio
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Callable, Iterable
|
||||
from typing import Any, Callable, Iterable
|
||||
|
||||
import socketio
|
||||
|
||||
@@ -11,7 +11,9 @@ from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
from openhands.core.exceptions import AgentRuntimeUnavailableError
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.schema.agent import AgentState
|
||||
from openhands.core.schema.observation import ObservationType
|
||||
from openhands.events.action import MessageAction
|
||||
from openhands.events.observation.commands import CmdOutputObservation
|
||||
from openhands.events.stream import EventStreamSubscriber, session_exists
|
||||
from openhands.llm.llm_registry import LLMRegistry
|
||||
from openhands.runtime import get_runtime_cls
|
||||
@@ -516,6 +518,18 @@ class StandaloneConversationManager(ConversationManager):
|
||||
conversation.total_tokens = (
|
||||
token_usage.prompt_tokens + token_usage.completion_tokens
|
||||
)
|
||||
|
||||
# Check for branch changes if this is a git-related event
|
||||
if event and self._is_git_related_event(event):
|
||||
logger.info(
|
||||
f'Git-related event detected, updating conversation branch for {conversation_id}',
|
||||
extra={
|
||||
'session_id': conversation_id,
|
||||
'command': getattr(event, 'command', 'unknown'),
|
||||
},
|
||||
)
|
||||
await self._update_conversation_branch(conversation)
|
||||
|
||||
default_title = get_default_conversation_title(conversation_id)
|
||||
if (
|
||||
conversation.title == default_title
|
||||
@@ -548,6 +562,154 @@ class StandaloneConversationManager(ConversationManager):
|
||||
|
||||
await conversation_store.save_metadata(conversation)
|
||||
|
||||
def _is_git_related_event(self, event) -> bool:
|
||||
"""
|
||||
Determine if an event is related to git operations that could change the branch.
|
||||
|
||||
Args:
|
||||
event: The event to check
|
||||
|
||||
Returns:
|
||||
True if the event is git-related and could change the branch, False otherwise
|
||||
"""
|
||||
# Early return if event is None or not the correct type
|
||||
if not event or not isinstance(event, CmdOutputObservation):
|
||||
return False
|
||||
|
||||
# Check CmdOutputObservation for git commands that change branches
|
||||
# We check the observation result, not the action request, to ensure the command actually succeeded
|
||||
if (
|
||||
event.observation == ObservationType.RUN
|
||||
and event.metadata.exit_code == 0 # Only consider successful commands
|
||||
):
|
||||
command = event.command.lower()
|
||||
|
||||
# Check if any git command that changes branches is present anywhere in the command
|
||||
# This handles compound commands like "cd workspace && git checkout feature-branch"
|
||||
git_commands = [
|
||||
'git checkout',
|
||||
'git switch',
|
||||
'git merge',
|
||||
'git rebase',
|
||||
'git reset',
|
||||
'git branch',
|
||||
]
|
||||
|
||||
is_git_related = any(git_cmd in command for git_cmd in git_commands)
|
||||
|
||||
if is_git_related:
|
||||
logger.debug(
|
||||
f'Detected git-related command: {command} with exit code {event.metadata.exit_code}',
|
||||
extra={'command': command, 'exit_code': event.metadata.exit_code},
|
||||
)
|
||||
|
||||
return is_git_related
|
||||
|
||||
return False
|
||||
|
||||
async def _update_conversation_branch(self, conversation: ConversationMetadata):
|
||||
"""
|
||||
Update the conversation's current branch if it has changed.
|
||||
|
||||
Args:
|
||||
conversation: The conversation metadata to update
|
||||
"""
|
||||
try:
|
||||
# Get the session and runtime for this conversation
|
||||
session, runtime = self._get_session_and_runtime(
|
||||
conversation.conversation_id
|
||||
)
|
||||
if not session or not runtime:
|
||||
return
|
||||
|
||||
# Get the current branch from the workspace
|
||||
current_branch = self._get_current_workspace_branch(
|
||||
runtime, conversation.selected_repository
|
||||
)
|
||||
|
||||
# Update branch if it has changed
|
||||
if self._should_update_branch(conversation.selected_branch, current_branch):
|
||||
self._update_branch_in_conversation(conversation, current_branch)
|
||||
|
||||
except Exception as e:
|
||||
# Log an error that occurred during branch update
|
||||
logger.warning(
|
||||
f'Failed to update conversation branch: {e}',
|
||||
extra={'session_id': conversation.conversation_id},
|
||||
)
|
||||
|
||||
def _get_session_and_runtime(
|
||||
self, conversation_id: str
|
||||
) -> tuple[Session | None, Any | None]:
|
||||
"""
|
||||
Get the session and runtime for a conversation.
|
||||
|
||||
Args:
|
||||
conversation_id: The conversation ID
|
||||
|
||||
Returns:
|
||||
Tuple of (session, runtime) or (None, None) if not found
|
||||
"""
|
||||
session = self._local_agent_loops_by_sid.get(conversation_id)
|
||||
if not session or not session.agent_session.runtime:
|
||||
return None, None
|
||||
return session, session.agent_session.runtime
|
||||
|
||||
def _get_current_workspace_branch(
|
||||
self, runtime: Any, selected_repository: str | None
|
||||
) -> str | None:
|
||||
"""
|
||||
Get the current branch from the workspace.
|
||||
|
||||
Args:
|
||||
runtime: The runtime instance
|
||||
selected_repository: The selected repository path or None
|
||||
|
||||
Returns:
|
||||
The current branch name or None if not found
|
||||
"""
|
||||
# Extract the repository name from the full repository path
|
||||
if not selected_repository:
|
||||
primary_repo_path = None
|
||||
else:
|
||||
# Extract the repository name from the full path (e.g., "org/repo" -> "repo")
|
||||
primary_repo_path = selected_repository.split('/')[-1]
|
||||
|
||||
return runtime.get_workspace_branch(primary_repo_path)
|
||||
|
||||
def _should_update_branch(
|
||||
self, current_branch: str | None, new_branch: str | None
|
||||
) -> bool:
|
||||
"""
|
||||
Determine if the branch should be updated.
|
||||
|
||||
Args:
|
||||
current_branch: The current branch in conversation metadata
|
||||
new_branch: The new branch from the workspace
|
||||
|
||||
Returns:
|
||||
True if the branch should be updated, False otherwise
|
||||
"""
|
||||
return new_branch is not None and new_branch != current_branch
|
||||
|
||||
def _update_branch_in_conversation(
|
||||
self, conversation: ConversationMetadata, new_branch: str | None
|
||||
):
|
||||
"""
|
||||
Update the branch in the conversation metadata.
|
||||
|
||||
Args:
|
||||
conversation: The conversation metadata to update
|
||||
new_branch: The new branch name
|
||||
"""
|
||||
old_branch = conversation.selected_branch
|
||||
conversation.selected_branch = new_branch
|
||||
|
||||
logger.info(
|
||||
f'Branch changed from {old_branch} to {new_branch}',
|
||||
extra={'session_id': conversation.conversation_id},
|
||||
)
|
||||
|
||||
async def get_agent_loop_info(
|
||||
self, user_id: str | None = None, filter_to_sids: set[str] | None = None
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user