fix(backend): show name of created branch in conversation list. (#10208)

This commit is contained in:
Hiep Le
2025-08-27 11:41:12 +07:00
committed by GitHub
parent 4849369ede
commit 18b5139237
4 changed files with 229 additions and 22 deletions

View File

@@ -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
):